From 7ed2d717cbbe87cc1d703631661f50bdeeb2b3bf Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Fri, 3 Jul 2026 16:17:21 -0700 Subject: [PATCH 01/36] feat(api): add express wrappers These wrappers add zod-based input validation, type-safety, and reflection capabilities. To be used in the future for generating openapi definitions. --- src/app.ts | 5 +- src/routes/api.ts | 79 +++++++++-------- src/routes/helper.ts | 199 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 245 insertions(+), 38 deletions(-) create mode 100644 src/routes/helper.ts diff --git a/src/app.ts b/src/app.ts index 7690451..bd158a4 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,11 +1,12 @@ import express from "express"; import mbus from "./routes/api" +import { addRouter, dumpReflectionInfo, reflection } from "./routes/helper"; const app = express(); app.use(express.json()); -app.use("/mbus/api/v3", mbus); +addRouter(app, "/mbus/api/v3", mbus); app.use("/docs", express.static("docs")); const PORT = process.env.PORT || 3000; @@ -13,4 +14,6 @@ const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); + if (reflection) + dumpReflectionInfo(); }); \ No newline at end of file diff --git a/src/routes/api.ts b/src/routes/api.ts index 8f24271..43085e4 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -9,6 +9,7 @@ import * as journeyService from '../services/journey'; import * as reminderService from '../services/reminder'; import * as graphBuilder from '../services/graphBuilder'; import { startBackgroundJobs } from '../jobs'; +import { addGetRoute, HandlerReturn, makeFailureResponse, makeSuccessResponse } from "./helper"; /** * Express router for the MBus API v3. @@ -431,7 +432,7 @@ export function getStartupInfo(req: express.Request, res: express.Response) { res.json({ min_supported_version: "2.0.0", why_update_message: { title: "Update Needed", subtitle: "You need to update to the latest version for the app to work properly." }, - persistant_message: { title: "", subtitle: ""}, + persistant_message: { title: "", subtitle: "" }, one_time_message: { title: "", subtitle: "" }, bus_image_version: "1", }); @@ -529,7 +530,7 @@ export function unsetReminder(req: express.Request, res: express.Response) { res.status(400); res.send(result.error.message); } else { - const { token ,stpid, rtid } = result.data; + const { token, stpid, rtid } = result.data; const info = reminderService.infoToUseForRoute(rtid); if (info === null) { res.status(400); @@ -567,42 +568,46 @@ export function swapToken(req: express.Request, res: express.Response) { } router.post('/swapToken', swapToken); -type ActiveReminderInfo = { stpid: string, rtid: string, thresh: number | null, eta: number | null }; +const Token = z.string().transform(reminderService.registrationToken).meta({ id: "Token" }) +const ActiveReminder = z.object({ + stpid: z.string(), + rtid: z.string(), + thresh: z.number().nullable(), + eta: z.number().nullable(), +}).meta({ id: "Reminder" }); -/** - * @param req - Express request, token is path encoded - * @param res - Express response - */ -export function activeRemindersForToken( - req: express.Request, - res: express.Response<{ reminders: Array }> -) { - const subscriptionInfo = (r: reminderService.PreThreshold | reminderService.PostThreshold): - ActiveReminderInfo => +addGetRoute( + router, '/activeReminders/:token', { - return { - stpid: r.event.stpid, - rtid: r.event.rtid, - thresh: r.stage === 0 ? r.thresh : null, - eta: r.stage === 0 ? r.candidateVidPredPrev : r.vidPredPrev + params: z.object({ token: Token }), + query: z.unknown(), + resBody: z.object({ reminders: z.array(ActiveReminder) }), + }, + ({token}, _) => { + const subscriptionInfo = (r: reminderService.PreThreshold | reminderService.PostThreshold) => { + return { + stpid: r.event.stpid, + rtid: r.event.rtid, + thresh: r.stage === 0 ? r.thresh : null, + eta: r.stage === 0 ? r.candidateVidPredPrev : r.vidPredPrev + }; }; - }; - const token = reminderService.registrationToken(req.params.registrationToken); - console.log(`Got request for active reminders of ${token}`); - res.status(200); - const universityReminders = reminderService - .universityReminderSubscriptions - .activeRemindersFor(token) - .map(subscriptionInfo); - const rideReminders = reminderService - .rideReminderSubscriptions - .activeRemindersFor(token) - .map(subscriptionInfo); - res.send({ - reminders: universityReminders.concat(rideReminders) - }); -} -router.get('/activeReminders/:registrationToken', activeRemindersForToken); + console.log(`Got request for active reminders of ${token}`); + const universityReminders = reminderService + .universityReminderSubscriptions + .activeRemindersFor(token) + .map(subscriptionInfo); + const rideReminders = reminderService + .rideReminderSubscriptions + .activeRemindersFor(token) + .map(subscriptionInfo); + return makeSuccessResponse(200, { reminders: universityReminders.concat(rideReminders) }); + }, + { + summary: "active reminders", + description: `big long description idk, gets the reminders associated with a **registration token**, which is gotten from fcm or smth` + }, +) const ModifyRemindersBody = z.object({ token: z.string(), @@ -640,7 +645,7 @@ export function modifyReminders(req: express.Request, res: express.Response) { reminderService.registrationToken(token), predsByStopId, Date.now() - ); + ); } else { reminderSubscriptions.remove( event, reminderService.registrationToken(token) @@ -665,7 +670,7 @@ export function notifyMeLater(req: express.Request, res: express.Response) { } setTimeout(() => { console.log(`sending test push notification to ${registrationToken}`); - reminderService.sendNotifToAll({ title: "hi", body: "hello world!"}, new Set([registrationToken])); + reminderService.sendNotifToAll({ title: "hi", body: "hello world!" }, new Set([registrationToken])); }, 0); res.sendStatus(200); } diff --git a/src/routes/helper.ts b/src/routes/helper.ts new file mode 100644 index 0000000..5a13335 --- /dev/null +++ b/src/routes/helper.ts @@ -0,0 +1,199 @@ +/** + * Wrappers around stuff you would otherwise do with express but with reflection + * capabilities used for openapi specification generation. + * + * The `req` and `res` objects aren't provided to the passed in handler + * functions, if you're doing something more complicated just use the router + * directly for now. + * + * Nested routing not supported yet, but should probably be added since api.ts + * is getting long. + * + * Extra functionality will be added as needed. + * + * TODO: add examples + * TODO: add tests + * TODO: use doc info, generate docs + * TODO: convert path acceptors from express format to openapi format + */ + +import express from 'express'; +import z from 'zod'; +import { JSONSchema, ToJSONSchemaParams } from 'zod/v4/core'; + +/** is reflection enabled? */ +export const reflection = true; +const info: ReflectionInfoRaw = { + routers: [], + routes: [] +}; + +/** + * unresolved: how to get descriptions from the ts-doc comments? + * probably handled by a typedoc plugin, or passed directly + */ +interface ReflectionInfoRaw { + routers: Array<{ route: string, router: express.Router }>, + /** full routes along with req+res schemas, routes are incomplete until info is finalized */ + routes: Array<{ + router: express.Router, + pathSuffix: string, + method: 'get', + params: z.ZodType, + query: z.ZodType, + resBody: z.ZodType, + }>, +}; + +interface ReflectionInfo { + routes: Array<{ + path: string, method: 'get', + params: JSONSchema.BaseSchema, query: JSONSchema.BaseSchema, resBody: JSONSchema.BaseSchema, + }>, + model: JSONSchema.BaseSchema, +}; + +function finalize(info: ReflectionInfoRaw): ReflectionInfo { + // TODO: try output first then fallback to input + const schemaOpts: ToJSONSchemaParams = { + reused: 'ref', + io: 'input', + } + const resultRoutes = []; + const model: Record = {}; + for (const route of info.routes) { + const basePath = info.routers.find((r) => r.router == route.router)?.route; + if (basePath == undefined) { + throw new Error('route has missing base path'); + } + const path = basePath + route.pathSuffix; + resultRoutes.push({ + path, method: route.method, + params: route.params.toJSONSchema(schemaOpts), + query: route.query.toJSONSchema(schemaOpts), + resBody: route.resBody.toJSONSchema(schemaOpts), + }); + model[path + ' params'] = route.params; + model[path + ' query'] = route.query; + model[path + ' resBody'] = route.resBody; + } + return { + routes: resultRoutes, + model: z.object(model).toJSONSchema(schemaOpts), + }; +} + +export function dumpReflectionInfo() { + const finalized = finalize(info); + console.log(JSON.stringify(finalized, null, 4)); +} + +export function addRouter(app: express.Express, route: string, router: express.Router) { + if (reflection) { + info.routers.push({ route, router }); + } + app.use(route, router); +} + +export interface GetFormat< + P extends z.ZodType, + Q extends z.ZodType, + RB extends z.ZodType +> { + /** path parameters */ + params: P, + query: Q, + resBody: RB, +} + +/** + * feel free to add more codes here and to the make*[a-z]Response functions as you need them + */ +export type HandlerReturn = { + success: true, status: 200 | 201 | 202 | 203 | 205, json: T +} | { + success: false, status: 400 | 401 | 403 | 404 | 500, error: string +}; + +/** + * helper functions that should avoid weird typechecker issues + */ +export function makeSuccessResponse(status: 200 | 201 | 202 | 203 | 205, json: T): HandlerReturn { + return { success: true, status, json }; +} + +/** + * helper functions that should avoid weird typechecker issues + */ +export function makeFailureResponse(status: 400 | 401 | 403 | 404 | 500, error: string): HandlerReturn { + return { success: false, status, error }; +} + +/** + * wrapper around router.get with built in validation and schema recording + * + * the `req` and `res` objects aren't provided to the passed in handler, if + * you're doing something more complicated just use the router direclty for + * now, the functionality needed will be incorporated + */ +export function addGetRoute< + P extends z.ZodType, + Q extends z.ZodType, + RB extends z.ZodType +>( + router: express.Router, + path: string, + format: GetFormat, + handler: (params: z.infer

, query: z.infer) => HandlerReturn>, + docs?: { + /** a short description of what is route does */ + summary?: string, + /** a longer explanation, commonmark accepted */ + description?: string, + }, +) { + const { params: paramsSchema, query: querySchema, resBody: resBodySchema } = format; + + if (reflection) { + info.routes.push({ + router, method: 'get', pathSuffix: path, + params: paramsSchema, query: querySchema, resBody: resBodySchema, + }) + } + + router.get(path, (req: express.Request, res: express.Response | { error: string }>) => { + const { status, json } = determineResponse(req); + res.status(status).json(json); + }); + + const determineResponse = (req: express.Request): { status: number, json: z.infer | { error: string } } => { + let params = paramsSchema.safeParse(req.params); + if (params.error) { + return { status: 400, json: { error: "invalid path params: " + params.error.message } }; + } + let query = querySchema.safeParse(req.query); + if (query.error) { + return { status: 400, json: { error: "invalid query params: " + query.error.message } }; + } + try { + const result = handler(params.data, query.data); + if (result.success) { + return { status: result.status, json: result.json }; + } else { + return { status: result.status, json: { error: result.error } }; + } + } catch (e) { + console.error(`uncaught exception in wrapped route: ${e}`) + if (e instanceof Error) { + return { status: 500, json: { error: e.message } } + } else { + return { status: 500, json: { error: JSON.stringify(e) } } + } + } + } +} + +/** + * wrapper around router.post with built in validation and schema recording + * TODO: make this + */ From 2dba4f2b38b608dd273ada74e5924e6206ef2429 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Fri, 3 Jul 2026 23:00:32 -0700 Subject: [PATCH 02/36] feat(api): openapi docs generation --- src/routes/api.ts | 6 +- src/routes/helper.ts | 164 ++++++++++++++++++++++++++++++++++--------- tsconfig.json | 2 +- 3 files changed, 135 insertions(+), 37 deletions(-) diff --git a/src/routes/api.ts b/src/routes/api.ts index 43085e4..bdccfe4 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -9,7 +9,7 @@ import * as journeyService from '../services/journey'; import * as reminderService from '../services/reminder'; import * as graphBuilder from '../services/graphBuilder'; import { startBackgroundJobs } from '../jobs'; -import { addGetRoute, HandlerReturn, makeFailureResponse, makeSuccessResponse } from "./helper"; +import { addGetRoute, makeSuccessResponse } from "./helper"; /** * Express router for the MBus API v3. @@ -580,10 +580,10 @@ addGetRoute( router, '/activeReminders/:token', { params: z.object({ token: Token }), - query: z.unknown(), + query: z.object(), resBody: z.object({ reminders: z.array(ActiveReminder) }), }, - ({token}, _) => { + ({ token }, _) => { const subscriptionInfo = (r: reminderService.PreThreshold | reminderService.PostThreshold) => { return { stpid: r.event.stpid, diff --git a/src/routes/helper.ts b/src/routes/helper.ts index 5a13335..cfa3ed7 100644 --- a/src/routes/helper.ts +++ b/src/routes/helper.ts @@ -11,10 +11,9 @@ * * Extra functionality will be added as needed. * + * TODO: post support * TODO: add examples * TODO: add tests - * TODO: use doc info, generate docs - * TODO: convert path acceptors from express format to openapi format */ import express from 'express'; @@ -39,53 +38,162 @@ interface ReflectionInfoRaw { router: express.Router, pathSuffix: string, method: 'get', - params: z.ZodType, - query: z.ZodType, + params: Record, + query: Record, resBody: z.ZodType, + summary: string, + description: string, }>, }; interface ReflectionInfo { routes: Array<{ - path: string, method: 'get', - params: JSONSchema.BaseSchema, query: JSONSchema.BaseSchema, resBody: JSONSchema.BaseSchema, + path: string, + method: 'get', + params: Record, + query: Record, + resBody: JSONSchema.BaseSchema, + summary: string, + description: string, }>, - model: JSONSchema.BaseSchema, + defs: Record, + // model: JSONSchema.BaseSchema, }; +interface OpenAPIGetPath { + summary: string, + description: string, + parameters: Array<{ + name: string, + in: "path" | "query", + schema: JSONSchema.JSONSchema, + required: boolean, + }> + responses: { + "2XX": { + description: "success", + content: { + "application/json": { + schema: JSONSchema.JSONSchema, + } + } + } + } +} + +/** the subset of the openapi format(s) we are concerned with generating */ +interface OpenAPI { + openapi: "3.1.2", + info: { + title: string, + version: string, + }, + components: { + schemas: Record, + }, + paths: Record>, +} + function finalize(info: ReflectionInfoRaw): ReflectionInfo { + // replace $def with components/schemas + const fixSchema = (s: T): T => { + if (typeof s !== 'object' || !s) return s; + if ('$ref' in s && typeof s.$ref == 'string') + s.$ref = s.$ref.replace('$defs', 'components/schemas'); + for (const v of Object.values(s)) { + fixSchema(v); + } + return s; + }; + // TODO: try output first then fallback to input const schemaOpts: ToJSONSchemaParams = { - reused: 'ref', + // reused: 'ref', io: 'input', } const resultRoutes = []; + + // used to get the shared $defs const model: Record = {}; + for (const route of info.routes) { const basePath = info.routers.find((r) => r.router == route.router)?.route; if (basePath == undefined) { throw new Error('route has missing base path'); } - const path = basePath + route.pathSuffix; + const path = (basePath + route.pathSuffix).replace(/:([A-Za-z0-9_]+)/, "{$1}"); + const finalParams: Record = {}; + for (const param in route.params) { + const zodSchema = route.params[param]; + model[path + ' params ' + param] = zodSchema; + finalParams[param] = fixSchema(zodSchema.toJSONSchema(schemaOpts)); + } + const finalQuery: Record = {}; + for (const key in route.query) { + const zodSchema = route.query[key]; + model[path + '?' + key] = zodSchema; + finalQuery[key] = fixSchema(zodSchema.toJSONSchema(schemaOpts)); + } resultRoutes.push({ path, method: route.method, - params: route.params.toJSONSchema(schemaOpts), - query: route.query.toJSONSchema(schemaOpts), - resBody: route.resBody.toJSONSchema(schemaOpts), + params: finalParams, + query: finalQuery, + resBody: fixSchema(route.resBody.toJSONSchema(schemaOpts)), + summary: route.summary, + description: route.description, }); - model[path + ' params'] = route.params; - model[path + ' query'] = route.query; model[path + ' resBody'] = route.resBody; } return { routes: resultRoutes, - model: z.object(model).toJSONSchema(schemaOpts), + defs: fixSchema(z.object(model).toJSONSchema(schemaOpts)).$defs ?? {}, }; } +function makeOpenAPI(info: ReflectionInfo): OpenAPI { + const pathsArray = info.routes.map((route) => { + const parameters: OpenAPIGetPath['parameters'] = []; + for (const name in route.params) { + parameters.push({ name: name, in: 'path', required: true, schema: route.params[name] }); + } + for (const name in route.query) { + parameters.push({ name: name, in: 'query', required: true, schema: route.query[name] }); + } + const responses: OpenAPIGetPath['responses'] = { + '2XX': { + description: 'success', + content: { + 'application/json': { schema: route.resBody } + } + } + }; + const path: OpenAPIGetPath = { + summary: route.summary, + description: route.description, + parameters, + responses, + }; + return { url: route.path, path: { get: path } }; + }); + const paths: OpenAPI['paths'] = {}; + for (const { url, path } of pathsArray) { + paths[url] = path; + } + return { + openapi: "3.1.2", + info: { + title: "Maize Bus Backend", + version: "", + }, + components: { schemas: info.defs }, + paths, + } +} + export function dumpReflectionInfo() { const finalized = finalize(info); - console.log(JSON.stringify(finalized, null, 4)); + const openAPI = makeOpenAPI(finalized); + console.log(JSON.stringify(openAPI, null, 4)); } export function addRouter(app: express.Express, route: string, router: express.Router) { @@ -95,17 +203,6 @@ export function addRouter(app: express.Express, route: string, router: express.R app.use(route, router); } -export interface GetFormat< - P extends z.ZodType, - Q extends z.ZodType, - RB extends z.ZodType -> { - /** path parameters */ - params: P, - query: Q, - resBody: RB, -} - /** * feel free to add more codes here and to the make*[a-z]Response functions as you need them */ @@ -116,14 +213,14 @@ export type HandlerReturn = { }; /** - * helper functions that should avoid weird typechecker issues + * helper function that should avoid weird typechecker issues */ export function makeSuccessResponse(status: 200 | 201 | 202 | 203 | 205, json: T): HandlerReturn { return { success: true, status, json }; } /** - * helper functions that should avoid weird typechecker issues + * helper function that should avoid weird typechecker issues */ export function makeFailureResponse(status: 400 | 401 | 403 | 404 | 500, error: string): HandlerReturn { return { success: false, status, error }; @@ -137,13 +234,13 @@ export function makeFailureResponse(status: 400 | 401 | 403 | 404 | 500, erro * now, the functionality needed will be incorporated */ export function addGetRoute< - P extends z.ZodType, - Q extends z.ZodType, + P extends z.ZodObject>, + Q extends z.ZodObject>, RB extends z.ZodType >( router: express.Router, path: string, - format: GetFormat, + format: { params: P, query: Q, resBody: RB }, handler: (params: z.infer

, query: z.infer) => HandlerReturn>, docs?: { /** a short description of what is route does */ @@ -157,7 +254,8 @@ export function addGetRoute< if (reflection) { info.routes.push({ router, method: 'get', pathSuffix: path, - params: paramsSchema, query: querySchema, resBody: resBodySchema, + params: paramsSchema.shape, query: querySchema.shape, resBody: resBodySchema, + summary: docs?.summary ?? "", description: docs?.description ?? "", }) } diff --git a/tsconfig.json b/tsconfig.json index 144c0e8..5e91057 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es6", + "target": "es2021", "module": "esnext", "moduleResolution": "Node", "esModuleInterop": true, From 45df1c7bcafd72ee54998a3a8e5aedec588f61a3 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Sat, 11 Jul 2026 21:08:37 -0700 Subject: [PATCH 03/36] feat(api): add support for defining post routes --- src/app.ts | 2 +- src/routes/api.ts | 26 +-- src/routes/{helper.ts => documented.ts} | 236 ++++++++++++++++++------ tsconfig.json | 4 +- 4 files changed, 191 insertions(+), 77 deletions(-) rename src/routes/{helper.ts => documented.ts} (54%) diff --git a/src/app.ts b/src/app.ts index bd158a4..f743a3d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,7 +1,7 @@ import express from "express"; import mbus from "./routes/api" -import { addRouter, dumpReflectionInfo, reflection } from "./routes/helper"; +import { addRouter, dumpReflectionInfo, reflection } from "./routes/documented"; const app = express(); diff --git a/src/routes/api.ts b/src/routes/api.ts index bdccfe4..0131ee4 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -9,7 +9,7 @@ import * as journeyService from '../services/journey'; import * as reminderService from '../services/reminder'; import * as graphBuilder from '../services/graphBuilder'; import { startBackgroundJobs } from '../jobs'; -import { addGetRoute, makeSuccessResponse } from "./helper"; +import { addGetRoute, addPostRoute, emptyFormat, makeFailureResponse, makeSuccessResponse } from "./documented"; /** * Express router for the MBus API v3. @@ -488,22 +488,12 @@ router.get('/get-key-stops', getKeyStops); // Notifications / Reminders const SetReminderBody = z.object({ token: z.string(), stpid: z.string(), rtid: z.string(), thresh: z.number() }); -/** - * @param req - Express request, expects `SetReminderBody` in the body - * @param res - Express response, error message as string if error occurs - */ -export function setReminder(req: express.Request, res: express.Response) { - const result = SetReminderBody.safeParse(req.body); - if (!result.success) { - res.status(400); - res.send(result.error.message); - } else { - const { token, stpid, rtid, thresh } = result.data; +addPostRoute( + router, '/setReminder', { ...emptyFormat, reqBody: SetReminderBody }, + (_, __, { token, stpid, rtid, thresh }) => { const info = reminderService.infoToUseForRoute(rtid); if (info === null) { - res.status(400); - res.send(`Invalid route ${rtid}`); - return; + return makeFailureResponse(400, `Invalid route ${rtid}`); } const { reminderSubscriptions, predsByStopId } = info; reminderSubscriptions.add( @@ -513,11 +503,9 @@ export function setReminder(req: express.Request, res: express.Response) { predsByStopId, Date.now(), ); - res.sendStatus(200); + return makeSuccessResponse(200, {}); } - -} -router.post('/setReminder', setReminder); +); const UnsetReminderBody = z.object({ token: z.string(), stpid: z.string(), rtid: z.string() }); /** diff --git a/src/routes/helper.ts b/src/routes/documented.ts similarity index 54% rename from src/routes/helper.ts rename to src/routes/documented.ts index cfa3ed7..a3f2012 100644 --- a/src/routes/helper.ts +++ b/src/routes/documented.ts @@ -7,13 +7,17 @@ * directly for now. * * Nested routing not supported yet, but should probably be added since api.ts - * is getting long. + * is getting long (or we could separate the functions from the route defintions?). * - * Extra functionality will be added as needed. + * Extra functionality can be added as needed. * - * TODO: post support + * EXAMPLES: + * + * TODO: post support [done?] * TODO: add examples - * TODO: add tests + * TODO: support empty request and response bodies + * TODO: make actually testable? + * TODO: add tests? */ import express from 'express'; @@ -28,8 +32,7 @@ const info: ReflectionInfoRaw = { }; /** - * unresolved: how to get descriptions from the ts-doc comments? - * probably handled by a typedoc plugin, or passed directly + * doc descriptions passed as data in summary and description fields */ interface ReflectionInfoRaw { routers: Array<{ route: string, router: express.Router }>, @@ -37,30 +40,27 @@ interface ReflectionInfoRaw { routes: Array<{ router: express.Router, pathSuffix: string, - method: 'get', + summary: string, + description: string, params: Record, query: Record, resBody: z.ZodType, - summary: string, - description: string, - }>, + } & ({ method: 'get' } | { method: 'post', reqBody: z.ZodType })>, }; interface ReflectionInfo { routes: Array<{ path: string, - method: 'get', + summary: string, + description: string, params: Record, query: Record, resBody: JSONSchema.BaseSchema, - summary: string, - description: string, - }>, + } & ({ method: 'get' } | { method: 'post', reqBody: JSONSchema.BaseSchema | null })>, defs: Record, - // model: JSONSchema.BaseSchema, }; -interface OpenAPIGetPath { +interface OpenAPIPathCommon { summary: string, description: string, parameters: Array<{ @@ -68,7 +68,7 @@ interface OpenAPIGetPath { in: "path" | "query", schema: JSONSchema.JSONSchema, required: boolean, - }> + }>, responses: { "2XX": { description: "success", @@ -78,8 +78,21 @@ interface OpenAPIGetPath { } } } - } -} + }, +}; + +interface OpenAPIGetPath extends OpenAPIPathCommon { }; + +interface OpenAPIPostPath extends OpenAPIPathCommon { + requestBody: { + content: { + "application/json": { + schema: JSONSchema.JSONSchema, + } + }, + required: boolean, + }, +}; /** the subset of the openapi format(s) we are concerned with generating */ interface OpenAPI { @@ -91,7 +104,7 @@ interface OpenAPI { components: { schemas: Record, }, - paths: Record>, + paths: Record*/>, } function finalize(info: ReflectionInfoRaw): ReflectionInfo { @@ -111,7 +124,7 @@ function finalize(info: ReflectionInfoRaw): ReflectionInfo { // reused: 'ref', io: 'input', } - const resultRoutes = []; + const resultRoutes: ReflectionInfo['routes'] = []; // used to get the shared $defs const model: Record = {}; @@ -134,14 +147,30 @@ function finalize(info: ReflectionInfoRaw): ReflectionInfo { model[path + '?' + key] = zodSchema; finalQuery[key] = fixSchema(zodSchema.toJSONSchema(schemaOpts)); } - resultRoutes.push({ - path, method: route.method, + const common = { + path, params: finalParams, query: finalQuery, resBody: fixSchema(route.resBody.toJSONSchema(schemaOpts)), summary: route.summary, description: route.description, - }); + }; + switch (route.method) { + case 'get': + resultRoutes.push({ ...common, method: 'get' }); + break; + case 'post': + resultRoutes.push({ + ...common, + method: 'post', + reqBody: fixSchema(route.reqBody.toJSONSchema(schemaOpts)), + }); + model[path + ' reqBody'] = route.resBody; + break; + default: + // TODO: use eslint exhaustiveness checking + const _: never = route; + } model[path + ' resBody'] = route.resBody; } return { @@ -151,33 +180,49 @@ function finalize(info: ReflectionInfoRaw): ReflectionInfo { } function makeOpenAPI(info: ReflectionInfo): OpenAPI { - const pathsArray = info.routes.map((route) => { - const parameters: OpenAPIGetPath['parameters'] = []; - for (const name in route.params) { - parameters.push({ name: name, in: 'path', required: true, schema: route.params[name] }); - } - for (const name in route.query) { - parameters.push({ name: name, in: 'query', required: true, schema: route.query[name] }); - } - const responses: OpenAPIGetPath['responses'] = { - '2XX': { - description: 'success', - content: { - 'application/json': { schema: route.resBody } + const pathsArray = info.routes + .map((route): { url: string } & ( + { method: 'get', path: OpenAPIGetPath } | { method: 'post', path: OpenAPIPostPath } + ) => { + const parameters: OpenAPIGetPath['parameters'] = []; + for (const name in route.params) { + parameters.push({ name: name, in: 'path', required: true, schema: route.params[name] }); + } + for (const name in route.query) { + parameters.push({ name: name, in: 'query', required: true, schema: route.query[name] }); + } + const responses: OpenAPIGetPath['responses'] = { + '2XX': { + description: 'success', + content: { + 'application/json': { schema: route.resBody } + } } + }; + const common: OpenAPIPathCommon = { + summary: route.summary, + description: route.description, + parameters, + responses, + }; + switch (route.method) { + case 'get': + return { url: route.path, method: 'get', path: common }; + case 'post': + return { + url: route.path, method: 'post', path: { + requestBody: { content: { "application/json": { schema: route.reqBody } } }, ...common + } + }; } - }; - const path: OpenAPIGetPath = { - summary: route.summary, - description: route.description, - parameters, - responses, - }; - return { url: route.path, path: { get: path } }; - }); + }); const paths: OpenAPI['paths'] = {}; - for (const { url, path } of pathsArray) { - paths[url] = path; + for (const { url, method, path } of pathsArray) { + if (!paths[url]) paths[url] = {}; + if (method === 'get') + paths[url].get = path; + else + paths[url].post = path; } return { openapi: "3.1.2", @@ -226,16 +271,28 @@ export function makeFailureResponse(status: 400 | 401 | 403 | 404 | 500, erro return { success: false, status, error }; } +/** z.object({ example: z.(...), hello: z.number(), ... }) */ +export type StandardZodObject = z.ZodObject>; + +export const emptyFormat = { + params: z.object(), query: z.object(), reqBody: z.unknown(), resBody: z.unknown(), +}; + /** * wrapper around router.get with built in validation and schema recording * * the `req` and `res` objects aren't provided to the passed in handler, if - * you're doing something more complicated just use the router direclty for + * you're doing something more complicated just use the router directly for * now, the functionality needed will be incorporated + * + * type parameters + * - P: path params + * - Q: query params + * - RB: response body */ export function addGetRoute< - P extends z.ZodObject>, - Q extends z.ZodObject>, + P extends StandardZodObject, + Q extends StandardZodObject, RB extends z.ZodType >( router: express.Router, @@ -243,7 +300,7 @@ export function addGetRoute< format: { params: P, query: Q, resBody: RB }, handler: (params: z.infer

, query: z.infer) => HandlerReturn>, docs?: { - /** a short description of what is route does */ + /** a short description of what is route does, becomes the title */ summary?: string, /** a longer explanation, commonmark accepted */ description?: string, @@ -256,7 +313,7 @@ export function addGetRoute< router, method: 'get', pathSuffix: path, params: paramsSchema.shape, query: querySchema.shape, resBody: resBodySchema, summary: docs?.summary ?? "", description: docs?.description ?? "", - }) + }); } router.get(path, (req: express.Request, res: express.Response | { error: string }>) => { @@ -293,5 +350,76 @@ export function addGetRoute< /** * wrapper around router.post with built in validation and schema recording - * TODO: make this + * + * the `req` and `res` objects aren't provided to the passed in handler, if + * you're doing something more complicated just use the router directly for + * now, the functionality needed will be incorporated + * + * type parameters + * - P: path params + * - Q: query params + * - B: request body + * - RB: response body */ +export function addPostRoute< + P extends StandardZodObject, + Q extends StandardZodObject, + B extends z.ZodType, + RB extends z.ZodType, +>( + router: express.Router, + path: string, + format: { params: P, query: Q, reqBody: B, resBody: RB }, + handler: (params: z.infer

, query: z.infer, body: z.infer) => HandlerReturn>, + docs?: { + /** a short description of what is route does, becomes the title */ + summary?: string, + /** a longer explanation, commonmark accepted */ + description?: string, + }, +) { + const { params: paramsSchema, query: querySchema, reqBody: reqBodySchema, resBody: resBodySchema } = format; + + if (reflection) { + info.routes.push({ + router, method: 'post', pathSuffix: path, + params: paramsSchema.shape, query: querySchema.shape, reqBody: reqBodySchema, resBody: resBodySchema, + summary: docs?.summary ?? "", description: docs?.description ?? "", + }); + } + + router.post(path, (req, res: express.Response | { error: string }>) => { + const { status, json } = determineResponse(req); + res.status(status).json(json); + }); + + const determineResponse = (req: express.Request): { status: number, json: z.infer | { error: string } } => { + let params = paramsSchema.safeParse(req.params); + if (params.error) { + return { status: 400, json: { error: "invalid path params: " + params.error.message } }; + } + let query = querySchema.safeParse(req.query); + if (query.error) { + return { status: 400, json: { error: "invalid query params: " + query.error.message } }; + } + let body = reqBodySchema.safeParse(req.body); + if (body.error) { + return { status: 400, json: { error: "invalid body: " + body.error.message } }; + } + try { + const result = handler(params.data, query.data, body.data); + if (result.success) { + return { status: result.status, json: result.json }; + } else { + return { status: result.status, json: { error: result.error } }; + } + } catch (e) { + console.error(`uncaught exception in wrapped route: ${e}`) + if (e instanceof Error) { + return { status: 500, json: { error: e.message } } + } else { + return { status: 500, json: { error: JSON.stringify(e) } } + } + } + } +} diff --git a/tsconfig.json b/tsconfig.json index 5e91057..88b09c0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,14 +1,12 @@ { "compilerOptions": { "target": "es2021", - "module": "esnext", - "moduleResolution": "Node", + "module": "preserve", "esModuleInterop": true, "resolveJsonModule": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, - "baseUrl": "./", "paths": { "@/*": [ "./src/*" From 2c918c4210e2f103a0336b9db2157868d31de52a Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Sun, 12 Jul 2026 12:32:57 -0700 Subject: [PATCH 04/36] feat(api): support having no req/res bodies Also reordered the definitions so that the main api is together and closer to the top + fixed a bug where req & res bodies were mixed up. --- .gitignore | 3 +- src/routes/documented.ts | 408 ++++++++++++++++++++------------------- 2 files changed, 215 insertions(+), 196 deletions(-) diff --git a/.gitignore b/.gitignore index 8586e4f..be77bc5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules/ package-lock.json .env .vscode/ -src/assets/walkingCache.json \ No newline at end of file +src/assets/walkingCache.json +*.log \ No newline at end of file diff --git a/src/routes/documented.ts b/src/routes/documented.ts index a3f2012..322f92a 100644 --- a/src/routes/documented.ts +++ b/src/routes/documented.ts @@ -26,6 +26,200 @@ import { JSONSchema, ToJSONSchemaParams } from 'zod/v4/core'; /** is reflection enabled? */ export const reflection = true; + +// === interface for people defining apis === + +export function addRouter(app: express.Express, route: string, router: express.Router) { + if (reflection) { + info.routers.push({ route, router }); + } + app.use(route, router); +} + +/** + * feel free to add more codes here and to the make*[a-z]Response functions as you need them + */ +export type HandlerReturn = { + success: true, status: 200 | 201 | 202 | 203 | 205, json: T +} | { + success: false, status: 400 | 401 | 403 | 404 | 500, error: string +}; + +/** + * helper function that should avoid weird typechecker issues + */ +export function makeSuccessResponse(status: 200 | 201 | 202 | 203 | 205, json: T): HandlerReturn { + return { success: true, status, json }; +} + +/** + * helper function that should avoid weird typechecker issues + */ +export function makeFailureResponse(status: 400 | 401 | 403 | 404 | 500, error: string): HandlerReturn { + return { success: false, status, error }; +} + +/** z.object({ example: z.(...), hello: z.number(), ... }) */ +export type StandardZodObject = z.ZodObject>; + +export const emptyFormat = { + params: z.object(), query: z.object(), reqBody: z.unknown(), resBody: z.unknown(), +}; + +/** + * wrapper around router.get with built in validation and schema recording + * + * the `req` and `res` objects aren't provided to the passed in handler, if + * you're doing something more complicated just use the router directly for + * now, the functionality needed will be incorporated + * + * type parameters + * - P: path params + * - Q: query params + * - RB: response body + */ +export function addGetRoute< + P extends StandardZodObject, + Q extends StandardZodObject, + RB extends z.ZodType +>( + router: express.Router, + path: string, + format: { params: P, query: Q, resBody: RB }, + handler: (params: z.infer

, query: z.infer) => HandlerReturn>, + docs?: { + /** a short description of what is route does, becomes the title */ + summary?: string, + /** a longer explanation, commonmark accepted */ + description?: string, + }, +) { + const { params: paramsSchema, query: querySchema, resBody: resBodySchema } = format; + + if (reflection) { + info.routes.push({ + router, method: 'get', + pathSuffix: path, + params: paramsSchema.shape, + query: querySchema.shape, + resBody: resBodySchema instanceof z.ZodUnknown ? null : resBodySchema, + summary: docs?.summary ?? "", description: docs?.description ?? "", + }); + } + + router.get(path, (req: express.Request, res: express.Response | { error: string }>) => { + const { status, json } = determineResponse(req); + res.status(status).json(json); + }); + + const determineResponse = (req: express.Request): { status: number, json: z.infer | { error: string } } => { + let params = paramsSchema.safeParse(req.params); + if (params.error) { + return { status: 400, json: { error: "invalid path params: " + params.error.message } }; + } + let query = querySchema.safeParse(req.query); + if (query.error) { + return { status: 400, json: { error: "invalid query params: " + query.error.message } }; + } + try { + const result = handler(params.data, query.data); + if (result.success) { + return { status: result.status, json: result.json }; + } else { + return { status: result.status, json: { error: result.error } }; + } + } catch (e) { + console.error(`uncaught exception in wrapped route: ${e}`) + if (e instanceof Error) { + return { status: 500, json: { error: e.message } } + } else { + return { status: 500, json: { error: JSON.stringify(e) } } + } + } + } +} + +/** + * wrapper around router.post with built in validation and schema recording + * + * the `req` and `res` objects aren't provided to the passed in handler, if + * you're doing something more complicated just use the router directly for + * now, the functionality needed will be incorporated + * + * type parameters + * - P: path params + * - Q: query params + * - B: request body + * - RB: response body + */ +export function addPostRoute< + P extends StandardZodObject, + Q extends StandardZodObject, + B extends z.ZodType, + RB extends z.ZodType, +>( + router: express.Router, + path: string, + format: { params: P, query: Q, reqBody: B, resBody: RB }, + handler: (params: z.infer

, query: z.infer, body: z.infer) => HandlerReturn>, + docs?: { + /** a short description of what is route does, becomes the title */ + summary?: string, + /** a longer explanation, commonmark accepted */ + description?: string, + }, +) { + const { params: paramsSchema, query: querySchema, reqBody: reqBodySchema, resBody: resBodySchema } = format; + + if (reflection) { + info.routes.push({ + router, method: 'post', pathSuffix: path, + params: paramsSchema.shape, + query: querySchema.shape, + reqBody: reqBodySchema instanceof z.ZodUnknown ? null : reqBodySchema, + resBody: resBodySchema instanceof z.ZodUnknown ? null : resBodySchema, + summary: docs?.summary ?? "", description: docs?.description ?? "", + }); + } + + router.post(path, (req, res: express.Response | { error: string }>) => { + const { status, json } = determineResponse(req); + res.status(status).json(json); + }); + + const determineResponse = (req: express.Request): { status: number, json: z.infer | { error: string } } => { + let params = paramsSchema.safeParse(req.params); + if (params.error) { + return { status: 400, json: { error: "invalid path params: " + params.error.message } }; + } + let query = querySchema.safeParse(req.query); + if (query.error) { + return { status: 400, json: { error: "invalid query params: " + query.error.message } }; + } + let body = reqBodySchema.safeParse(req.body); + if (body.error) { + return { status: 400, json: { error: "invalid body: " + body.error.message } }; + } + try { + const result = handler(params.data, query.data, body.data); + if (result.success) { + return { status: result.status, json: result.json }; + } else { + return { status: result.status, json: { error: result.error } }; + } + } catch (e) { + console.error(`uncaught exception in wrapped route: ${e}`) + if (e instanceof Error) { + return { status: 500, json: { error: e.message } } + } else { + return { status: 500, json: { error: JSON.stringify(e) } } + } + } + } +} + +// === end of interface for api defining === + const info: ReflectionInfoRaw = { routers: [], routes: [] @@ -44,8 +238,8 @@ interface ReflectionInfoRaw { description: string, params: Record, query: Record, - resBody: z.ZodType, - } & ({ method: 'get' } | { method: 'post', reqBody: z.ZodType })>, + resBody: z.ZodType | null, + } & ({ method: 'get' } | { method: 'post', reqBody: z.ZodType | null })>, }; interface ReflectionInfo { @@ -55,7 +249,7 @@ interface ReflectionInfo { description: string, params: Record, query: Record, - resBody: JSONSchema.BaseSchema, + resBody: JSONSchema.BaseSchema | null, } & ({ method: 'get' } | { method: 'post', reqBody: JSONSchema.BaseSchema | null })>, defs: Record, }; @@ -72,7 +266,7 @@ interface OpenAPIPathCommon { responses: { "2XX": { description: "success", - content: { + content?: { "application/json": { schema: JSONSchema.JSONSchema, } @@ -84,7 +278,7 @@ interface OpenAPIPathCommon { interface OpenAPIGetPath extends OpenAPIPathCommon { }; interface OpenAPIPostPath extends OpenAPIPathCommon { - requestBody: { + requestBody?: { content: { "application/json": { schema: JSONSchema.JSONSchema, @@ -151,7 +345,7 @@ function finalize(info: ReflectionInfoRaw): ReflectionInfo { path, params: finalParams, query: finalQuery, - resBody: fixSchema(route.resBody.toJSONSchema(schemaOpts)), + resBody: route.resBody === null ? null : fixSchema(route.resBody.toJSONSchema(schemaOpts)), summary: route.summary, description: route.description, }; @@ -163,15 +357,17 @@ function finalize(info: ReflectionInfoRaw): ReflectionInfo { resultRoutes.push({ ...common, method: 'post', - reqBody: fixSchema(route.reqBody.toJSONSchema(schemaOpts)), + reqBody: route.reqBody === null ? null : fixSchema(route.reqBody.toJSONSchema(schemaOpts)), }); - model[path + ' reqBody'] = route.resBody; + if (route.reqBody) + model[path + ' reqBody'] = route.reqBody; break; default: // TODO: use eslint exhaustiveness checking const _: never = route; } - model[path + ' resBody'] = route.resBody; + if (route.resBody) + model[path + ' resBody'] = route.resBody; } return { routes: resultRoutes, @@ -191,12 +387,13 @@ function makeOpenAPI(info: ReflectionInfo): OpenAPI { for (const name in route.query) { parameters.push({ name: name, in: 'query', required: true, schema: route.query[name] }); } + const content = route.resBody === null + ? undefined + : { 'application/json': { schema: route.resBody } }; const responses: OpenAPIGetPath['responses'] = { '2XX': { description: 'success', - content: { - 'application/json': { schema: route.resBody } - } + content, } }; const common: OpenAPIPathCommon = { @@ -209,9 +406,12 @@ function makeOpenAPI(info: ReflectionInfo): OpenAPI { case 'get': return { url: route.path, method: 'get', path: common }; case 'post': + const requestBody = route.reqBody == null + ? undefined + : { content: { "application/json": { schema: route.reqBody } }, required: true }; return { url: route.path, method: 'post', path: { - requestBody: { content: { "application/json": { schema: route.reqBody } } }, ...common + requestBody, ...common } }; } @@ -241,185 +441,3 @@ export function dumpReflectionInfo() { console.log(JSON.stringify(openAPI, null, 4)); } -export function addRouter(app: express.Express, route: string, router: express.Router) { - if (reflection) { - info.routers.push({ route, router }); - } - app.use(route, router); -} - -/** - * feel free to add more codes here and to the make*[a-z]Response functions as you need them - */ -export type HandlerReturn = { - success: true, status: 200 | 201 | 202 | 203 | 205, json: T -} | { - success: false, status: 400 | 401 | 403 | 404 | 500, error: string -}; - -/** - * helper function that should avoid weird typechecker issues - */ -export function makeSuccessResponse(status: 200 | 201 | 202 | 203 | 205, json: T): HandlerReturn { - return { success: true, status, json }; -} - -/** - * helper function that should avoid weird typechecker issues - */ -export function makeFailureResponse(status: 400 | 401 | 403 | 404 | 500, error: string): HandlerReturn { - return { success: false, status, error }; -} - -/** z.object({ example: z.(...), hello: z.number(), ... }) */ -export type StandardZodObject = z.ZodObject>; - -export const emptyFormat = { - params: z.object(), query: z.object(), reqBody: z.unknown(), resBody: z.unknown(), -}; - -/** - * wrapper around router.get with built in validation and schema recording - * - * the `req` and `res` objects aren't provided to the passed in handler, if - * you're doing something more complicated just use the router directly for - * now, the functionality needed will be incorporated - * - * type parameters - * - P: path params - * - Q: query params - * - RB: response body - */ -export function addGetRoute< - P extends StandardZodObject, - Q extends StandardZodObject, - RB extends z.ZodType ->( - router: express.Router, - path: string, - format: { params: P, query: Q, resBody: RB }, - handler: (params: z.infer

, query: z.infer) => HandlerReturn>, - docs?: { - /** a short description of what is route does, becomes the title */ - summary?: string, - /** a longer explanation, commonmark accepted */ - description?: string, - }, -) { - const { params: paramsSchema, query: querySchema, resBody: resBodySchema } = format; - - if (reflection) { - info.routes.push({ - router, method: 'get', pathSuffix: path, - params: paramsSchema.shape, query: querySchema.shape, resBody: resBodySchema, - summary: docs?.summary ?? "", description: docs?.description ?? "", - }); - } - - router.get(path, (req: express.Request, res: express.Response | { error: string }>) => { - const { status, json } = determineResponse(req); - res.status(status).json(json); - }); - - const determineResponse = (req: express.Request): { status: number, json: z.infer | { error: string } } => { - let params = paramsSchema.safeParse(req.params); - if (params.error) { - return { status: 400, json: { error: "invalid path params: " + params.error.message } }; - } - let query = querySchema.safeParse(req.query); - if (query.error) { - return { status: 400, json: { error: "invalid query params: " + query.error.message } }; - } - try { - const result = handler(params.data, query.data); - if (result.success) { - return { status: result.status, json: result.json }; - } else { - return { status: result.status, json: { error: result.error } }; - } - } catch (e) { - console.error(`uncaught exception in wrapped route: ${e}`) - if (e instanceof Error) { - return { status: 500, json: { error: e.message } } - } else { - return { status: 500, json: { error: JSON.stringify(e) } } - } - } - } -} - -/** - * wrapper around router.post with built in validation and schema recording - * - * the `req` and `res` objects aren't provided to the passed in handler, if - * you're doing something more complicated just use the router directly for - * now, the functionality needed will be incorporated - * - * type parameters - * - P: path params - * - Q: query params - * - B: request body - * - RB: response body - */ -export function addPostRoute< - P extends StandardZodObject, - Q extends StandardZodObject, - B extends z.ZodType, - RB extends z.ZodType, ->( - router: express.Router, - path: string, - format: { params: P, query: Q, reqBody: B, resBody: RB }, - handler: (params: z.infer

, query: z.infer, body: z.infer) => HandlerReturn>, - docs?: { - /** a short description of what is route does, becomes the title */ - summary?: string, - /** a longer explanation, commonmark accepted */ - description?: string, - }, -) { - const { params: paramsSchema, query: querySchema, reqBody: reqBodySchema, resBody: resBodySchema } = format; - - if (reflection) { - info.routes.push({ - router, method: 'post', pathSuffix: path, - params: paramsSchema.shape, query: querySchema.shape, reqBody: reqBodySchema, resBody: resBodySchema, - summary: docs?.summary ?? "", description: docs?.description ?? "", - }); - } - - router.post(path, (req, res: express.Response | { error: string }>) => { - const { status, json } = determineResponse(req); - res.status(status).json(json); - }); - - const determineResponse = (req: express.Request): { status: number, json: z.infer | { error: string } } => { - let params = paramsSchema.safeParse(req.params); - if (params.error) { - return { status: 400, json: { error: "invalid path params: " + params.error.message } }; - } - let query = querySchema.safeParse(req.query); - if (query.error) { - return { status: 400, json: { error: "invalid query params: " + query.error.message } }; - } - let body = reqBodySchema.safeParse(req.body); - if (body.error) { - return { status: 400, json: { error: "invalid body: " + body.error.message } }; - } - try { - const result = handler(params.data, query.data, body.data); - if (result.success) { - return { status: result.status, json: result.json }; - } else { - return { status: result.status, json: { error: result.error } }; - } - } catch (e) { - console.error(`uncaught exception in wrapped route: ${e}`) - if (e instanceof Error) { - return { status: 500, json: { error: e.message } } - } else { - return { status: 500, json: { error: JSON.stringify(e) } } - } - } - } -} From 24a7945d710113c259b72f5afeb36bee21290ec5 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Wed, 15 Jul 2026 11:17:24 -0700 Subject: [PATCH 05/36] clean up the openapi output a bit and fix a typo confusing the request and response schemas --- src/routes/documented.ts | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/routes/documented.ts b/src/routes/documented.ts index 322f92a..675f54b 100644 --- a/src/routes/documented.ts +++ b/src/routes/documented.ts @@ -37,7 +37,7 @@ export function addRouter(app: express.Express, route: string, router: express.R } /** - * feel free to add more codes here and to the make*[a-z]Response functions as you need them + * feel free to add more codes here and to the make[A-Za-z]*Response functions as you need them */ export type HandlerReturn = { success: true, status: 200 | 201 | 202 | 203 | 205, json: T @@ -303,16 +303,30 @@ interface OpenAPI { function finalize(info: ReflectionInfoRaw): ReflectionInfo { // replace $def with components/schemas - const fixSchema = (s: T): T => { + const fixSchema = (s: T, shouldStripDefs: boolean): T => { + stripExtraKeys(s, shouldStripDefs); if (typeof s !== 'object' || !s) return s; if ('$ref' in s && typeof s.$ref == 'string') s.$ref = s.$ref.replace('$defs', 'components/schemas'); for (const v of Object.values(s)) { - fixSchema(v); + fixSchema(v, shouldStripDefs); } return s; }; + const stripExtraKeys = (s: T, shouldStripDefs: boolean): T => { + if (typeof s !== 'object' || !s) return s; + if (shouldStripDefs && '$defs' in s) { + s['$defs'] = undefined; + } + if ('$schema' in s) s['$schema'] = undefined; + if ('id' in s) s['id'] = undefined; + for (const v of Object.values(s)) { + stripExtraKeys(v, shouldStripDefs); + } + return s; + } + // TODO: try output first then fallback to input const schemaOpts: ToJSONSchemaParams = { // reused: 'ref', @@ -333,19 +347,19 @@ function finalize(info: ReflectionInfoRaw): ReflectionInfo { for (const param in route.params) { const zodSchema = route.params[param]; model[path + ' params ' + param] = zodSchema; - finalParams[param] = fixSchema(zodSchema.toJSONSchema(schemaOpts)); + finalParams[param] = fixSchema(zodSchema.toJSONSchema(schemaOpts), true); } const finalQuery: Record = {}; for (const key in route.query) { const zodSchema = route.query[key]; model[path + '?' + key] = zodSchema; - finalQuery[key] = fixSchema(zodSchema.toJSONSchema(schemaOpts)); + finalQuery[key] = fixSchema(zodSchema.toJSONSchema(schemaOpts), true); } const common = { path, params: finalParams, query: finalQuery, - resBody: route.resBody === null ? null : fixSchema(route.resBody.toJSONSchema(schemaOpts)), + resBody: route.resBody === null ? null : fixSchema(route.resBody.toJSONSchema(schemaOpts), true), summary: route.summary, description: route.description, }; @@ -357,7 +371,9 @@ function finalize(info: ReflectionInfoRaw): ReflectionInfo { resultRoutes.push({ ...common, method: 'post', - reqBody: route.reqBody === null ? null : fixSchema(route.reqBody.toJSONSchema(schemaOpts)), + reqBody: route.reqBody === null + ? null + : fixSchema(route.reqBody.toJSONSchema(schemaOpts), true), }); if (route.reqBody) model[path + ' reqBody'] = route.reqBody; @@ -371,7 +387,7 @@ function finalize(info: ReflectionInfoRaw): ReflectionInfo { } return { routes: resultRoutes, - defs: fixSchema(z.object(model).toJSONSchema(schemaOpts)).$defs ?? {}, + defs: fixSchema(z.object(model).toJSONSchema(schemaOpts), false).$defs ?? {}, }; } From 403ef7effd92ac66a6518a7130193b700a59204c Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Thu, 16 Jul 2026 22:51:32 -0700 Subject: [PATCH 06/36] try demo action --- .github/workflows/github-actions-demo.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/github-actions-demo.yml diff --git a/.github/workflows/github-actions-demo.yml b/.github/workflows/github-actions-demo.yml new file mode 100644 index 0000000..a51e406 --- /dev/null +++ b/.github/workflows/github-actions-demo.yml @@ -0,0 +1,17 @@ +name: GitHub Actions Demo +run-name: ${{ github.actor }} is testing out GitHub Actions +on: [push] +jobs: + Explore-GitHub-Actions: + runs-on: ubuntu-latest + steps: + - run: echo "job triggered by ${{ github.event_name }}" + - run: echo "running on ${{ runner.os }}" + - run: echo "branch is ${{ github.ref }}, repo is ${{ github.repository }}" + - name: Check out repository code + uses: actions/checkout@v6 + - run: echo "cloned" + - name: List files in the repo + run: | + ls ${{ github.workspace }} + - run: "job status: ${{ job.status }}" From 6bc0ca3a0c288d985ed5ebd6bf91b16bf3a02e67 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Thu, 16 Jul 2026 22:59:28 -0700 Subject: [PATCH 07/36] do some actual stuff --- .github/workflows/github-actions-demo.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/github-actions-demo.yml b/.github/workflows/github-actions-demo.yml index a51e406..3c938e6 100644 --- a/.github/workflows/github-actions-demo.yml +++ b/.github/workflows/github-actions-demo.yml @@ -14,4 +14,16 @@ jobs: - name: List files in the repo run: | ls ${{ github.workspace }} - - run: "job status: ${{ job.status }}" + # - name: Placeholder + # run: echo hi + # working-directory: ${{ github.workspace }} + - name: NPM Install + run: npm i + working-directory: ${{ github.workspace }} + - name: Typecheck + run: tsc --noEmit + working-directory: ${{ github.workspace }} + - name: Test Suite + run: npm test + working-directory: ${{ github.workspace }} + - run: echo "job status: ${{ job.status }}" From 325dfb847a9481633874c07ba5a3c3ca3d4b6ffc Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Thu, 16 Jul 2026 23:02:35 -0700 Subject: [PATCH 08/36] remove colon --- .github/workflows/github-actions-demo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-actions-demo.yml b/.github/workflows/github-actions-demo.yml index 3c938e6..5bce95d 100644 --- a/.github/workflows/github-actions-demo.yml +++ b/.github/workflows/github-actions-demo.yml @@ -26,4 +26,4 @@ jobs: - name: Test Suite run: npm test working-directory: ${{ github.workspace }} - - run: echo "job status: ${{ job.status }}" + - run: echo "job status ${{ job.status }}" From 12cc6b9f40be21092abf0e58b7dc40030c35d228 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Thu, 16 Jul 2026 23:06:42 -0700 Subject: [PATCH 09/36] behavior when jobs are ran in parallel? --- .github/workflows/github-actions-demo.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/github-actions-demo.yml b/.github/workflows/github-actions-demo.yml index 5bce95d..3765bd9 100644 --- a/.github/workflows/github-actions-demo.yml +++ b/.github/workflows/github-actions-demo.yml @@ -2,6 +2,10 @@ name: GitHub Actions Demo run-name: ${{ github.actor }} is testing out GitHub Actions on: [push] jobs: + Other-Job: + runs-on: ubuntu-latest + steps: + - run: echo "I am a step" Explore-GitHub-Actions: runs-on: ubuntu-latest steps: From 2f8e99a7e07ffc66647215e52f8f45b3b7e273a6 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Thu, 16 Jul 2026 23:13:31 -0700 Subject: [PATCH 10/36] typecheck and test in parallel --- .github/workflows/github-actions-demo.yml | 24 ++++++++++------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/.github/workflows/github-actions-demo.yml b/.github/workflows/github-actions-demo.yml index 3765bd9..fa9b946 100644 --- a/.github/workflows/github-actions-demo.yml +++ b/.github/workflows/github-actions-demo.yml @@ -2,22 +2,22 @@ name: GitHub Actions Demo run-name: ${{ github.actor }} is testing out GitHub Actions on: [push] jobs: - Other-Job: + Tests: runs-on: ubuntu-latest steps: - - run: echo "I am a step" - Explore-GitHub-Actions: + - name: Check out repository code + uses: actions/checkout@v6 + - name: NPM Install + run: npm i + working-directory: ${{ github.workspace }} + - name: Test Suite + run: npm test + working-directory: ${{ github.workspace }} + Typecheck: runs-on: ubuntu-latest steps: - - run: echo "job triggered by ${{ github.event_name }}" - - run: echo "running on ${{ runner.os }}" - - run: echo "branch is ${{ github.ref }}, repo is ${{ github.repository }}" - name: Check out repository code uses: actions/checkout@v6 - - run: echo "cloned" - - name: List files in the repo - run: | - ls ${{ github.workspace }} # - name: Placeholder # run: echo hi # working-directory: ${{ github.workspace }} @@ -27,7 +27,3 @@ jobs: - name: Typecheck run: tsc --noEmit working-directory: ${{ github.workspace }} - - name: Test Suite - run: npm test - working-directory: ${{ github.workspace }} - - run: echo "job status ${{ job.status }}" From 25bd00a9904ed79df0a3235fef950383cdf24c13 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Thu, 16 Jul 2026 23:22:36 -0700 Subject: [PATCH 11/36] tests wait for backend to start, adjust tsconfig --- .github/workflows/github-actions-demo.yml | 9 ++++++++- tsconfig.json | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/github-actions-demo.yml b/.github/workflows/github-actions-demo.yml index fa9b946..fda864e 100644 --- a/.github/workflows/github-actions-demo.yml +++ b/.github/workflows/github-actions-demo.yml @@ -11,7 +11,14 @@ jobs: run: npm i working-directory: ${{ github.workspace }} - name: Test Suite - run: npm test + run: | + npm start & + until curl localhost:3000 + do + echo "waiting..." + sleep 1 + done + npm test working-directory: ${{ github.workspace }} Typecheck: runs-on: ubuntu-latest diff --git a/tsconfig.json b/tsconfig.json index 144c0e8..10ce3c8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,14 +1,14 @@ { "compilerOptions": { - "target": "es6", + "target": "es2021", "module": "esnext", - "moduleResolution": "Node", + // "moduleResolution": "Node", "esModuleInterop": true, "resolveJsonModule": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, - "baseUrl": "./", + // "baseUrl": "./", "paths": { "@/*": [ "./src/*" From 4d80143dd14107e092bc5294facd89a988e6a7e1 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Fri, 17 Jul 2026 21:11:21 -0700 Subject: [PATCH 12/36] rename and clean up title --- .../workflows/{github-actions-demo.yml => ci.yml} | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) rename .github/workflows/{github-actions-demo.yml => ci.yml} (83%) diff --git a/.github/workflows/github-actions-demo.yml b/.github/workflows/ci.yml similarity index 83% rename from .github/workflows/github-actions-demo.yml rename to .github/workflows/ci.yml index fda864e..d793911 100644 --- a/.github/workflows/github-actions-demo.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,10 @@ -name: GitHub Actions Demo -run-name: ${{ github.actor }} is testing out GitHub Actions -on: [push] +name: CI +run-name: CI for ${{ github.ref }} +on: + - push: + branches-ignore: + - "tools/*" + - pull_request jobs: Tests: runs-on: ubuntu-latest @@ -13,7 +17,7 @@ jobs: - name: Test Suite run: | npm start & - until curl localhost:3000 + until curl localhost:3000 > /dev/null 2>&1 do echo "waiting..." sleep 1 From 4264eda39427b7e62051eb4eccab6a813b88f6df Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Fri, 17 Jul 2026 21:16:29 -0700 Subject: [PATCH 13/36] use mock bustimes --- .github/workflows/ci.yml | 6 ++++-- src/services/mbus.ts | 2 +- src/services/ride.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d793911..d8f27ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,10 @@ name: CI run-name: CI for ${{ github.ref }} on: - - push: + push: branches-ignore: - "tools/*" - - pull_request + pull_request: jobs: Tests: runs-on: ubuntu-latest @@ -16,6 +16,8 @@ jobs: working-directory: ${{ github.workspace }} - name: Test Suite run: | + export MBUS_URL=https://mbus.bustime.mock.mb.thething.fyi/ + export RIDE_URL=https://ride.bustime.mock.mb.thething.fyi/ npm start & until curl localhost:3000 > /dev/null 2>&1 do diff --git a/src/services/mbus.ts b/src/services/mbus.ts index 1c9eeeb..a479f5a 100644 --- a/src/services/mbus.ts +++ b/src/services/mbus.ts @@ -5,7 +5,7 @@ import dotenv from "dotenv"; dotenv.config(); const API_KEY = process.env.MBUS_API_KEY; -const BASE_URL = 'https://mbus.ltp.umich.edu/bustime/api/v3/'; +const BASE_URL = process.env.MBUS_URL || 'https://mbus.ltp.umich.edu/bustime/api/v3/'; const client = axios.create({ baseURL: BASE_URL, diff --git a/src/services/ride.ts b/src/services/ride.ts index 2a6455f..bd8fd31 100644 --- a/src/services/ride.ts +++ b/src/services/ride.ts @@ -7,7 +7,7 @@ import dotenv from "dotenv"; dotenv.config(); const RIDE_API_KEY = process.env.RIDE_API_KEY; -const BASE_URL = 'https://rt.theride.org/bustime/api/v3/'; +const BASE_URL = process.env.RIDE_URL || 'https://rt.theride.org/bustime/api/v3/'; const client = axios.create({ baseURL: BASE_URL, From 3e2ceb22e71e7871932ce92c54a79ddde37584c5 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Fri, 17 Jul 2026 21:40:18 -0700 Subject: [PATCH 14/36] add delay so backend starts correctly before tests are run --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8f27ab..0a7c316 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,7 @@ jobs: echo "waiting..." sleep 1 done + sleep 10 npm test working-directory: ${{ github.workspace }} Typecheck: From 1d639c55a4ff6741a11dfeeff8620c815ef9f10e Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Fri, 17 Jul 2026 22:11:21 -0700 Subject: [PATCH 15/36] attempt fix of timeouts, reduce amount of logging --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a7c316..8711e78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,11 +21,11 @@ jobs: npm start & until curl localhost:3000 > /dev/null 2>&1 do - echo "waiting..." sleep 1 done sleep 10 - npm test + npx vitest run test --no-file-parallelism + # npm test working-directory: ${{ github.workspace }} Typecheck: runs-on: ubuntu-latest From 2fff9bbf226b590c8c6e1f31161327ea2792d46a Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Fri, 17 Jul 2026 22:30:33 -0700 Subject: [PATCH 16/36] add more waiting for walking cache --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8711e78..e44d0b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,11 @@ jobs: sleep 1 done sleep 10 - npx vitest run test --no-file-parallelism + until curl localhost:3000 > /dev/null 2>&1 + do + sleep 1 # waits for the walking cache to populate + done + npx vitest run test # npm test working-directory: ${{ github.workspace }} Typecheck: From 18ca7cdea6dc93f69ea0bf280c1f7148fd66770d Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Fri, 17 Jul 2026 22:50:14 -0700 Subject: [PATCH 17/36] add even more delays to fix graph not being built --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e44d0b9..1fa0b5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,13 +21,14 @@ jobs: npm start & until curl localhost:3000 > /dev/null 2>&1 do - sleep 1 + sleep 1 # waits for initial startup done sleep 10 until curl localhost:3000 > /dev/null 2>&1 do sleep 1 # waits for the walking cache to populate done + sleep 10 # waits for the graph/predictions to be built npx vitest run test # npm test working-directory: ${{ github.workspace }} From 1de0112c7a4fbc38911103aecadd1794d774090c Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Sat, 18 Jul 2026 07:19:41 -0700 Subject: [PATCH 18/36] make reminder tests work with mock bustimes --- test/reminder.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/reminder.test.ts b/test/reminder.test.ts index 5a2a8f8..0d991e8 100644 --- a/test/reminder.test.ts +++ b/test/reminder.test.ts @@ -203,14 +203,14 @@ describe('Reminders', () => { it('should get unix timestamp from ride bus api', async () => { configDotenv(); const RIDE_API_KEY = process.env.RIDE_API_KEY; - const BASE_URL = 'https://rt.theride.org/bustime/api/v3/'; + const BASE_URL = process.env.RIDE_URL || 'https://rt.theride.org/bustime/api/v3/'; const client = axios.create({ baseURL: BASE_URL, params: { key: RIDE_API_KEY, format: 'json' } }); const res = await client.get('/gettime', { params: { unixTime: true } }); - expect(Math.abs(parseInt(res.data["bustime-response"]["tm"]) - Date.now())).toBeLessThan(60 * 1000); + expect(Math.abs(parseInt(res.data["bustime-response"]["tm"]) - Date.now())).toBeLessThan(2 * 24 * 60 * 60 * 1000); }); it('should have cached preds in a good state', async () => { @@ -229,7 +229,7 @@ describe('Reminders', () => { for (const k in sample) { expect(k + ":" +typeof x[k]).toBe(k + ":" + typeof sample[k]); if (k == "prdtm") { - expect(x[k]).toBeGreaterThanOrEqual(Date.now() - 15 * 1000); + expect(x[k]).toBeGreaterThanOrEqual(Date.now() - 2 * 24 * 60 * 60 * 1000); } } }; From 625547412d4c347d0c5820c5fdd4cd9e3955a73f Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Sat, 18 Jul 2026 21:01:41 -0700 Subject: [PATCH 19/36] FTP test --- .github/workflows/ci.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1fa0b5d..fb7b89b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,7 @@ jobs: npx vitest run test # npm test working-directory: ${{ github.workspace }} + Typecheck: runs-on: ubuntu-latest steps: @@ -46,3 +47,17 @@ jobs: - name: Typecheck run: tsc --noEmit working-directory: ${{ github.workspace }} + + Typedoc: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v6 + - name: Sync files + uses: SamKirkland/FTP-Deploy-Action@v4.4.0 + with: + server: ${{ secrets.FTP_SERVER }} + username: ${{ secrets.FTP_USERNAME }} + password: ${{ secrets.FTP_PASSWORD }} + local-dir: ${{ github.workspace }}/docs/ + server-dir: ${{ github.ref }}/typedoc/ From 8d723cce1bc9b1c8c4a95a07ebee3d43c5388211 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Sat, 18 Jul 2026 21:03:24 -0700 Subject: [PATCH 20/36] fix missing port --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb7b89b..a82ad7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,6 +57,7 @@ jobs: uses: SamKirkland/FTP-Deploy-Action@v4.4.0 with: server: ${{ secrets.FTP_SERVER }} + port: ${{ secrets.FTP_PORT }} username: ${{ secrets.FTP_USERNAME }} password: ${{ secrets.FTP_PASSWORD }} local-dir: ${{ github.workspace }}/docs/ From 14e56428e2e6dea034b994468bedde88389d3c5a Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Sat, 18 Jul 2026 21:33:02 -0700 Subject: [PATCH 21/36] remove existing docs, build from scratch --- .github/workflows/ci.yml | 6 + docs/.nojekyll | 1 - docs/assets/hierarchy.js | 1 - docs/assets/highlight.css | 43 - docs/assets/icons.js | 18 - docs/assets/icons.svg | 1 - docs/assets/main.js | 60 - docs/assets/navigation.js | 1 - docs/assets/search.js | 1 - docs/assets/style.css | 1633 ----------------- ...r_McRaptorAlgorithm.McRaptorAlgorithm.html | 30 - docs/classes/raptor_McStructs.Bag.html | 13 - docs/classes/raptor_McStructs.Label.html | 36 - docs/functions/jobs.startBackgroundJobs.html | 2 - .../routes_api.activeRemindersForToken.html | 3 - .../routes_api.getAllPredictions.html | 5 - .../routes_api.getAllRideRoutes.html | 5 - .../functions/routes_api.getAllRideStops.html | 5 - docs/functions/routes_api.getAllRoutes.html | 5 - docs/functions/routes_api.getAllStops.html | 5 - .../routes_api.getBuildingLocations.html | 5 - .../functions/routes_api.getBusPositions.html | 5 - .../routes_api.getBusPredictions.html | 5 - .../routes_api.getBusPredictionsLegacy.html | 4 - .../functions/routes_api.getFrontendData.html | 5 - docs/functions/routes_api.getKeyStops.html | 5 - .../functions/routes_api.getNearestStops.html | 5 - .../routes_api.getRidePositions.html | 5 - .../routes_api.getRidePredictions.html | 5 - .../routes_api.getRideStopPredictions.html | 5 - docs/functions/routes_api.getRouteCache.html | 5 - docs/functions/routes_api.getRouteColor.html | 5 - docs/functions/routes_api.getRouteColors.html | 5 - .../routes_api.getRouteInfoVersion.html | 5 - .../routes_api.getRouteInformation.html | 5 - .../routes_api.getSelectableRoutes.html | 5 - docs/functions/routes_api.getStartupInfo.html | 5 - .../routes_api.getStartupMessages.html | 5 - .../routes_api.getStopPredictions.html | 5 - .../functions/routes_api.getVehicleImage.html | 5 - .../routes_api.getVehiclePositions.html | 5 - .../functions/routes_api.modifyReminders.html | 4 - docs/functions/routes_api.notifyMeLater.html | 1 - docs/functions/routes_api.planJourney.html | 5 - docs/functions/routes_api.saveGraph.html | 5 - docs/functions/routes_api.setReminder.html | 3 - docs/functions/routes_api.swapToken.html | 5 - docs/functions/routes_api.unsetReminder.html | 3 - ...ervices_graphBuilder.findNearestStops.html | 2 - ...ervices_graphBuilder.initializeRoutes.html | 2 - .../services_graphBuilder.rebuildGraph.html | 2 - .../services_graphBuilder.saveGraphState.html | 2 - .../services_graphBuilder.sortPreds.html | 2 - ...vices_graphBuilder.updateBusPositions.html | 2 - .../services_journey.planJourney.html | 8 - .../services_mbus.fetchPatterns.html | 2 - .../services_mbus.fetchPredictions.html | 2 - docs/functions/services_mbus.fetchRoutes.html | 2 - .../services_mbus.fetchVehicles.html | 2 - .../services_metadata.getAllRouteConfig.html | 2 - .../services_metadata.getRouteColor.html | 2 - .../services_metadata.getRouteImage.html | 2 - .../services_reminder.eventsEqual.html | 1 - .../services_reminder.infoToUseForRoute.html | 1 - ...ervices_reminder.processRideReminders.html | 1 - ...s_reminder.processUniversityReminders.html | 1 - .../services_reminder.sendNotifToAll.html | 1 - .../services_reminderTypes.baseEvent.html | 2 - .../services_reminderTypes.delayEvent.html | 2 - .../services_reminderTypes.eventsEqual.html | 1 - .../services_reminderTypes.fromKey.html | 1 - ...vices_reminderTypes.registrationToken.html | 1 - .../services_reminderTypes.sameBaseEvent.html | 1 - ...services_reminderTypes.thresholdEvent.html | 2 - .../services_reminderTypes.toKey.html | 2 - .../services_ride.fetchPatterns.html | 2 - .../services_ride.fetchPredictions.html | 2 - docs/functions/services_ride.fetchRoutes.html | 2 - .../services_ride.fetchVehicles.html | 2 - .../state_transitState.setCachedGraph.html | 2 - ...ansitState.setCachedRideStopLocations.html | 2 - ...e_transitState.setCachedStopLocations.html | 2 - docs/functions/walking_loadMap.haversine.html | 7 - docs/functions/walking_loadMap.loadMap.html | 16 - .../walking_walkingMap.buildStopNodeMap.html | 4 - ...alking_walkingMap.ensureCacheForStops.html | 5 - .../walking_walkingMap.getCachedWalk.html | 5 - ...ng_walkingMap.getWalkingDistancesFrom.html | 8 - ...walking_walkingMap.getWalkingResponse.html | 7 - docs/hierarchy.html | 1 - docs/index.html | 39 - .../raptor_McRaptorAlgorithm.Journey.html | 6 - .../raptor_McRaptorAlgorithm.JourneyLeg.html | 14 - docs/interfaces/raptor_types.Leg.html | 4 - docs/interfaces/raptor_types.StopTime.html | 16 - .../interfaces/raptor_types.TimetableLeg.html | 6 - docs/interfaces/raptor_types.Transfer.html | 9 - docs/interfaces/raptor_types.Trip.html | 7 - ...walking_walkingMap.BatchWalkingResult.html | 8 - .../walking_walkingMap.WalkingResponse.html | 8 - docs/media/logo.png | Bin 61262 -> 0 bytes docs/media/test_example.png | Bin 54791 -> 0 bytes docs/modules.html | 1 - docs/modules/app.html | 1 - docs/modules/jobs.html | 1 - docs/modules/raptor_McRaptorAlgorithm.html | 1 - docs/modules/raptor_McStructs.html | 1 - docs/modules/raptor_types.html | 1 - docs/modules/routes_api.html | 1 - docs/modules/services_graphBuilder.html | 1 - docs/modules/services_journey.html | 1 - docs/modules/services_mbus.html | 1 - docs/modules/services_metadata.html | 1 - docs/modules/services_reminder.html | 1 - docs/modules/services_reminderTypes.html | 1 - docs/modules/services_ride.html | 1 - docs/modules/state_transitState.html | 1 - docs/modules/types.html | 1 - docs/modules/walking_loadMap.html | 1 - docs/modules/walking_types.html | 1 - docs/modules/walking_walkingMap.html | 1 - docs/types/raptor_McStructs.Criteria.html | 5 - docs/types/raptor_types.Duration.html | 2 - docs/types/raptor_types.Interchange.html | 2 - docs/types/raptor_types.StopID.html | 2 - docs/types/raptor_types.Time.html | 3 - .../types/raptor_types.TransfersByOrigin.html | 2 - docs/types/raptor_types.TripID.html | 2 - .../services_reminder.PostThreshold.html | 7 - .../types/services_reminder.PreThreshold.html | 15 - .../services_reminderTypes.BaseEvent.html | 1 - .../services_reminderTypes.DelayEvent.html | 1 - docs/types/services_reminderTypes.Key.html | 1 - ...vices_reminderTypes.RegistrationToken.html | 1 - ...services_reminderTypes.ThresholdEvent.html | 1 - docs/types/state_transitState.Prediction.html | 2 - docs/types/types.Route.html | 2 - docs/types/walking_types.GraphMLEdge.html | 8 - docs/types/walking_types.GraphMLNode.html | 8 - docs/types/walking_types.LandmarkDef.html | 10 - docs/variables/routes_api.default.html | 3 - .../services_metadata.staticData.html | 2 - ...es_reminder.rideReminderSubscriptions.html | 1 - docs/variables/services_reminder.testing.html | 2 - ...inder.universityReminderSubscriptions.html | 1 - .../state_transitState.cachedGraph.html | 2 - ...tate_transitState.cachedPredsByStopId.html | 2 - .../state_transitState.cachedPredsByVid.html | 2 - ..._transitState.cachedRidePredsByStopId.html | 2 - ...ate_transitState.cachedRidePredsByVid.html | 2 - .../state_transitState.cachedRideRoutes.html | 2 - ..._transitState.cachedRideStopLocations.html | 1 - .../state_transitState.cachedRoutes.html | 2 - ...tate_transitState.cachedStopLocations.html | 2 - .../state_transitState.curBusPositions.html | 2 - .../state_transitState.curRidePositions.html | 2 - .../state_transitState.rideStopIdToName.html | 2 - .../state_transitState.routeTimingCache.html | 2 - .../state_transitState.stopIdToName.html | 2 - .../state_transitState.tatripidToRt.html | 2 - .../state_transitState.validRideRoutes.html | 2 - .../state_transitState.validRoutes.html | 2 - 162 files changed, 6 insertions(+), 2369 deletions(-) delete mode 100644 docs/.nojekyll delete mode 100644 docs/assets/hierarchy.js delete mode 100644 docs/assets/highlight.css delete mode 100644 docs/assets/icons.js delete mode 100644 docs/assets/icons.svg delete mode 100644 docs/assets/main.js delete mode 100644 docs/assets/navigation.js delete mode 100644 docs/assets/search.js delete mode 100644 docs/assets/style.css delete mode 100644 docs/classes/raptor_McRaptorAlgorithm.McRaptorAlgorithm.html delete mode 100644 docs/classes/raptor_McStructs.Bag.html delete mode 100644 docs/classes/raptor_McStructs.Label.html delete mode 100644 docs/functions/jobs.startBackgroundJobs.html delete mode 100644 docs/functions/routes_api.activeRemindersForToken.html delete mode 100644 docs/functions/routes_api.getAllPredictions.html delete mode 100644 docs/functions/routes_api.getAllRideRoutes.html delete mode 100644 docs/functions/routes_api.getAllRideStops.html delete mode 100644 docs/functions/routes_api.getAllRoutes.html delete mode 100644 docs/functions/routes_api.getAllStops.html delete mode 100644 docs/functions/routes_api.getBuildingLocations.html delete mode 100644 docs/functions/routes_api.getBusPositions.html delete mode 100644 docs/functions/routes_api.getBusPredictions.html delete mode 100644 docs/functions/routes_api.getBusPredictionsLegacy.html delete mode 100644 docs/functions/routes_api.getFrontendData.html delete mode 100644 docs/functions/routes_api.getKeyStops.html delete mode 100644 docs/functions/routes_api.getNearestStops.html delete mode 100644 docs/functions/routes_api.getRidePositions.html delete mode 100644 docs/functions/routes_api.getRidePredictions.html delete mode 100644 docs/functions/routes_api.getRideStopPredictions.html delete mode 100644 docs/functions/routes_api.getRouteCache.html delete mode 100644 docs/functions/routes_api.getRouteColor.html delete mode 100644 docs/functions/routes_api.getRouteColors.html delete mode 100644 docs/functions/routes_api.getRouteInfoVersion.html delete mode 100644 docs/functions/routes_api.getRouteInformation.html delete mode 100644 docs/functions/routes_api.getSelectableRoutes.html delete mode 100644 docs/functions/routes_api.getStartupInfo.html delete mode 100644 docs/functions/routes_api.getStartupMessages.html delete mode 100644 docs/functions/routes_api.getStopPredictions.html delete mode 100644 docs/functions/routes_api.getVehicleImage.html delete mode 100644 docs/functions/routes_api.getVehiclePositions.html delete mode 100644 docs/functions/routes_api.modifyReminders.html delete mode 100644 docs/functions/routes_api.notifyMeLater.html delete mode 100644 docs/functions/routes_api.planJourney.html delete mode 100644 docs/functions/routes_api.saveGraph.html delete mode 100644 docs/functions/routes_api.setReminder.html delete mode 100644 docs/functions/routes_api.swapToken.html delete mode 100644 docs/functions/routes_api.unsetReminder.html delete mode 100644 docs/functions/services_graphBuilder.findNearestStops.html delete mode 100644 docs/functions/services_graphBuilder.initializeRoutes.html delete mode 100644 docs/functions/services_graphBuilder.rebuildGraph.html delete mode 100644 docs/functions/services_graphBuilder.saveGraphState.html delete mode 100644 docs/functions/services_graphBuilder.sortPreds.html delete mode 100644 docs/functions/services_graphBuilder.updateBusPositions.html delete mode 100644 docs/functions/services_journey.planJourney.html delete mode 100644 docs/functions/services_mbus.fetchPatterns.html delete mode 100644 docs/functions/services_mbus.fetchPredictions.html delete mode 100644 docs/functions/services_mbus.fetchRoutes.html delete mode 100644 docs/functions/services_mbus.fetchVehicles.html delete mode 100644 docs/functions/services_metadata.getAllRouteConfig.html delete mode 100644 docs/functions/services_metadata.getRouteColor.html delete mode 100644 docs/functions/services_metadata.getRouteImage.html delete mode 100644 docs/functions/services_reminder.eventsEqual.html delete mode 100644 docs/functions/services_reminder.infoToUseForRoute.html delete mode 100644 docs/functions/services_reminder.processRideReminders.html delete mode 100644 docs/functions/services_reminder.processUniversityReminders.html delete mode 100644 docs/functions/services_reminder.sendNotifToAll.html delete mode 100644 docs/functions/services_reminderTypes.baseEvent.html delete mode 100644 docs/functions/services_reminderTypes.delayEvent.html delete mode 100644 docs/functions/services_reminderTypes.eventsEqual.html delete mode 100644 docs/functions/services_reminderTypes.fromKey.html delete mode 100644 docs/functions/services_reminderTypes.registrationToken.html delete mode 100644 docs/functions/services_reminderTypes.sameBaseEvent.html delete mode 100644 docs/functions/services_reminderTypes.thresholdEvent.html delete mode 100644 docs/functions/services_reminderTypes.toKey.html delete mode 100644 docs/functions/services_ride.fetchPatterns.html delete mode 100644 docs/functions/services_ride.fetchPredictions.html delete mode 100644 docs/functions/services_ride.fetchRoutes.html delete mode 100644 docs/functions/services_ride.fetchVehicles.html delete mode 100644 docs/functions/state_transitState.setCachedGraph.html delete mode 100644 docs/functions/state_transitState.setCachedRideStopLocations.html delete mode 100644 docs/functions/state_transitState.setCachedStopLocations.html delete mode 100644 docs/functions/walking_loadMap.haversine.html delete mode 100644 docs/functions/walking_loadMap.loadMap.html delete mode 100644 docs/functions/walking_walkingMap.buildStopNodeMap.html delete mode 100644 docs/functions/walking_walkingMap.ensureCacheForStops.html delete mode 100644 docs/functions/walking_walkingMap.getCachedWalk.html delete mode 100644 docs/functions/walking_walkingMap.getWalkingDistancesFrom.html delete mode 100644 docs/functions/walking_walkingMap.getWalkingResponse.html delete mode 100644 docs/hierarchy.html delete mode 100644 docs/index.html delete mode 100644 docs/interfaces/raptor_McRaptorAlgorithm.Journey.html delete mode 100644 docs/interfaces/raptor_McRaptorAlgorithm.JourneyLeg.html delete mode 100644 docs/interfaces/raptor_types.Leg.html delete mode 100644 docs/interfaces/raptor_types.StopTime.html delete mode 100644 docs/interfaces/raptor_types.TimetableLeg.html delete mode 100644 docs/interfaces/raptor_types.Transfer.html delete mode 100644 docs/interfaces/raptor_types.Trip.html delete mode 100644 docs/interfaces/walking_walkingMap.BatchWalkingResult.html delete mode 100644 docs/interfaces/walking_walkingMap.WalkingResponse.html delete mode 100644 docs/media/logo.png delete mode 100644 docs/media/test_example.png delete mode 100644 docs/modules.html delete mode 100644 docs/modules/app.html delete mode 100644 docs/modules/jobs.html delete mode 100644 docs/modules/raptor_McRaptorAlgorithm.html delete mode 100644 docs/modules/raptor_McStructs.html delete mode 100644 docs/modules/raptor_types.html delete mode 100644 docs/modules/routes_api.html delete mode 100644 docs/modules/services_graphBuilder.html delete mode 100644 docs/modules/services_journey.html delete mode 100644 docs/modules/services_mbus.html delete mode 100644 docs/modules/services_metadata.html delete mode 100644 docs/modules/services_reminder.html delete mode 100644 docs/modules/services_reminderTypes.html delete mode 100644 docs/modules/services_ride.html delete mode 100644 docs/modules/state_transitState.html delete mode 100644 docs/modules/types.html delete mode 100644 docs/modules/walking_loadMap.html delete mode 100644 docs/modules/walking_types.html delete mode 100644 docs/modules/walking_walkingMap.html delete mode 100644 docs/types/raptor_McStructs.Criteria.html delete mode 100644 docs/types/raptor_types.Duration.html delete mode 100644 docs/types/raptor_types.Interchange.html delete mode 100644 docs/types/raptor_types.StopID.html delete mode 100644 docs/types/raptor_types.Time.html delete mode 100644 docs/types/raptor_types.TransfersByOrigin.html delete mode 100644 docs/types/raptor_types.TripID.html delete mode 100644 docs/types/services_reminder.PostThreshold.html delete mode 100644 docs/types/services_reminder.PreThreshold.html delete mode 100644 docs/types/services_reminderTypes.BaseEvent.html delete mode 100644 docs/types/services_reminderTypes.DelayEvent.html delete mode 100644 docs/types/services_reminderTypes.Key.html delete mode 100644 docs/types/services_reminderTypes.RegistrationToken.html delete mode 100644 docs/types/services_reminderTypes.ThresholdEvent.html delete mode 100644 docs/types/state_transitState.Prediction.html delete mode 100644 docs/types/types.Route.html delete mode 100644 docs/types/walking_types.GraphMLEdge.html delete mode 100644 docs/types/walking_types.GraphMLNode.html delete mode 100644 docs/types/walking_types.LandmarkDef.html delete mode 100644 docs/variables/routes_api.default.html delete mode 100644 docs/variables/services_metadata.staticData.html delete mode 100644 docs/variables/services_reminder.rideReminderSubscriptions.html delete mode 100644 docs/variables/services_reminder.testing.html delete mode 100644 docs/variables/services_reminder.universityReminderSubscriptions.html delete mode 100644 docs/variables/state_transitState.cachedGraph.html delete mode 100644 docs/variables/state_transitState.cachedPredsByStopId.html delete mode 100644 docs/variables/state_transitState.cachedPredsByVid.html delete mode 100644 docs/variables/state_transitState.cachedRidePredsByStopId.html delete mode 100644 docs/variables/state_transitState.cachedRidePredsByVid.html delete mode 100644 docs/variables/state_transitState.cachedRideRoutes.html delete mode 100644 docs/variables/state_transitState.cachedRideStopLocations.html delete mode 100644 docs/variables/state_transitState.cachedRoutes.html delete mode 100644 docs/variables/state_transitState.cachedStopLocations.html delete mode 100644 docs/variables/state_transitState.curBusPositions.html delete mode 100644 docs/variables/state_transitState.curRidePositions.html delete mode 100644 docs/variables/state_transitState.rideStopIdToName.html delete mode 100644 docs/variables/state_transitState.routeTimingCache.html delete mode 100644 docs/variables/state_transitState.stopIdToName.html delete mode 100644 docs/variables/state_transitState.tatripidToRt.html delete mode 100644 docs/variables/state_transitState.validRideRoutes.html delete mode 100644 docs/variables/state_transitState.validRoutes.html diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a82ad7d..fb9522c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,12 @@ jobs: steps: - name: Check out repository code uses: actions/checkout@v6 + - name: NPM Install + run: npm i + working-directory: ${{ github.workspace }} + - name: Build Docs + run: npm run docs + working-directory: ${{ github.workspace }} - name: Sync files uses: SamKirkland/FTP-Deploy-Action@v4.4.0 with: diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e2ac661..0000000 --- a/docs/.nojekyll +++ /dev/null @@ -1 +0,0 @@ -TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/docs/assets/hierarchy.js b/docs/assets/hierarchy.js deleted file mode 100644 index c11396f..0000000 --- a/docs/assets/hierarchy.js +++ /dev/null @@ -1 +0,0 @@ -window.hierarchyData = "eJyVjjEOgzAMRe/iOVAFVAbO0LFbhVAKpkQNCXLcoULcvU5VVYwwWfr28/sLUAgcob7pomwUEA4OO7bBS7aAhGl4MyHUcMEHKHha30NdnCsFL3ISW89Ig+kwnsjMHKjl94wxl/N85MkJ0zkT5SFw7LPEZ38mLUfrekL/LVEpXepmVaKuNuqrnZDN3eHBDltuR5nkLfXWS8bHAemQ88fs8a3rB573gTo=" \ No newline at end of file diff --git a/docs/assets/highlight.css b/docs/assets/highlight.css deleted file mode 100644 index bc36a19..0000000 --- a/docs/assets/highlight.css +++ /dev/null @@ -1,43 +0,0 @@ -:root { - --light-hl-0: #795E26; - --dark-hl-0: #DCDCAA; - --light-hl-1: #000000; - --dark-hl-1: #D4D4D4; - --light-hl-2: #A31515; - --dark-hl-2: #CE9178; - --light-code-background: #FFFFFF; - --dark-code-background: #1E1E1E; -} - -@media (prefers-color-scheme: light) { :root { - --hl-0: var(--light-hl-0); - --hl-1: var(--light-hl-1); - --hl-2: var(--light-hl-2); - --code-background: var(--light-code-background); -} } - -@media (prefers-color-scheme: dark) { :root { - --hl-0: var(--dark-hl-0); - --hl-1: var(--dark-hl-1); - --hl-2: var(--dark-hl-2); - --code-background: var(--dark-code-background); -} } - -:root[data-theme='light'] { - --hl-0: var(--light-hl-0); - --hl-1: var(--light-hl-1); - --hl-2: var(--light-hl-2); - --code-background: var(--light-code-background); -} - -:root[data-theme='dark'] { - --hl-0: var(--dark-hl-0); - --hl-1: var(--dark-hl-1); - --hl-2: var(--dark-hl-2); - --code-background: var(--dark-code-background); -} - -.hl-0 { color: var(--hl-0); } -.hl-1 { color: var(--hl-1); } -.hl-2 { color: var(--hl-2); } -pre, code { background: var(--code-background); } diff --git a/docs/assets/icons.js b/docs/assets/icons.js deleted file mode 100644 index 58882d7..0000000 --- a/docs/assets/icons.js +++ /dev/null @@ -1,18 +0,0 @@ -(function() { - addIcons(); - function addIcons() { - if (document.readyState === "loading") return document.addEventListener("DOMContentLoaded", addIcons); - const svg = document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); - svg.innerHTML = `MMNEPVFCICPMFPCPTTAAATR`; - svg.style.display = "none"; - if (location.protocol === "file:") updateUseElements(); - } - - function updateUseElements() { - document.querySelectorAll("use").forEach(el => { - if (el.getAttribute("href").includes("#icon-")) { - el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); - } - }); - } -})() \ No newline at end of file diff --git a/docs/assets/icons.svg b/docs/assets/icons.svg deleted file mode 100644 index 50ad579..0000000 --- a/docs/assets/icons.svg +++ /dev/null @@ -1 +0,0 @@ -MMNEPVFCICPMFPCPTTAAATR \ No newline at end of file diff --git a/docs/assets/main.js b/docs/assets/main.js deleted file mode 100644 index 64b80ab..0000000 --- a/docs/assets/main.js +++ /dev/null @@ -1,60 +0,0 @@ -"use strict"; -window.translations={"copy":"Copy","copied":"Copied!","normally_hidden":"This member is normally hidden due to your filter settings.","hierarchy_expand":"Expand","hierarchy_collapse":"Collapse","folder":"Folder","search_index_not_available":"The search index is not available","search_no_results_found_for_0":"No results found for {0}","kind_1":"Project","kind_2":"Module","kind_4":"Namespace","kind_8":"Enumeration","kind_16":"Enumeration Member","kind_32":"Variable","kind_64":"Function","kind_128":"Class","kind_256":"Interface","kind_512":"Constructor","kind_1024":"Property","kind_2048":"Method","kind_4096":"Call Signature","kind_8192":"Index Signature","kind_16384":"Constructor Signature","kind_32768":"Parameter","kind_65536":"Type Literal","kind_131072":"Type Parameter","kind_262144":"Accessor","kind_524288":"Get Signature","kind_1048576":"Set Signature","kind_2097152":"Type Alias","kind_4194304":"Reference","kind_8388608":"Document"}; -"use strict";(()=>{var Ke=Object.create;var he=Object.defineProperty;var Ge=Object.getOwnPropertyDescriptor;var Ze=Object.getOwnPropertyNames;var Xe=Object.getPrototypeOf,Ye=Object.prototype.hasOwnProperty;var et=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var tt=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Ze(e))!Ye.call(t,i)&&i!==n&&he(t,i,{get:()=>e[i],enumerable:!(r=Ge(e,i))||r.enumerable});return t};var nt=(t,e,n)=>(n=t!=null?Ke(Xe(t)):{},tt(e||!t||!t.__esModule?he(n,"default",{value:t,enumerable:!0}):n,t));var ye=et((me,ge)=>{(function(){var t=function(e){var n=new t.Builder;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),n.searchPipeline.add(t.stemmer),e.call(n,n),n.build()};t.version="2.3.9";t.utils={},t.utils.warn=(function(e){return function(n){e.console&&console.warn&&console.warn(n)}})(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var n=Object.create(null),r=Object.keys(e),i=0;i0){var d=t.utils.clone(n)||{};d.position=[a,l],d.index=s.length,s.push(new t.Token(r.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. -`,e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(r){var i=t.Pipeline.registeredFunctions[r];if(i)n.add(i);else throw new Error("Cannot load unregistered function: "+r)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(n){t.Pipeline.warnIfFunctionNotRegistered(n),this._stack.push(n)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");r=r+1,this._stack.splice(r,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");this._stack.splice(r,0,n)},t.Pipeline.prototype.remove=function(e){var n=this._stack.indexOf(e);n!=-1&&this._stack.splice(n,1)},t.Pipeline.prototype.run=function(e){for(var n=this._stack.length,r=0;r1&&(oe&&(r=s),o!=e);)i=r-n,s=n+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(oc?d+=2:a==c&&(n+=r[l+1]*i[d+1],l+=2,d+=2);return n},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),n=1,r=0;n0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}if(s.str.length==0&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}s.str.length==1&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),f=s.str.charAt(1),p;f in s.node.edges?p=s.node.edges[f]:(p=new t.TokenSet,s.node.edges[f]=p),s.str.length==1&&(p.final=!0),i.push({node:p,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return r},t.TokenSet.fromString=function(e){for(var n=new t.TokenSet,r=n,i=0,s=e.length;i=e;n--){var r=this.uncheckedNodes[n],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(n){var r=new t.QueryParser(e,n);r.parse()})},t.Index.prototype.query=function(e){for(var n=new t.Query(this.fields),r=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),c=0;c1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,n){var r=e[this._ref],i=Object.keys(this._fields);this._documents[r]=n||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,n;do e=this.next(),n=e.charCodeAt(0);while(n>47&&n<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var n=e.next();if(n==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(n.charCodeAt(0)==92){e.escapeCharacter();continue}if(n==":")return t.QueryLexer.lexField;if(n=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(n=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(n=="+"&&e.width()===1||n=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(n.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,n){this.lexer=new t.QueryLexer(e),this.query=n,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var n=e.peekLexeme();if(n!=null)switch(n.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+n.type;throw n.str.length>=1&&(r+=" with value '"+n.str+"'"),new t.QueryParseError(r,n.start,n.end)}},t.QueryParser.parsePresence=function(e){var n=e.consumeLexeme();if(n!=null){switch(n.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator'"+n.str+"'";throw new t.QueryParseError(r,n.start,n.end)}var i=e.peekLexeme();if(i==null){var r="expecting term or field, found nothing";throw new t.QueryParseError(r,n.start,n.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(r,i.start,i.end)}}},t.QueryParser.parseField=function(e){var n=e.consumeLexeme();if(n!=null){if(e.query.allFields.indexOf(n.str)==-1){var r=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+n.str+"', possible fields: "+r;throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.fields=[n.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,n.start,n.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var n=e.consumeLexeme();if(n!=null){e.currentClause.term=n.str.toLowerCase(),n.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(r==null){e.nextClause();return}switch(r.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+r.type+"'";throw new t.QueryParseError(i,r.start,r.end)}}},t.QueryParser.parseEditDistance=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="edit distance must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.editDistance=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="boost must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.boost=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},(function(e,n){typeof define=="function"&&define.amd?define(n):typeof me=="object"?ge.exports=n():e.lunr=n()})(this,function(){return t})})()});var M,G={getItem(){return null},setItem(){}},K;try{K=localStorage,M=K}catch{K=G,M=G}var S={getItem:t=>M.getItem(t),setItem:(t,e)=>M.setItem(t,e),disableWritingLocalStorage(){M=G},disable(){localStorage.clear(),M=G},enable(){M=K}};window.TypeDoc||={disableWritingLocalStorage(){S.disableWritingLocalStorage()},disableLocalStorage:()=>{S.disable()},enableLocalStorage:()=>{S.enable()}};window.translations||={copy:"Copy",copied:"Copied!",normally_hidden:"This member is normally hidden due to your filter settings.",hierarchy_expand:"Expand",hierarchy_collapse:"Collapse",search_index_not_available:"The search index is not available",search_no_results_found_for_0:"No results found for {0}",folder:"Folder",kind_1:"Project",kind_2:"Module",kind_4:"Namespace",kind_8:"Enumeration",kind_16:"Enumeration Member",kind_32:"Variable",kind_64:"Function",kind_128:"Class",kind_256:"Interface",kind_512:"Constructor",kind_1024:"Property",kind_2048:"Method",kind_4096:"Call Signature",kind_8192:"Index Signature",kind_16384:"Constructor Signature",kind_32768:"Parameter",kind_65536:"Type Literal",kind_131072:"Type Parameter",kind_262144:"Accessor",kind_524288:"Get Signature",kind_1048576:"Set Signature",kind_2097152:"Type Alias",kind_4194304:"Reference",kind_8388608:"Document"};var pe=[];function X(t,e){pe.push({selector:e,constructor:t})}var Z=class{alwaysVisibleMember=null;constructor(){this.createComponents(document.body),this.ensureFocusedElementVisible(),this.listenForCodeCopies(),window.addEventListener("hashchange",()=>this.ensureFocusedElementVisible()),document.body.style.display||(this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}createComponents(e){pe.forEach(n=>{e.querySelectorAll(n.selector).forEach(r=>{r.dataset.hasInstance||(new n.constructor({el:r,app:this}),r.dataset.hasInstance=String(!0))})})}filterChanged(){this.ensureFocusedElementVisible()}showPage(){document.body.style.display&&(document.body.style.removeProperty("display"),this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}scrollToHash(){if(location.hash){let e=document.getElementById(location.hash.substring(1));if(!e)return;e.scrollIntoView({behavior:"instant",block:"start"})}}ensureActivePageVisible(){let e=document.querySelector(".tsd-navigation .current"),n=e?.parentElement;for(;n&&!n.classList.contains(".tsd-navigation");)n instanceof HTMLDetailsElement&&(n.open=!0),n=n.parentElement;if(e&&!rt(e)){let r=e.getBoundingClientRect().top-document.documentElement.clientHeight/4;document.querySelector(".site-menu").scrollTop=r,document.querySelector(".col-sidebar").scrollTop=r}}updateIndexVisibility(){let e=document.querySelector(".tsd-index-content"),n=e?.open;e&&(e.open=!0),document.querySelectorAll(".tsd-index-section").forEach(r=>{r.style.display="block";let i=Array.from(r.querySelectorAll(".tsd-index-link")).every(s=>s.offsetParent==null);r.style.display=i?"none":"block"}),e&&(e.open=n)}ensureFocusedElementVisible(){if(this.alwaysVisibleMember&&(this.alwaysVisibleMember.classList.remove("always-visible"),this.alwaysVisibleMember.firstElementChild.remove(),this.alwaysVisibleMember=null),!location.hash)return;let e=document.getElementById(location.hash.substring(1));if(!e)return;let n=e.parentElement;for(;n&&n.tagName!=="SECTION";)n=n.parentElement;if(!n)return;let r=n.offsetParent==null,i=n;for(;i!==document.body;)i instanceof HTMLDetailsElement&&(i.open=!0),i=i.parentElement;if(n.offsetParent==null){this.alwaysVisibleMember=n,n.classList.add("always-visible");let s=document.createElement("p");s.classList.add("warning"),s.textContent=window.translations.normally_hidden,n.prepend(s)}r&&e.scrollIntoView()}listenForCodeCopies(){document.querySelectorAll("pre > button").forEach(e=>{let n;e.addEventListener("click",()=>{e.previousElementSibling instanceof HTMLElement&&navigator.clipboard.writeText(e.previousElementSibling.innerText.trim()),e.textContent=window.translations.copied,e.classList.add("visible"),clearTimeout(n),n=setTimeout(()=>{e.classList.remove("visible"),n=setTimeout(()=>{e.textContent=window.translations.copy},100)},1e3)})})}};function rt(t){let e=t.getBoundingClientRect(),n=Math.max(document.documentElement.clientHeight,window.innerHeight);return!(e.bottom<0||e.top-n>=0)}var fe=(t,e=100)=>{let n;return()=>{clearTimeout(n),n=setTimeout(()=>t(),e)}};var Ie=nt(ye(),1);async function R(t){let e=Uint8Array.from(atob(t),s=>s.charCodeAt(0)),r=new Blob([e]).stream().pipeThrough(new DecompressionStream("deflate")),i=await new Response(r).text();return JSON.parse(i)}var Y="closing",ae="tsd-overlay";function it(){let t=Math.abs(window.innerWidth-document.documentElement.clientWidth);document.body.style.overflow="hidden",document.body.style.paddingRight=`${t}px`}function st(){document.body.style.removeProperty("overflow"),document.body.style.removeProperty("padding-right")}function xe(t,e){t.addEventListener("animationend",()=>{t.classList.contains(Y)&&(t.classList.remove(Y),document.getElementById(ae)?.remove(),t.close(),st())}),t.addEventListener("cancel",n=>{n.preventDefault(),ve(t)}),e?.closeOnClick&&document.addEventListener("click",n=>{t.open&&!t.contains(n.target)&&ve(t)},!0)}function Ee(t){if(t.open)return;let e=document.createElement("div");e.id=ae,document.body.appendChild(e),t.showModal(),it()}function ve(t){if(!t.open)return;document.getElementById(ae)?.classList.add(Y),t.classList.add(Y)}var I=class{el;app;constructor(e){this.el=e.el,this.app=e.app}};var be=document.head.appendChild(document.createElement("style"));be.dataset.for="filters";var le={};function we(t){for(let e of t.split(/\s+/))if(le.hasOwnProperty(e)&&!le[e])return!0;return!1}var ee=class extends I{key;value;constructor(e){super(e),this.key=`filter-${this.el.name}`,this.value=this.el.checked,this.el.addEventListener("change",()=>{this.setLocalStorage(this.el.checked)}),this.setLocalStorage(this.fromLocalStorage()),be.innerHTML+=`html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } -`,this.app.updateIndexVisibility()}fromLocalStorage(){let e=S.getItem(this.key);return e?e==="true":this.el.checked}setLocalStorage(e){S.setItem(this.key,e.toString()),this.value=e,this.handleValueChange()}handleValueChange(){this.el.checked=this.value,document.documentElement.classList.toggle(this.key,this.value),le[`tsd-is-${this.el.name}`]=this.value,this.app.filterChanged(),this.app.updateIndexVisibility()}};var Le=0;async function Se(t,e){if(!window.searchData)return;let n=await R(window.searchData);t.data=n,t.index=Ie.Index.load(n.index),e.innerHTML=""}function _e(){let t=document.getElementById("tsd-search-trigger"),e=document.getElementById("tsd-search"),n=document.getElementById("tsd-search-input"),r=document.getElementById("tsd-search-results"),i=document.getElementById("tsd-search-script"),s=document.getElementById("tsd-search-status");if(!(t&&e&&n&&r&&i&&s))throw new Error("Search controls missing");let o={base:document.documentElement.dataset.base};o.base.endsWith("/")||(o.base+="/"),i.addEventListener("error",()=>{let a=window.translations.search_index_not_available;Pe(s,a)}),i.addEventListener("load",()=>{Se(o,s)}),Se(o,s),ot({trigger:t,searchEl:e,results:r,field:n,status:s},o)}function ot(t,e){let{field:n,results:r,searchEl:i,status:s,trigger:o}=t;xe(i,{closeOnClick:!0});function a(){Ee(i),n.setSelectionRange(0,n.value.length)}o.addEventListener("click",a),n.addEventListener("input",fe(()=>{at(r,n,s,e)},200)),n.addEventListener("keydown",l=>{if(r.childElementCount===0||l.ctrlKey||l.metaKey||l.altKey)return;let d=n.getAttribute("aria-activedescendant"),f=d?document.getElementById(d):null;if(f){let p=!1,v=!1;switch(l.key){case"Home":case"End":case"ArrowLeft":case"ArrowRight":v=!0;break;case"ArrowDown":case"ArrowUp":p=l.shiftKey;break}(p||v)&&ke(n)}if(!l.shiftKey)switch(l.key){case"Enter":f?.querySelector("a")?.click();break;case"ArrowUp":Te(r,n,f,-1),l.preventDefault();break;case"ArrowDown":Te(r,n,f,1),l.preventDefault();break}});function c(){ke(n)}n.addEventListener("change",c),n.addEventListener("blur",c),n.addEventListener("click",c),document.body.addEventListener("keydown",l=>{if(l.altKey||l.metaKey||l.shiftKey)return;let d=l.ctrlKey&&l.key==="k",f=!l.ctrlKey&&!ut()&&l.key==="/";(d||f)&&(l.preventDefault(),a())})}function at(t,e,n,r){if(!r.index||!r.data)return;t.innerHTML="",n.innerHTML="",Le+=1;let i=e.value.trim(),s;if(i){let a=i.split(" ").map(c=>c.length?`*${c}*`:"").join(" ");s=r.index.search(a).filter(({ref:c})=>{let l=r.data.rows[Number(c)].classes;return!l||!we(l)})}else s=[];if(s.length===0&&i){let a=window.translations.search_no_results_found_for_0.replace("{0}",` "${te(i)}" `);Pe(n,a);return}for(let a=0;ac.score-a.score);let o=Math.min(10,s.length);for(let a=0;a`,f=Ce(c.name,i);globalThis.DEBUG_SEARCH_WEIGHTS&&(f+=` (score: ${s[a].score.toFixed(2)})`),c.parent&&(f=` - ${Ce(c.parent,i)}.${f}`);let p=document.createElement("li");p.id=`tsd-search:${Le}-${a}`,p.role="option",p.ariaSelected="false",p.classList.value=c.classes??"";let v=document.createElement("a");v.tabIndex=-1,v.href=r.base+c.url,v.innerHTML=d+`${f}`,p.append(v),t.appendChild(p)}}function Te(t,e,n,r){let i;if(r===1?i=n?.nextElementSibling||t.firstElementChild:i=n?.previousElementSibling||t.lastElementChild,i!==n){if(!i||i.role!=="option"){console.error("Option missing");return}i.ariaSelected="true",i.scrollIntoView({behavior:"smooth",block:"nearest"}),e.setAttribute("aria-activedescendant",i.id),n?.setAttribute("aria-selected","false")}}function ke(t){let e=t.getAttribute("aria-activedescendant");(e?document.getElementById(e):null)?.setAttribute("aria-selected","false"),t.setAttribute("aria-activedescendant","")}function Ce(t,e){if(e==="")return t;let n=t.toLocaleLowerCase(),r=e.toLocaleLowerCase(),i=[],s=0,o=n.indexOf(r);for(;o!=-1;)i.push(te(t.substring(s,o)),`${te(t.substring(o,o+r.length))}`),s=o+r.length,o=n.indexOf(r,s);return i.push(te(t.substring(s))),i.join("")}var lt={"&":"&","<":"<",">":">","'":"'",'"':"""};function te(t){return t.replace(/[&<>"'"]/g,e=>lt[e])}function Pe(t,e){t.innerHTML=e?`

${e}
`:""}var ct=["button","checkbox","file","hidden","image","radio","range","reset","submit"];function ut(){let t=document.activeElement;return t?t.isContentEditable||t.tagName==="TEXTAREA"||t.tagName==="SEARCH"?!0:t.tagName==="INPUT"&&!ct.includes(t.type):!1}var D="mousedown",Me="mousemove",$="mouseup",ne={x:0,y:0},Qe=!1,ce=!1,dt=!1,F=!1,Oe=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(Oe?"is-mobile":"not-mobile");Oe&&"ontouchstart"in document.documentElement&&(dt=!0,D="touchstart",Me="touchmove",$="touchend");document.addEventListener(D,t=>{ce=!0,F=!1;let e=D=="touchstart"?t.targetTouches[0]:t;ne.y=e.pageY||0,ne.x=e.pageX||0});document.addEventListener(Me,t=>{if(ce&&!F){let e=D=="touchstart"?t.targetTouches[0]:t,n=ne.x-(e.pageX||0),r=ne.y-(e.pageY||0);F=Math.sqrt(n*n+r*r)>10}});document.addEventListener($,()=>{ce=!1});document.addEventListener("click",t=>{Qe&&(t.preventDefault(),t.stopImmediatePropagation(),Qe=!1)});var re=class extends I{active;className;constructor(e){super(e),this.className=this.el.dataset.toggle||"",this.el.addEventListener($,n=>this.onPointerUp(n)),this.el.addEventListener("click",n=>n.preventDefault()),document.addEventListener(D,n=>this.onDocumentPointerDown(n)),document.addEventListener($,n=>this.onDocumentPointerUp(n))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let n=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(n),setTimeout(()=>document.documentElement.classList.remove(n),500)}onPointerUp(e){F||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-sidebar, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!F&&this.active&&e.target.closest(".col-sidebar")){let n=e.target.closest("a");if(n){let r=window.location.href;r.indexOf("#")!=-1&&(r=r.substring(0,r.indexOf("#"))),n.href.substring(0,r.length)==r&&setTimeout(()=>this.setActive(!1),250)}}}};var ue=new Map,de=class{open;accordions=[];key;constructor(e,n){this.key=e,this.open=n}add(e){this.accordions.push(e),e.open=this.open,e.addEventListener("toggle",()=>{this.toggle(e.open)})}toggle(e){for(let n of this.accordions)n.open=e;S.setItem(this.key,e.toString())}},ie=class extends I{constructor(e){super(e);let n=this.el.querySelector("summary"),r=n.querySelector("a");r&&r.addEventListener("click",()=>{location.assign(r.href)});let i=`tsd-accordion-${n.dataset.key??n.textContent.trim().replace(/\s+/g,"-").toLowerCase()}`,s;if(ue.has(i))s=ue.get(i);else{let o=S.getItem(i),a=o?o==="true":this.el.open;s=new de(i,a),ue.set(i,s)}s.add(this.el)}};function He(t){let e=S.getItem("tsd-theme")||"os";t.value=e,Ae(e),t.addEventListener("change",()=>{S.setItem("tsd-theme",t.value),Ae(t.value)})}function Ae(t){document.documentElement.dataset.theme=t}var se;function Ne(){let t=document.getElementById("tsd-nav-script");t&&(t.addEventListener("load",Re),Re())}async function Re(){let t=document.getElementById("tsd-nav-container");if(!t||!window.navigationData)return;let e=await R(window.navigationData);se=document.documentElement.dataset.base,se.endsWith("/")||(se+="/"),t.innerHTML="";for(let n of e)Be(n,t,[]);window.app.createComponents(t),window.app.showPage(),window.app.ensureActivePageVisible()}function Be(t,e,n){let r=e.appendChild(document.createElement("li"));if(t.children){let i=[...n,t.text],s=r.appendChild(document.createElement("details"));s.className=t.class?`${t.class} tsd-accordion`:"tsd-accordion";let o=s.appendChild(document.createElement("summary"));o.className="tsd-accordion-summary",o.dataset.key=i.join("$"),o.innerHTML='',De(t,o);let a=s.appendChild(document.createElement("div"));a.className="tsd-accordion-details";let c=a.appendChild(document.createElement("ul"));c.className="tsd-nested-navigation";for(let l of t.children)Be(l,c,i)}else De(t,r,t.class)}function De(t,e,n){if(t.path){let r=e.appendChild(document.createElement("a"));if(r.href=se+t.path,n&&(r.className=n),location.pathname===r.pathname&&!r.href.includes("#")&&(r.classList.add("current"),r.ariaCurrent="page"),t.kind){let i=window.translations[`kind_${t.kind}`].replaceAll('"',""");r.innerHTML=``}r.appendChild(Fe(t.text,document.createElement("span")))}else{let r=e.appendChild(document.createElement("span")),i=window.translations.folder.replaceAll('"',""");r.innerHTML=``,r.appendChild(Fe(t.text,document.createElement("span")))}}function Fe(t,e){let n=t.split(/(?<=[^A-Z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[_-])(?=[^_-])/);for(let r=0;r{let i=r.target;for(;i.parentElement&&i.parentElement.tagName!="LI";)i=i.parentElement;i.dataset.dropdown&&(i.dataset.dropdown=String(i.dataset.dropdown!=="true"))});let t=new Map,e=new Set;for(let r of document.querySelectorAll(".tsd-full-hierarchy [data-refl]")){let i=r.querySelector("ul");t.has(r.dataset.refl)?e.add(r.dataset.refl):i&&t.set(r.dataset.refl,i)}for(let r of e)n(r);function n(r){let i=t.get(r).cloneNode(!0);i.querySelectorAll("[id]").forEach(s=>{s.removeAttribute("id")}),i.querySelectorAll("[data-dropdown]").forEach(s=>{s.dataset.dropdown="false"});for(let s of document.querySelectorAll(`[data-refl="${r}"]`)){let o=gt(),a=s.querySelector("ul");s.insertBefore(o,a),o.dataset.dropdown=String(!!a),a||s.appendChild(i.cloneNode(!0))}}}function pt(){let t=document.getElementById("tsd-hierarchy-script");t&&(t.addEventListener("load",Ve),Ve())}async function Ve(){let t=document.querySelector(".tsd-panel.tsd-hierarchy:has(h4 a)");if(!t||!window.hierarchyData)return;let e=+t.dataset.refl,n=await R(window.hierarchyData),r=t.querySelector("ul"),i=document.createElement("ul");if(i.classList.add("tsd-hierarchy"),ft(i,n,e),r.querySelectorAll("li").length==i.querySelectorAll("li").length)return;let s=document.createElement("span");s.classList.add("tsd-hierarchy-toggle"),s.textContent=window.translations.hierarchy_expand,t.querySelector("h4 a")?.insertAdjacentElement("afterend",s),s.insertAdjacentText("beforebegin",", "),s.addEventListener("click",()=>{s.textContent===window.translations.hierarchy_expand?(r.insertAdjacentElement("afterend",i),r.remove(),s.textContent=window.translations.hierarchy_collapse):(i.insertAdjacentElement("afterend",r),i.remove(),s.textContent=window.translations.hierarchy_expand)})}function ft(t,e,n){let r=e.roots.filter(i=>mt(e,i,n));for(let i of r)t.appendChild(je(e,i,n))}function je(t,e,n,r=new Set){if(r.has(e))return;r.add(e);let i=t.reflections[e],s=document.createElement("li");if(s.classList.add("tsd-hierarchy-item"),e===n){let o=s.appendChild(document.createElement("span"));o.textContent=i.name,o.classList.add("tsd-hierarchy-target")}else{for(let a of i.uniqueNameParents||[]){let c=t.reflections[a],l=s.appendChild(document.createElement("a"));l.textContent=c.name,l.href=oe+c.url,l.className=c.class+" tsd-signature-type",s.append(document.createTextNode("."))}let o=s.appendChild(document.createElement("a"));o.textContent=t.reflections[e].name,o.href=oe+i.url,o.className=i.class+" tsd-signature-type"}if(i.children){let o=s.appendChild(document.createElement("ul"));o.classList.add("tsd-hierarchy");for(let a of i.children){let c=je(t,a,n,r);c&&o.appendChild(c)}}return r.delete(e),s}function mt(t,e,n){if(e===n)return!0;let r=new Set,i=[t.reflections[e]];for(;i.length;){let s=i.pop();if(!r.has(s)){r.add(s);for(let o of s.children||[]){if(o===n)return!0;i.push(t.reflections[o])}}}return!1}function gt(){let t=document.createElementNS("http://www.w3.org/2000/svg","svg");return t.setAttribute("width","20"),t.setAttribute("height","20"),t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("fill","none"),t.innerHTML='',t}X(re,"a[data-toggle]");X(ie,".tsd-accordion");X(ee,".tsd-filter-item input[type=checkbox]");var qe=document.getElementById("tsd-theme");qe&&He(qe);var yt=new Z;Object.defineProperty(window,"app",{value:yt});_e();Ne();$e();"virtualKeyboard"in navigator&&(navigator.virtualKeyboard.overlaysContent=!0);})(); -/*! Bundled license information: - -lunr/lunr.js: - (** - * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 - * Copyright (C) 2020 Oliver Nightingale - * @license MIT - *) - (*! - * lunr.utils - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.Set - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.tokenizer - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.Pipeline - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.Vector - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.stemmer - * Copyright (C) 2020 Oliver Nightingale - * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt - *) - (*! - * lunr.stopWordFilter - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.trimmer - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.TokenSet - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.Index - * Copyright (C) 2020 Oliver Nightingale - *) - (*! - * lunr.Builder - * Copyright (C) 2020 Oliver Nightingale - *) -*/ diff --git a/docs/assets/navigation.js b/docs/assets/navigation.js deleted file mode 100644 index ee9438b..0000000 --- a/docs/assets/navigation.js +++ /dev/null @@ -1 +0,0 @@ -window.navigationData = "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" \ No newline at end of file diff --git a/docs/assets/search.js b/docs/assets/search.js deleted file mode 100644 index 474edbc..0000000 --- a/docs/assets/search.js +++ /dev/null @@ -1 +0,0 @@ -window.searchData = "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"; \ No newline at end of file diff --git a/docs/assets/style.css b/docs/assets/style.css deleted file mode 100644 index 44328e9..0000000 --- a/docs/assets/style.css +++ /dev/null @@ -1,1633 +0,0 @@ -@layer typedoc { - :root { - --dim-toolbar-contents-height: 2.5rem; - --dim-toolbar-border-bottom-width: 1px; - --dim-header-height: calc( - var(--dim-toolbar-border-bottom-width) + - var(--dim-toolbar-contents-height) - ); - - /* 0rem For mobile; unit is required for calculation in `calc` */ - --dim-container-main-margin-y: 0rem; - - --dim-footer-height: 3.5rem; - - --modal-animation-duration: 0.2s; - } - - :root { - /* Light */ - --light-color-background: #f2f4f8; - --light-color-background-secondary: #eff0f1; - /* Not to be confused with [:active](https://developer.mozilla.org/en-US/docs/Web/CSS/:active) */ - --light-color-background-active: #d6d8da; - --light-color-background-warning: #e6e600; - --light-color-warning-text: #222; - --light-color-accent: #c5c7c9; - --light-color-active-menu-item: var(--light-color-background-active); - --light-color-text: #222; - --light-color-contrast-text: #000; - --light-color-text-aside: #5e5e5e; - - --light-color-icon-background: var(--light-color-background); - --light-color-icon-text: var(--light-color-text); - - --light-color-comment-tag-text: var(--light-color-text); - --light-color-comment-tag: var(--light-color-background); - - --light-color-link: #1f70c2; - --light-color-focus-outline: #3584e4; - - --light-color-ts-keyword: #056bd6; - --light-color-ts-project: #b111c9; - --light-color-ts-module: var(--light-color-ts-project); - --light-color-ts-namespace: var(--light-color-ts-project); - --light-color-ts-enum: #7e6f15; - --light-color-ts-enum-member: var(--light-color-ts-enum); - --light-color-ts-variable: #4760ec; - --light-color-ts-function: #572be7; - --light-color-ts-class: #1f70c2; - --light-color-ts-interface: #108024; - --light-color-ts-constructor: var(--light-color-ts-class); - --light-color-ts-property: #9f5f30; - --light-color-ts-method: #be3989; - --light-color-ts-reference: #ff4d82; - --light-color-ts-call-signature: var(--light-color-ts-method); - --light-color-ts-index-signature: var(--light-color-ts-property); - --light-color-ts-constructor-signature: var( - --light-color-ts-constructor - ); - --light-color-ts-parameter: var(--light-color-ts-variable); - /* type literal not included as links will never be generated to it */ - --light-color-ts-type-parameter: #a55c0e; - --light-color-ts-accessor: #c73c3c; - --light-color-ts-get-signature: var(--light-color-ts-accessor); - --light-color-ts-set-signature: var(--light-color-ts-accessor); - --light-color-ts-type-alias: #d51270; - /* reference not included as links will be colored with the kind that it points to */ - --light-color-document: #000000; - - --light-color-alert-note: #0969d9; - --light-color-alert-tip: #1a7f37; - --light-color-alert-important: #8250df; - --light-color-alert-warning: #9a6700; - --light-color-alert-caution: #cf222e; - - --light-external-icon: url("data:image/svg+xml;utf8,"); - --light-color-scheme: light; - } - - :root { - /* Dark */ - --dark-color-background: #2b2e33; - --dark-color-background-secondary: #1e2024; - /* Not to be confused with [:active](https://developer.mozilla.org/en-US/docs/Web/CSS/:active) */ - --dark-color-background-active: #5d5d6a; - --dark-color-background-warning: #bebe00; - --dark-color-warning-text: #222; - --dark-color-accent: #9096a2; - --dark-color-active-menu-item: var(--dark-color-background-active); - --dark-color-text: #f5f5f5; - --dark-color-contrast-text: #ffffff; - --dark-color-text-aside: #dddddd; - - --dark-color-icon-background: var(--dark-color-background-secondary); - --dark-color-icon-text: var(--dark-color-text); - - --dark-color-comment-tag-text: var(--dark-color-text); - --dark-color-comment-tag: var(--dark-color-background); - - --dark-color-link: #00aff4; - --dark-color-focus-outline: #4c97f2; - - --dark-color-ts-keyword: #3399ff; - --dark-color-ts-project: #e358ff; - --dark-color-ts-module: var(--dark-color-ts-project); - --dark-color-ts-namespace: var(--dark-color-ts-project); - --dark-color-ts-enum: #f4d93e; - --dark-color-ts-enum-member: var(--dark-color-ts-enum); - --dark-color-ts-variable: #798dff; - --dark-color-ts-function: #a280ff; - --dark-color-ts-class: #8ac4ff; - --dark-color-ts-interface: #6cff87; - --dark-color-ts-constructor: var(--dark-color-ts-class); - --dark-color-ts-property: #ff984d; - --dark-color-ts-method: #ff4db8; - --dark-color-ts-reference: #ff4d82; - --dark-color-ts-call-signature: var(--dark-color-ts-method); - --dark-color-ts-index-signature: var(--dark-color-ts-property); - --dark-color-ts-constructor-signature: var(--dark-color-ts-constructor); - --dark-color-ts-parameter: var(--dark-color-ts-variable); - /* type literal not included as links will never be generated to it */ - --dark-color-ts-type-parameter: #e07d13; - --dark-color-ts-accessor: #ff6060; - --dark-color-ts-get-signature: var(--dark-color-ts-accessor); - --dark-color-ts-set-signature: var(--dark-color-ts-accessor); - --dark-color-ts-type-alias: #ff6492; - /* reference not included as links will be colored with the kind that it points to */ - --dark-color-document: #ffffff; - - --dark-color-alert-note: #0969d9; - --dark-color-alert-tip: #1a7f37; - --dark-color-alert-important: #8250df; - --dark-color-alert-warning: #9a6700; - --dark-color-alert-caution: #cf222e; - - --dark-external-icon: url("data:image/svg+xml;utf8,"); - --dark-color-scheme: dark; - } - - @media (prefers-color-scheme: light) { - :root { - --color-background: var(--light-color-background); - --color-background-secondary: var( - --light-color-background-secondary - ); - --color-background-active: var(--light-color-background-active); - --color-background-warning: var(--light-color-background-warning); - --color-warning-text: var(--light-color-warning-text); - --color-accent: var(--light-color-accent); - --color-active-menu-item: var(--light-color-active-menu-item); - --color-text: var(--light-color-text); - --color-contrast-text: var(--light-color-contrast-text); - --color-text-aside: var(--light-color-text-aside); - - --color-icon-background: var(--light-color-icon-background); - --color-icon-text: var(--light-color-icon-text); - - --color-comment-tag-text: var(--light-color-text); - --color-comment-tag: var(--light-color-background); - - --color-link: var(--light-color-link); - --color-focus-outline: var(--light-color-focus-outline); - - --color-ts-keyword: var(--light-color-ts-keyword); - --color-ts-project: var(--light-color-ts-project); - --color-ts-module: var(--light-color-ts-module); - --color-ts-namespace: var(--light-color-ts-namespace); - --color-ts-enum: var(--light-color-ts-enum); - --color-ts-enum-member: var(--light-color-ts-enum-member); - --color-ts-variable: var(--light-color-ts-variable); - --color-ts-function: var(--light-color-ts-function); - --color-ts-class: var(--light-color-ts-class); - --color-ts-interface: var(--light-color-ts-interface); - --color-ts-constructor: var(--light-color-ts-constructor); - --color-ts-property: var(--light-color-ts-property); - --color-ts-method: var(--light-color-ts-method); - --color-ts-reference: var(--light-color-ts-reference); - --color-ts-call-signature: var(--light-color-ts-call-signature); - --color-ts-index-signature: var(--light-color-ts-index-signature); - --color-ts-constructor-signature: var( - --light-color-ts-constructor-signature - ); - --color-ts-parameter: var(--light-color-ts-parameter); - --color-ts-type-parameter: var(--light-color-ts-type-parameter); - --color-ts-accessor: var(--light-color-ts-accessor); - --color-ts-get-signature: var(--light-color-ts-get-signature); - --color-ts-set-signature: var(--light-color-ts-set-signature); - --color-ts-type-alias: var(--light-color-ts-type-alias); - --color-document: var(--light-color-document); - - --color-alert-note: var(--light-color-alert-note); - --color-alert-tip: var(--light-color-alert-tip); - --color-alert-important: var(--light-color-alert-important); - --color-alert-warning: var(--light-color-alert-warning); - --color-alert-caution: var(--light-color-alert-caution); - - --external-icon: var(--light-external-icon); - --color-scheme: var(--light-color-scheme); - } - } - - @media (prefers-color-scheme: dark) { - :root { - --color-background: var(--dark-color-background); - --color-background-secondary: var( - --dark-color-background-secondary - ); - --color-background-active: var(--dark-color-background-active); - --color-background-warning: var(--dark-color-background-warning); - --color-warning-text: var(--dark-color-warning-text); - --color-accent: var(--dark-color-accent); - --color-active-menu-item: var(--dark-color-active-menu-item); - --color-text: var(--dark-color-text); - --color-contrast-text: var(--dark-color-contrast-text); - --color-text-aside: var(--dark-color-text-aside); - - --color-icon-background: var(--dark-color-icon-background); - --color-icon-text: var(--dark-color-icon-text); - - --color-comment-tag-text: var(--dark-color-text); - --color-comment-tag: var(--dark-color-background); - - --color-link: var(--dark-color-link); - --color-focus-outline: var(--dark-color-focus-outline); - - --color-ts-keyword: var(--dark-color-ts-keyword); - --color-ts-project: var(--dark-color-ts-project); - --color-ts-module: var(--dark-color-ts-module); - --color-ts-namespace: var(--dark-color-ts-namespace); - --color-ts-enum: var(--dark-color-ts-enum); - --color-ts-enum-member: var(--dark-color-ts-enum-member); - --color-ts-variable: var(--dark-color-ts-variable); - --color-ts-function: var(--dark-color-ts-function); - --color-ts-class: var(--dark-color-ts-class); - --color-ts-interface: var(--dark-color-ts-interface); - --color-ts-constructor: var(--dark-color-ts-constructor); - --color-ts-property: var(--dark-color-ts-property); - --color-ts-method: var(--dark-color-ts-method); - --color-ts-reference: var(--dark-color-ts-reference); - --color-ts-call-signature: var(--dark-color-ts-call-signature); - --color-ts-index-signature: var(--dark-color-ts-index-signature); - --color-ts-constructor-signature: var( - --dark-color-ts-constructor-signature - ); - --color-ts-parameter: var(--dark-color-ts-parameter); - --color-ts-type-parameter: var(--dark-color-ts-type-parameter); - --color-ts-accessor: var(--dark-color-ts-accessor); - --color-ts-get-signature: var(--dark-color-ts-get-signature); - --color-ts-set-signature: var(--dark-color-ts-set-signature); - --color-ts-type-alias: var(--dark-color-ts-type-alias); - --color-document: var(--dark-color-document); - - --color-alert-note: var(--dark-color-alert-note); - --color-alert-tip: var(--dark-color-alert-tip); - --color-alert-important: var(--dark-color-alert-important); - --color-alert-warning: var(--dark-color-alert-warning); - --color-alert-caution: var(--dark-color-alert-caution); - - --external-icon: var(--dark-external-icon); - --color-scheme: var(--dark-color-scheme); - } - } - - :root[data-theme="light"] { - --color-background: var(--light-color-background); - --color-background-secondary: var(--light-color-background-secondary); - --color-background-active: var(--light-color-background-active); - --color-background-warning: var(--light-color-background-warning); - --color-warning-text: var(--light-color-warning-text); - --color-icon-background: var(--light-color-icon-background); - --color-accent: var(--light-color-accent); - --color-active-menu-item: var(--light-color-active-menu-item); - --color-text: var(--light-color-text); - --color-contrast-text: var(--light-color-contrast-text); - --color-text-aside: var(--light-color-text-aside); - --color-icon-text: var(--light-color-icon-text); - - --color-comment-tag-text: var(--light-color-text); - --color-comment-tag: var(--light-color-background); - - --color-link: var(--light-color-link); - --color-focus-outline: var(--light-color-focus-outline); - - --color-ts-keyword: var(--light-color-ts-keyword); - --color-ts-project: var(--light-color-ts-project); - --color-ts-module: var(--light-color-ts-module); - --color-ts-namespace: var(--light-color-ts-namespace); - --color-ts-enum: var(--light-color-ts-enum); - --color-ts-enum-member: var(--light-color-ts-enum-member); - --color-ts-variable: var(--light-color-ts-variable); - --color-ts-function: var(--light-color-ts-function); - --color-ts-class: var(--light-color-ts-class); - --color-ts-interface: var(--light-color-ts-interface); - --color-ts-constructor: var(--light-color-ts-constructor); - --color-ts-property: var(--light-color-ts-property); - --color-ts-method: var(--light-color-ts-method); - --color-ts-reference: var(--light-color-ts-reference); - --color-ts-call-signature: var(--light-color-ts-call-signature); - --color-ts-index-signature: var(--light-color-ts-index-signature); - --color-ts-constructor-signature: var( - --light-color-ts-constructor-signature - ); - --color-ts-parameter: var(--light-color-ts-parameter); - --color-ts-type-parameter: var(--light-color-ts-type-parameter); - --color-ts-accessor: var(--light-color-ts-accessor); - --color-ts-get-signature: var(--light-color-ts-get-signature); - --color-ts-set-signature: var(--light-color-ts-set-signature); - --color-ts-type-alias: var(--light-color-ts-type-alias); - --color-document: var(--light-color-document); - - --color-note: var(--light-color-note); - --color-tip: var(--light-color-tip); - --color-important: var(--light-color-important); - --color-warning: var(--light-color-warning); - --color-caution: var(--light-color-caution); - - --external-icon: var(--light-external-icon); - --color-scheme: var(--light-color-scheme); - } - - :root[data-theme="dark"] { - --color-background: var(--dark-color-background); - --color-background-secondary: var(--dark-color-background-secondary); - --color-background-active: var(--dark-color-background-active); - --color-background-warning: var(--dark-color-background-warning); - --color-warning-text: var(--dark-color-warning-text); - --color-icon-background: var(--dark-color-icon-background); - --color-accent: var(--dark-color-accent); - --color-active-menu-item: var(--dark-color-active-menu-item); - --color-text: var(--dark-color-text); - --color-contrast-text: var(--dark-color-contrast-text); - --color-text-aside: var(--dark-color-text-aside); - --color-icon-text: var(--dark-color-icon-text); - - --color-comment-tag-text: var(--dark-color-text); - --color-comment-tag: var(--dark-color-background); - - --color-link: var(--dark-color-link); - --color-focus-outline: var(--dark-color-focus-outline); - - --color-ts-keyword: var(--dark-color-ts-keyword); - --color-ts-project: var(--dark-color-ts-project); - --color-ts-module: var(--dark-color-ts-module); - --color-ts-namespace: var(--dark-color-ts-namespace); - --color-ts-enum: var(--dark-color-ts-enum); - --color-ts-enum-member: var(--dark-color-ts-enum-member); - --color-ts-variable: var(--dark-color-ts-variable); - --color-ts-function: var(--dark-color-ts-function); - --color-ts-class: var(--dark-color-ts-class); - --color-ts-interface: var(--dark-color-ts-interface); - --color-ts-constructor: var(--dark-color-ts-constructor); - --color-ts-property: var(--dark-color-ts-property); - --color-ts-method: var(--dark-color-ts-method); - --color-ts-reference: var(--dark-color-ts-reference); - --color-ts-call-signature: var(--dark-color-ts-call-signature); - --color-ts-index-signature: var(--dark-color-ts-index-signature); - --color-ts-constructor-signature: var( - --dark-color-ts-constructor-signature - ); - --color-ts-parameter: var(--dark-color-ts-parameter); - --color-ts-type-parameter: var(--dark-color-ts-type-parameter); - --color-ts-accessor: var(--dark-color-ts-accessor); - --color-ts-get-signature: var(--dark-color-ts-get-signature); - --color-ts-set-signature: var(--dark-color-ts-set-signature); - --color-ts-type-alias: var(--dark-color-ts-type-alias); - --color-document: var(--dark-color-document); - - --color-note: var(--dark-color-note); - --color-tip: var(--dark-color-tip); - --color-important: var(--dark-color-important); - --color-warning: var(--dark-color-warning); - --color-caution: var(--dark-color-caution); - - --external-icon: var(--dark-external-icon); - --color-scheme: var(--dark-color-scheme); - } - - html { - color-scheme: var(--color-scheme); - @media (prefers-reduced-motion: no-preference) { - scroll-behavior: smooth; - } - } - - *:focus-visible, - .tsd-accordion-summary:focus-visible svg { - outline: 2px solid var(--color-focus-outline); - } - - .always-visible, - .always-visible .tsd-signatures { - display: inherit !important; - } - - h1, - h2, - h3, - h4, - h5, - h6 { - line-height: 1.2; - } - - h1 { - font-size: 1.875rem; - margin: 0.67rem 0; - } - - h2 { - font-size: 1.5rem; - margin: 0.83rem 0; - } - - h3 { - font-size: 1.25rem; - margin: 1rem 0; - } - - h4 { - font-size: 1.05rem; - margin: 1.33rem 0; - } - - h5 { - font-size: 1rem; - margin: 1.5rem 0; - } - - h6 { - font-size: 0.875rem; - margin: 2.33rem 0; - } - - dl, - menu, - ol, - ul { - margin: 1em 0; - } - - dd { - margin: 0 0 0 34px; - } - - .container { - max-width: 1700px; - padding: 0 2rem; - } - - /* Footer */ - footer { - border-top: 1px solid var(--color-accent); - padding-top: 1rem; - padding-bottom: 1rem; - max-height: var(--dim-footer-height); - } - footer > p { - margin: 0 1em; - } - - .container-main { - margin: var(--dim-container-main-margin-y) auto; - /* toolbar, footer, margin */ - min-height: calc( - 100svh - var(--dim-header-height) - var(--dim-footer-height) - - 2 * var(--dim-container-main-margin-y) - ); - } - - @keyframes fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - @keyframes fade-out { - from { - opacity: 1; - visibility: visible; - } - to { - opacity: 0; - } - } - @keyframes pop-in-from-right { - from { - transform: translate(100%, 0); - } - to { - transform: translate(0, 0); - } - } - @keyframes pop-out-to-right { - from { - transform: translate(0, 0); - visibility: visible; - } - to { - transform: translate(100%, 0); - } - } - body { - background: var(--color-background); - font-family: - -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", - Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; - font-size: 16px; - color: var(--color-text); - margin: 0; - } - - a { - color: var(--color-link); - text-decoration: none; - } - a:hover { - text-decoration: underline; - } - a.external[target="_blank"] { - background-image: var(--external-icon); - background-position: top 3px right; - background-repeat: no-repeat; - padding-right: 13px; - } - a.tsd-anchor-link { - color: var(--color-text); - } - :target { - scroll-margin-block: calc(var(--dim-header-height) + 0.5rem); - } - - code, - pre { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - padding: 0.2em; - margin: 0; - font-size: 0.875rem; - border-radius: 0.8em; - } - - pre { - position: relative; - white-space: pre-wrap; - word-wrap: break-word; - padding: 10px; - border: 1px solid var(--color-accent); - margin-bottom: 8px; - } - pre code { - padding: 0; - font-size: 100%; - } - pre > button { - position: absolute; - top: 10px; - right: 10px; - opacity: 0; - transition: opacity 0.1s; - box-sizing: border-box; - } - pre:hover > button, - pre > button.visible, - pre > button:focus-visible { - opacity: 1; - } - - blockquote { - margin: 1em 0; - padding-left: 1em; - border-left: 4px solid gray; - } - - img { - max-width: 100%; - } - - * { - scrollbar-width: thin; - scrollbar-color: var(--color-accent) var(--color-icon-background); - } - - *::-webkit-scrollbar { - width: 0.75rem; - } - - *::-webkit-scrollbar-track { - background: var(--color-icon-background); - } - - *::-webkit-scrollbar-thumb { - background-color: var(--color-accent); - border-radius: 999rem; - border: 0.25rem solid var(--color-icon-background); - } - - dialog { - border: none; - outline: none; - padding: 0; - background-color: var(--color-background); - } - dialog::backdrop { - display: none; - } - #tsd-overlay { - background-color: rgba(0, 0, 0, 0.5); - position: fixed; - z-index: 9999; - top: 0; - left: 0; - right: 0; - bottom: 0; - animation: fade-in var(--modal-animation-duration) forwards; - } - #tsd-overlay.closing { - animation-name: fade-out; - } - - .tsd-typography { - line-height: 1.333em; - } - .tsd-typography ul { - list-style: square; - padding: 0 0 0 20px; - margin: 0; - } - .tsd-typography .tsd-index-panel h3, - .tsd-index-panel .tsd-typography h3, - .tsd-typography h4, - .tsd-typography h5, - .tsd-typography h6 { - font-size: 1em; - } - .tsd-typography h5, - .tsd-typography h6 { - font-weight: normal; - } - .tsd-typography p, - .tsd-typography ul, - .tsd-typography ol { - margin: 1em 0; - } - .tsd-typography table { - border-collapse: collapse; - border: none; - } - .tsd-typography td, - .tsd-typography th { - padding: 6px 13px; - border: 1px solid var(--color-accent); - } - .tsd-typography thead, - .tsd-typography tr:nth-child(even) { - background-color: var(--color-background-secondary); - } - - .tsd-alert { - padding: 8px 16px; - margin-bottom: 16px; - border-left: 0.25em solid var(--alert-color); - } - .tsd-alert blockquote > :last-child, - .tsd-alert > :last-child { - margin-bottom: 0; - } - .tsd-alert-title { - color: var(--alert-color); - display: inline-flex; - align-items: center; - } - .tsd-alert-title span { - margin-left: 4px; - } - - .tsd-alert-note { - --alert-color: var(--color-alert-note); - } - .tsd-alert-tip { - --alert-color: var(--color-alert-tip); - } - .tsd-alert-important { - --alert-color: var(--color-alert-important); - } - .tsd-alert-warning { - --alert-color: var(--color-alert-warning); - } - .tsd-alert-caution { - --alert-color: var(--color-alert-caution); - } - - .tsd-breadcrumb { - margin: 0; - margin-top: 1rem; - padding: 0; - color: var(--color-text-aside); - } - .tsd-breadcrumb a { - color: var(--color-text-aside); - text-decoration: none; - } - .tsd-breadcrumb a:hover { - text-decoration: underline; - } - .tsd-breadcrumb li { - display: inline; - } - .tsd-breadcrumb li:after { - content: " / "; - } - - .tsd-comment-tags { - display: flex; - flex-direction: column; - } - dl.tsd-comment-tag-group { - display: flex; - align-items: center; - overflow: hidden; - margin: 0.5em 0; - } - dl.tsd-comment-tag-group dt { - display: flex; - margin-right: 0.5em; - font-size: 0.875em; - font-weight: normal; - } - dl.tsd-comment-tag-group dd { - margin: 0; - } - code.tsd-tag { - padding: 0.25em 0.4em; - border: 0.1em solid var(--color-accent); - margin-right: 0.25em; - font-size: 70%; - } - h1 code.tsd-tag:first-of-type { - margin-left: 0.25em; - } - - dl.tsd-comment-tag-group dd:before, - dl.tsd-comment-tag-group dd:after { - content: " "; - } - dl.tsd-comment-tag-group dd pre, - dl.tsd-comment-tag-group dd:after { - clear: both; - } - dl.tsd-comment-tag-group p { - margin: 0; - } - - .tsd-panel.tsd-comment .lead { - font-size: 1.1em; - line-height: 1.333em; - margin-bottom: 2em; - } - .tsd-panel.tsd-comment .lead:last-child { - margin-bottom: 0; - } - - .tsd-filter-visibility h4 { - font-size: 1rem; - padding-top: 0.75rem; - padding-bottom: 0.5rem; - margin: 0; - } - .tsd-filter-item:not(:last-child) { - margin-bottom: 0.5rem; - } - .tsd-filter-input { - display: flex; - width: -moz-fit-content; - width: fit-content; - align-items: center; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - cursor: pointer; - } - .tsd-filter-input input[type="checkbox"] { - cursor: pointer; - position: absolute; - width: 1.5em; - height: 1.5em; - opacity: 0; - } - .tsd-filter-input input[type="checkbox"]:disabled { - pointer-events: none; - } - .tsd-filter-input svg { - cursor: pointer; - width: 1.5em; - height: 1.5em; - margin-right: 0.5em; - border-radius: 0.33em; - /* Leaving this at full opacity breaks event listeners on Firefox. - Don't remove unless you know what you're doing. */ - opacity: 0.99; - } - .tsd-filter-input input[type="checkbox"]:focus-visible + svg { - outline: 2px solid var(--color-focus-outline); - } - .tsd-checkbox-background { - fill: var(--color-accent); - } - input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark { - stroke: var(--color-text); - } - .tsd-filter-input input:disabled ~ svg > .tsd-checkbox-background { - fill: var(--color-background); - stroke: var(--color-accent); - stroke-width: 0.25rem; - } - .tsd-filter-input input:disabled ~ svg > .tsd-checkbox-checkmark { - stroke: var(--color-accent); - } - - .settings-label { - font-weight: bold; - text-transform: uppercase; - display: inline-block; - } - - .tsd-filter-visibility .settings-label { - margin: 0.75rem 0 0.5rem 0; - } - - .tsd-theme-toggle .settings-label { - margin: 0.75rem 0.75rem 0 0; - } - - .tsd-hierarchy h4 label:hover span { - text-decoration: underline; - } - - .tsd-hierarchy { - list-style: square; - margin: 0; - } - .tsd-hierarchy-target { - font-weight: bold; - } - .tsd-hierarchy-toggle { - color: var(--color-link); - cursor: pointer; - } - - .tsd-full-hierarchy:not(:last-child) { - margin-bottom: 1em; - padding-bottom: 1em; - border-bottom: 1px solid var(--color-accent); - } - .tsd-full-hierarchy, - .tsd-full-hierarchy ul { - list-style: none; - margin: 0; - padding: 0; - } - .tsd-full-hierarchy ul { - padding-left: 1.5rem; - } - .tsd-full-hierarchy a { - padding: 0.25rem 0 !important; - font-size: 1rem; - display: inline-flex; - align-items: center; - color: var(--color-text); - } - .tsd-full-hierarchy svg[data-dropdown] { - cursor: pointer; - } - .tsd-full-hierarchy svg[data-dropdown="false"] { - transform: rotate(-90deg); - } - .tsd-full-hierarchy svg[data-dropdown="false"] ~ ul { - display: none; - } - - .tsd-panel-group.tsd-index-group { - margin-bottom: 0; - } - .tsd-index-panel .tsd-index-list { - list-style: none; - line-height: 1.333em; - margin: 0; - padding: 0.25rem 0 0 0; - overflow: hidden; - display: grid; - grid-template-columns: repeat(3, 1fr); - column-gap: 1rem; - grid-template-rows: auto; - } - @media (max-width: 1024px) { - .tsd-index-panel .tsd-index-list { - grid-template-columns: repeat(2, 1fr); - } - } - @media (max-width: 768px) { - .tsd-index-panel .tsd-index-list { - grid-template-columns: repeat(1, 1fr); - } - } - .tsd-index-panel .tsd-index-list li { - -webkit-page-break-inside: avoid; - -moz-page-break-inside: avoid; - -ms-page-break-inside: avoid; - -o-page-break-inside: avoid; - page-break-inside: avoid; - } - - .tsd-flag { - display: inline-block; - padding: 0.25em 0.4em; - border-radius: 4px; - color: var(--color-comment-tag-text); - background-color: var(--color-comment-tag); - text-indent: 0; - font-size: 75%; - line-height: 1; - font-weight: normal; - } - - .tsd-anchor { - position: relative; - top: -100px; - } - - .tsd-member { - position: relative; - } - .tsd-member .tsd-anchor + h3 { - display: flex; - align-items: center; - margin-top: 0; - margin-bottom: 0; - border-bottom: none; - } - - .tsd-navigation.settings { - margin: 0; - margin-bottom: 1rem; - } - .tsd-navigation > a, - .tsd-navigation .tsd-accordion-summary { - width: calc(100% - 0.25rem); - display: flex; - align-items: center; - } - .tsd-navigation a, - .tsd-navigation summary > span, - .tsd-page-navigation a { - display: flex; - width: calc(100% - 0.25rem); - align-items: center; - padding: 0.25rem; - color: var(--color-text); - text-decoration: none; - box-sizing: border-box; - } - .tsd-navigation a.current, - .tsd-page-navigation a.current { - background: var(--color-active-menu-item); - color: var(--color-contrast-text); - } - .tsd-navigation a:hover, - .tsd-page-navigation a:hover { - text-decoration: underline; - } - .tsd-navigation ul, - .tsd-page-navigation ul { - margin-top: 0; - margin-bottom: 0; - padding: 0; - list-style: none; - } - .tsd-navigation li, - .tsd-page-navigation li { - padding: 0; - max-width: 100%; - } - .tsd-navigation .tsd-nav-link { - display: none; - } - .tsd-nested-navigation { - margin-left: 3rem; - } - .tsd-nested-navigation > li > details { - margin-left: -1.5rem; - } - .tsd-small-nested-navigation { - margin-left: 1.5rem; - } - .tsd-small-nested-navigation > li > details { - margin-left: -1.5rem; - } - - .tsd-page-navigation-section > summary { - padding: 0.25rem; - } - .tsd-page-navigation-section > summary > svg { - margin-right: 0.25rem; - } - .tsd-page-navigation-section > div { - margin-left: 30px; - } - .tsd-page-navigation ul { - padding-left: 1.75rem; - } - - #tsd-sidebar-links a { - margin-top: 0; - margin-bottom: 0.5rem; - line-height: 1.25rem; - } - #tsd-sidebar-links a:last-of-type { - margin-bottom: 0; - } - - a.tsd-index-link { - padding: 0.25rem 0 !important; - font-size: 1rem; - line-height: 1.25rem; - display: inline-flex; - align-items: center; - color: var(--color-text); - } - .tsd-accordion-summary { - list-style-type: none; /* hide marker on non-safari */ - outline: none; /* broken on safari, so just hide it */ - display: flex; - align-items: center; - gap: 0.25rem; - box-sizing: border-box; - } - .tsd-accordion-summary::-webkit-details-marker { - display: none; /* hide marker on safari */ - } - .tsd-accordion-summary, - .tsd-accordion-summary a { - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; - - cursor: pointer; - } - .tsd-accordion-summary a { - width: calc(100% - 1.5rem); - } - .tsd-accordion-summary > * { - margin-top: 0; - margin-bottom: 0; - padding-top: 0; - padding-bottom: 0; - } - /* - * We need to be careful to target the arrow indicating whether the accordion - * is open, but not any other SVGs included in the details element. - */ - .tsd-accordion:not([open]) > .tsd-accordion-summary > svg:first-child { - transform: rotate(-90deg); - } - .tsd-index-content > :not(:first-child) { - margin-top: 0.75rem; - } - .tsd-index-summary { - margin-top: 1.5rem; - margin-bottom: 0.75rem; - display: flex; - align-content: center; - } - - .tsd-no-select { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - } - .tsd-kind-icon { - margin-right: 0.5rem; - width: 1.25rem; - height: 1.25rem; - min-width: 1.25rem; - min-height: 1.25rem; - } - .tsd-signature > .tsd-kind-icon { - margin-right: 0.8rem; - } - - .tsd-panel { - margin-bottom: 2.5rem; - } - .tsd-panel.tsd-member { - margin-bottom: 4rem; - } - .tsd-panel:empty { - display: none; - } - .tsd-panel > h1, - .tsd-panel > h2, - .tsd-panel > h3 { - margin: 1.5rem -1.5rem 0.75rem -1.5rem; - padding: 0 1.5rem 0.75rem 1.5rem; - } - .tsd-panel > h1.tsd-before-signature, - .tsd-panel > h2.tsd-before-signature, - .tsd-panel > h3.tsd-before-signature { - margin-bottom: 0; - border-bottom: none; - } - - .tsd-panel-group { - margin: 2rem 0; - } - .tsd-panel-group.tsd-index-group { - margin: 2rem 0; - } - .tsd-panel-group.tsd-index-group details { - margin: 2rem 0; - } - .tsd-panel-group > .tsd-accordion-summary { - margin-bottom: 1rem; - } - - #tsd-search[open] { - animation: fade-in var(--modal-animation-duration) ease-out forwards; - } - #tsd-search[open].closing { - animation-name: fade-out; - } - - /* Avoid setting `display` on closed dialog */ - #tsd-search[open] { - display: flex; - flex-direction: column; - padding: 1rem; - width: 32rem; - max-width: 90vw; - max-height: calc(100vh - env(keyboard-inset-height, 0px) - 25vh); - /* Anchor dialog to top */ - margin-top: 10vh; - border-radius: 6px; - will-change: max-height; - } - #tsd-search-input { - box-sizing: border-box; - width: 100%; - padding: 0 0.625rem; /* 10px */ - outline: 0; - border: 2px solid var(--color-accent); - background-color: transparent; - color: var(--color-text); - border-radius: 4px; - height: 2.5rem; - flex: 0 0 auto; - font-size: 0.875rem; - transition: border-color 0.2s, background-color 0.2s; - } - #tsd-search-input:focus-visible { - background-color: var(--color-background-active); - border-color: transparent; - color: var(--color-contrast-text); - } - #tsd-search-input::placeholder { - color: inherit; - opacity: 0.8; - } - #tsd-search-results { - margin: 0; - padding: 0; - list-style: none; - flex: 1 1 auto; - display: flex; - flex-direction: column; - overflow-y: auto; - } - #tsd-search-results:not(:empty) { - margin-top: 0.5rem; - } - #tsd-search-results > li { - background-color: var(--color-background); - line-height: 1.5; - box-sizing: border-box; - border-radius: 4px; - } - #tsd-search-results > li:nth-child(even) { - background-color: var(--color-background-secondary); - } - #tsd-search-results > li:is(:hover, [aria-selected="true"]) { - background-color: var(--color-background-active); - color: var(--color-contrast-text); - } - /* It's important that this takes full size of parent `li`, to capture a click on `li` */ - #tsd-search-results > li > a { - display: flex; - align-items: center; - padding: 0.5rem 0.25rem; - box-sizing: border-box; - width: 100%; - } - #tsd-search-results > li > a > .text { - flex: 1 1 auto; - min-width: 0; - overflow-wrap: anywhere; - } - #tsd-search-results > li > a .parent { - color: var(--color-text-aside); - } - #tsd-search-results > li > a mark { - color: inherit; - background-color: inherit; - font-weight: bold; - } - #tsd-search-status { - flex: 1; - display: grid; - place-content: center; - text-align: center; - overflow-wrap: anywhere; - } - #tsd-search-status:not(:empty) { - min-height: 6rem; - } - - .tsd-signature { - margin: 0 0 1rem 0; - padding: 1rem 0.5rem; - border: 1px solid var(--color-accent); - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - font-size: 14px; - overflow-x: auto; - } - - .tsd-signature-keyword { - color: var(--color-ts-keyword); - font-weight: normal; - } - - .tsd-signature-symbol { - color: var(--color-text-aside); - font-weight: normal; - } - - .tsd-signature-type { - font-style: italic; - font-weight: normal; - } - - .tsd-signatures { - padding: 0; - margin: 0 0 1em 0; - list-style-type: none; - } - .tsd-signatures .tsd-signature { - margin: 0; - border-color: var(--color-accent); - border-width: 1px 0; - transition: background-color 0.1s; - } - .tsd-signatures .tsd-index-signature:not(:last-child) { - margin-bottom: 1em; - } - .tsd-signatures .tsd-index-signature .tsd-signature { - border-width: 1px; - } - .tsd-description .tsd-signatures .tsd-signature { - border-width: 1px; - } - - ul.tsd-parameter-list, - ul.tsd-type-parameter-list { - list-style: square; - margin: 0; - padding-left: 20px; - } - ul.tsd-parameter-list > li.tsd-parameter-signature, - ul.tsd-type-parameter-list > li.tsd-parameter-signature { - list-style: none; - margin-left: -20px; - } - ul.tsd-parameter-list h5, - ul.tsd-type-parameter-list h5 { - font-size: 16px; - margin: 1em 0 0.5em 0; - } - .tsd-sources { - margin-top: 1rem; - font-size: 0.875em; - } - .tsd-sources a { - color: var(--color-text-aside); - text-decoration: underline; - } - .tsd-sources ul { - list-style: none; - padding: 0; - } - - .tsd-page-toolbar { - position: sticky; - z-index: 1; - top: 0; - left: 0; - width: 100%; - color: var(--color-text); - background: var(--color-background-secondary); - border-bottom: var(--dim-toolbar-border-bottom-width) - var(--color-accent) solid; - transition: transform 0.3s ease-in-out; - } - .tsd-page-toolbar a { - color: var(--color-text); - } - .tsd-toolbar-contents { - display: flex; - align-items: center; - height: var(--dim-toolbar-contents-height); - margin: 0 auto; - } - .tsd-toolbar-contents > .title { - font-weight: bold; - margin-right: auto; - } - #tsd-toolbar-links { - display: flex; - align-items: center; - gap: 1.5rem; - margin-right: 1rem; - } - - .tsd-widget { - box-sizing: border-box; - display: inline-block; - opacity: 0.8; - height: 2.5rem; - width: 2.5rem; - transition: opacity 0.1s, background-color 0.1s; - text-align: center; - cursor: pointer; - border: none; - background-color: transparent; - } - .tsd-widget:hover { - opacity: 0.9; - } - .tsd-widget:active { - opacity: 1; - background-color: var(--color-accent); - } - #tsd-toolbar-menu-trigger { - display: none; - } - - .tsd-member-summary-name { - display: inline-flex; - align-items: center; - padding: 0.25rem; - text-decoration: none; - } - - .tsd-anchor-icon { - display: inline-flex; - align-items: center; - margin-left: 0.5rem; - color: var(--color-text); - vertical-align: middle; - } - - .tsd-anchor-icon svg { - width: 1em; - height: 1em; - visibility: hidden; - } - - .tsd-member-summary-name:hover > .tsd-anchor-icon svg, - .tsd-anchor-link:hover > .tsd-anchor-icon svg, - .tsd-anchor-icon:focus-visible svg { - visibility: visible; - } - - .deprecated { - text-decoration: line-through !important; - } - - .warning { - padding: 1rem; - color: var(--color-warning-text); - background: var(--color-background-warning); - } - - .tsd-kind-project { - color: var(--color-ts-project); - } - .tsd-kind-module { - color: var(--color-ts-module); - } - .tsd-kind-namespace { - color: var(--color-ts-namespace); - } - .tsd-kind-enum { - color: var(--color-ts-enum); - } - .tsd-kind-enum-member { - color: var(--color-ts-enum-member); - } - .tsd-kind-variable { - color: var(--color-ts-variable); - } - .tsd-kind-function { - color: var(--color-ts-function); - } - .tsd-kind-class { - color: var(--color-ts-class); - } - .tsd-kind-interface { - color: var(--color-ts-interface); - } - .tsd-kind-constructor { - color: var(--color-ts-constructor); - } - .tsd-kind-property { - color: var(--color-ts-property); - } - .tsd-kind-method { - color: var(--color-ts-method); - } - .tsd-kind-reference { - color: var(--color-ts-reference); - } - .tsd-kind-call-signature { - color: var(--color-ts-call-signature); - } - .tsd-kind-index-signature { - color: var(--color-ts-index-signature); - } - .tsd-kind-constructor-signature { - color: var(--color-ts-constructor-signature); - } - .tsd-kind-parameter { - color: var(--color-ts-parameter); - } - .tsd-kind-type-parameter { - color: var(--color-ts-type-parameter); - } - .tsd-kind-accessor { - color: var(--color-ts-accessor); - } - .tsd-kind-get-signature { - color: var(--color-ts-get-signature); - } - .tsd-kind-set-signature { - color: var(--color-ts-set-signature); - } - .tsd-kind-type-alias { - color: var(--color-ts-type-alias); - } - - /* if we have a kind icon, don't color the text by kind */ - .tsd-kind-icon ~ span { - color: var(--color-text); - } - - /* mobile */ - @media (max-width: 769px) { - #tsd-toolbar-menu-trigger { - display: inline-block; - /* temporary fix to vertically align, for compatibility */ - line-height: 2.5; - } - #tsd-toolbar-links { - display: none; - } - - .container-main { - display: flex; - } - .col-content { - float: none; - max-width: 100%; - width: 100%; - } - .col-sidebar { - position: fixed !important; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - z-index: 1024; - top: 0 !important; - bottom: 0 !important; - left: auto !important; - right: 0 !important; - padding: 1.5rem 1.5rem 0 0; - width: 75vw; - visibility: hidden; - background-color: var(--color-background); - transform: translate(100%, 0); - } - .col-sidebar > *:last-child { - padding-bottom: 20px; - } - .overlay { - content: ""; - display: block; - position: fixed; - z-index: 1023; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.75); - visibility: hidden; - } - - .to-has-menu .overlay { - animation: fade-in 0.4s; - } - - .to-has-menu .col-sidebar { - animation: pop-in-from-right 0.4s; - } - - .from-has-menu .overlay { - animation: fade-out 0.4s; - } - - .from-has-menu .col-sidebar { - animation: pop-out-to-right 0.4s; - } - - .has-menu body { - overflow: hidden; - } - .has-menu .overlay { - visibility: visible; - } - .has-menu .col-sidebar { - visibility: visible; - transform: translate(0, 0); - display: flex; - flex-direction: column; - gap: 1.5rem; - max-height: 100vh; - padding: 1rem 2rem; - } - .has-menu .tsd-navigation { - max-height: 100%; - } - .tsd-navigation .tsd-nav-link { - display: flex; - } - } - - /* one sidebar */ - @media (min-width: 770px) { - .container-main { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); - grid-template-areas: "sidebar content"; - --dim-container-main-margin-y: 2rem; - } - - .tsd-breadcrumb { - margin-top: 0; - } - - .col-sidebar { - grid-area: sidebar; - } - .col-content { - grid-area: content; - padding: 0 1rem; - } - } - @media (min-width: 770px) and (max-width: 1399px) { - .col-sidebar { - max-height: calc( - 100vh - var(--dim-header-height) - var(--dim-footer-height) - - 2 * var(--dim-container-main-margin-y) - ); - overflow: auto; - position: sticky; - top: calc( - var(--dim-header-height) + var(--dim-container-main-margin-y) - ); - } - .site-menu { - margin-top: 1rem; - } - } - - /* two sidebars */ - @media (min-width: 1200px) { - .container-main { - grid-template-columns: - minmax(0, 1fr) minmax(0, 2.5fr) minmax( - 0, - 20rem - ); - grid-template-areas: "sidebar content toc"; - } - - .col-sidebar { - display: contents; - } - - .page-menu { - grid-area: toc; - padding-left: 1rem; - } - .site-menu { - grid-area: sidebar; - } - - .site-menu { - margin-top: 0rem; - } - - .page-menu, - .site-menu { - max-height: calc( - 100vh - var(--dim-header-height) - var(--dim-footer-height) - - 2 * var(--dim-container-main-margin-y) - ); - overflow: auto; - position: sticky; - top: calc( - var(--dim-header-height) + var(--dim-container-main-margin-y) - ); - } - } -} diff --git a/docs/classes/raptor_McRaptorAlgorithm.McRaptorAlgorithm.html b/docs/classes/raptor_McRaptorAlgorithm.McRaptorAlgorithm.html deleted file mode 100644 index 2ebb4a4..0000000 --- a/docs/classes/raptor_McRaptorAlgorithm.McRaptorAlgorithm.html +++ /dev/null @@ -1,30 +0,0 @@ -McRaptorAlgorithm | mbus-backend
mbus-backend
    Preparing search index...

    Implementation of the McRAPTOR (Multi-Criteria Round-Based Public Transit Routing) algorithm. -Optimizes for arrival time, walking distance, and number of transfers.

    -
    Index

    Constructors

    Methods

    • Calculates optimal journeys for a specific departure time.

      -

      Parameters

      • origin: string

        The starting Stop ID.

        -
      • destination: string

        The destination Stop ID.

        -
      • departureTime: number

        The exact departure time.

        -

      Returns Journey[]

      A list of optimal Journey objects.

      -
    • Finds the best journeys within a time window, filtering for Pareto optimality across all departures.

      -

      Parameters

      • origin: string

        The starting Stop ID.

        -
      • destination: string

        The destination Stop ID.

        -
      • startTime: number

        The start of the departure window.

        -
      • range: number

        The duration of the window to search (e.g. 3600s).

        -

      Returns Journey[]

      A deduplicated list of the best journeys found in the time range.

      -
    • Executes the McRAPTOR algorithm to find all non-dominated paths to the destination.

      -

      Parameters

      • origin: string

        The starting Stop ID.

        -
      • destination: string

        The destination Stop ID.

        -
      • departureTime: number

        The time of departure.

        -

      Returns Bag

      A Bag containing Pareto-optimal labels for the destination.

      -
    • Sets the penalty multiplier for walking (default is 1).

      -

      Parameters

      • penalty: number

        The multiplier for walking duration cost.

        -

      Returns void

    diff --git a/docs/classes/raptor_McStructs.Bag.html b/docs/classes/raptor_McStructs.Bag.html deleted file mode 100644 index 7463c61..0000000 --- a/docs/classes/raptor_McStructs.Bag.html +++ /dev/null @@ -1,13 +0,0 @@ -Bag | mbus-backend
    mbus-backend
      Preparing search index...

      A container for Labels at a specific stop that maintains a Pareto frontier. -Only keeps labels that are not dominated by any other label in the bag.

      -
      Index

      Constructors

      Properties

      Methods

      Constructors

      Properties

      labels: Label[] = []

      Methods

      • Attempts to add a label to the bag.

        -

        Parameters

        Returns boolean

        True if the label was added (it was non-dominated), False otherwise.

        -
      • Merges another bag's labels into this one.

        -

        Parameters

        Returns boolean

        True if the merge resulted in any changes to this bag.

        -
      diff --git a/docs/classes/raptor_McStructs.Label.html b/docs/classes/raptor_McStructs.Label.html deleted file mode 100644 index ca4ff9f..0000000 --- a/docs/classes/raptor_McStructs.Label.html +++ /dev/null @@ -1,36 +0,0 @@ -Label | mbus-backend
      mbus-backend
        Preparing search index...

        Represents a state or arrival at a specific stop in the routing graph. -Used to trace back the path and store performance metrics.

        -
        Index

        Constructors

        • Parameters

          • arrivalTime: number

            Time of arrival at this stop.

            -
          • walkingDistance: number

            Cumulative walking distance in meters.

            -
          • transferCount: number

            Number of transfers taken so far.

            -
          • parent: Label | null = null

            The previous label in the chain (used for backtracking).

            -
          • trip: Trip | null = null

            The trip taken to reach this state (if applicable).

            -
          • transfer: Transfer | null = null

            The transfer used to reach this state (if applicable).

            -
          • stop: string | null = null

            The ID of the current stop.

            -
          • enterTime: number = 0

            Time when the passenger boarded the vehicle.

            -
          • stopIndex: number = -1

            Index of the current stop in the trip's sequence.

            -

          Returns Label

        Properties

        arrivalTime: number

        Time of arrival at this stop.

        -
        enterTime: number = 0

        Time when the passenger boarded the vehicle.

        -
        parent: Label | null = null

        The previous label in the chain (used for backtracking).

        -
        stop: string | null = null

        The ID of the current stop.

        -
        stopIndex: number = -1

        Index of the current stop in the trip's sequence.

        -
        transfer: Transfer | null = null

        The transfer used to reach this state (if applicable).

        -
        transferCount: number

        Number of transfers taken so far.

        -
        trip: Trip | null = null

        The trip taken to reach this state (if applicable).

        -
        walkingDistance: number

        Cumulative walking distance in meters.

        -

        Methods

        • Determines if this label is strictly better than another label. -A label dominates if it is better or equal in all criteria and strictly better in at least one.

          -

          Parameters

          Returns boolean

        diff --git a/docs/functions/jobs.startBackgroundJobs.html b/docs/functions/jobs.startBackgroundJobs.html deleted file mode 100644 index 2eed3bd..0000000 --- a/docs/functions/jobs.startBackgroundJobs.html +++ /dev/null @@ -1,2 +0,0 @@ -startBackgroundJobs | mbus-backend
        mbus-backend
          Preparing search index...

          Function startBackgroundJobs

          • Starts background jobs for updating bus positions, initializing routes, and rebuilding the graph.

            -

            Returns void

          diff --git a/docs/functions/routes_api.activeRemindersForToken.html b/docs/functions/routes_api.activeRemindersForToken.html deleted file mode 100644 index af2db27..0000000 --- a/docs/functions/routes_api.activeRemindersForToken.html +++ /dev/null @@ -1,3 +0,0 @@ -activeRemindersForToken | mbus-backend
          mbus-backend
            Preparing search index...

            Function activeRemindersForToken

            • Parameters

              • req: Request

                Express request, token is path encoded

                -
              • res: Response<{ reminders: ActiveReminderInfo[] }>

                Express response

                -

              Returns void

            diff --git a/docs/functions/routes_api.getAllPredictions.html b/docs/functions/routes_api.getAllPredictions.html deleted file mode 100644 index 8b3990a..0000000 --- a/docs/functions/routes_api.getAllPredictions.html +++ /dev/null @@ -1,5 +0,0 @@ -getAllPredictions | mbus-backend
            mbus-backend
              Preparing search index...

              Function getAllPredictions

              • Returns all active bus predictions grouped by vehicle ID.

                -

                Parameters

                • req: Request

                  Express request

                  -
                • res: Response

                  Express response

                  -

                Returns void

                JSON array of objects, each with vid and stops (predictions).

                -
              diff --git a/docs/functions/routes_api.getAllRideRoutes.html b/docs/functions/routes_api.getAllRideRoutes.html deleted file mode 100644 index 23cfae4..0000000 --- a/docs/functions/routes_api.getAllRideRoutes.html +++ /dev/null @@ -1,5 +0,0 @@ -getAllRideRoutes | mbus-backend
              mbus-backend
                Preparing search index...

                Function getAllRideRoutes

                • Returns all cached ride route patterns.

                  -

                  Parameters

                  • req: Request

                    Express request

                    -
                  • res: Response

                    Express response

                    -

                  Returns void

                  JSON object with routes mapping route IDs to patterns.

                  -
                diff --git a/docs/functions/routes_api.getAllRideStops.html b/docs/functions/routes_api.getAllRideStops.html deleted file mode 100644 index ba9acd2..0000000 --- a/docs/functions/routes_api.getAllRideStops.html +++ /dev/null @@ -1,5 +0,0 @@ -getAllRideStops | mbus-backend
                mbus-backend
                  Preparing search index...

                  Function getAllRideStops

                  • Returns a list of all known stops with their locations.

                    -

                    Parameters

                    • req: Request

                      Express request

                      -
                    • res: Response

                      Express response

                      -

                    Returns void

                    JSON array of stop objects.

                    -
                  diff --git a/docs/functions/routes_api.getAllRoutes.html b/docs/functions/routes_api.getAllRoutes.html deleted file mode 100644 index f764bd9..0000000 --- a/docs/functions/routes_api.getAllRoutes.html +++ /dev/null @@ -1,5 +0,0 @@ -getAllRoutes | mbus-backend
                  mbus-backend
                    Preparing search index...

                    Function getAllRoutes

                    • Returns all cached route patterns.

                      -

                      Parameters

                      • req: Request

                        Express request

                        -
                      • res: Response

                        Express response

                        -

                      Returns void

                      JSON object with routes mapping route IDs to patterns.

                      -
                    diff --git a/docs/functions/routes_api.getAllStops.html b/docs/functions/routes_api.getAllStops.html deleted file mode 100644 index c997411..0000000 --- a/docs/functions/routes_api.getAllStops.html +++ /dev/null @@ -1,5 +0,0 @@ -getAllStops | mbus-backend
                    mbus-backend
                      Preparing search index...

                      Function getAllStops

                      • Returns a list of all known stops with their locations.

                        -

                        Parameters

                        • req: Request

                          Express request

                          -
                        • res: Response

                          Express response

                          -

                        Returns void

                        JSON array of stop objects.

                        -
                      diff --git a/docs/functions/routes_api.getBuildingLocations.html b/docs/functions/routes_api.getBuildingLocations.html deleted file mode 100644 index bd10bd4..0000000 --- a/docs/functions/routes_api.getBuildingLocations.html +++ /dev/null @@ -1,5 +0,0 @@ -getBuildingLocations | mbus-backend
                      mbus-backend
                        Preparing search index...

                        Function getBuildingLocations

                        • Serves the building locations JSON file.

                          -

                          Parameters

                          • req: Request

                            Express request

                            -
                          • res: Response

                            Express response

                            -

                          Returns void

                          JSON file containing building data.

                          -
                        diff --git a/docs/functions/routes_api.getBusPositions.html b/docs/functions/routes_api.getBusPositions.html deleted file mode 100644 index 4f304a7..0000000 --- a/docs/functions/routes_api.getBusPositions.html +++ /dev/null @@ -1,5 +0,0 @@ -getBusPositions | mbus-backend
                        mbus-backend
                          Preparing search index...

                          Function getBusPositions

                          • Returns current positions of all michgan buses.

                            -

                            Parameters

                            • req: Request

                              Express request

                              -
                            • res: Response

                              Express response

                              -

                            Returns void

                            JSON object with buses array.

                            -
                          diff --git a/docs/functions/routes_api.getBusPredictions.html b/docs/functions/routes_api.getBusPredictions.html deleted file mode 100644 index 8e85c0b..0000000 --- a/docs/functions/routes_api.getBusPredictions.html +++ /dev/null @@ -1,5 +0,0 @@ -getBusPredictions | mbus-backend
                          mbus-backend
                            Preparing search index...

                            Function getBusPredictions

                            • Returns predictions for a specific bus ID.

                              -

                              Parameters

                              • req: Request

                                Express request

                                -
                              • res: Response

                                Express response

                                -

                              Returns void

                              JSON object with bustime-response containing prd (predictions).

                              -
                            diff --git a/docs/functions/routes_api.getBusPredictionsLegacy.html b/docs/functions/routes_api.getBusPredictionsLegacy.html deleted file mode 100644 index bb8f4b8..0000000 --- a/docs/functions/routes_api.getBusPredictionsLegacy.html +++ /dev/null @@ -1,4 +0,0 @@ -getBusPredictionsLegacy | mbus-backend
                            mbus-backend
                              Preparing search index...

                              Function getBusPredictionsLegacy

                              • Legacy/test endpoint for bus predictions.

                                -

                                Parameters

                                • req: Request

                                  Express request

                                  -
                                • res: Response

                                  Express response

                                  -

                                Returns void

                              diff --git a/docs/functions/routes_api.getFrontendData.html b/docs/functions/routes_api.getFrontendData.html deleted file mode 100644 index cd99eab..0000000 --- a/docs/functions/routes_api.getFrontendData.html +++ /dev/null @@ -1,5 +0,0 @@ -getFrontendData | mbus-backend
                              mbus-backend
                                Preparing search index...

                                Function getFrontendData

                                • Returns aggregated data for the frontend, including route configs and metadata.

                                  -

                                  Parameters

                                  • req: Request

                                    Express request

                                    -
                                  • res: Response

                                    Express response

                                    -

                                  Returns void

                                  JSON object with routes (array of route details) and metadata.

                                  -
                                diff --git a/docs/functions/routes_api.getKeyStops.html b/docs/functions/routes_api.getKeyStops.html deleted file mode 100644 index 0aaba13..0000000 --- a/docs/functions/routes_api.getKeyStops.html +++ /dev/null @@ -1,5 +0,0 @@ -getKeyStops | mbus-backend
                                mbus-backend
                                  Preparing search index...

                                  Function getKeyStops

                                  • Returns key stops.

                                    -

                                    Parameters

                                    • req: Request

                                      Express request

                                      -
                                    • res: Response

                                      Express response

                                      -

                                    Returns void

                                    JSON object with message details.

                                    -
                                  diff --git a/docs/functions/routes_api.getNearestStops.html b/docs/functions/routes_api.getNearestStops.html deleted file mode 100644 index e360e8b..0000000 --- a/docs/functions/routes_api.getNearestStops.html +++ /dev/null @@ -1,5 +0,0 @@ -getNearestStops | mbus-backend
                                  mbus-backend
                                    Preparing search index...

                                    Function getNearestStops

                                    • Returns the nearest k stops to a given latitude and longitude.

                                      -

                                      Parameters

                                      • req: Request

                                        Express request

                                        -
                                      • res: Response

                                        Express response

                                        -

                                      Returns void

                                      JSON object with nearestStops array.

                                      -
                                    diff --git a/docs/functions/routes_api.getRidePositions.html b/docs/functions/routes_api.getRidePositions.html deleted file mode 100644 index 9c4abad..0000000 --- a/docs/functions/routes_api.getRidePositions.html +++ /dev/null @@ -1,5 +0,0 @@ -getRidePositions | mbus-backend
                                    mbus-backend
                                      Preparing search index...

                                      Function getRidePositions

                                      • returns positions of all ride busses

                                        -

                                        Parameters

                                        • req: Request

                                          Express request

                                          -
                                        • res: Response

                                          Express response

                                          -

                                        Returns void

                                        JSON object with buses array.

                                        -
                                      diff --git a/docs/functions/routes_api.getRidePredictions.html b/docs/functions/routes_api.getRidePredictions.html deleted file mode 100644 index a91929a..0000000 --- a/docs/functions/routes_api.getRidePredictions.html +++ /dev/null @@ -1,5 +0,0 @@ -getRidePredictions | mbus-backend
                                      mbus-backend
                                        Preparing search index...

                                        Function getRidePredictions

                                        • Returns predictions for a specific ride bus ID.

                                          -

                                          Parameters

                                          • req: Request

                                            Express request

                                            -
                                          • res: Response

                                            Express response

                                            -

                                          Returns void

                                          JSON object with bustime-response containing prd (predictions).

                                          -
                                        diff --git a/docs/functions/routes_api.getRideStopPredictions.html b/docs/functions/routes_api.getRideStopPredictions.html deleted file mode 100644 index ca779c0..0000000 --- a/docs/functions/routes_api.getRideStopPredictions.html +++ /dev/null @@ -1,5 +0,0 @@ -getRideStopPredictions | mbus-backend
                                        mbus-backend
                                          Preparing search index...

                                          Function getRideStopPredictions

                                          • Returns predictions for a specific ride stop ID.

                                            -

                                            Parameters

                                            • req: Request

                                              Express request

                                              -
                                            • res: Response

                                              Express response

                                              -

                                            Returns void

                                            JSON object with bustime-response containing prd (predictions).

                                            -
                                          diff --git a/docs/functions/routes_api.getRouteCache.html b/docs/functions/routes_api.getRouteCache.html deleted file mode 100644 index 5e3222b..0000000 --- a/docs/functions/routes_api.getRouteCache.html +++ /dev/null @@ -1,5 +0,0 @@ -getRouteCache | mbus-backend
                                          mbus-backend
                                            Preparing search index...

                                            Function getRouteCache

                                            • Returns the route timing cache used for extrapolation.

                                              -

                                              Parameters

                                              • req: Request

                                                Express request

                                                -
                                              • res: Response

                                                Express response

                                                -

                                              Returns void

                                              JSON object with routes containing timing data.

                                              -
                                            diff --git a/docs/functions/routes_api.getRouteColor.html b/docs/functions/routes_api.getRouteColor.html deleted file mode 100644 index 2b49c96..0000000 --- a/docs/functions/routes_api.getRouteColor.html +++ /dev/null @@ -1,5 +0,0 @@ -getRouteColor | mbus-backend
                                            mbus-backend
                                              Preparing search index...

                                              Function getRouteColor

                                              • Returns the color and image for a specific route ID.

                                                -

                                                Parameters

                                                • req: Request

                                                  Express request

                                                  -
                                                • res: Response

                                                  Express response

                                                  -

                                                Returns void

                                                JSON object with routeId, color, and image.

                                                -
                                              diff --git a/docs/functions/routes_api.getRouteColors.html b/docs/functions/routes_api.getRouteColors.html deleted file mode 100644 index 25a616d..0000000 --- a/docs/functions/routes_api.getRouteColors.html +++ /dev/null @@ -1,5 +0,0 @@ -getRouteColors | mbus-backend
                                              mbus-backend
                                                Preparing search index...

                                                Function getRouteColors

                                                • Returns configuration (colors and images) for all routes.

                                                  -

                                                  Parameters

                                                  • req: Request

                                                    Express request

                                                    -
                                                  • res: Response

                                                    Express response

                                                    -

                                                  Returns void

                                                  JSON object with a routes array containing route configs.

                                                  -
                                                diff --git a/docs/functions/routes_api.getRouteInfoVersion.html b/docs/functions/routes_api.getRouteInfoVersion.html deleted file mode 100644 index 5623b6d..0000000 --- a/docs/functions/routes_api.getRouteInfoVersion.html +++ /dev/null @@ -1,5 +0,0 @@ -getRouteInfoVersion | mbus-backend
                                                mbus-backend
                                                  Preparing search index...

                                                  Function getRouteInfoVersion

                                                  • Returns the current version of the route information.

                                                    -

                                                    Parameters

                                                    • req: Request

                                                      Express request

                                                      -
                                                    • res: Response

                                                      Express response

                                                      -

                                                    Returns void

                                                    JSON object with the version string.

                                                    -
                                                  diff --git a/docs/functions/routes_api.getRouteInformation.html b/docs/functions/routes_api.getRouteInformation.html deleted file mode 100644 index a583c28..0000000 --- a/docs/functions/routes_api.getRouteInformation.html +++ /dev/null @@ -1,5 +0,0 @@ -getRouteInformation | mbus-backend
                                                  mbus-backend
                                                    Preparing search index...

                                                    Function getRouteInformation

                                                    • Returns static route metadata including names, images, and colors.

                                                      -

                                                      Parameters

                                                      • req: Request

                                                        Express request

                                                        -
                                                      • res: Response

                                                        Express response

                                                        -

                                                      Returns void

                                                      JSON object containing routeIdToName, routeImages, metadata, and routeColors.

                                                      -
                                                    diff --git a/docs/functions/routes_api.getSelectableRoutes.html b/docs/functions/routes_api.getSelectableRoutes.html deleted file mode 100644 index 253334e..0000000 --- a/docs/functions/routes_api.getSelectableRoutes.html +++ /dev/null @@ -1,5 +0,0 @@ -getSelectableRoutes | mbus-backend
                                                    mbus-backend
                                                      Preparing search index...

                                                      Function getSelectableRoutes

                                                      • Returns a list of all selectable routes with their names and colors.

                                                        -

                                                        Parameters

                                                        • req: Request

                                                          Express request

                                                          -
                                                        • res: Response

                                                          Express response

                                                          -

                                                        Returns void

                                                        JSON object with a bustime-response containing a list of routes.

                                                        -
                                                      diff --git a/docs/functions/routes_api.getStartupInfo.html b/docs/functions/routes_api.getStartupInfo.html deleted file mode 100644 index 5a12518..0000000 --- a/docs/functions/routes_api.getStartupInfo.html +++ /dev/null @@ -1,5 +0,0 @@ -getStartupInfo | mbus-backend
                                                      mbus-backend
                                                        Preparing search index...

                                                        Function getStartupInfo

                                                        • Returns startup configuration info including supported versions and messages.

                                                          -

                                                          Parameters

                                                          • req: Request

                                                            Express request

                                                            -
                                                          • res: Response

                                                            Express response

                                                            -

                                                          Returns void

                                                          JSON object with version info and messages.

                                                          -
                                                        diff --git a/docs/functions/routes_api.getStartupMessages.html b/docs/functions/routes_api.getStartupMessages.html deleted file mode 100644 index df88d25..0000000 --- a/docs/functions/routes_api.getStartupMessages.html +++ /dev/null @@ -1,5 +0,0 @@ -getStartupMessages | mbus-backend
                                                        mbus-backend
                                                          Preparing search index...

                                                          Function getStartupMessages

                                                          • Returns special startup messages (e.g., holiday greetings).

                                                            -

                                                            Parameters

                                                            • req: Request

                                                              Express request

                                                              -
                                                            • res: Response

                                                              Express response

                                                              -

                                                            Returns void

                                                            JSON object with message details.

                                                            -
                                                          diff --git a/docs/functions/routes_api.getStopPredictions.html b/docs/functions/routes_api.getStopPredictions.html deleted file mode 100644 index ff7f2ec..0000000 --- a/docs/functions/routes_api.getStopPredictions.html +++ /dev/null @@ -1,5 +0,0 @@ -getStopPredictions | mbus-backend
                                                          mbus-backend
                                                            Preparing search index...

                                                            Function getStopPredictions

                                                            • Returns predictions for a specific stop ID.

                                                              -

                                                              Parameters

                                                              • req: Request

                                                                Express request

                                                                -
                                                              • res: Response

                                                                Express response

                                                                -

                                                              Returns void

                                                              JSON object with bustime-response containing prd (predictions).

                                                              -
                                                            diff --git a/docs/functions/routes_api.getVehicleImage.html b/docs/functions/routes_api.getVehicleImage.html deleted file mode 100644 index d7d2c1c..0000000 --- a/docs/functions/routes_api.getVehicleImage.html +++ /dev/null @@ -1,5 +0,0 @@ -getVehicleImage | mbus-backend
                                                            mbus-backend
                                                              Preparing search index...

                                                              Function getVehicleImage

                                                              • Serves the image file for a specific route.

                                                                -

                                                                Parameters

                                                                • req: Request

                                                                  Express request

                                                                  -
                                                                • res: Response

                                                                  Express response

                                                                  -

                                                                Returns void

                                                                Image file (PNG).

                                                                -
                                                              diff --git a/docs/functions/routes_api.getVehiclePositions.html b/docs/functions/routes_api.getVehiclePositions.html deleted file mode 100644 index bb3d311..0000000 --- a/docs/functions/routes_api.getVehiclePositions.html +++ /dev/null @@ -1,5 +0,0 @@ -getVehiclePositions | mbus-backend
                                                              mbus-backend
                                                                Preparing search index...

                                                                Function getVehiclePositions

                                                                • Alias for getBusPositions.

                                                                  -

                                                                  Parameters

                                                                  • req: Request

                                                                    Express request

                                                                    -
                                                                  • res: Response

                                                                    Express response

                                                                    -

                                                                  Returns void

                                                                  JSON object with buses array.

                                                                  -
                                                                diff --git a/docs/functions/routes_api.modifyReminders.html b/docs/functions/routes_api.modifyReminders.html deleted file mode 100644 index 7d03dcd..0000000 --- a/docs/functions/routes_api.modifyReminders.html +++ /dev/null @@ -1,4 +0,0 @@ -modifyReminders | mbus-backend
                                                                mbus-backend
                                                                  Preparing search index...

                                                                  Function modifyReminders

                                                                  • Lets you run the equivalent of several setReminder and unsetReminders in one call

                                                                    -

                                                                    Parameters

                                                                    • req: Request

                                                                      Express request expecting ModifyRemindersBody in body

                                                                      -
                                                                    • res: Response

                                                                      Express response

                                                                      -

                                                                    Returns void

                                                                  diff --git a/docs/functions/routes_api.notifyMeLater.html b/docs/functions/routes_api.notifyMeLater.html deleted file mode 100644 index 7035476..0000000 --- a/docs/functions/routes_api.notifyMeLater.html +++ /dev/null @@ -1 +0,0 @@ -notifyMeLater | mbus-backend
                                                                  mbus-backend
                                                                    Preparing search index...

                                                                    Function notifyMeLater

                                                                    • Parameters

                                                                      • req: Request
                                                                      • res: Response

                                                                      Returns void

                                                                    diff --git a/docs/functions/routes_api.planJourney.html b/docs/functions/routes_api.planJourney.html deleted file mode 100644 index 73be18d..0000000 --- a/docs/functions/routes_api.planJourney.html +++ /dev/null @@ -1,5 +0,0 @@ -planJourney | mbus-backend
                                                                    mbus-backend
                                                                      Preparing search index...

                                                                      Function planJourney

                                                                      • Plans a journey between origin and destination coordinates.

                                                                        -

                                                                        Parameters

                                                                        • req: Request

                                                                          Express request

                                                                          -
                                                                        • res: Response

                                                                          Express response

                                                                          -

                                                                        Returns Promise<void>

                                                                        JSON object with journeys array containing possible routes.

                                                                        -
                                                                      diff --git a/docs/functions/routes_api.saveGraph.html b/docs/functions/routes_api.saveGraph.html deleted file mode 100644 index 2dbfb87..0000000 --- a/docs/functions/routes_api.saveGraph.html +++ /dev/null @@ -1,5 +0,0 @@ -saveGraph | mbus-backend
                                                                      mbus-backend
                                                                        Preparing search index...

                                                                        Function saveGraph

                                                                        • Saves the current graph state to a file (DEV mode only).

                                                                          -

                                                                          Parameters

                                                                          • req: Request

                                                                            Express request

                                                                            -
                                                                          • res: Response

                                                                            Express response

                                                                            -

                                                                          Returns Promise<void>

                                                                          JSON message confirming the save path.

                                                                          -
                                                                        diff --git a/docs/functions/routes_api.setReminder.html b/docs/functions/routes_api.setReminder.html deleted file mode 100644 index 0903168..0000000 --- a/docs/functions/routes_api.setReminder.html +++ /dev/null @@ -1,3 +0,0 @@ -setReminder | mbus-backend
                                                                        mbus-backend
                                                                          Preparing search index...

                                                                          Function setReminder

                                                                          • Parameters

                                                                            • req: Request

                                                                              Express request, expects SetReminderBody in the body

                                                                              -
                                                                            • res: Response

                                                                              Express response, error message as string if error occurs

                                                                              -

                                                                            Returns void

                                                                          diff --git a/docs/functions/routes_api.swapToken.html b/docs/functions/routes_api.swapToken.html deleted file mode 100644 index d624bfc..0000000 --- a/docs/functions/routes_api.swapToken.html +++ /dev/null @@ -1,5 +0,0 @@ -swapToken | mbus-backend
                                                                          mbus-backend
                                                                            Preparing search index...

                                                                            Function swapToken

                                                                            • Parameters

                                                                              • req: Request

                                                                                Express request, SwapTokenBody in the body

                                                                                -
                                                                              • res: Response

                                                                                Express response, error message as string if error occurs

                                                                                -

                                                                                Upon responding with 200, future calls to /setReminder, /unsetReminder, and /activeReminders -will need the new token

                                                                                -

                                                                              Returns void

                                                                            diff --git a/docs/functions/routes_api.unsetReminder.html b/docs/functions/routes_api.unsetReminder.html deleted file mode 100644 index 64bb8e6..0000000 --- a/docs/functions/routes_api.unsetReminder.html +++ /dev/null @@ -1,3 +0,0 @@ -unsetReminder | mbus-backend
                                                                            mbus-backend
                                                                              Preparing search index...

                                                                              Function unsetReminder

                                                                              • Parameters

                                                                                • req: Request

                                                                                  Express request, UnsetReminderBody in the body

                                                                                  -
                                                                                • res: Response

                                                                                  Express response, error message as string if error occurs

                                                                                  -

                                                                                Returns void

                                                                              diff --git a/docs/functions/services_graphBuilder.findNearestStops.html b/docs/functions/services_graphBuilder.findNearestStops.html deleted file mode 100644 index 845e914..0000000 --- a/docs/functions/services_graphBuilder.findNearestStops.html +++ /dev/null @@ -1,2 +0,0 @@ -findNearestStops | mbus-backend
                                                                              mbus-backend
                                                                                Preparing search index...

                                                                                Function findNearestStops

                                                                                • Finds the nearest k stops to a given coordinate.

                                                                                  -

                                                                                  Parameters

                                                                                  • lat: number
                                                                                  • lon: number
                                                                                  • k: number = 2

                                                                                  Returns { distance: number; lat: number; lon: number; name: string; stpid: string }[]

                                                                                diff --git a/docs/functions/services_graphBuilder.initializeRoutes.html b/docs/functions/services_graphBuilder.initializeRoutes.html deleted file mode 100644 index 0be282b..0000000 --- a/docs/functions/services_graphBuilder.initializeRoutes.html +++ /dev/null @@ -1,2 +0,0 @@ -initializeRoutes | mbus-backend
                                                                                mbus-backend
                                                                                  Preparing search index...

                                                                                  Function initializeRoutes

                                                                                  • Initializes route data, caching patterns and stop locations.

                                                                                    -

                                                                                    Returns Promise<void>

                                                                                  diff --git a/docs/functions/services_graphBuilder.rebuildGraph.html b/docs/functions/services_graphBuilder.rebuildGraph.html deleted file mode 100644 index eef8bcb..0000000 --- a/docs/functions/services_graphBuilder.rebuildGraph.html +++ /dev/null @@ -1,2 +0,0 @@ -rebuildGraph | mbus-backend
                                                                                  mbus-backend
                                                                                    Preparing search index...
                                                                                    • Rebuilds the routing graph based on current predictions and static data.

                                                                                      -

                                                                                      Returns Promise<void>

                                                                                    diff --git a/docs/functions/services_graphBuilder.saveGraphState.html b/docs/functions/services_graphBuilder.saveGraphState.html deleted file mode 100644 index 36d84c0..0000000 --- a/docs/functions/services_graphBuilder.saveGraphState.html +++ /dev/null @@ -1,2 +0,0 @@ -saveGraphState | mbus-backend
                                                                                    mbus-backend
                                                                                      Preparing search index...
                                                                                      • Saves the current graph and state to a JSON file (DEV mode only).

                                                                                        -

                                                                                        Returns string

                                                                                      diff --git a/docs/functions/services_graphBuilder.sortPreds.html b/docs/functions/services_graphBuilder.sortPreds.html deleted file mode 100644 index d9e28f5..0000000 --- a/docs/functions/services_graphBuilder.sortPreds.html +++ /dev/null @@ -1,2 +0,0 @@ -sortPreds | mbus-backend
                                                                                      mbus-backend
                                                                                        Preparing search index...
                                                                                        diff --git a/docs/functions/services_graphBuilder.updateBusPositions.html b/docs/functions/services_graphBuilder.updateBusPositions.html deleted file mode 100644 index 914fac7..0000000 --- a/docs/functions/services_graphBuilder.updateBusPositions.html +++ /dev/null @@ -1,2 +0,0 @@ -updateBusPositions | mbus-backend
                                                                                        mbus-backend
                                                                                          Preparing search index...

                                                                                          Function updateBusPositions

                                                                                          • Fetches and updates current bus positions in state.

                                                                                            -

                                                                                            Returns Promise<void>

                                                                                          diff --git a/docs/functions/services_journey.planJourney.html b/docs/functions/services_journey.planJourney.html deleted file mode 100644 index c3c51ec..0000000 --- a/docs/functions/services_journey.planJourney.html +++ /dev/null @@ -1,8 +0,0 @@ -planJourney | mbus-backend
                                                                                          mbus-backend
                                                                                            Preparing search index...

                                                                                            Function planJourney

                                                                                            • Plans a journey between two coordinates using the McRaptor algorithm.

                                                                                              -

                                                                                              Parameters

                                                                                              • oLat: number

                                                                                                Origin latitude

                                                                                                -
                                                                                              • oLon: number

                                                                                                Origin longitude

                                                                                                -
                                                                                              • dLat: number

                                                                                                Destination latitude

                                                                                                -
                                                                                              • dLon: number

                                                                                                Destination longitude

                                                                                                -
                                                                                              • time: number

                                                                                                Start time in seconds since midnight

                                                                                                -
                                                                                              • options: { range?: number; walkingPenalty?: number }

                                                                                                Optional parameters for walking penalty and search range

                                                                                                -

                                                                                              Returns Promise<
                                                                                                  (
                                                                                                      | {
                                                                                                          arrivalTime: number;
                                                                                                          criteria: {
                                                                                                              arrivalTime: number;
                                                                                                              transferCount: number;
                                                                                                              walkingDistance: number;
                                                                                                          };
                                                                                                          departureTime: number;
                                                                                                          legs: any[];
                                                                                                      }
                                                                                                      | null
                                                                                                  )[],
                                                                                              >

                                                                                            diff --git a/docs/functions/services_mbus.fetchPatterns.html b/docs/functions/services_mbus.fetchPatterns.html deleted file mode 100644 index 3d27eac..0000000 --- a/docs/functions/services_mbus.fetchPatterns.html +++ /dev/null @@ -1,2 +0,0 @@ -fetchPatterns | mbus-backend
                                                                                            mbus-backend
                                                                                              Preparing search index...

                                                                                              Function fetchPatterns

                                                                                              • Fetches route patterns (path points) for a specific route.

                                                                                                -

                                                                                                Parameters

                                                                                                • rt: string

                                                                                                Returns Promise<any>

                                                                                              diff --git a/docs/functions/services_mbus.fetchPredictions.html b/docs/functions/services_mbus.fetchPredictions.html deleted file mode 100644 index b8b7d84..0000000 --- a/docs/functions/services_mbus.fetchPredictions.html +++ /dev/null @@ -1,2 +0,0 @@ -fetchPredictions | mbus-backend
                                                                                              mbus-backend
                                                                                                Preparing search index...

                                                                                                Function fetchPredictions

                                                                                                • Fetches predictions for multiple stop IDs.

                                                                                                  -

                                                                                                  Parameters

                                                                                                  • stopIds: string[]
                                                                                                  • routes: string[]

                                                                                                  Returns Promise<any[]>

                                                                                                diff --git a/docs/functions/services_mbus.fetchRoutes.html b/docs/functions/services_mbus.fetchRoutes.html deleted file mode 100644 index 3a62cea..0000000 --- a/docs/functions/services_mbus.fetchRoutes.html +++ /dev/null @@ -1,2 +0,0 @@ -fetchRoutes | mbus-backend
                                                                                                mbus-backend
                                                                                                  Preparing search index...

                                                                                                  Function fetchRoutes

                                                                                                  • Fetches all available routes.

                                                                                                    -

                                                                                                    Returns Promise<any>

                                                                                                  diff --git a/docs/functions/services_mbus.fetchVehicles.html b/docs/functions/services_mbus.fetchVehicles.html deleted file mode 100644 index 9bd7543..0000000 --- a/docs/functions/services_mbus.fetchVehicles.html +++ /dev/null @@ -1,2 +0,0 @@ -fetchVehicles | mbus-backend
                                                                                                  mbus-backend
                                                                                                    Preparing search index...

                                                                                                    Function fetchVehicles

                                                                                                    • Fetches vehicle positions for the given routes.

                                                                                                      -

                                                                                                      Parameters

                                                                                                      • routes: string[]

                                                                                                      Returns Promise<any[]>

                                                                                                    diff --git a/docs/functions/services_metadata.getAllRouteConfig.html b/docs/functions/services_metadata.getAllRouteConfig.html deleted file mode 100644 index 68c8d0c..0000000 --- a/docs/functions/services_metadata.getAllRouteConfig.html +++ /dev/null @@ -1,2 +0,0 @@ -getAllRouteConfig | mbus-backend
                                                                                                    mbus-backend
                                                                                                      Preparing search index...

                                                                                                      Function getAllRouteConfig

                                                                                                      • Returns configuration for all routes.

                                                                                                        -

                                                                                                        Returns { color: string; image: string; routeId: string }[]

                                                                                                      diff --git a/docs/functions/services_metadata.getRouteColor.html b/docs/functions/services_metadata.getRouteColor.html deleted file mode 100644 index 4a6765a..0000000 --- a/docs/functions/services_metadata.getRouteColor.html +++ /dev/null @@ -1,2 +0,0 @@ -getRouteColor | mbus-backend
                                                                                                      mbus-backend
                                                                                                        Preparing search index...

                                                                                                        Function getRouteColor

                                                                                                        • Gets the color for a specific route ID.

                                                                                                          -

                                                                                                          Parameters

                                                                                                          • routeId: string

                                                                                                          Returns string | null

                                                                                                        diff --git a/docs/functions/services_metadata.getRouteImage.html b/docs/functions/services_metadata.getRouteImage.html deleted file mode 100644 index 1bfc935..0000000 --- a/docs/functions/services_metadata.getRouteImage.html +++ /dev/null @@ -1,2 +0,0 @@ -getRouteImage | mbus-backend
                                                                                                        mbus-backend
                                                                                                          Preparing search index...

                                                                                                          Function getRouteImage

                                                                                                          • Gets the image filename for a specific route ID.

                                                                                                            -

                                                                                                            Parameters

                                                                                                            • routeId: string

                                                                                                            Returns string | null

                                                                                                          diff --git a/docs/functions/services_reminder.eventsEqual.html b/docs/functions/services_reminder.eventsEqual.html deleted file mode 100644 index 0f538f6..0000000 --- a/docs/functions/services_reminder.eventsEqual.html +++ /dev/null @@ -1 +0,0 @@ -eventsEqual | mbus-backend
                                                                                                          mbus-backend
                                                                                                            Preparing search index...

                                                                                                            Function eventsEqual

                                                                                                            diff --git a/docs/functions/services_reminder.infoToUseForRoute.html b/docs/functions/services_reminder.infoToUseForRoute.html deleted file mode 100644 index 6677cfb..0000000 --- a/docs/functions/services_reminder.infoToUseForRoute.html +++ /dev/null @@ -1 +0,0 @@ -infoToUseForRoute | mbus-backend
                                                                                                            mbus-backend
                                                                                                              Preparing search index...

                                                                                                              Function infoToUseForRoute

                                                                                                              • Parameters

                                                                                                                • rtid: string

                                                                                                                Returns
                                                                                                                    | {
                                                                                                                        predsByStopId: Record<string, Prediction[]>;
                                                                                                                        predsByVid: Record<string, Prediction[]>;
                                                                                                                        reminderSubscriptions: ReminderSubscriptions;
                                                                                                                    }
                                                                                                                    | null

                                                                                                              diff --git a/docs/functions/services_reminder.processRideReminders.html b/docs/functions/services_reminder.processRideReminders.html deleted file mode 100644 index 562319d..0000000 --- a/docs/functions/services_reminder.processRideReminders.html +++ /dev/null @@ -1 +0,0 @@ -processRideReminders | mbus-backend
                                                                                                              mbus-backend
                                                                                                                Preparing search index...

                                                                                                                Function processRideReminders

                                                                                                                diff --git a/docs/functions/services_reminder.processUniversityReminders.html b/docs/functions/services_reminder.processUniversityReminders.html deleted file mode 100644 index 01b0549..0000000 --- a/docs/functions/services_reminder.processUniversityReminders.html +++ /dev/null @@ -1 +0,0 @@ -processUniversityReminders | mbus-backend
                                                                                                                mbus-backend
                                                                                                                  Preparing search index...

                                                                                                                  Function processUniversityReminders

                                                                                                                  diff --git a/docs/functions/services_reminder.sendNotifToAll.html b/docs/functions/services_reminder.sendNotifToAll.html deleted file mode 100644 index 6b6f2e0..0000000 --- a/docs/functions/services_reminder.sendNotifToAll.html +++ /dev/null @@ -1 +0,0 @@ -sendNotifToAll | mbus-backend
                                                                                                                  mbus-backend
                                                                                                                    Preparing search index...

                                                                                                                    Function sendNotifToAll

                                                                                                                    • Parameters

                                                                                                                      • notif: { body: string; title: string }
                                                                                                                      • tokens: Set<string>

                                                                                                                      Returns void

                                                                                                                    diff --git a/docs/functions/services_reminderTypes.baseEvent.html b/docs/functions/services_reminderTypes.baseEvent.html deleted file mode 100644 index ad93406..0000000 --- a/docs/functions/services_reminderTypes.baseEvent.html +++ /dev/null @@ -1,2 +0,0 @@ -baseEvent | mbus-backend
                                                                                                                    mbus-backend
                                                                                                                      Preparing search index...
                                                                                                                      diff --git a/docs/functions/services_reminderTypes.delayEvent.html b/docs/functions/services_reminderTypes.delayEvent.html deleted file mode 100644 index 1207c39..0000000 --- a/docs/functions/services_reminderTypes.delayEvent.html +++ /dev/null @@ -1,2 +0,0 @@ -delayEvent | mbus-backend
                                                                                                                      mbus-backend
                                                                                                                        Preparing search index...
                                                                                                                        • factory

                                                                                                                          -

                                                                                                                          Parameters

                                                                                                                          • x: { currPred: number; prevPred: number; rtid: string; stpid: string }

                                                                                                                          Returns DelayEvent

                                                                                                                        diff --git a/docs/functions/services_reminderTypes.eventsEqual.html b/docs/functions/services_reminderTypes.eventsEqual.html deleted file mode 100644 index 83d0898..0000000 --- a/docs/functions/services_reminderTypes.eventsEqual.html +++ /dev/null @@ -1 +0,0 @@ -eventsEqual | mbus-backend
                                                                                                                        mbus-backend
                                                                                                                          Preparing search index...
                                                                                                                          diff --git a/docs/functions/services_reminderTypes.fromKey.html b/docs/functions/services_reminderTypes.fromKey.html deleted file mode 100644 index 426b120..0000000 --- a/docs/functions/services_reminderTypes.fromKey.html +++ /dev/null @@ -1 +0,0 @@ -fromKey | mbus-backend
                                                                                                                          mbus-backend
                                                                                                                            Preparing search index...
                                                                                                                            diff --git a/docs/functions/services_reminderTypes.registrationToken.html b/docs/functions/services_reminderTypes.registrationToken.html deleted file mode 100644 index a884ca7..0000000 --- a/docs/functions/services_reminderTypes.registrationToken.html +++ /dev/null @@ -1 +0,0 @@ -registrationToken | mbus-backend
                                                                                                                            mbus-backend
                                                                                                                              Preparing search index...

                                                                                                                              Function registrationToken

                                                                                                                              diff --git a/docs/functions/services_reminderTypes.sameBaseEvent.html b/docs/functions/services_reminderTypes.sameBaseEvent.html deleted file mode 100644 index b6a0765..0000000 --- a/docs/functions/services_reminderTypes.sameBaseEvent.html +++ /dev/null @@ -1 +0,0 @@ -sameBaseEvent | mbus-backend
                                                                                                                              mbus-backend
                                                                                                                                Preparing search index...
                                                                                                                                • Parameters

                                                                                                                                  • e1: CoreEvent
                                                                                                                                  • e2: CoreEvent

                                                                                                                                  Returns boolean

                                                                                                                                diff --git a/docs/functions/services_reminderTypes.thresholdEvent.html b/docs/functions/services_reminderTypes.thresholdEvent.html deleted file mode 100644 index af4ff72..0000000 --- a/docs/functions/services_reminderTypes.thresholdEvent.html +++ /dev/null @@ -1,2 +0,0 @@ -thresholdEvent | mbus-backend
                                                                                                                                mbus-backend
                                                                                                                                  Preparing search index...
                                                                                                                                  diff --git a/docs/functions/services_reminderTypes.toKey.html b/docs/functions/services_reminderTypes.toKey.html deleted file mode 100644 index bcbf4ea..0000000 --- a/docs/functions/services_reminderTypes.toKey.html +++ /dev/null @@ -1,2 +0,0 @@ -toKey | mbus-backend
                                                                                                                                  mbus-backend
                                                                                                                                    Preparing search index...
                                                                                                                                    diff --git a/docs/functions/services_ride.fetchPatterns.html b/docs/functions/services_ride.fetchPatterns.html deleted file mode 100644 index 6850074..0000000 --- a/docs/functions/services_ride.fetchPatterns.html +++ /dev/null @@ -1,2 +0,0 @@ -fetchPatterns | mbus-backend
                                                                                                                                    mbus-backend
                                                                                                                                      Preparing search index...

                                                                                                                                      Function fetchPatterns

                                                                                                                                      • Fetches route patterns (path points) for a specific route.

                                                                                                                                        -

                                                                                                                                        Parameters

                                                                                                                                        • rt: string

                                                                                                                                        Returns Promise<any>

                                                                                                                                      diff --git a/docs/functions/services_ride.fetchPredictions.html b/docs/functions/services_ride.fetchPredictions.html deleted file mode 100644 index 32d4c42..0000000 --- a/docs/functions/services_ride.fetchPredictions.html +++ /dev/null @@ -1,2 +0,0 @@ -fetchPredictions | mbus-backend
                                                                                                                                      mbus-backend
                                                                                                                                        Preparing search index...

                                                                                                                                        Function fetchPredictions

                                                                                                                                        • Fetches predictions for multiple stop IDs.

                                                                                                                                          -

                                                                                                                                          Parameters

                                                                                                                                          • stopIds: string[]
                                                                                                                                          • routes: string[]

                                                                                                                                          Returns Promise<any[]>

                                                                                                                                        diff --git a/docs/functions/services_ride.fetchRoutes.html b/docs/functions/services_ride.fetchRoutes.html deleted file mode 100644 index 21a65e6..0000000 --- a/docs/functions/services_ride.fetchRoutes.html +++ /dev/null @@ -1,2 +0,0 @@ -fetchRoutes | mbus-backend
                                                                                                                                        mbus-backend
                                                                                                                                          Preparing search index...

                                                                                                                                          Function fetchRoutes

                                                                                                                                          • Fetches all available routes.

                                                                                                                                            -

                                                                                                                                            Returns Promise<any>

                                                                                                                                          diff --git a/docs/functions/services_ride.fetchVehicles.html b/docs/functions/services_ride.fetchVehicles.html deleted file mode 100644 index 3dd3a95..0000000 --- a/docs/functions/services_ride.fetchVehicles.html +++ /dev/null @@ -1,2 +0,0 @@ -fetchVehicles | mbus-backend
                                                                                                                                          mbus-backend
                                                                                                                                            Preparing search index...

                                                                                                                                            Function fetchVehicles

                                                                                                                                            • Fetches vehicle positions for the given routes.

                                                                                                                                              -

                                                                                                                                              Parameters

                                                                                                                                              • routes: string[]

                                                                                                                                              Returns Promise<any[]>

                                                                                                                                            diff --git a/docs/functions/state_transitState.setCachedGraph.html b/docs/functions/state_transitState.setCachedGraph.html deleted file mode 100644 index 63000ff..0000000 --- a/docs/functions/state_transitState.setCachedGraph.html +++ /dev/null @@ -1,2 +0,0 @@ -setCachedGraph | mbus-backend
                                                                                                                                            mbus-backend
                                                                                                                                              Preparing search index...

                                                                                                                                              Function setCachedGraph

                                                                                                                                              diff --git a/docs/functions/state_transitState.setCachedRideStopLocations.html b/docs/functions/state_transitState.setCachedRideStopLocations.html deleted file mode 100644 index 7331229..0000000 --- a/docs/functions/state_transitState.setCachedRideStopLocations.html +++ /dev/null @@ -1,2 +0,0 @@ -setCachedRideStopLocations | mbus-backend
                                                                                                                                              mbus-backend
                                                                                                                                                Preparing search index...

                                                                                                                                                Function setCachedRideStopLocations

                                                                                                                                                • Updates the cached ride stop locations.

                                                                                                                                                  -

                                                                                                                                                  Parameters

                                                                                                                                                  • newLocs: Record<string, { lat: number; lon: number; name: string }>

                                                                                                                                                  Returns void

                                                                                                                                                diff --git a/docs/functions/state_transitState.setCachedStopLocations.html b/docs/functions/state_transitState.setCachedStopLocations.html deleted file mode 100644 index 9bbf317..0000000 --- a/docs/functions/state_transitState.setCachedStopLocations.html +++ /dev/null @@ -1,2 +0,0 @@ -setCachedStopLocations | mbus-backend
                                                                                                                                                mbus-backend
                                                                                                                                                  Preparing search index...

                                                                                                                                                  Function setCachedStopLocations

                                                                                                                                                  • Updates the cached stop locations.

                                                                                                                                                    -

                                                                                                                                                    Parameters

                                                                                                                                                    • newLocs: Record<string, { lat: number; lon: number; name: string }>

                                                                                                                                                    Returns void

                                                                                                                                                  diff --git a/docs/functions/walking_loadMap.haversine.html b/docs/functions/walking_loadMap.haversine.html deleted file mode 100644 index 79efb48..0000000 --- a/docs/functions/walking_loadMap.haversine.html +++ /dev/null @@ -1,7 +0,0 @@ -haversine | mbus-backend
                                                                                                                                                  mbus-backend
                                                                                                                                                    Preparing search index...

                                                                                                                                                    Function haversine

                                                                                                                                                    • Calculates the great-circle distance between two points using the Haversine formula.

                                                                                                                                                      -

                                                                                                                                                      Parameters

                                                                                                                                                      • aLat: number

                                                                                                                                                        Latitude of point A.

                                                                                                                                                        -
                                                                                                                                                      • aLon: number

                                                                                                                                                        Longitude of point A.

                                                                                                                                                        -
                                                                                                                                                      • bLat: number

                                                                                                                                                        Latitude of point B.

                                                                                                                                                        -
                                                                                                                                                      • bLon: number

                                                                                                                                                        Longitude of point B.

                                                                                                                                                        -

                                                                                                                                                      Returns number

                                                                                                                                                      Distance in meters.

                                                                                                                                                      -
                                                                                                                                                    diff --git a/docs/functions/walking_loadMap.loadMap.html b/docs/functions/walking_loadMap.loadMap.html deleted file mode 100644 index 6618d45..0000000 --- a/docs/functions/walking_loadMap.loadMap.html +++ /dev/null @@ -1,16 +0,0 @@ -loadMap | mbus-backend
                                                                                                                                                    mbus-backend
                                                                                                                                                      Preparing search index...

                                                                                                                                                      Function loadMap

                                                                                                                                                      • Loads and parses the GraphML map file into a usable graph structure.

                                                                                                                                                        -
                                                                                                                                                          -
                                                                                                                                                        • Performs the following steps:
                                                                                                                                                        • -
                                                                                                                                                        -
                                                                                                                                                          -
                                                                                                                                                        1. Reads the XML file.
                                                                                                                                                        2. -
                                                                                                                                                        3. Extracts Nodes (lat/lon).
                                                                                                                                                        4. -
                                                                                                                                                        5. Extracts Edges (filtering for walkable types like 'footway', 'path').
                                                                                                                                                        6. -
                                                                                                                                                        7. Parses edge geometry (WKT) if available.
                                                                                                                                                        8. -
                                                                                                                                                        9. Prunes disconnected components, keeping only the largest connected subgraph.
                                                                                                                                                        10. -
                                                                                                                                                        -
                                                                                                                                                          -
                                                                                                                                                        • -
                                                                                                                                                        -

                                                                                                                                                        Returns { graph: Map<string, GraphMLEdge[]>; nodes: Map<string, GraphMLNode> }

                                                                                                                                                        An object containing the map of Nodes and the Adjacency List (graph).

                                                                                                                                                        -
                                                                                                                                                      diff --git a/docs/functions/walking_walkingMap.buildStopNodeMap.html b/docs/functions/walking_walkingMap.buildStopNodeMap.html deleted file mode 100644 index 10e668b..0000000 --- a/docs/functions/walking_walkingMap.buildStopNodeMap.html +++ /dev/null @@ -1,4 +0,0 @@ -buildStopNodeMap | mbus-backend
                                                                                                                                                      mbus-backend
                                                                                                                                                        Preparing search index...

                                                                                                                                                        Function buildStopNodeMap

                                                                                                                                                        • Maps a list of bus stops to their nearest nodes on the street graph. -This optimizes future lookups by caching the StopID -> NodeID relationship.

                                                                                                                                                          -

                                                                                                                                                          Parameters

                                                                                                                                                          • locations: Record<string, { lat: number; lon: number }>

                                                                                                                                                            A map of StopID to {lat, lon}.

                                                                                                                                                            -

                                                                                                                                                          Returns void

                                                                                                                                                        diff --git a/docs/functions/walking_walkingMap.ensureCacheForStops.html b/docs/functions/walking_walkingMap.ensureCacheForStops.html deleted file mode 100644 index e90fe83..0000000 --- a/docs/functions/walking_walkingMap.ensureCacheForStops.html +++ /dev/null @@ -1,5 +0,0 @@ -ensureCacheForStops | mbus-backend
                                                                                                                                                        mbus-backend
                                                                                                                                                          Preparing search index...

                                                                                                                                                          Function ensureCacheForStops

                                                                                                                                                          • Ensures walking paths between all provided stops are calculated and cached. -Fetches missing paths in parallel and updates the disk cache.

                                                                                                                                                            -

                                                                                                                                                            Parameters

                                                                                                                                                            • stopIds: Set<string>

                                                                                                                                                              Set of stop IDs to verify.

                                                                                                                                                              -
                                                                                                                                                            • stopLocations: Record<string, { lat: number; lon: number }>

                                                                                                                                                              Map of Stop ID to coordinates.

                                                                                                                                                              -

                                                                                                                                                            Returns Promise<void>

                                                                                                                                                          diff --git a/docs/functions/walking_walkingMap.getCachedWalk.html b/docs/functions/walking_walkingMap.getCachedWalk.html deleted file mode 100644 index 65d8590..0000000 --- a/docs/functions/walking_walkingMap.getCachedWalk.html +++ /dev/null @@ -1,5 +0,0 @@ -getCachedWalk | mbus-backend
                                                                                                                                                          mbus-backend
                                                                                                                                                            Preparing search index...

                                                                                                                                                            Function getCachedWalk

                                                                                                                                                            • Retrieves a cached walking path between two stops.

                                                                                                                                                              -

                                                                                                                                                              Parameters

                                                                                                                                                              • originId: string

                                                                                                                                                                Origin Stop ID.

                                                                                                                                                                -
                                                                                                                                                              • destId: string

                                                                                                                                                                Destination Stop ID.

                                                                                                                                                                -

                                                                                                                                                              Returns WalkingResponse | undefined

                                                                                                                                                              Cached walking data or undefined.

                                                                                                                                                              -
                                                                                                                                                            diff --git a/docs/functions/walking_walkingMap.getWalkingDistancesFrom.html b/docs/functions/walking_walkingMap.getWalkingDistancesFrom.html deleted file mode 100644 index 916cc29..0000000 --- a/docs/functions/walking_walkingMap.getWalkingDistancesFrom.html +++ /dev/null @@ -1,8 +0,0 @@ -getWalkingDistancesFrom | mbus-backend
                                                                                                                                                            mbus-backend
                                                                                                                                                              Preparing search index...

                                                                                                                                                              Function getWalkingDistancesFrom

                                                                                                                                                              • Optimized method to get walking distances from an origin to ALL known bus stops. -Optionally includes a direct walk to a specific destination point. -Uses a single Dijkstra pass with early termination and LRU Cache.

                                                                                                                                                                -

                                                                                                                                                                Parameters

                                                                                                                                                                • lat: number

                                                                                                                                                                  Origin latitude.

                                                                                                                                                                  -
                                                                                                                                                                • lon: number

                                                                                                                                                                  Origin longitude.

                                                                                                                                                                  -
                                                                                                                                                                • OptionaldestLat: number

                                                                                                                                                                  (Optional) Destination latitude for direct walk.

                                                                                                                                                                  -
                                                                                                                                                                • OptionaldestLon: number

                                                                                                                                                                  (Optional) Destination longitude for direct walk.

                                                                                                                                                                  -

                                                                                                                                                                Returns { duration: number; stopId: string }[]

                                                                                                                                                              diff --git a/docs/functions/walking_walkingMap.getWalkingResponse.html b/docs/functions/walking_walkingMap.getWalkingResponse.html deleted file mode 100644 index cd96549..0000000 --- a/docs/functions/walking_walkingMap.getWalkingResponse.html +++ /dev/null @@ -1,7 +0,0 @@ -getWalkingResponse | mbus-backend
                                                                                                                                                              mbus-backend
                                                                                                                                                                Preparing search index...

                                                                                                                                                                Function getWalkingResponse

                                                                                                                                                                • Calculates a detailed walking path between two coordinates using A*. -Includes path geometry for rendering.

                                                                                                                                                                  -

                                                                                                                                                                  Parameters

                                                                                                                                                                  • originLat: number

                                                                                                                                                                    Latitude of origin.

                                                                                                                                                                    -
                                                                                                                                                                  • originLon: number

                                                                                                                                                                    Longitude of origin.

                                                                                                                                                                    -
                                                                                                                                                                  • destLat: number

                                                                                                                                                                    Latitude of destination.

                                                                                                                                                                    -
                                                                                                                                                                  • destLon: number

                                                                                                                                                                    Longitude of destination.

                                                                                                                                                                    -

                                                                                                                                                                  Returns Promise<WalkingResponse>

                                                                                                                                                                diff --git a/docs/hierarchy.html b/docs/hierarchy.html deleted file mode 100644 index e83590c..0000000 --- a/docs/hierarchy.html +++ /dev/null @@ -1 +0,0 @@ -mbus-backend
                                                                                                                                                                mbus-backend
                                                                                                                                                                  Preparing search index...

                                                                                                                                                                  mbus-backend

                                                                                                                                                                  Hierarchy Summary

                                                                                                                                                                  diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index e44630e..0000000 --- a/docs/index.html +++ /dev/null @@ -1,39 +0,0 @@ -mbus-backend
                                                                                                                                                                  mbus-backend
                                                                                                                                                                    Preparing search index...

                                                                                                                                                                    mbus-backend

                                                                                                                                                                    - Maizebus Logo -

                                                                                                                                                                    Maizebus Backend

                                                                                                                                                                    -

                                                                                                                                                                    - - Documentation - - License -

                                                                                                                                                                    -

                                                                                                                                                                    - Backend service for the Magic Bus application. Handles real-time bus tracking, route management, and multi-modal journey planning (bus + walking) using the McRaptor algorithm. -

                                                                                                                                                                    -
                                                                                                                                                                    -

                                                                                                                                                                    First, obtain an API key for the Magic Bus backend from the official Magic Bus Website.

                                                                                                                                                                    -

                                                                                                                                                                    Then, define the MBUS_API_KEY environment variable with your API key.

                                                                                                                                                                    -

                                                                                                                                                                    To install dependencies, run npm i

                                                                                                                                                                    -

                                                                                                                                                                    Create a Firebase project if you haven't already, and set FIREBASE_PROJECT_ID to its id. Follow the steps -here to create a service -account key file. Place this file into secrets/ and point GOOGLE_APPLICATION_CREDENTIALS to it. -(you can do this temporarily with export GOOGLE_APPLICATION_CREDENTIALS="./secrets/your-filename.json")

                                                                                                                                                                    -

                                                                                                                                                                    Some additional resources for iOS here. Make -sure that the firebase project is configured with the same app id as xcode.

                                                                                                                                                                    -

                                                                                                                                                                    To run the backend, run npm start.

                                                                                                                                                                    -

                                                                                                                                                                    By default, the service runs on port 3000. To define a port for the service to run on, define an environment variable called PORT before running the backend.

                                                                                                                                                                    -

                                                                                                                                                                    This project uses TSDoc for code documentation. -To compile the static documentation:

                                                                                                                                                                    -
                                                                                                                                                                    npm run docs
                                                                                                                                                                    -
                                                                                                                                                                    - -

                                                                                                                                                                    The documentation is served at http://localhost:3000/docs or this link.

                                                                                                                                                                    -

                                                                                                                                                                    This project uses Vitest for fast unit and integration testing.

                                                                                                                                                                    -
                                                                                                                                                                      -
                                                                                                                                                                    • Run all tests: npm test
                                                                                                                                                                    • -
                                                                                                                                                                    • Run stress tests: npm run stress-test
                                                                                                                                                                    • -
                                                                                                                                                                    -

                                                                                                                                                                    An example of passing the tests is below.

                                                                                                                                                                    -test example -

                                                                                                                                                                    Before submitting a pull request to main, please make sure you pass all the tests and can compile docs. If you believe some of the tests faulty or no longer needed after your commit, please contact Andrew Yu or Ryan Lu on Slack.

                                                                                                                                                                    -
                                                                                                                                                                    diff --git a/docs/interfaces/raptor_McRaptorAlgorithm.Journey.html b/docs/interfaces/raptor_McRaptorAlgorithm.Journey.html deleted file mode 100644 index b0210ca..0000000 --- a/docs/interfaces/raptor_McRaptorAlgorithm.Journey.html +++ /dev/null @@ -1,6 +0,0 @@ -Journey | mbus-backend
                                                                                                                                                                    mbus-backend
                                                                                                                                                                      Preparing search index...

                                                                                                                                                                      Represents a complete transit journey consisting of multiple legs.

                                                                                                                                                                      -
                                                                                                                                                                      interface Journey {
                                                                                                                                                                          criteria: {
                                                                                                                                                                              arrivalTime: number;
                                                                                                                                                                              transferCount: number;
                                                                                                                                                                              walkingDistance: number;
                                                                                                                                                                          };
                                                                                                                                                                          legs: JourneyLeg[];
                                                                                                                                                                      }
                                                                                                                                                                      Index

                                                                                                                                                                      Properties

                                                                                                                                                                      Properties

                                                                                                                                                                      criteria: {
                                                                                                                                                                          arrivalTime: number;
                                                                                                                                                                          transferCount: number;
                                                                                                                                                                          walkingDistance: number;
                                                                                                                                                                      }

                                                                                                                                                                      The performance metrics for this journey.

                                                                                                                                                                      -
                                                                                                                                                                      legs: JourneyLeg[]

                                                                                                                                                                      The sequence of legs (trips or walking transfers) in the journey.

                                                                                                                                                                      -
                                                                                                                                                                      diff --git a/docs/interfaces/raptor_McRaptorAlgorithm.JourneyLeg.html b/docs/interfaces/raptor_McRaptorAlgorithm.JourneyLeg.html deleted file mode 100644 index b6da33e..0000000 --- a/docs/interfaces/raptor_McRaptorAlgorithm.JourneyLeg.html +++ /dev/null @@ -1,14 +0,0 @@ -JourneyLeg | mbus-backend
                                                                                                                                                                      mbus-backend
                                                                                                                                                                        Preparing search index...

                                                                                                                                                                        Represents a single segment of a journey, either a transit trip or a walking transfer.

                                                                                                                                                                        -
                                                                                                                                                                        interface JourneyLeg {
                                                                                                                                                                            destination: string;
                                                                                                                                                                            destinationID: string;
                                                                                                                                                                            duration: number;
                                                                                                                                                                            endTime: number;
                                                                                                                                                                            origin: string;
                                                                                                                                                                            originID: string;
                                                                                                                                                                            rt?: string;
                                                                                                                                                                            startTime: number;
                                                                                                                                                                            stopTimes?: StopTime[];
                                                                                                                                                                            transfer?: Transfer;
                                                                                                                                                                            trip?: Trip;
                                                                                                                                                                            type: "Trip" | "Transfer";
                                                                                                                                                                        }
                                                                                                                                                                        Index

                                                                                                                                                                        Properties

                                                                                                                                                                        destination: string
                                                                                                                                                                        destinationID: string
                                                                                                                                                                        duration: number
                                                                                                                                                                        endTime: number
                                                                                                                                                                        origin: string
                                                                                                                                                                        originID: string
                                                                                                                                                                        rt?: string
                                                                                                                                                                        startTime: number
                                                                                                                                                                        stopTimes?: StopTime[]
                                                                                                                                                                        transfer?: Transfer
                                                                                                                                                                        trip?: Trip
                                                                                                                                                                        type: "Trip" | "Transfer"
                                                                                                                                                                        diff --git a/docs/interfaces/raptor_types.Leg.html b/docs/interfaces/raptor_types.Leg.html deleted file mode 100644 index e209caf..0000000 --- a/docs/interfaces/raptor_types.Leg.html +++ /dev/null @@ -1,4 +0,0 @@ -Leg | mbus-backend
                                                                                                                                                                        mbus-backend
                                                                                                                                                                          Preparing search index...

                                                                                                                                                                          Interface Leg

                                                                                                                                                                          Abstract representation of a connection between two stops.

                                                                                                                                                                          -
                                                                                                                                                                          interface Leg {
                                                                                                                                                                              destination: string;
                                                                                                                                                                              origin: string;
                                                                                                                                                                          }

                                                                                                                                                                          Hierarchy (View Summary)

                                                                                                                                                                          Index

                                                                                                                                                                          Properties

                                                                                                                                                                          Properties

                                                                                                                                                                          destination: string
                                                                                                                                                                          origin: string
                                                                                                                                                                          diff --git a/docs/interfaces/raptor_types.StopTime.html b/docs/interfaces/raptor_types.StopTime.html deleted file mode 100644 index eafc571..0000000 --- a/docs/interfaces/raptor_types.StopTime.html +++ /dev/null @@ -1,16 +0,0 @@ -StopTime | mbus-backend
                                                                                                                                                                          mbus-backend
                                                                                                                                                                            Preparing search index...

                                                                                                                                                                            Interface StopTime

                                                                                                                                                                            Represents a scheduled stop event within a trip (GTFS StopTime).

                                                                                                                                                                            -
                                                                                                                                                                            interface StopTime {
                                                                                                                                                                                arrivalTime: number;
                                                                                                                                                                                departureTime: number;
                                                                                                                                                                                dropOff: boolean;
                                                                                                                                                                                heursticCost?: number;
                                                                                                                                                                                isExtrapolated?: boolean;
                                                                                                                                                                                pickUp: boolean;
                                                                                                                                                                                rt?: string;
                                                                                                                                                                                stop: string;
                                                                                                                                                                            }
                                                                                                                                                                            Index

                                                                                                                                                                            Properties

                                                                                                                                                                            arrivalTime: number
                                                                                                                                                                            departureTime: number
                                                                                                                                                                            dropOff: boolean

                                                                                                                                                                            Whether passengers can alight from the vehicle here.

                                                                                                                                                                            -
                                                                                                                                                                            heursticCost?: number

                                                                                                                                                                            Optional pre-calculated cost for routing heuristics.

                                                                                                                                                                            -
                                                                                                                                                                            isExtrapolated?: boolean

                                                                                                                                                                            If the stop is predicted or not.

                                                                                                                                                                            -
                                                                                                                                                                            pickUp: boolean

                                                                                                                                                                            Whether passengers can board the vehicle here.

                                                                                                                                                                            -
                                                                                                                                                                            rt?: string

                                                                                                                                                                            Real-time status string (if available).

                                                                                                                                                                            -
                                                                                                                                                                            stop: string

                                                                                                                                                                            The ID of the stop.

                                                                                                                                                                            -
                                                                                                                                                                            diff --git a/docs/interfaces/raptor_types.TimetableLeg.html b/docs/interfaces/raptor_types.TimetableLeg.html deleted file mode 100644 index 347f2af..0000000 --- a/docs/interfaces/raptor_types.TimetableLeg.html +++ /dev/null @@ -1,6 +0,0 @@ -TimetableLeg | mbus-backend
                                                                                                                                                                            mbus-backend
                                                                                                                                                                              Preparing search index...

                                                                                                                                                                              Interface TimetableLeg

                                                                                                                                                                              A specific segment of a scheduled trip with fixed times.

                                                                                                                                                                              -
                                                                                                                                                                              interface TimetableLeg {
                                                                                                                                                                                  destination: string;
                                                                                                                                                                                  origin: string;
                                                                                                                                                                                  stopTimes: StopTime[];
                                                                                                                                                                                  trip: Trip;
                                                                                                                                                                              }

                                                                                                                                                                              Hierarchy (View Summary)

                                                                                                                                                                              • Leg
                                                                                                                                                                                • TimetableLeg
                                                                                                                                                                              Index

                                                                                                                                                                              Properties

                                                                                                                                                                              destination: string
                                                                                                                                                                              origin: string
                                                                                                                                                                              stopTimes: StopTime[]
                                                                                                                                                                              trip: Trip
                                                                                                                                                                              diff --git a/docs/interfaces/raptor_types.Transfer.html b/docs/interfaces/raptor_types.Transfer.html deleted file mode 100644 index 5249ef0..0000000 --- a/docs/interfaces/raptor_types.Transfer.html +++ /dev/null @@ -1,9 +0,0 @@ -Transfer | mbus-backend
                                                                                                                                                                              mbus-backend
                                                                                                                                                                                Preparing search index...

                                                                                                                                                                                Interface Transfer

                                                                                                                                                                                A walking connection between two stops with a defined duration.

                                                                                                                                                                                -
                                                                                                                                                                                interface Transfer {
                                                                                                                                                                                    destination: string;
                                                                                                                                                                                    duration: number;
                                                                                                                                                                                    endTime: number;
                                                                                                                                                                                    origin: string;
                                                                                                                                                                                    startTime: number;
                                                                                                                                                                                }

                                                                                                                                                                                Hierarchy (View Summary)

                                                                                                                                                                                Index

                                                                                                                                                                                Properties

                                                                                                                                                                                destination: string
                                                                                                                                                                                duration: number
                                                                                                                                                                                endTime: number

                                                                                                                                                                                Valid end time for this transfer window.

                                                                                                                                                                                -
                                                                                                                                                                                origin: string
                                                                                                                                                                                startTime: number

                                                                                                                                                                                Valid start time for this transfer window.

                                                                                                                                                                                -
                                                                                                                                                                                diff --git a/docs/interfaces/raptor_types.Trip.html b/docs/interfaces/raptor_types.Trip.html deleted file mode 100644 index 946cd62..0000000 --- a/docs/interfaces/raptor_types.Trip.html +++ /dev/null @@ -1,7 +0,0 @@ -Trip | mbus-backend
                                                                                                                                                                                mbus-backend
                                                                                                                                                                                  Preparing search index...

                                                                                                                                                                                  Interface Trip

                                                                                                                                                                                  Represents a transit vehicle run (GTFS Trip).

                                                                                                                                                                                  -
                                                                                                                                                                                  interface Trip {
                                                                                                                                                                                      stopTimes: StopTime[];
                                                                                                                                                                                      tripId: string;
                                                                                                                                                                                      vid: string | null;
                                                                                                                                                                                  }
                                                                                                                                                                                  Index

                                                                                                                                                                                  Properties

                                                                                                                                                                                  Properties

                                                                                                                                                                                  stopTimes: StopTime[]

                                                                                                                                                                                  Ordered list of stops made by this trip.

                                                                                                                                                                                  -
                                                                                                                                                                                  tripId: string
                                                                                                                                                                                  vid: string | null

                                                                                                                                                                                  Vehicle ID, if available.

                                                                                                                                                                                  -
                                                                                                                                                                                  diff --git a/docs/interfaces/walking_walkingMap.BatchWalkingResult.html b/docs/interfaces/walking_walkingMap.BatchWalkingResult.html deleted file mode 100644 index 7995faa..0000000 --- a/docs/interfaces/walking_walkingMap.BatchWalkingResult.html +++ /dev/null @@ -1,8 +0,0 @@ -BatchWalkingResult | mbus-backend
                                                                                                                                                                                  mbus-backend
                                                                                                                                                                                    Preparing search index...

                                                                                                                                                                                    Interface BatchWalkingResult

                                                                                                                                                                                    Result of a batch query from a single origin node to multiple destinations.

                                                                                                                                                                                    -
                                                                                                                                                                                    interface BatchWalkingResult {
                                                                                                                                                                                        distanceToNode: number;
                                                                                                                                                                                        nearestNodeId: string;
                                                                                                                                                                                        nodeDistances: Map<string, number>;
                                                                                                                                                                                    }
                                                                                                                                                                                    Index

                                                                                                                                                                                    Properties

                                                                                                                                                                                    distanceToNode: number

                                                                                                                                                                                    The straight-line distance from the origin coordinates to the street node.

                                                                                                                                                                                    -
                                                                                                                                                                                    nearestNodeId: string

                                                                                                                                                                                    The ID of the street node closest to the origin coordinates.

                                                                                                                                                                                    -
                                                                                                                                                                                    nodeDistances: Map<string, number>

                                                                                                                                                                                    A map of NodeID -> Distance (in meters) for all reachable nodes.

                                                                                                                                                                                    -
                                                                                                                                                                                    diff --git a/docs/interfaces/walking_walkingMap.WalkingResponse.html b/docs/interfaces/walking_walkingMap.WalkingResponse.html deleted file mode 100644 index 2889134..0000000 --- a/docs/interfaces/walking_walkingMap.WalkingResponse.html +++ /dev/null @@ -1,8 +0,0 @@ -WalkingResponse | mbus-backend
                                                                                                                                                                                    mbus-backend
                                                                                                                                                                                      Preparing search index...

                                                                                                                                                                                      Interface WalkingResponse

                                                                                                                                                                                      Standard response for a single point-to-point walking query.

                                                                                                                                                                                      -
                                                                                                                                                                                      interface WalkingResponse {
                                                                                                                                                                                          distance: number;
                                                                                                                                                                                          duration: number;
                                                                                                                                                                                          path_coords: { lat: number; lon: number }[];
                                                                                                                                                                                      }
                                                                                                                                                                                      Index

                                                                                                                                                                                      Properties

                                                                                                                                                                                      distance: number

                                                                                                                                                                                      Walking distance in meters.

                                                                                                                                                                                      -
                                                                                                                                                                                      duration: number

                                                                                                                                                                                      Walking duration in seconds.

                                                                                                                                                                                      -
                                                                                                                                                                                      path_coords: { lat: number; lon: number }[]

                                                                                                                                                                                      Ordered list of coordinates representing the walking path geometry.

                                                                                                                                                                                      -
                                                                                                                                                                                      diff --git a/docs/media/logo.png b/docs/media/logo.png deleted file mode 100644 index b086987ec872bbefac07b441bf516ec733165d16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61262 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 Az!d~(0f_22^# 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 diff --git a/docs/media/test_example.png b/docs/media/test_example.png deleted file mode 100644 index 41667bd90cfe0b40e87815c9225d2301d1d7a7e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54791 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 O&EVQGJq diff --git a/docs/modules.html b/docs/modules.html deleted file mode 100644 index 9561e10..0000000 --- a/docs/modules.html +++ /dev/null @@ -1 +0,0 @@ -mbus-backend
                                                                                                                                                                                      diff --git a/docs/modules/app.html b/docs/modules/app.html deleted file mode 100644 index baf826b..0000000 --- a/docs/modules/app.html +++ /dev/null @@ -1 +0,0 @@ -app | mbus-backend
                                                                                                                                                                                      mbus-backend
                                                                                                                                                                                        Preparing search index...

                                                                                                                                                                                        Module app

                                                                                                                                                                                        diff --git a/docs/modules/jobs.html b/docs/modules/jobs.html deleted file mode 100644 index 217f3e1..0000000 --- a/docs/modules/jobs.html +++ /dev/null @@ -1 +0,0 @@ -jobs | mbus-backend
                                                                                                                                                                                        mbus-backend
                                                                                                                                                                                          Preparing search index...

                                                                                                                                                                                          Module jobs

                                                                                                                                                                                          Functions

                                                                                                                                                                                          startBackgroundJobs
                                                                                                                                                                                          diff --git a/docs/modules/raptor_McRaptorAlgorithm.html b/docs/modules/raptor_McRaptorAlgorithm.html deleted file mode 100644 index b3e9157..0000000 --- a/docs/modules/raptor_McRaptorAlgorithm.html +++ /dev/null @@ -1 +0,0 @@ -raptor/McRaptorAlgorithm | mbus-backend
                                                                                                                                                                                          mbus-backend
                                                                                                                                                                                            Preparing search index...

                                                                                                                                                                                            Module raptor/McRaptorAlgorithm

                                                                                                                                                                                            Classes

                                                                                                                                                                                            McRaptorAlgorithm

                                                                                                                                                                                            Interfaces

                                                                                                                                                                                            Journey
                                                                                                                                                                                            JourneyLeg
                                                                                                                                                                                            diff --git a/docs/modules/raptor_McStructs.html b/docs/modules/raptor_McStructs.html deleted file mode 100644 index 77efccd..0000000 --- a/docs/modules/raptor_McStructs.html +++ /dev/null @@ -1 +0,0 @@ -raptor/McStructs | mbus-backend
                                                                                                                                                                                            mbus-backend
                                                                                                                                                                                              Preparing search index...

                                                                                                                                                                                              Module raptor/McStructs

                                                                                                                                                                                              Classes

                                                                                                                                                                                              Bag
                                                                                                                                                                                              Label

                                                                                                                                                                                              Type Aliases

                                                                                                                                                                                              Criteria
                                                                                                                                                                                              diff --git a/docs/modules/raptor_types.html b/docs/modules/raptor_types.html deleted file mode 100644 index 432b43e..0000000 --- a/docs/modules/raptor_types.html +++ /dev/null @@ -1 +0,0 @@ -raptor/types | mbus-backend
                                                                                                                                                                                              mbus-backend
                                                                                                                                                                                                Preparing search index...
                                                                                                                                                                                                diff --git a/docs/modules/routes_api.html b/docs/modules/routes_api.html deleted file mode 100644 index 92b0831..0000000 --- a/docs/modules/routes_api.html +++ /dev/null @@ -1 +0,0 @@ -routes/api | mbus-backend
                                                                                                                                                                                                mbus-backend
                                                                                                                                                                                                  Preparing search index...
                                                                                                                                                                                                  diff --git a/docs/modules/services_graphBuilder.html b/docs/modules/services_graphBuilder.html deleted file mode 100644 index c6dd5c0..0000000 --- a/docs/modules/services_graphBuilder.html +++ /dev/null @@ -1 +0,0 @@ -services/graphBuilder | mbus-backend
                                                                                                                                                                                                  mbus-backend
                                                                                                                                                                                                    Preparing search index...
                                                                                                                                                                                                    diff --git a/docs/modules/services_journey.html b/docs/modules/services_journey.html deleted file mode 100644 index 4bf332e..0000000 --- a/docs/modules/services_journey.html +++ /dev/null @@ -1 +0,0 @@ -services/journey | mbus-backend
                                                                                                                                                                                                    mbus-backend
                                                                                                                                                                                                      Preparing search index...

                                                                                                                                                                                                      Module services/journey

                                                                                                                                                                                                      Functions

                                                                                                                                                                                                      planJourney
                                                                                                                                                                                                      diff --git a/docs/modules/services_mbus.html b/docs/modules/services_mbus.html deleted file mode 100644 index 5c2a1c4..0000000 --- a/docs/modules/services_mbus.html +++ /dev/null @@ -1 +0,0 @@ -services/mbus | mbus-backend
                                                                                                                                                                                                      mbus-backend
                                                                                                                                                                                                        Preparing search index...
                                                                                                                                                                                                        diff --git a/docs/modules/services_metadata.html b/docs/modules/services_metadata.html deleted file mode 100644 index dff1332..0000000 --- a/docs/modules/services_metadata.html +++ /dev/null @@ -1 +0,0 @@ -services/metadata | mbus-backend
                                                                                                                                                                                                        mbus-backend
                                                                                                                                                                                                          Preparing search index...
                                                                                                                                                                                                          diff --git a/docs/modules/services_reminder.html b/docs/modules/services_reminder.html deleted file mode 100644 index e60ffda..0000000 --- a/docs/modules/services_reminder.html +++ /dev/null @@ -1 +0,0 @@ -services/reminder | mbus-backend
                                                                                                                                                                                                          mbus-backend
                                                                                                                                                                                                            Preparing search index...

                                                                                                                                                                                                            Module services/reminder

                                                                                                                                                                                                            Type Aliases

                                                                                                                                                                                                            PostThreshold
                                                                                                                                                                                                            PreThreshold

                                                                                                                                                                                                            Variables

                                                                                                                                                                                                            rideReminderSubscriptions
                                                                                                                                                                                                            testing
                                                                                                                                                                                                            universityReminderSubscriptions

                                                                                                                                                                                                            Functions

                                                                                                                                                                                                            eventsEqual
                                                                                                                                                                                                            infoToUseForRoute
                                                                                                                                                                                                            processRideReminders
                                                                                                                                                                                                            processUniversityReminders
                                                                                                                                                                                                            sendNotifToAll

                                                                                                                                                                                                            References

                                                                                                                                                                                                            baseEvent → baseEvent
                                                                                                                                                                                                            BaseEvent → BaseEvent
                                                                                                                                                                                                            delayEvent → delayEvent
                                                                                                                                                                                                            DelayEvent → DelayEvent
                                                                                                                                                                                                            fromKey → fromKey
                                                                                                                                                                                                            Key → Key
                                                                                                                                                                                                            registrationToken → registrationToken
                                                                                                                                                                                                            RegistrationToken → RegistrationToken
                                                                                                                                                                                                            sameBaseEvent → sameBaseEvent
                                                                                                                                                                                                            thresholdEvent → thresholdEvent
                                                                                                                                                                                                            ThresholdEvent → ThresholdEvent
                                                                                                                                                                                                            toKey → toKey
                                                                                                                                                                                                            diff --git a/docs/modules/services_reminderTypes.html b/docs/modules/services_reminderTypes.html deleted file mode 100644 index da54182..0000000 --- a/docs/modules/services_reminderTypes.html +++ /dev/null @@ -1 +0,0 @@ -services/reminderTypes | mbus-backend
                                                                                                                                                                                                            mbus-backend
                                                                                                                                                                                                              Preparing search index...
                                                                                                                                                                                                              diff --git a/docs/modules/services_ride.html b/docs/modules/services_ride.html deleted file mode 100644 index dff55af..0000000 --- a/docs/modules/services_ride.html +++ /dev/null @@ -1 +0,0 @@ -services/ride | mbus-backend
                                                                                                                                                                                                              mbus-backend
                                                                                                                                                                                                                Preparing search index...
                                                                                                                                                                                                                diff --git a/docs/modules/state_transitState.html b/docs/modules/state_transitState.html deleted file mode 100644 index b09cede..0000000 --- a/docs/modules/state_transitState.html +++ /dev/null @@ -1 +0,0 @@ -state/transitState | mbus-backend
                                                                                                                                                                                                                mbus-backend
                                                                                                                                                                                                                  Preparing search index...
                                                                                                                                                                                                                  diff --git a/docs/modules/types.html b/docs/modules/types.html deleted file mode 100644 index 2859f15..0000000 --- a/docs/modules/types.html +++ /dev/null @@ -1 +0,0 @@ -types | mbus-backend
                                                                                                                                                                                                                  mbus-backend
                                                                                                                                                                                                                    Preparing search index...

                                                                                                                                                                                                                    Module types

                                                                                                                                                                                                                    Type Aliases

                                                                                                                                                                                                                    Route
                                                                                                                                                                                                                    diff --git a/docs/modules/walking_loadMap.html b/docs/modules/walking_loadMap.html deleted file mode 100644 index a1cc5b7..0000000 --- a/docs/modules/walking_loadMap.html +++ /dev/null @@ -1 +0,0 @@ -walking/loadMap | mbus-backend
                                                                                                                                                                                                                    mbus-backend
                                                                                                                                                                                                                      Preparing search index...

                                                                                                                                                                                                                      Module walking/loadMap

                                                                                                                                                                                                                      Functions

                                                                                                                                                                                                                      haversine
                                                                                                                                                                                                                      loadMap
                                                                                                                                                                                                                      diff --git a/docs/modules/walking_types.html b/docs/modules/walking_types.html deleted file mode 100644 index 247a6fb..0000000 --- a/docs/modules/walking_types.html +++ /dev/null @@ -1 +0,0 @@ -walking/types | mbus-backend
                                                                                                                                                                                                                      mbus-backend
                                                                                                                                                                                                                        Preparing search index...

                                                                                                                                                                                                                        Module walking/types

                                                                                                                                                                                                                        Type Aliases

                                                                                                                                                                                                                        GraphMLEdge
                                                                                                                                                                                                                        GraphMLNode
                                                                                                                                                                                                                        LandmarkDef
                                                                                                                                                                                                                        diff --git a/docs/modules/walking_walkingMap.html b/docs/modules/walking_walkingMap.html deleted file mode 100644 index bbb0db7..0000000 --- a/docs/modules/walking_walkingMap.html +++ /dev/null @@ -1 +0,0 @@ -walking/walkingMap | mbus-backend
                                                                                                                                                                                                                        mbus-backend
                                                                                                                                                                                                                          Preparing search index...
                                                                                                                                                                                                                          diff --git a/docs/types/raptor_McStructs.Criteria.html b/docs/types/raptor_McStructs.Criteria.html deleted file mode 100644 index f731f5d..0000000 --- a/docs/types/raptor_McStructs.Criteria.html +++ /dev/null @@ -1,5 +0,0 @@ -Criteria | mbus-backend
                                                                                                                                                                                                                          mbus-backend
                                                                                                                                                                                                                            Preparing search index...

                                                                                                                                                                                                                            Type Alias Criteria

                                                                                                                                                                                                                            Defines the optimization metrics used to compare different journey options.

                                                                                                                                                                                                                            -
                                                                                                                                                                                                                            type Criteria = {
                                                                                                                                                                                                                                arrivalTime: number;
                                                                                                                                                                                                                                transferCount: number;
                                                                                                                                                                                                                                walkingDistance: number;
                                                                                                                                                                                                                            }
                                                                                                                                                                                                                            Index

                                                                                                                                                                                                                            Properties

                                                                                                                                                                                                                            arrivalTime: number
                                                                                                                                                                                                                            transferCount: number
                                                                                                                                                                                                                            walkingDistance: number
                                                                                                                                                                                                                            diff --git a/docs/types/raptor_types.Duration.html b/docs/types/raptor_types.Duration.html deleted file mode 100644 index bd2ff41..0000000 --- a/docs/types/raptor_types.Duration.html +++ /dev/null @@ -1,2 +0,0 @@ -Duration | mbus-backend
                                                                                                                                                                                                                            mbus-backend
                                                                                                                                                                                                                              Preparing search index...

                                                                                                                                                                                                                              Type Alias Duration

                                                                                                                                                                                                                              Duration: number

                                                                                                                                                                                                                              A span of time measured in seconds.

                                                                                                                                                                                                                              -
                                                                                                                                                                                                                              diff --git a/docs/types/raptor_types.Interchange.html b/docs/types/raptor_types.Interchange.html deleted file mode 100644 index 018d219..0000000 --- a/docs/types/raptor_types.Interchange.html +++ /dev/null @@ -1,2 +0,0 @@ -Interchange | mbus-backend
                                                                                                                                                                                                                              mbus-backend
                                                                                                                                                                                                                                Preparing search index...

                                                                                                                                                                                                                                Type Alias Interchange

                                                                                                                                                                                                                                Interchange: Record<StopID, Time>

                                                                                                                                                                                                                                Map defining the minimum time required to switch vehicles at each stop.

                                                                                                                                                                                                                                -
                                                                                                                                                                                                                                diff --git a/docs/types/raptor_types.StopID.html b/docs/types/raptor_types.StopID.html deleted file mode 100644 index eec8b94..0000000 --- a/docs/types/raptor_types.StopID.html +++ /dev/null @@ -1,2 +0,0 @@ -StopID | mbus-backend
                                                                                                                                                                                                                                mbus-backend
                                                                                                                                                                                                                                  Preparing search index...

                                                                                                                                                                                                                                  Type Alias StopID

                                                                                                                                                                                                                                  StopID: string

                                                                                                                                                                                                                                  Unique identifier for a transit stop (e.g., "NRW").

                                                                                                                                                                                                                                  -
                                                                                                                                                                                                                                  diff --git a/docs/types/raptor_types.Time.html b/docs/types/raptor_types.Time.html deleted file mode 100644 index bdf5077..0000000 --- a/docs/types/raptor_types.Time.html +++ /dev/null @@ -1,3 +0,0 @@ -Time | mbus-backend
                                                                                                                                                                                                                                  mbus-backend
                                                                                                                                                                                                                                    Preparing search index...

                                                                                                                                                                                                                                    Type Alias Time

                                                                                                                                                                                                                                    Time: number

                                                                                                                                                                                                                                    Time represented as seconds since midnight. -Values may exceed 86400 (24 hours) for trips extending into the next day.

                                                                                                                                                                                                                                    -
                                                                                                                                                                                                                                    diff --git a/docs/types/raptor_types.TransfersByOrigin.html b/docs/types/raptor_types.TransfersByOrigin.html deleted file mode 100644 index b24eb74..0000000 --- a/docs/types/raptor_types.TransfersByOrigin.html +++ /dev/null @@ -1,2 +0,0 @@ -TransfersByOrigin | mbus-backend
                                                                                                                                                                                                                                    mbus-backend
                                                                                                                                                                                                                                      Preparing search index...

                                                                                                                                                                                                                                      Type Alias TransfersByOrigin

                                                                                                                                                                                                                                      TransfersByOrigin: Record<StopID, Transfer[]>

                                                                                                                                                                                                                                      Lookup map for walking transfers, indexed by the origin Stop ID.

                                                                                                                                                                                                                                      -
                                                                                                                                                                                                                                      diff --git a/docs/types/raptor_types.TripID.html b/docs/types/raptor_types.TripID.html deleted file mode 100644 index ae31e00..0000000 --- a/docs/types/raptor_types.TripID.html +++ /dev/null @@ -1,2 +0,0 @@ -TripID | mbus-backend
                                                                                                                                                                                                                                      mbus-backend
                                                                                                                                                                                                                                        Preparing search index...

                                                                                                                                                                                                                                        Type Alias TripID

                                                                                                                                                                                                                                        TripID: string

                                                                                                                                                                                                                                        Unique identifier for a GTFS trip.

                                                                                                                                                                                                                                        -
                                                                                                                                                                                                                                        diff --git a/docs/types/services_reminder.PostThreshold.html b/docs/types/services_reminder.PostThreshold.html deleted file mode 100644 index 2eca7ea..0000000 --- a/docs/types/services_reminder.PostThreshold.html +++ /dev/null @@ -1,7 +0,0 @@ -PostThreshold | mbus-backend
                                                                                                                                                                                                                                        mbus-backend
                                                                                                                                                                                                                                          Preparing search index...

                                                                                                                                                                                                                                          Type Alias PostThreshold

                                                                                                                                                                                                                                          Waiting for the bus indicated by vid to be at the stop indicated by stpid. Logic for what notification to send -is complicated by arrival times sometimes skipping DUE, see ReminderSubscriptons.process for details.

                                                                                                                                                                                                                                          -
                                                                                                                                                                                                                                          type PostThreshold = {
                                                                                                                                                                                                                                              event: BaseEvent;
                                                                                                                                                                                                                                              stage: 1;
                                                                                                                                                                                                                                              vid: string;
                                                                                                                                                                                                                                              vidPredPrev: number | null;
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                          Index

                                                                                                                                                                                                                                          Properties

                                                                                                                                                                                                                                          Properties

                                                                                                                                                                                                                                          event: BaseEvent
                                                                                                                                                                                                                                          stage: 1
                                                                                                                                                                                                                                          vid: string
                                                                                                                                                                                                                                          vidPredPrev: number | null
                                                                                                                                                                                                                                          diff --git a/docs/types/services_reminder.PreThreshold.html b/docs/types/services_reminder.PreThreshold.html deleted file mode 100644 index 041f7fe..0000000 --- a/docs/types/services_reminder.PreThreshold.html +++ /dev/null @@ -1,15 +0,0 @@ -PreThreshold | mbus-backend
                                                                                                                                                                                                                                          mbus-backend
                                                                                                                                                                                                                                            Preparing search index...

                                                                                                                                                                                                                                            Type Alias PreThreshold

                                                                                                                                                                                                                                            Waiting for a prediction of the right event to have a arrival timestamp that is at or after mustBeAfter and an -arrival time less than thresh. A bus is xx minutes from the stop notification is then sent. To handle delayed and -disappeared notifications, a candidateVid is set to the soonest arriving bus that arrives after mustbeAfter.

                                                                                                                                                                                                                                            -
                                                                                                                                                                                                                                            type PreThreshold = {
                                                                                                                                                                                                                                                candidateVid: string | null;
                                                                                                                                                                                                                                                candidateVidPredPrev: number | null;
                                                                                                                                                                                                                                                event: BaseEvent;
                                                                                                                                                                                                                                                mustBeAfter: number;
                                                                                                                                                                                                                                                stage: 0;
                                                                                                                                                                                                                                                thresh: number;
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            Index

                                                                                                                                                                                                                                            Properties

                                                                                                                                                                                                                                            candidateVid: string | null

                                                                                                                                                                                                                                            stores the bus that'll likely trigger the threshold notification, being only a candidate this can change -as things are updated and such a change won't trigger a disappeared notification

                                                                                                                                                                                                                                            -
                                                                                                                                                                                                                                            candidateVidPredPrev: number | null

                                                                                                                                                                                                                                            minutes

                                                                                                                                                                                                                                            -
                                                                                                                                                                                                                                            event: BaseEvent
                                                                                                                                                                                                                                            mustBeAfter: number

                                                                                                                                                                                                                                            unix epoch milliseconds

                                                                                                                                                                                                                                            -
                                                                                                                                                                                                                                            stage: 0
                                                                                                                                                                                                                                            thresh: number

                                                                                                                                                                                                                                            minutes

                                                                                                                                                                                                                                            -
                                                                                                                                                                                                                                            diff --git a/docs/types/services_reminderTypes.BaseEvent.html b/docs/types/services_reminderTypes.BaseEvent.html deleted file mode 100644 index 1dcbcc2..0000000 --- a/docs/types/services_reminderTypes.BaseEvent.html +++ /dev/null @@ -1 +0,0 @@ -BaseEvent | mbus-backend
                                                                                                                                                                                                                                            mbus-backend
                                                                                                                                                                                                                                              Preparing search index...
                                                                                                                                                                                                                                              BaseEvent: CoreEvent & { __brand: "event" }
                                                                                                                                                                                                                                              diff --git a/docs/types/services_reminderTypes.DelayEvent.html b/docs/types/services_reminderTypes.DelayEvent.html deleted file mode 100644 index 44c549c..0000000 --- a/docs/types/services_reminderTypes.DelayEvent.html +++ /dev/null @@ -1 +0,0 @@ -DelayEvent | mbus-backend
                                                                                                                                                                                                                                              mbus-backend
                                                                                                                                                                                                                                                Preparing search index...
                                                                                                                                                                                                                                                DelayEvent: CoreEvent & {
                                                                                                                                                                                                                                                    __brand: "delayEvent";
                                                                                                                                                                                                                                                    currPred: number;
                                                                                                                                                                                                                                                    prevPred: number;
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                                diff --git a/docs/types/services_reminderTypes.Key.html b/docs/types/services_reminderTypes.Key.html deleted file mode 100644 index f07b39c..0000000 --- a/docs/types/services_reminderTypes.Key.html +++ /dev/null @@ -1 +0,0 @@ -Key | mbus-backend
                                                                                                                                                                                                                                                mbus-backend
                                                                                                                                                                                                                                                  Preparing search index...

                                                                                                                                                                                                                                                  Type Alias Key<T>

                                                                                                                                                                                                                                                  Key: string & { __brand: "key"; __phantomData: T }

                                                                                                                                                                                                                                                  Type Parameters

                                                                                                                                                                                                                                                  • T
                                                                                                                                                                                                                                                  diff --git a/docs/types/services_reminderTypes.RegistrationToken.html b/docs/types/services_reminderTypes.RegistrationToken.html deleted file mode 100644 index a46cd40..0000000 --- a/docs/types/services_reminderTypes.RegistrationToken.html +++ /dev/null @@ -1 +0,0 @@ -RegistrationToken | mbus-backend
                                                                                                                                                                                                                                                  mbus-backend
                                                                                                                                                                                                                                                    Preparing search index...

                                                                                                                                                                                                                                                    Type Alias RegistrationToken

                                                                                                                                                                                                                                                    RegistrationToken: string & { __brand: "registrationToken" }
                                                                                                                                                                                                                                                    diff --git a/docs/types/services_reminderTypes.ThresholdEvent.html b/docs/types/services_reminderTypes.ThresholdEvent.html deleted file mode 100644 index 453a64b..0000000 --- a/docs/types/services_reminderTypes.ThresholdEvent.html +++ /dev/null @@ -1 +0,0 @@ -ThresholdEvent | mbus-backend
                                                                                                                                                                                                                                                    mbus-backend
                                                                                                                                                                                                                                                      Preparing search index...

                                                                                                                                                                                                                                                      Type Alias ThresholdEvent

                                                                                                                                                                                                                                                      ThresholdEvent: CoreEvent & { __brand: "thresholdEvent"; threshold: number }
                                                                                                                                                                                                                                                      diff --git a/docs/types/state_transitState.Prediction.html b/docs/types/state_transitState.Prediction.html deleted file mode 100644 index 9d40436..0000000 --- a/docs/types/state_transitState.Prediction.html +++ /dev/null @@ -1,2 +0,0 @@ -Prediction | mbus-backend
                                                                                                                                                                                                                                                      mbus-backend
                                                                                                                                                                                                                                                        Preparing search index...

                                                                                                                                                                                                                                                        Type Alias Prediction

                                                                                                                                                                                                                                                        Prediction: {
                                                                                                                                                                                                                                                            prdctdn: string;
                                                                                                                                                                                                                                                            prdtm: number;
                                                                                                                                                                                                                                                            rt: string;
                                                                                                                                                                                                                                                            stpid: string;
                                                                                                                                                                                                                                                            vid: string;
                                                                                                                                                                                                                                                        } & Record<string, any>

                                                                                                                                                                                                                                                        Represents a bus prediction.

                                                                                                                                                                                                                                                        -
                                                                                                                                                                                                                                                        diff --git a/docs/types/types.Route.html b/docs/types/types.Route.html deleted file mode 100644 index c373f72..0000000 --- a/docs/types/types.Route.html +++ /dev/null @@ -1,2 +0,0 @@ -Route | mbus-backend
                                                                                                                                                                                                                                                        mbus-backend
                                                                                                                                                                                                                                                          Preparing search index...

                                                                                                                                                                                                                                                          Type Alias Route

                                                                                                                                                                                                                                                          type Route = {
                                                                                                                                                                                                                                                              rt: string;
                                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                                          Index

                                                                                                                                                                                                                                                          Properties

                                                                                                                                                                                                                                                          rt -

                                                                                                                                                                                                                                                          Properties

                                                                                                                                                                                                                                                          rt: string
                                                                                                                                                                                                                                                          diff --git a/docs/types/walking_types.GraphMLEdge.html b/docs/types/walking_types.GraphMLEdge.html deleted file mode 100644 index 1ed522b..0000000 --- a/docs/types/walking_types.GraphMLEdge.html +++ /dev/null @@ -1,8 +0,0 @@ -GraphMLEdge | mbus-backend
                                                                                                                                                                                                                                                          mbus-backend
                                                                                                                                                                                                                                                            Preparing search index...

                                                                                                                                                                                                                                                            Type Alias GraphMLEdge

                                                                                                                                                                                                                                                            Represents a directed edge (street segment) connecting two nodes.

                                                                                                                                                                                                                                                            -
                                                                                                                                                                                                                                                            type GraphMLEdge = {
                                                                                                                                                                                                                                                                dist: number;
                                                                                                                                                                                                                                                                geometry?: { lat: number; lon: number }[];
                                                                                                                                                                                                                                                                to: string;
                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                            Index

                                                                                                                                                                                                                                                            Properties

                                                                                                                                                                                                                                                            Properties

                                                                                                                                                                                                                                                            dist: number

                                                                                                                                                                                                                                                            Length of the edge in meters.

                                                                                                                                                                                                                                                            -
                                                                                                                                                                                                                                                            geometry?: { lat: number; lon: number }[]

                                                                                                                                                                                                                                                            Detailed geometry points (WKT) for rendering curved paths.

                                                                                                                                                                                                                                                            -
                                                                                                                                                                                                                                                            to: string

                                                                                                                                                                                                                                                            The ID of the target node this edge leads to.

                                                                                                                                                                                                                                                            -
                                                                                                                                                                                                                                                            diff --git a/docs/types/walking_types.GraphMLNode.html b/docs/types/walking_types.GraphMLNode.html deleted file mode 100644 index f225f2c..0000000 --- a/docs/types/walking_types.GraphMLNode.html +++ /dev/null @@ -1,8 +0,0 @@ -GraphMLNode | mbus-backend
                                                                                                                                                                                                                                                            mbus-backend
                                                                                                                                                                                                                                                              Preparing search index...

                                                                                                                                                                                                                                                              Type Alias GraphMLNode

                                                                                                                                                                                                                                                              Represents a node in the street graph derived from GraphML.

                                                                                                                                                                                                                                                              -
                                                                                                                                                                                                                                                              type GraphMLNode = {
                                                                                                                                                                                                                                                                  id: string;
                                                                                                                                                                                                                                                                  lat: number;
                                                                                                                                                                                                                                                                  lon: number;
                                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                                              Index

                                                                                                                                                                                                                                                              Properties

                                                                                                                                                                                                                                                              id -lat -lon -

                                                                                                                                                                                                                                                              Properties

                                                                                                                                                                                                                                                              id: string

                                                                                                                                                                                                                                                              Unique identifier for the node (from OSM/GraphML).

                                                                                                                                                                                                                                                              -
                                                                                                                                                                                                                                                              lat: number

                                                                                                                                                                                                                                                              Latitude coordinate.

                                                                                                                                                                                                                                                              -
                                                                                                                                                                                                                                                              lon: number

                                                                                                                                                                                                                                                              Longitude coordinate.

                                                                                                                                                                                                                                                              -
                                                                                                                                                                                                                                                              diff --git a/docs/types/walking_types.LandmarkDef.html b/docs/types/walking_types.LandmarkDef.html deleted file mode 100644 index 915b411..0000000 --- a/docs/types/walking_types.LandmarkDef.html +++ /dev/null @@ -1,10 +0,0 @@ -LandmarkDef | mbus-backend
                                                                                                                                                                                                                                                              mbus-backend
                                                                                                                                                                                                                                                                Preparing search index...

                                                                                                                                                                                                                                                                Type Alias LandmarkDef

                                                                                                                                                                                                                                                                Definition for a navigation landmark used in the ALT heuristic algorithm.

                                                                                                                                                                                                                                                                -
                                                                                                                                                                                                                                                                type LandmarkDef = {
                                                                                                                                                                                                                                                                    lat: number;
                                                                                                                                                                                                                                                                    lon: number;
                                                                                                                                                                                                                                                                    name: string;
                                                                                                                                                                                                                                                                    nodeId?: string;
                                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                                                Index

                                                                                                                                                                                                                                                                Properties

                                                                                                                                                                                                                                                                Properties

                                                                                                                                                                                                                                                                lat: number

                                                                                                                                                                                                                                                                Latitude coordinate.

                                                                                                                                                                                                                                                                -
                                                                                                                                                                                                                                                                lon: number

                                                                                                                                                                                                                                                                Longitude coordinate.

                                                                                                                                                                                                                                                                -
                                                                                                                                                                                                                                                                name: string

                                                                                                                                                                                                                                                                Display name of the landmark.

                                                                                                                                                                                                                                                                -
                                                                                                                                                                                                                                                                nodeId?: string

                                                                                                                                                                                                                                                                The Graph Node ID nearest to this landmark (computed at runtime).

                                                                                                                                                                                                                                                                -
                                                                                                                                                                                                                                                                diff --git a/docs/variables/routes_api.default.html b/docs/variables/routes_api.default.html deleted file mode 100644 index b90e1b3..0000000 --- a/docs/variables/routes_api.default.html +++ /dev/null @@ -1,3 +0,0 @@ -default | mbus-backend
                                                                                                                                                                                                                                                                mbus-backend
                                                                                                                                                                                                                                                                  Preparing search index...

                                                                                                                                                                                                                                                                  Variable defaultConst

                                                                                                                                                                                                                                                                  default: Router = ...

                                                                                                                                                                                                                                                                  Express router for the MBus API v3. -Handles routes for static data, state debugging, journey planning, and startup info.

                                                                                                                                                                                                                                                                  -
                                                                                                                                                                                                                                                                  diff --git a/docs/variables/services_metadata.staticData.html b/docs/variables/services_metadata.staticData.html deleted file mode 100644 index 02a705b..0000000 --- a/docs/variables/services_metadata.staticData.html +++ /dev/null @@ -1,2 +0,0 @@ -staticData | mbus-backend
                                                                                                                                                                                                                                                                  mbus-backend
                                                                                                                                                                                                                                                                    Preparing search index...

                                                                                                                                                                                                                                                                    Variable staticDataConst

                                                                                                                                                                                                                                                                    staticData: any = metadata

                                                                                                                                                                                                                                                                    Raw static metadata from JSON.

                                                                                                                                                                                                                                                                    -
                                                                                                                                                                                                                                                                    diff --git a/docs/variables/services_reminder.rideReminderSubscriptions.html b/docs/variables/services_reminder.rideReminderSubscriptions.html deleted file mode 100644 index f0c65df..0000000 --- a/docs/variables/services_reminder.rideReminderSubscriptions.html +++ /dev/null @@ -1 +0,0 @@ -rideReminderSubscriptions | mbus-backend
                                                                                                                                                                                                                                                                    mbus-backend
                                                                                                                                                                                                                                                                      Preparing search index...

                                                                                                                                                                                                                                                                      Variable rideReminderSubscriptionsConst

                                                                                                                                                                                                                                                                      rideReminderSubscriptions: ReminderSubscriptions = ...
                                                                                                                                                                                                                                                                      diff --git a/docs/variables/services_reminder.testing.html b/docs/variables/services_reminder.testing.html deleted file mode 100644 index 2f9349e..0000000 --- a/docs/variables/services_reminder.testing.html +++ /dev/null @@ -1,2 +0,0 @@ -testing | mbus-backend
                                                                                                                                                                                                                                                                      mbus-backend
                                                                                                                                                                                                                                                                        Preparing search index...

                                                                                                                                                                                                                                                                        Variable testingConst

                                                                                                                                                                                                                                                                        testing: {
                                                                                                                                                                                                                                                                            eventsEqual: (e1: BaseEvent, e2: BaseEvent) => boolean;
                                                                                                                                                                                                                                                                            ReminderSubscriptions: typeof ReminderSubscriptions;
                                                                                                                                                                                                                                                                        } = ...

                                                                                                                                                                                                                                                                        exported for tests

                                                                                                                                                                                                                                                                        -

                                                                                                                                                                                                                                                                        Type Declaration

                                                                                                                                                                                                                                                                        • eventsEqual: (e1: BaseEvent, e2: BaseEvent) => boolean
                                                                                                                                                                                                                                                                        • ReminderSubscriptions: typeof ReminderSubscriptions
                                                                                                                                                                                                                                                                        diff --git a/docs/variables/services_reminder.universityReminderSubscriptions.html b/docs/variables/services_reminder.universityReminderSubscriptions.html deleted file mode 100644 index b19565d..0000000 --- a/docs/variables/services_reminder.universityReminderSubscriptions.html +++ /dev/null @@ -1 +0,0 @@ -universityReminderSubscriptions | mbus-backend
                                                                                                                                                                                                                                                                        mbus-backend
                                                                                                                                                                                                                                                                          Preparing search index...

                                                                                                                                                                                                                                                                          Variable universityReminderSubscriptionsConst

                                                                                                                                                                                                                                                                          universityReminderSubscriptions: ReminderSubscriptions = ...
                                                                                                                                                                                                                                                                          diff --git a/docs/variables/state_transitState.cachedGraph.html b/docs/variables/state_transitState.cachedGraph.html deleted file mode 100644 index 9b2a870..0000000 --- a/docs/variables/state_transitState.cachedGraph.html +++ /dev/null @@ -1,2 +0,0 @@ -cachedGraph | mbus-backend
                                                                                                                                                                                                                                                                          mbus-backend
                                                                                                                                                                                                                                                                            Preparing search index...

                                                                                                                                                                                                                                                                            Variable cachedGraph

                                                                                                                                                                                                                                                                            cachedGraph: {
                                                                                                                                                                                                                                                                                interchange: Interchange;
                                                                                                                                                                                                                                                                                transfers: TransfersByOrigin;
                                                                                                                                                                                                                                                                                trips: Trip[];
                                                                                                                                                                                                                                                                            } = ...

                                                                                                                                                                                                                                                                            The current transit graph used for routing.

                                                                                                                                                                                                                                                                            -

                                                                                                                                                                                                                                                                            Type Declaration

                                                                                                                                                                                                                                                                            diff --git a/docs/variables/state_transitState.cachedPredsByStopId.html b/docs/variables/state_transitState.cachedPredsByStopId.html deleted file mode 100644 index 14a287b..0000000 --- a/docs/variables/state_transitState.cachedPredsByStopId.html +++ /dev/null @@ -1,2 +0,0 @@ -cachedPredsByStopId | mbus-backend
                                                                                                                                                                                                                                                                            mbus-backend
                                                                                                                                                                                                                                                                              Preparing search index...

                                                                                                                                                                                                                                                                              Variable cachedPredsByStopIdConst

                                                                                                                                                                                                                                                                              cachedPredsByStopId: Record<string, Prediction[]> = {}

                                                                                                                                                                                                                                                                              Predictions indexed by stop ID.

                                                                                                                                                                                                                                                                              -
                                                                                                                                                                                                                                                                              diff --git a/docs/variables/state_transitState.cachedPredsByVid.html b/docs/variables/state_transitState.cachedPredsByVid.html deleted file mode 100644 index e2f14ae..0000000 --- a/docs/variables/state_transitState.cachedPredsByVid.html +++ /dev/null @@ -1,2 +0,0 @@ -cachedPredsByVid | mbus-backend
                                                                                                                                                                                                                                                                              mbus-backend
                                                                                                                                                                                                                                                                                Preparing search index...

                                                                                                                                                                                                                                                                                Variable cachedPredsByVidConst

                                                                                                                                                                                                                                                                                cachedPredsByVid: Record<string, Prediction[]> = {}

                                                                                                                                                                                                                                                                                Predictions indexed by vehicle ID.

                                                                                                                                                                                                                                                                                -
                                                                                                                                                                                                                                                                                diff --git a/docs/variables/state_transitState.cachedRidePredsByStopId.html b/docs/variables/state_transitState.cachedRidePredsByStopId.html deleted file mode 100644 index a2d90a8..0000000 --- a/docs/variables/state_transitState.cachedRidePredsByStopId.html +++ /dev/null @@ -1,2 +0,0 @@ -cachedRidePredsByStopId | mbus-backend
                                                                                                                                                                                                                                                                                mbus-backend
                                                                                                                                                                                                                                                                                  Preparing search index...

                                                                                                                                                                                                                                                                                  Variable cachedRidePredsByStopIdConst

                                                                                                                                                                                                                                                                                  cachedRidePredsByStopId: Record<string, Prediction[]> = {}

                                                                                                                                                                                                                                                                                  Predictions indexed by ride stop ID.

                                                                                                                                                                                                                                                                                  -
                                                                                                                                                                                                                                                                                  diff --git a/docs/variables/state_transitState.cachedRidePredsByVid.html b/docs/variables/state_transitState.cachedRidePredsByVid.html deleted file mode 100644 index 67a79b1..0000000 --- a/docs/variables/state_transitState.cachedRidePredsByVid.html +++ /dev/null @@ -1,2 +0,0 @@ -cachedRidePredsByVid | mbus-backend
                                                                                                                                                                                                                                                                                  mbus-backend
                                                                                                                                                                                                                                                                                    Preparing search index...

                                                                                                                                                                                                                                                                                    Variable cachedRidePredsByVidConst

                                                                                                                                                                                                                                                                                    cachedRidePredsByVid: Record<string, Prediction[]> = {}

                                                                                                                                                                                                                                                                                    Predictions indexed by ride vehicle ID.

                                                                                                                                                                                                                                                                                    -
                                                                                                                                                                                                                                                                                    diff --git a/docs/variables/state_transitState.cachedRideRoutes.html b/docs/variables/state_transitState.cachedRideRoutes.html deleted file mode 100644 index a85f23b..0000000 --- a/docs/variables/state_transitState.cachedRideRoutes.html +++ /dev/null @@ -1,2 +0,0 @@ -cachedRideRoutes | mbus-backend
                                                                                                                                                                                                                                                                                    mbus-backend
                                                                                                                                                                                                                                                                                      Preparing search index...

                                                                                                                                                                                                                                                                                      Variable cachedRideRoutesConst

                                                                                                                                                                                                                                                                                      cachedRideRoutes: Record<string, any> = {}

                                                                                                                                                                                                                                                                                      Cache of route patterns and static data for the ride.

                                                                                                                                                                                                                                                                                      -
                                                                                                                                                                                                                                                                                      diff --git a/docs/variables/state_transitState.cachedRideStopLocations.html b/docs/variables/state_transitState.cachedRideStopLocations.html deleted file mode 100644 index 8eaa636..0000000 --- a/docs/variables/state_transitState.cachedRideStopLocations.html +++ /dev/null @@ -1 +0,0 @@ -cachedRideStopLocations | mbus-backend
                                                                                                                                                                                                                                                                                      mbus-backend
                                                                                                                                                                                                                                                                                        Preparing search index...

                                                                                                                                                                                                                                                                                        Variable cachedRideStopLocations

                                                                                                                                                                                                                                                                                        cachedRideStopLocations: Record<
                                                                                                                                                                                                                                                                                            string,
                                                                                                                                                                                                                                                                                            { lat: number; lon: number; name: string },
                                                                                                                                                                                                                                                                                        > = {}
                                                                                                                                                                                                                                                                                        diff --git a/docs/variables/state_transitState.cachedRoutes.html b/docs/variables/state_transitState.cachedRoutes.html deleted file mode 100644 index 4c54c10..0000000 --- a/docs/variables/state_transitState.cachedRoutes.html +++ /dev/null @@ -1,2 +0,0 @@ -cachedRoutes | mbus-backend
                                                                                                                                                                                                                                                                                        mbus-backend
                                                                                                                                                                                                                                                                                          Preparing search index...

                                                                                                                                                                                                                                                                                          Variable cachedRoutesConst

                                                                                                                                                                                                                                                                                          cachedRoutes: Record<string, any> = {}

                                                                                                                                                                                                                                                                                          Cache of route patterns and static data.

                                                                                                                                                                                                                                                                                          -
                                                                                                                                                                                                                                                                                          diff --git a/docs/variables/state_transitState.cachedStopLocations.html b/docs/variables/state_transitState.cachedStopLocations.html deleted file mode 100644 index d89fc66..0000000 --- a/docs/variables/state_transitState.cachedStopLocations.html +++ /dev/null @@ -1,2 +0,0 @@ -cachedStopLocations | mbus-backend
                                                                                                                                                                                                                                                                                          mbus-backend
                                                                                                                                                                                                                                                                                            Preparing search index...

                                                                                                                                                                                                                                                                                            Variable cachedStopLocations

                                                                                                                                                                                                                                                                                            cachedStopLocations: Record<string, { lat: number; lon: number; name: string }> = {}

                                                                                                                                                                                                                                                                                            Cache of stop locations (lat/lon).

                                                                                                                                                                                                                                                                                            -
                                                                                                                                                                                                                                                                                            diff --git a/docs/variables/state_transitState.curBusPositions.html b/docs/variables/state_transitState.curBusPositions.html deleted file mode 100644 index 7bc30bd..0000000 --- a/docs/variables/state_transitState.curBusPositions.html +++ /dev/null @@ -1,2 +0,0 @@ -curBusPositions | mbus-backend
                                                                                                                                                                                                                                                                                            mbus-backend
                                                                                                                                                                                                                                                                                              Preparing search index...

                                                                                                                                                                                                                                                                                              Variable curBusPositionsConst

                                                                                                                                                                                                                                                                                              curBusPositions: { buses: any[] } = ...

                                                                                                                                                                                                                                                                                              Current positions of all buses.

                                                                                                                                                                                                                                                                                              -

                                                                                                                                                                                                                                                                                              Type Declaration

                                                                                                                                                                                                                                                                                              • buses: any[]
                                                                                                                                                                                                                                                                                              diff --git a/docs/variables/state_transitState.curRidePositions.html b/docs/variables/state_transitState.curRidePositions.html deleted file mode 100644 index 96ca1c9..0000000 --- a/docs/variables/state_transitState.curRidePositions.html +++ /dev/null @@ -1,2 +0,0 @@ -curRidePositions | mbus-backend
                                                                                                                                                                                                                                                                                              mbus-backend
                                                                                                                                                                                                                                                                                                Preparing search index...

                                                                                                                                                                                                                                                                                                Variable curRidePositionsConst

                                                                                                                                                                                                                                                                                                curRidePositions: { buses: any[] } = ...

                                                                                                                                                                                                                                                                                                Current positions of all ride buses.

                                                                                                                                                                                                                                                                                                -

                                                                                                                                                                                                                                                                                                Type Declaration

                                                                                                                                                                                                                                                                                                • buses: any[]
                                                                                                                                                                                                                                                                                                diff --git a/docs/variables/state_transitState.rideStopIdToName.html b/docs/variables/state_transitState.rideStopIdToName.html deleted file mode 100644 index b36e20b..0000000 --- a/docs/variables/state_transitState.rideStopIdToName.html +++ /dev/null @@ -1,2 +0,0 @@ -rideStopIdToName | mbus-backend
                                                                                                                                                                                                                                                                                                mbus-backend
                                                                                                                                                                                                                                                                                                  Preparing search index...

                                                                                                                                                                                                                                                                                                  Variable rideStopIdToNameConst

                                                                                                                                                                                                                                                                                                  rideStopIdToName: Record<string, string> = {}

                                                                                                                                                                                                                                                                                                  Map of ride stop IDs to their human-readable names.

                                                                                                                                                                                                                                                                                                  -
                                                                                                                                                                                                                                                                                                  diff --git a/docs/variables/state_transitState.routeTimingCache.html b/docs/variables/state_transitState.routeTimingCache.html deleted file mode 100644 index 3a6c458..0000000 --- a/docs/variables/state_transitState.routeTimingCache.html +++ /dev/null @@ -1,2 +0,0 @@ -routeTimingCache | mbus-backend
                                                                                                                                                                                                                                                                                                  mbus-backend
                                                                                                                                                                                                                                                                                                    Preparing search index...

                                                                                                                                                                                                                                                                                                    Variable routeTimingCacheConst

                                                                                                                                                                                                                                                                                                    routeTimingCache: Record<
                                                                                                                                                                                                                                                                                                        string,
                                                                                                                                                                                                                                                                                                        Record<
                                                                                                                                                                                                                                                                                                            string,
                                                                                                                                                                                                                                                                                                            Record<string, { diff: number; rtdir: string; rtNext: string }>,
                                                                                                                                                                                                                                                                                                        >,
                                                                                                                                                                                                                                                                                                    > = ...

                                                                                                                                                                                                                                                                                                    Cache of timing differences between stops for extrapolation.

                                                                                                                                                                                                                                                                                                    -
                                                                                                                                                                                                                                                                                                    diff --git a/docs/variables/state_transitState.stopIdToName.html b/docs/variables/state_transitState.stopIdToName.html deleted file mode 100644 index 567074d..0000000 --- a/docs/variables/state_transitState.stopIdToName.html +++ /dev/null @@ -1,2 +0,0 @@ -stopIdToName | mbus-backend
                                                                                                                                                                                                                                                                                                    mbus-backend
                                                                                                                                                                                                                                                                                                      Preparing search index...

                                                                                                                                                                                                                                                                                                      Variable stopIdToNameConst

                                                                                                                                                                                                                                                                                                      stopIdToName: Record<string, string> = {}

                                                                                                                                                                                                                                                                                                      Map of stop IDs to their human-readable names.

                                                                                                                                                                                                                                                                                                      -
                                                                                                                                                                                                                                                                                                      diff --git a/docs/variables/state_transitState.tatripidToRt.html b/docs/variables/state_transitState.tatripidToRt.html deleted file mode 100644 index f36d203..0000000 --- a/docs/variables/state_transitState.tatripidToRt.html +++ /dev/null @@ -1,2 +0,0 @@ -tatripidToRt | mbus-backend
                                                                                                                                                                                                                                                                                                      mbus-backend
                                                                                                                                                                                                                                                                                                        Preparing search index...

                                                                                                                                                                                                                                                                                                        Variable tatripidToRtConst

                                                                                                                                                                                                                                                                                                        tatripidToRt: Record<string, string> = {}

                                                                                                                                                                                                                                                                                                        Map of trip IDs to route names.

                                                                                                                                                                                                                                                                                                        -
                                                                                                                                                                                                                                                                                                        diff --git a/docs/variables/state_transitState.validRideRoutes.html b/docs/variables/state_transitState.validRideRoutes.html deleted file mode 100644 index dcd6c7d..0000000 --- a/docs/variables/state_transitState.validRideRoutes.html +++ /dev/null @@ -1,2 +0,0 @@ -validRideRoutes | mbus-backend
                                                                                                                                                                                                                                                                                                        mbus-backend
                                                                                                                                                                                                                                                                                                          Preparing search index...

                                                                                                                                                                                                                                                                                                          Variable validRideRoutesConst

                                                                                                                                                                                                                                                                                                          validRideRoutes: Set<string> = ...

                                                                                                                                                                                                                                                                                                          Set of currently valid ride route IDs.

                                                                                                                                                                                                                                                                                                          -
                                                                                                                                                                                                                                                                                                          diff --git a/docs/variables/state_transitState.validRoutes.html b/docs/variables/state_transitState.validRoutes.html deleted file mode 100644 index dd1e43a..0000000 --- a/docs/variables/state_transitState.validRoutes.html +++ /dev/null @@ -1,2 +0,0 @@ -validRoutes | mbus-backend
                                                                                                                                                                                                                                                                                                          mbus-backend
                                                                                                                                                                                                                                                                                                            Preparing search index...

                                                                                                                                                                                                                                                                                                            Variable validRoutesConst

                                                                                                                                                                                                                                                                                                            validRoutes: Set<string> = ...

                                                                                                                                                                                                                                                                                                            Set of currently valid route IDs.

                                                                                                                                                                                                                                                                                                            -
                                                                                                                                                                                                                                                                                                            From d860546e5521c748b7b6859beccd821d894ce3ea Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Sat, 18 Jul 2026 21:52:03 -0700 Subject: [PATCH 22/36] adjust tsconfig again --- .gitignore | 3 ++- tsconfig.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8586e4f..f4b55a4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules/ package-lock.json .env .vscode/ -src/assets/walkingCache.json \ No newline at end of file +src/assets/walkingCache.json +/docs/ \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 10ce3c8..3422bf1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "es2021", "module": "esnext", - // "moduleResolution": "Node", + "moduleResolution": "bundler", "esModuleInterop": true, "resolveJsonModule": true, "forceConsistentCasingInFileNames": true, From 412cb3bf0ba577bff87d313ded4d922eb13b76f7 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Mon, 20 Jul 2026 09:45:57 -0700 Subject: [PATCH 23/36] be more strict with warnings when generating docs --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb9522c..f7647b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: run: npm i working-directory: ${{ github.workspace }} - name: Build Docs - run: npm run docs + run: npx typedoc --entryPointStrategy expand ./src --exclude \"**/legacy/**\" --treatWarningsAsErrors working-directory: ${{ github.workspace }} - name: Sync files uses: SamKirkland/FTP-Deploy-Action@v4.4.0 From f3b3e15bfc0861fb5250ccc0a7dffa5ed51cdedc Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Mon, 20 Jul 2026 10:02:11 -0700 Subject: [PATCH 24/36] fix docs --- .github/workflows/ci.yml | 2 +- package.json | 2 +- src/legacy/busService-reminders.ts | 683 --------------------- src/legacy/busService.ts | 751 ------------------------ src/legacy/reminderService-reminders.ts | 240 -------- src/legacy/v3-reminders.ts | 660 --------------------- src/legacy/v3.ts | 561 ------------------ src/routes/api.ts | 7 +- src/services/reminder.ts | 4 +- src/services/reminderTypes.ts | 4 +- 10 files changed, 12 insertions(+), 2902 deletions(-) delete mode 100644 src/legacy/busService-reminders.ts delete mode 100644 src/legacy/busService.ts delete mode 100644 src/legacy/reminderService-reminders.ts delete mode 100644 src/legacy/v3-reminders.ts delete mode 100644 src/legacy/v3.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7647b6..64f2ccf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: run: npm i working-directory: ${{ github.workspace }} - name: Build Docs - run: npx typedoc --entryPointStrategy expand ./src --exclude \"**/legacy/**\" --treatWarningsAsErrors + run: npx typedoc --entryPointStrategy expand ./src --treatWarningsAsErrors working-directory: ${{ github.workspace }} - name: Sync files uses: SamKirkland/FTP-Deploy-Action@v4.4.0 diff --git a/package.json b/package.json index 64c5d38..2b7b90a 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "start": "tsx src/app.ts", "test": "vitest run test", "stress-test": "vitest run test/search-stress.test.ts", - "docs": "typedoc --entryPointStrategy expand ./src --exclude \"**/legacy/**\"" + "docs": "typedoc --entryPointStrategy expand ./src" }, "author": "Efe Akinci", "license": "ISC", diff --git a/src/legacy/busService-reminders.ts b/src/legacy/busService-reminders.ts deleted file mode 100644 index 1ead44c..0000000 --- a/src/legacy/busService-reminders.ts +++ /dev/null @@ -1,683 +0,0 @@ -import * as process from "node:process"; - -import axios from 'axios'; -import dotenv from "dotenv"; -import { Route } from "@/types"; -import { - Trip, - StopTime, - Transfer, - StopID, - TransfersByOrigin, - Interchange -} from "./raptor/types"; - -dotenv.config(); - -const API_KEY = process.env.MBUS_API_KEY; -if (API_KEY === undefined) { - throw new Error("MBus API key not set."); -} - -const curBusPositions: { - buses: any[] -} = { - "buses": [] -} - -const cachedRoutes: {[k: string]: any} = {}; -type Prediction = { vid: string; stpid: string } & Record; - -let cachedPredsByVid: Record = {}; -let cachedPredsByStopId: Record = {}; -// routeTimingCache: route -> fromStop -> toStop -> latest diff (minutes) -const routeTimingCache: Record>> = -{ - "CN": { - "N434NORTHBOUND": { - "N500": { - "diff": 5, - "rtdir": "SOUTHBOUND", - "rtNext": "CS" - } - }, - }, - "CS":{ - "S002SOUTHBOUND": { - "S001": { - "diff": 5, - "rtdir": "NORTHBOUND", - "rtNext": "CN" - } - } - } -}; - -const validRoutes = new Set(); -let curRouteSelections = {}; -const routes = ["BB", "CN", "CS", "CSX", "DD", "MX", "NE", "NW", "NX", "OS", "NES", "WS", "WX"]; -let cachedStopLocations: { [stopId: string]: {name : string, lat: number, lon: number } } = { - -}; - -const message = {id: "gradamatation", title: "Congrats Grads 🥳", message: "Congrats to everyone who is gradamatating! Enjoy some grad hats on the buses, and don't forget to celebrate!", buildVersion: '99'} - -let cachedGraph: { - trips: Trip[]; - transfers: TransfersByOrigin; - interchange: Interchange; -} - -let stopIdToName: Record = {}; -let tatripidToRt: Record = {}; - -const sortStopTimesByRouteSequence = (stopTimes: StopTime[]): StopTime[] => { - if (stopTimes.length <= 1) return stopTimes; - - // Sort by arrival time - return stopTimes.sort((a, b) => a.arrivalTime - b.arrivalTime); -}; - -const rebuildGraph = async () => { - try { - const predictions = await getAllBusPredictions(); - if (!predictions || predictions.length === 0) { - return; - } - - // Build stopIdToName and tatripidToRt maps - stopIdToName = {}; - tatripidToRt = {}; - predictions.forEach((trip: any) => { - if (trip.tatripid && trip.stops && trip.stops.length > 0) { - // Find first stop with rt - const firstStopWithRt = trip.stops.find((stop: any) => stop.rt); - if (firstStopWithRt && firstStopWithRt.rt) { - tatripidToRt[trip.tatripid] = firstStopWithRt.rt; - } - } - trip.stops.forEach((stop: any) => { - if (stop.stpid && stop.stpnm) { - stopIdToName[stop.stpid] = stop.stpnm; - } - }); - }); - - const now = new Date(); - const currentTime = now.getUTCHours() * 3600 + now.getUTCMinutes() * 60 + now.getUTCSeconds(); - - const transfers = cachedGraph?.transfers || {}; - const interchange = cachedGraph?.interchange || {}; - - const allStops = new Set(); - predictions.forEach((trip: any) => { - trip.stops.forEach((stop: any) => { - allStops.add(stop.stpid); - }); - }); - - allStops.forEach(stopId => { - if (!transfers[stopId]) { - transfers[stopId] = []; - } - if (!interchange[stopId]) { - interchange[stopId] = 60; // 1 minute interchange time - } - }); - - const stopPredictions: Record = {}; - predictions.forEach((trip: any) => { - trip.stops.forEach((stop: any) => { - if (!stopPredictions[stop.stpid]) { - stopPredictions[stop.stpid] = []; - } - stopPredictions[stop.stpid].push({ - stpid: stop.stpid, - prdctdn: stop.prdctdn, - tatripid: trip.tatripid - }); - }); - }); - - const trips: Trip[] = []; - interface TripPrediction { - vid: string; - stops: { - stpid: string; - prdctdn: string; - rt: string; - }[]; - } - - const tripPredictions: Record = {}; - - predictions.forEach((trip: any) => { - if (!tripPredictions[trip.tatripid]) { - tripPredictions[trip.tatripid] = { - vid: trip.vid, - stops: [] - }; - } - trip.stops.forEach((stop: any) => { - tripPredictions[trip.tatripid].stops.push({ - stpid: stop.stpid, - prdctdn: stop.prdctdn, - rt: stop.rt - }); - }); - }); - - Object.entries(tripPredictions).forEach(([tripId, preds]) => { - // Create stop times with prediction times - const stopTimes: StopTime[] = preds.stops.map(pred => ({ - stop: pred.stpid, - arrivalTime: currentTime + (parseInt(pred.prdctdn) * 60), - departureTime: currentTime + (parseInt(pred.prdctdn) * 60), - pickUp: true, - dropOff: true, - rt: pred.rt - })); - - // Sort stop times by their sequence in the route - const sortedStopTimes = sortStopTimesByRouteSequence(stopTimes); - - trips.push({ - tripId, - vid: preds.vid, - stopTimes: sortedStopTimes - }); - }); - - - cachedGraph = { - trips, - transfers, - interchange - }; - - // Add virtual stops and their interchange - const originStopId = 'VIRTUAL_ORIGIN'; - const destStopId = 'VIRTUAL_DESTINATION'; - cachedGraph.transfers[originStopId] = []; - cachedGraph.transfers[destStopId] = []; - cachedGraph.interchange[originStopId] = 60; - cachedGraph.interchange[destStopId] = 60; - const virtualOriginTrip = { - tripId: 'VIRTUAL_ORIGIN_TRIP', - vid: null, - stopTimes: [{ - stop: originStopId, - arrivalTime: 0, - departureTime: 0, - pickUp: true, - dropOff: true - }] - }; - const virtualDestTrip = { - tripId: 'VIRTUAL_DESTINATION_TRIP', - vid: null, - stopTimes: [{ - stop: destStopId, - arrivalTime: 0, - departureTime: 0, - pickUp: true, - dropOff: true - }] - }; - cachedGraph.trips.push(virtualOriginTrip); - cachedGraph.trips.push(virtualDestTrip); - - } catch (error) { - console.error('Error rebuilding graph:', error); - } -}; - -const getAllBusPredictions = async () => { - try { - // Get all unique stop IDs from cached routes - const allStopIds = new Set(); - Object.values(cachedRoutes).forEach((routePatterns: any) => { - if (Array.isArray(routePatterns)) { - routePatterns.forEach((pattern: any) => { - if (pattern.pt && Array.isArray(pattern.pt)) { - pattern.pt.forEach((point: any) => { - if (point.stpid) { - allStopIds.add(point.stpid); - } - }); - } - }); - } - }); - - const stopIdsArray = Array.from(allStopIds); - const chunks = []; - - for (let i = 0; i < stopIdsArray.length; i += 10) { - chunks.push(stopIdsArray.slice(i, i + 10)); - } - - const predictions = await Promise.all( - chunks.map(async (chunk) => { - const stopIds = chunk.join(','); - const response = await axios.get(`https://mbus.ltp.umich.edu/bustime/api/v3/getpredictions`, { - params: { - requestType: 'getpredictions', - locale: 'en', - stpid: stopIds, - rt: routes.join(','), - tmres: 's', - rtpidatafeed: 'bustime', - key: API_KEY, - format: 'json' - } - }); - return response.data; - }) - ); - - const formattedPredictions = predictions.flat().reduce((acc, predictionChunk) => { - if (predictionChunk['bustime-response'] && predictionChunk['bustime-response']['prd']) { - predictionChunk['bustime-response']['prd'].forEach((prd: any) => { - const tatripid = prd.tatripid; - const stopName = prd.stpnm; - const stopId = prd.stpid; - const rt = prd.rt; - const rtdir = prd.rtdir; - const vid = prd.vid; - let prdctdn = prd.prdctdn; - prdctdn = prdctdn === "DUE" ? "1" : prdctdn; - - let trip = acc.find((t: any) => t.tatripid === tatripid); - - if (!trip) { - if (vid) { // if no trip, go by vid - trip = acc.find((t: any) => t.vid === vid); - } - } - - if (!trip) { // if no trip, create one - trip = { tatripid, vid, stops: [] }; - acc.push(trip); - } else { // merge with existing trip - if (!trip.tatripid) { - trip.tatripid = tatripid; - } - if (!trip.vid && vid) { - trip.vid = vid; - } - } - - let stop = trip.stops.find((s: any) => s.stpnm === stopName && s.stpid === stopId); - if (!stop) { - stop = { stpnm: stopName, stpid: stopId, prdctdn: null, rt: null, rtdir : null }; - trip.stops.push(stop); - } - stop.rtdir = rtdir; - stop.rt = rt; - stop.prdctdn = prdctdn; - }); - } - return acc; - }, []); - - // Cache predictions by vid and stopId - cachedPredsByVid = {}; - cachedPredsByStopId = {}; - predictions.flat().forEach((predictionChunk) => { - const prds = predictionChunk['bustime-response']?.['prd']; - if (!prds) return; - - prds.forEach((prd: Prediction) => { - const { vid, stpid } = prd; - // stpid -> [pred, pred...] - if (!cachedPredsByStopId[stpid]) { - cachedPredsByStopId[stpid] = []; - } - cachedPredsByStopId[stpid].push(prd); // store reference - - if (!vid) return; - // vid -> [pred, pred...] - if (!cachedPredsByVid[vid]) { - cachedPredsByVid[vid] = []; - } - cachedPredsByVid[vid].push(prd); - - }); - }); - - // Cache predictions per route in routeTimingCache for extrapolation - - // Record of stop ids in order using routes - const routeInfoFilter: Record = {}; - for (const [routeName, routeList] of Object.entries(cachedRoutes as Record)) { - for (const route of routeList) { - const rtdir = route.rtdir; - const routeKey = routeName + rtdir; - if (!routeInfoFilter[routeKey]) { - routeInfoFilter[routeKey] = []; - } - for (const point of route.pt) { - if (point.typ !== "W" && point.stpid) { - routeInfoFilter[routeKey].push({ stpid: point.stpid, rtdir }); - } - } - } - } - - const stopIdToName: Record = {}; - formattedPredictions.forEach((trip: any) => { - trip.stops.forEach((stop: any) => { - if (stop.stpid && stop.stpnm) stopIdToName[stop.stpid] = stop.stpnm; - }); - }); - - // Create indices for route -> stop order - const routeStopIndexMaps = new Map>(); - for (const [routeId, stopOrder] of Object.entries(routeInfoFilter)) { - const stopIndexMap = new Map(stopOrder.map(({ stpid }, i) => [stpid, i])); - routeStopIndexMaps.set(routeId, stopIndexMap); - } - - formattedPredictions.forEach((trip: any) => { - if(trip.stops.length == 0) return; - const minPrdctdn = Math.min(...trip.stops.map((s : any) => parseInt(s.prdctdn, 10))); - const firstRoute = trip.stops.find((s: any) => parseInt(s.prdctdn, 10) === minPrdctdn)?.rt; - - if (!firstRoute) return; - // Sort by predicted time - trip.stops.sort((a: any, b: any) => { - const diffTime = parseInt(a.prdctdn, 10) - parseInt(b.prdctdn, 10); - if (diffTime !== 0) return diffTime; // primary sort - - // If not same route, put first route in front - if (a.rt + a.rtdir !== b.rt + b.rtdir) { - if (a.rt === firstRoute) return -1; - if (b.rt === firstRoute) return 1; - - return a.rt.localeCompare(b.rt); - } - // If same route, sort by stop order - const aMap = routeStopIndexMaps.get(a.rt + a.rtdir); - const bMap = routeStopIndexMaps.get(b.rt + b.rtdir); - - const aIdx = aMap?.get(a.stpid) ?? Number.MAX_SAFE_INTEGER; - const bIdx = bMap?.get(b.stpid) ?? Number.MAX_SAFE_INTEGER; - return aIdx - bIdx; - }); - // Create edges based on sorted order - for (let i = 0; i < trip.stops.length - 1; i++) { - const from = trip.stops[i]; - const to = trip.stops[i + 1]; - const diff = parseInt(to.prdctdn, 10) - parseInt(from.prdctdn, 10); - const rt = from.rt; - - const stopIndexMap = routeStopIndexMaps.get(from.rt + from.rtdir); - if (!stopIndexMap) continue; - - const fromIdx = stopIndexMap.get(from.stpid); - const toIdx = stopIndexMap.get(to.stpid); - // Ensure valid follow up stop by idx or end of idx - const isValidFollowUp = ( - fromIdx !== undefined && - toIdx !== undefined && - (toIdx === fromIdx + 1 || fromIdx === stopIndexMap.size - 1) - ); - if (!isValidFollowUp) continue; - - if (!routeTimingCache[rt]) routeTimingCache[rt] = {}; - const fromKey = from.stpid + (from.rtdir || ""); - if (!routeTimingCache[rt][fromKey]) routeTimingCache[rt][fromKey] = {}; - routeTimingCache[rt][fromKey][to.stpid] = { - diff : diff, - rtdir: to.rtdir, - rtNext: to.rt - }; - } - }); - - // Extrapolate future stops based on routeTimingCache - formattedPredictions.forEach((trip: any) => { - let stopsAdded = 0; - while (stopsAdded < 20 && trip.stops.length > 0) { - const lastStop = trip.stops[trip.stops.length - 1]; - const rt = lastStop.rt; - if (!rt) break; - - const fromKey = lastStop.stpid + (lastStop.rtdir || ""); - const nextStops = routeTimingCache[rt]?.[fromKey]; - if (!nextStops) break; - - const nextEntries = Object.entries(nextStops); - if (nextEntries.length === 0) break; - - const [nextStopId, { diff, rtdir, rtNext}] = nextEntries[0]; - const nextPrdctdn = (parseInt(lastStop.prdctdn, 10) + diff).toString(); - - trip.stops.push({ - stpnm: stopIdToName[nextStopId] || nextStopId, - stpid: nextStopId, - prdctdn: nextPrdctdn, - rt : rtNext, - rtdir : rtdir - }); - stopsAdded++; - } - }); - - return formattedPredictions; - } catch (err: unknown) { - const message = err instanceof Error ? err.message : String(err); - console.error("Error in getAllBusPredictions:", message); - return []; - } -}; - -const client = axios.create({ - baseURL: 'https://mbus.ltp.umich.edu/bustime/api/v3/', - params: { - key: API_KEY, - format: 'json' - } -}); - -const getBuses = async () => { - const getChunk = async (routesChunk: string[]) => { - try { - const res = await client.get('/getvehicles', { - params: { requestType: 'getvehicles', rt: routesChunk.join(',') }, - }); - - if ( - 'bustime-response' in res.data && - 'vehicle' in res.data['bustime-response'] - ) { - return res.data['bustime-response']['vehicle']; - } - - return []; - } catch (error) { - console.warn('getChunk failed for routes', routesChunk, error instanceof Error ? error.message : error); - return []; - } - }; - - const chunks = [] - for (let i = 0; i < routes.length; i += 10) { - chunks.push(routes.slice(i, i + 10)); - } - - let buses = await Promise.all(chunks.map(getChunk)); - buses = buses.flat(); - - return buses; -} - -const updateBusPositions = async () => { - curBusPositions.buses = await getBuses(); -} - -const addToCachedRoutes = async (rt: string) => { - try { - const res = await client.get('/getpatterns', { - params: { - requestType: 'getpatterns', - rtpidatafeed: 'bustime', - rt: rt - } - }); - - if (res.data['bustime-response'] && res.data['bustime-response']['ptr']) { - cachedRoutes[rt] = res.data['bustime-response']['ptr']; - } - } catch (e) { - console.log(`Error while getting routes: ${e}`); - } -} - -const getSelectableRoutes = () => { - axios.get(`https://mbus.ltp.umich.edu/bustime/api/v3/getroutes?requestType=getroutes&locale=en&key=${API_KEY}&format=json`).then(res => { - curRouteSelections = res.data; - validRoutes.clear(); - try { - res.data['bustime-response']['routes'].forEach((e: Route) => { - validRoutes.add(e['rt']); - addToCachedRoutes(e['rt']); - }); - } catch (e) { - if (res.data['bustime-response'].error !== undefined) { - console.log(res.data['bustime-response'].error); - } - console.log(`Failed to parse valid routes: ${e}`); - } - }) - .catch((err) => console.log(`Error while getting selectable routes: ${err}`)) - .finally(async () => { - // Update transfers - try { - if (!cachedGraph) { - cachedGraph = { - trips: [], - transfers: {}, - interchange: {} - }; - } - // Rebuild stop locations cache from cached routes - cachedStopLocations = {}; - console.log("Caching Transfers..") - Object.values(cachedRoutes).forEach((routePatterns: any) => { - if (Array.isArray(routePatterns)) { - routePatterns.forEach((pattern: any) => { - if (pattern.pt && Array.isArray(pattern.pt)) { - pattern.pt.forEach((point: any) => { - if (point.stpid && point.lat && point.lon) { - cachedStopLocations[point.stpid] = { - name: point.stpnm, - lat: parseFloat(point.lat), - lon: parseFloat(point.lon) - }; - } - }); - } - }); - } - }); - - console.log(`Number of stop locations: ${Object.keys(cachedStopLocations).length}`); - - const WALKING_SPEED_KMH = 4; - const WALKING_SPEED_MS = WALKING_SPEED_KMH * 1000 / 3600; // Convert to m/s - - const routeStops = new Set(); - Object.values(cachedRoutes).forEach((routePatterns: any) => { - if (Array.isArray(routePatterns)) { - routePatterns.forEach((pattern: any) => { - if (pattern.pt && Array.isArray(pattern.pt)) { - pattern.pt.forEach((point: any) => { - if (point.stpid) { - routeStops.add(point.stpid); - } - }); - } - }); - } - }); - - routeStops.forEach(stopId => { - cachedGraph.transfers[stopId] = []; - }); - - routeStops.forEach(stopId => { - if (!cachedGraph.interchange[stopId]) { - cachedGraph.interchange[stopId] = 60; // 1 minute interchange time - } - }); - - // Create transfers between all stops - routeStops.forEach(stopId => { - routeStops.forEach(otherStopId => { - if (stopId !== otherStopId) { - const stop1 = cachedStopLocations[stopId]; - const stop2 = cachedStopLocations[otherStopId]; - - let transferDuration: number; - - if (stop1 && stop2) { - // Compute diff with lat and lon - const latDiff = (stop2.lat - stop1.lat) * 111320; - const lonDiff = (stop2.lon - stop1.lon) * 111320 * Math.cos(stop1.lat * Math.PI / 180); - const distance = Math.sqrt(latDiff * latDiff + lonDiff * lonDiff); - let walkingTimeSeconds = distance / WALKING_SPEED_MS; - // if (distance > 1200) { - // walkingTimeSeconds *= 1.5; // penatly for too big distances - // } - transferDuration = Math.round(walkingTimeSeconds); - } else { - console.log('Invalid stop'); - transferDuration = 60000; - } - const existingTransfer = cachedGraph.transfers[stopId].find(t => t.destination === otherStopId); - - if (existingTransfer) { - existingTransfer.duration = transferDuration; - } else { - const transfer: Transfer = { - origin: stopId, - destination: otherStopId, - duration: transferDuration, - startTime: 0, - endTime: Number.MAX_SAFE_INTEGER - }; - cachedGraph.transfers[stopId].push(transfer); - } - } - }); - }); - - const totalTransfers = Object.values(cachedGraph.transfers).reduce((total, transfers) => total + transfers.length, 0); - console.log(`Total transfers in cachedGraph: ${totalTransfers}`); - } catch (error) { - console.error('Error updating transfers:', error); - } - }); -} - -export { - curBusPositions, - cachedRoutes, - cachedPredsByVid, - cachedPredsByStopId, - validRoutes, - curRouteSelections, - routes, - cachedStopLocations, - routeTimingCache, - cachedGraph, - stopIdToName, - tatripidToRt, - getAllBusPredictions, - updateBusPositions, - getSelectableRoutes, - rebuildGraph -}; - diff --git a/src/legacy/busService.ts b/src/legacy/busService.ts deleted file mode 100644 index bccae67..0000000 --- a/src/legacy/busService.ts +++ /dev/null @@ -1,751 +0,0 @@ -import * as process from "node:process"; -import * as fs from 'fs'; -import * as path from 'path'; -import * as walking from '../walking/walkingMap'; - -import axios from 'axios'; -import dotenv from "dotenv"; -import { Route } from "@/types"; -import { - Trip, - StopTime, - Transfer, - StopID, - TransfersByOrigin, - Interchange -} from "../raptor/types"; - -import { writeFileSync, readFileSync, existsSync } from "fs"; - -dotenv.config(); - -const API_KEY = process.env.MBUS_API_KEY; -if (API_KEY === undefined) { - throw new Error("MBus API key not set."); -} - -const curBusPositions: { - buses: any[] -} = { - "buses": [] -} - -const cachedRoutes: { [k: string]: any } = {}; -type Prediction = { vid: string; stpid: string } & Record; - -let cachedPredsByVid: Record = {}; -let cachedPredsByStopId: Record = {}; -// routeTimingCache: route -> fromStop -> toStop -> latest diff (minutes) -const routeTimingCache: Record>> = -{ - "CN": { - "N434NORTHBOUND": { - "N500": { - "diff": 5, - "rtdir": "SOUTHBOUND", - "rtNext": "CS" - } - }, - }, - "CS": { - "S002SOUTHBOUND": { - "S001": { - "diff": 5, - "rtdir": "NORTHBOUND", - "rtNext": "CN" - } - } - } -}; - -const validRoutes = new Set(); -let curRouteSelections = {}; -const routes = ["BB", "CN", "CS", "CSX", "DD", "MX", "NE", "NW", "NX", "OS", "NES", "WS", "WX"]; -let cachedStopLocations: { [stopId: string]: { name: string, lat: number, lon: number } } = { - -}; - -const message = { id: "gradamatation", title: "Congrats Grads 🥳", message: "Congrats to everyone who is gradamatating! Enjoy some grad hats on the buses, and don't forget to celebrate!", buildVersion: '99' } - -let cachedGraph: { - trips: Trip[]; - transfers: TransfersByOrigin; - interchange: Interchange; -} - -let stopIdToName: Record = {}; -let tatripidToRt: Record = {}; - -const sortStopTimesByRouteSequence = (stopTimes: StopTime[]): StopTime[] => { - if (stopTimes.length <= 1) return stopTimes; - - // Sort by arrival time - return stopTimes.sort((a, b) => a.arrivalTime - b.arrivalTime); -}; - -const rebuildGraph = async () => { - if (process.env.DEV_CACHE === 'true') { - if (cachedGraph && cachedGraph.trips && cachedGraph.trips.length > 0) return; - try { - const filePath = path.resolve(process.cwd(), 'saved_graph.json'); - if (fs.existsSync(filePath)) { - const data = JSON.parse(fs.readFileSync(filePath, 'utf8')); - cachedGraph = data.graph; - cachedStopLocations = data.stopLocations; - stopIdToName = data.stopNames; - cachedPredsByVid = data.predsByVid || {}; - cachedPredsByStopId = data.predsByStopId || {}; - console.log('Loaded graph and state from saved_graph.json'); - } else { - console.warn('DEV_CACHE set but saved_graph.json not found'); - } - } catch (err) { - console.error('Error loading cached graph:', err); - } - return; - } - try { - const predictions = await getAllBusPredictions(); - if (!predictions || predictions.length === 0) { - return; - } - - // Build stopIdToName and tatripidToRt maps - stopIdToName = {}; - tatripidToRt = {}; - predictions.forEach((trip: any) => { - if (trip.tatripid && trip.stops && trip.stops.length > 0) { - // Find first stop with rt - const firstStopWithRt = trip.stops.find((stop: any) => stop.rt); - if (firstStopWithRt && firstStopWithRt.rt) { - tatripidToRt[trip.tatripid] = firstStopWithRt.rt; - } - } - trip.stops.forEach((stop: any) => { - if (stop.stpid && stop.stpnm) { - stopIdToName[stop.stpid] = stop.stpnm; - } - }); - }); - - const now = new Date(); - const currentTime = now.getUTCHours() * 3600 + now.getUTCMinutes() * 60 + now.getUTCSeconds(); - - const transfers = cachedGraph?.transfers || {}; - const interchange = cachedGraph?.interchange || {}; - - const allStops = new Set(); - predictions.forEach((trip: any) => { - trip.stops.forEach((stop: any) => { - allStops.add(stop.stpid); - }); - }); - - allStops.forEach(stopId => { - if (!transfers[stopId]) { - transfers[stopId] = []; - } - if (!interchange[stopId]) { - interchange[stopId] = 30; // 30 seconds interchange time - } - }); - - const stopPredictions: Record = {}; - predictions.forEach((trip: any) => { - trip.stops.forEach((stop: any) => { - if (!stopPredictions[stop.stpid]) { - stopPredictions[stop.stpid] = []; - } - stopPredictions[stop.stpid].push({ - stpid: stop.stpid, - prdctdn: stop.prdctdn, - tatripid: trip.tatripid - }); - }); - }); - - const trips: Trip[] = []; - interface TripPrediction { - vid: string; - stops: { - stpid: string; - prdctdn: string; - rt: string; - }[]; - } - - const tripPredictions: Record = {}; - - predictions.forEach((trip: any) => { - if (!tripPredictions[trip.tatripid]) { - tripPredictions[trip.tatripid] = { - vid: trip.vid, - stops: [] - }; - } - trip.stops.forEach((stop: any) => { - tripPredictions[trip.tatripid].stops.push({ - stpid: stop.stpid, - prdctdn: stop.prdctdn, - rt: stop.rt - }); - }); - }); - - Object.entries(tripPredictions).forEach(([tripId, preds]) => { - // Create stop times with prediction times - const stopTimes: StopTime[] = preds.stops.map(pred => ({ - stop: pred.stpid, - arrivalTime: currentTime + (parseInt(pred.prdctdn) * 60), - departureTime: currentTime + (parseInt(pred.prdctdn) * 60), - pickUp: true, - dropOff: true, - rt: pred.rt - })); - - // Sort stop times by their sequence in the route - const sortedStopTimes = sortStopTimesByRouteSequence(stopTimes); - - trips.push({ - tripId, - vid: preds.vid, - stopTimes: sortedStopTimes - }); - }); - - - cachedGraph = { - trips, - transfers, - interchange - }; - - // Add virtual stops and their interchange - const originStopId = 'VIRTUAL_ORIGIN'; - const destStopId = 'VIRTUAL_DESTINATION'; - cachedGraph.transfers[originStopId] = []; - cachedGraph.transfers[destStopId] = []; - cachedGraph.interchange[originStopId] = 30; - cachedGraph.interchange[destStopId] = 30; - const virtualOriginTrip = { - tripId: 'VIRTUAL_ORIGIN_TRIP', - vid: null, - stopTimes: [{ - stop: originStopId, - arrivalTime: 0, - departureTime: 0, - pickUp: true, - dropOff: true - }] - }; - const virtualDestTrip = { - tripId: 'VIRTUAL_DESTINATION_TRIP', - vid: null, - stopTimes: [{ - stop: destStopId, - arrivalTime: 0, - departureTime: 0, - pickUp: true, - dropOff: true - }] - }; - cachedGraph.trips.push(virtualOriginTrip); - cachedGraph.trips.push(virtualDestTrip); - - } catch (error) { - console.error('Error rebuilding graph:', error); - } -}; - -const getAllBusPredictions = async () => { - try { - // Get all unique stop IDs from cached routes - const allStopIds = new Set(); - Object.values(cachedRoutes).forEach((routePatterns: any) => { - if (Array.isArray(routePatterns)) { - routePatterns.forEach((pattern: any) => { - if (pattern.pt && Array.isArray(pattern.pt)) { - pattern.pt.forEach((point: any) => { - if (point.stpid) { - allStopIds.add(point.stpid); - } - }); - } - }); - } - }); - - const stopIdsArray = Array.from(allStopIds); - const chunks = []; - - if (process.env.DEV_CACHE === 'true') { - return []; - } - - for (let i = 0; i < stopIdsArray.length; i += 10) { - chunks.push(stopIdsArray.slice(i, i + 10)); - } - - let formattedPredictions = []; - - - cachedPredsByVid = {}; - cachedPredsByStopId = {}; - - if (process.env.DEV === 'true') { - // In DEV mode, load static dummy data instead of fetching from the live API - try { - const dummyDataPath = path.resolve(process.cwd(), 'dummy_bus_data.json'); - const dummyData = fs.readFileSync(dummyDataPath, 'utf8'); - formattedPredictions = JSON.parse(dummyData); - - // Populate internal caches from the loaded dummy data - formattedPredictions.forEach((trip: any) => { - const vid = trip.vid; - trip.stops.forEach((stop: any) => { - const stpid = stop.stpid; - const prd: Prediction = { - tmstmp: new Date().toISOString(), - typ: "A", - stpnm: stop.stpnm, - stpid: stop.stpid, - vid: vid, - dstp: 0, - rt: stop.rt, - rtdd: stop.rt, - rtdir: stop.rtdir, - des: "", - prdctdn: stop.prdctdn, - tablockid: "", - tatripid: trip.tatripid, - dly: false, - prdctm: "", - zone: "" - }; - - if (!cachedPredsByStopId[stpid]) { - cachedPredsByStopId[stpid] = []; - } - cachedPredsByStopId[stpid].push(prd); - - if (vid) { - if (!cachedPredsByVid[vid]) { - cachedPredsByVid[vid] = []; - } - cachedPredsByVid[vid].push(prd); - } - }); - }); - - } catch (err) { - console.error('Error loading dummy bus data:', err); - formattedPredictions = []; - } - } else { - const predictions = await Promise.all( - chunks.map(async (chunk) => { - const stopIds = chunk.join(','); - const response = await axios.get(`https://mbus.ltp.umich.edu/bustime/api/v3/getpredictions`, { - params: { - requestType: 'getpredictions', - locale: 'en', - stpid: stopIds, - rt: routes.join(','), - tmres: 's', - rtpidatafeed: 'bustime', - key: API_KEY, - format: 'json' - } - }); - return response.data; - }) - ); - - formattedPredictions = predictions.flat().reduce((acc, predictionChunk) => { - if (predictionChunk['bustime-response'] && predictionChunk['bustime-response']['prd']) { - predictionChunk['bustime-response']['prd'].forEach((prd: any) => { - const tatripid = prd.tatripid; - const stopName = prd.stpnm; - const stopId = prd.stpid; - const rt = prd.rt; - const rtdir = prd.rtdir; - const vid = prd.vid; - let prdctdn = prd.prdctdn; - prdctdn = prdctdn === "DUE" ? "1" : prdctdn; - - let trip = acc.find((t: any) => t.tatripid === tatripid); - - if (!trip) { - if (vid) { - trip = acc.find((t: any) => t.vid === vid); - } - } - - if (!trip) { - trip = { tatripid, vid, stops: [] }; - acc.push(trip); - } else { - if (!trip.tatripid) { - trip.tatripid = tatripid; - } - if (!trip.vid && vid) { - trip.vid = vid; - } - } - - let stop = trip.stops.find((s: any) => s.stpnm === stopName && s.stpid === stopId); - if (!stop) { - stop = { stpnm: stopName, stpid: stopId, prdctdn: null, rt: null, rtdir: null }; - trip.stops.push(stop); - } - stop.rtdir = rtdir; - stop.rt = rt; - stop.prdctdn = prdctdn; - }); - } - return acc; - }, []); - - predictions.flat().forEach((predictionChunk) => { - const prds = predictionChunk['bustime-response']?.['prd']; - if (!prds) return; - - prds.forEach((prd: Prediction) => { - const { vid, stpid } = prd; - if (!cachedPredsByStopId[stpid]) { - cachedPredsByStopId[stpid] = []; - } - cachedPredsByStopId[stpid].push(prd); - - if (!vid) return; - if (!cachedPredsByVid[vid]) { - cachedPredsByVid[vid] = []; - } - cachedPredsByVid[vid].push(prd); - - }); - }); - } - - - // Cache predictions per route in routeTimingCache for extrapolation - - // Record of stop ids in order using routes - const routeInfoFilter: Record = {}; - for (const [routeName, routeList] of Object.entries(cachedRoutes as Record)) { - for (const route of routeList) { - const rtdir = route.rtdir; - const routeKey = routeName + rtdir; - if (!routeInfoFilter[routeKey]) { - routeInfoFilter[routeKey] = []; - } - for (const point of route.pt) { - if (point.typ !== "W" && point.stpid) { - routeInfoFilter[routeKey].push({ stpid: point.stpid, rtdir }); - } - } - } - } - - const stopIdToName: Record = {}; - formattedPredictions.forEach((trip: any) => { - trip.stops.forEach((stop: any) => { - if (stop.stpid && stop.stpnm) stopIdToName[stop.stpid] = stop.stpnm; - }); - }); - - // Create indices for route -> stop order - const routeStopIndexMaps = new Map>(); - for (const [routeId, stopOrder] of Object.entries(routeInfoFilter)) { - const stopIndexMap = new Map(stopOrder.map(({ stpid }, i) => [stpid, i])); - routeStopIndexMaps.set(routeId, stopIndexMap); - } - - formattedPredictions.forEach((trip: any) => { - if (trip.stops.length == 0) return; - const minPrdctdn = Math.min(...trip.stops.map((s: any) => parseInt(s.prdctdn, 10))); - const firstRoute = trip.stops.find((s: any) => parseInt(s.prdctdn, 10) === minPrdctdn)?.rt; - - if (!firstRoute) return; - // Sort by predicted time - trip.stops.sort((a: any, b: any) => { - const diffTime = parseInt(a.prdctdn, 10) - parseInt(b.prdctdn, 10); - if (diffTime !== 0) return diffTime; // primary sort - - // If not same route, put first route in front - if (a.rt + a.rtdir !== b.rt + b.rtdir) { - if (a.rt === firstRoute) return -1; - if (b.rt === firstRoute) return 1; - - return a.rt.localeCompare(b.rt); - } - // If same route, sort by stop order - const aMap = routeStopIndexMaps.get(a.rt + a.rtdir); - const bMap = routeStopIndexMaps.get(b.rt + b.rtdir); - - const aIdx = aMap?.get(a.stpid) ?? Number.MAX_SAFE_INTEGER; - const bIdx = bMap?.get(b.stpid) ?? Number.MAX_SAFE_INTEGER; - return aIdx - bIdx; - }); - // Create edges based on sorted order - for (let i = 0; i < trip.stops.length - 1; i++) { - const from = trip.stops[i]; - const to = trip.stops[i + 1]; - const diff = parseInt(to.prdctdn, 10) - parseInt(from.prdctdn, 10); - const rt = from.rt; - - const stopIndexMap = routeStopIndexMaps.get(from.rt + from.rtdir); - if (!stopIndexMap) continue; - - const fromIdx = stopIndexMap.get(from.stpid); - const toIdx = stopIndexMap.get(to.stpid); - // Ensure valid follow up stop by idx or end of idx - const isValidFollowUp = ( - fromIdx !== undefined && - toIdx !== undefined && - (toIdx === fromIdx + 1 || fromIdx === stopIndexMap.size - 1) - ); - if (!isValidFollowUp) continue; - - if (!routeTimingCache[rt]) routeTimingCache[rt] = {}; - const fromKey = from.stpid + (from.rtdir || ""); - if (!routeTimingCache[rt][fromKey]) routeTimingCache[rt][fromKey] = {}; - routeTimingCache[rt][fromKey][to.stpid] = { - diff: diff, - rtdir: to.rtdir, - rtNext: to.rt - }; - } - }); - - // Extrapolate future stops based on routeTimingCache - formattedPredictions.forEach((trip: any) => { - let stopsAdded = 0; - while (stopsAdded < 20 && trip.stops.length > 0) { - const lastStop = trip.stops[trip.stops.length - 1]; - const rt = lastStop.rt; - if (!rt) break; - - const fromKey = lastStop.stpid + (lastStop.rtdir || ""); - const nextStops = routeTimingCache[rt]?.[fromKey]; - if (!nextStops) break; - - const nextEntries = Object.entries(nextStops); - if (nextEntries.length === 0) break; - - const [nextStopId, { diff, rtdir, rtNext }] = nextEntries[0]; - const nextPrdctdn = (parseInt(lastStop.prdctdn, 10) + diff).toString(); - - trip.stops.push({ - stpnm: stopIdToName[nextStopId] || nextStopId, - stpid: nextStopId, - prdctdn: nextPrdctdn, - rt: rtNext, - rtdir: rtdir - }); - stopsAdded++; - } - }); - - return formattedPredictions; - } catch (err: unknown) { - const message = err instanceof Error ? err.message : String(err); - console.error("Error in getAllBusPredictions:", message); - return []; - } -}; - -const client = axios.create({ - baseURL: 'https://mbus.ltp.umich.edu/bustime/api/v3/', - params: { - key: API_KEY, - format: 'json' - } -}); - -const getBuses = async () => { - const getChunk = async (routesChunk: string[]) => { - try { - const res = await client.get('/getvehicles', { - params: { requestType: 'getvehicles', rt: routesChunk.join(',') }, - }); - - if ( - 'bustime-response' in res.data && - 'vehicle' in res.data['bustime-response'] - ) { - return res.data['bustime-response']['vehicle']; - } - - return []; - } catch (error) { - console.warn('getChunk failed for routes', routesChunk, error instanceof Error ? error.message : error); - return []; - } - }; - - const chunks = [] - for (let i = 0; i < routes.length; i += 10) { - chunks.push(routes.slice(i, i + 10)); - } - - let buses = await Promise.all(chunks.map(getChunk)); - buses = buses.flat(); - - return buses; -} - -const updateBusPositions = async () => { - curBusPositions.buses = await getBuses(); -} - -const addToCachedRoutes = async (rt: string) => { - try { - const res = await client.get('/getpatterns', { - params: { - requestType: 'getpatterns', - rtpidatafeed: 'bustime', - rt: rt - } - }); - - - if (res.data['bustime-response'] && res.data['bustime-response']['ptr']) { - cachedRoutes[rt] = res.data['bustime-response']['ptr']; - } - } catch (e) { - console.log(`Error while getting routes: ${e}`); - } -} - -const getSelectableRoutes = () => { - if (process.env.DEV_CACHE === 'true') return; - - axios.get(`https://mbus.ltp.umich.edu/bustime/api/v3/getroutes?requestType=getroutes&locale=en&key=${API_KEY}&format=json`).then(async res => { - curRouteSelections = res.data; - validRoutes.clear(); - try { - const promises: Promise[] = []; - res.data['bustime-response']['routes'].forEach((e: Route) => { - validRoutes.add(e['rt']); - promises.push(addToCachedRoutes(e['rt'])); - }); - await Promise.all(promises); - } catch (e) { - if (res.data['bustime-response'].error !== undefined) { - console.log(res.data['bustime-response'].error); - } - console.log(`Failed to parse valid routes: ${e}`); - } - }) - .catch((err) => console.log(`Error while getting selectable routes: ${err}`)) - .finally(async () => { - // Update transfers - try { - if (!cachedGraph) { - cachedGraph = { - trips: [], - transfers: {}, - interchange: {} - }; - } - // Rebuild stop locations cache from cached routes - cachedStopLocations = {}; - console.log("Caching Transfers..") - Object.values(cachedRoutes).forEach((routePatterns: any) => { - if (Array.isArray(routePatterns)) { - routePatterns.forEach((pattern: any) => { - if (pattern.pt && Array.isArray(pattern.pt)) { - pattern.pt.forEach((point: any) => { - if (point.stpid && point.lat && point.lon) { - cachedStopLocations[point.stpid] = { - name: point.stpnm, - lat: parseFloat(point.lat), - lon: parseFloat(point.lon) - }; - } - }); - } - }); - } - }); - - console.log(`Number of stop locations: ${Object.keys(cachedStopLocations).length}`); - walking.buildStopNodeMap(cachedStopLocations); - - const routeStops = new Set(); - Object.values(cachedRoutes).forEach((routePatterns: any) => { - if (Array.isArray(routePatterns)) { - routePatterns.forEach((pattern: any) => { - if (pattern.pt && Array.isArray(pattern.pt)) { - pattern.pt.forEach((point: any) => { - if (point.stpid) { - routeStops.add(point.stpid); - } - }); - } - }); - } - }); - - routeStops.forEach(stopId => { - cachedGraph.transfers[stopId] = []; - }); - - routeStops.forEach(stopId => { - if (!cachedGraph.interchange[stopId]) { - cachedGraph.interchange[stopId] = 30; // 30 seconds interchange time - } - }); - - // Create transfers between all stops - await walking.ensureCacheForStops(routeStops, cachedStopLocations); - - routeStops.forEach(stopId => { - routeStops.forEach(otherStopId => { - if (stopId !== otherStopId) { - - const cachedData = walking.getCachedWalk(stopId, otherStopId); - - if (cachedData) { - cachedGraph.transfers[stopId].push({ - origin: stopId, - destination: otherStopId, - duration: cachedData.duration, - startTime: 0, - endTime: Number.MAX_SAFE_INTEGER - }); - } else { - console.warn(`Unexpected missing walk data: ${stopId} -> ${otherStopId}`); - } - } - }); - }); - - const totalTransfers = Object.values(cachedGraph.transfers).reduce((total, t) => total + t.length, 0); - console.log(`Total transfers in cachedGraph: ${totalTransfers}`); - - } catch (error) { - console.error('Error updating transfers:', error); - } - }); -} - -export { - curBusPositions, - cachedRoutes, - cachedPredsByVid, - cachedPredsByStopId, - validRoutes, - curRouteSelections, - routes, - cachedStopLocations, - routeTimingCache, - cachedGraph, - stopIdToName, - tatripidToRt, - getAllBusPredictions, - updateBusPositions, - getSelectableRoutes, - rebuildGraph -}; diff --git a/src/legacy/reminderService-reminders.ts b/src/legacy/reminderService-reminders.ts deleted file mode 100644 index b997fcd..0000000 --- a/src/legacy/reminderService-reminders.ts +++ /dev/null @@ -1,240 +0,0 @@ -import dotenv from "dotenv"; -import { cachedPredsByStopId, stopIdToName } from "./busService"; -import { getMessaging } from "firebase-admin/messaging"; -import { applicationDefault, initializeApp } from "firebase-admin/app"; - -dotenv.config() - -// Initialize Firebase -initializeApp({ credential: applicationDefault() }); - -type Event = { - stpid: string, - rtid: string, - readonly __brand: "event" -} - -type RegistrationToken = string & { readonly __brand: "registration_token" } -type EventKey = string & { readonly __brand: "event_key" } - -function encodeEvent(e: Event): EventKey { - return `${e.stpid}|${e.rtid}` as EventKey; -} - -function decodeEvent(e: EventKey): Event { - const split = e.split('|'); - return { stpid: split[0], rtid: split[1] } as Event; -} - -// Subscriptions go through a pipeline, starting in the waiting for reminder -// stage. After the bus in x minutes notification is set they move to the -// waiting for bus stage, where they stay until the bus is arriving notification -// is sent. Being in the second stage is repsented by having a threshold of null. -class ReminderSubscriptions { - // a thresh of null means being in the second stage - subscriptions: Array<{ event: Event, thresh: number | null, token: RegistrationToken }> - - constructor() { - this.subscriptions = []; - } - - // addition is done to the start of the pipeline - add(event: Event, thresh: number, token: RegistrationToken) { - console.log("Adding a reminder subscription"); - this.subscriptions.push({ event, thresh, token }); - } - - // removes all subscriptions involving both `event` and `token` - remove(event: Event, token: RegistrationToken) { - console.log("Removing a reminder subscription"); - this.subscriptions = this.subscriptions - .filter((s) => s.event.rtid !== event.rtid || s.event.stpid !== event.stpid || s.token !== token); - } - - // updates the status of all registrations, returning an object representing the - // notifications that should be sent - process(arrivalTimes: Map): { - reminder: Map>, - atTheStop: Map>, - disappeared: Map>, - delayed: Map> - } { - const addHelper = (map: Map>, key: EventKey, token: RegistrationToken) => { - let tokens = map.get(key); - if (tokens === undefined) { - map.set(key, new Set()); - tokens = map.get(key)!; - } - tokens.add(token); - }; - const notifications = { - reminder: new Map(), atTheStop: new Map(), disappeared: new Map(), delayed: new Map() - }; - const newSubscriptions: typeof this.subscriptions = []; - for (const subscription of this.subscriptions) { - const key = encodeEvent(subscription.event); - const arrivalTime = arrivalTimes.get(key); - if (arrivalTime === undefined || arrivalTime.curr === null) { - addHelper(notifications.disappeared, key, subscription.token); - } else if (subscription.thresh === null && arrivalTime.curr === 0) { - addHelper(notifications.atTheStop, key, subscription.token); - } else if (arrivalTime.prev !== null && arrivalTime.curr > arrivalTime.prev) { - addHelper(notifications.delayed, key, subscription.token); - newSubscriptions.push(subscription); // keep subscription if delayed - } else if (subscription.thresh !== null && arrivalTime.prev !== null - && arrivalTime.curr <= subscription.thresh && arrivalTime.curr < arrivalTime.prev) { - addHelper(notifications.reminder, key, subscription.token); - // replace with next in pipeline, a subscription to bus at stop - newSubscriptions.push({ event: subscription.event, thresh: null, token: subscription.token }); - } else { - newSubscriptions.push(subscription); // keep subscription by default - } - } - this.subscriptions = newSubscriptions; - console.log(`Process completed with ${notifications.reminder.size}, ${notifications.atTheStop.size}, ${notifications.delayed.size}, ${notifications.disappeared.size}`); - return notifications; - } - - // removes all subscriptions involving `event` - removeAllFor(event: Event) { - this.subscriptions = this.subscriptions - .filter((s) => s.event.rtid !== event.rtid || s.event.stpid !== event.stpid); - } - - swapToken(from: RegistrationToken, to: RegistrationToken) { - this.subscriptions = this.subscriptions.map((s) => { - if (s.token === from) { - return { ...s, token: to }; - } else { - return s; - } - }); - } - - activeRemindersFor(id: RegistrationToken): Array<{ stpid: string, rtid: string, thresh: number | null }> { - return this.subscriptions - .filter((s) => s.token === id) - .map((s) => { - return {stpid: s.event.stpid, rtid: s.event.rtid, thresh: s.thresh }; - }); - } - - describe() { - console.log(`There are ${this.subscriptions.length} reminder subscriptions`); - } -} - -const reminderSubscriptions = new ReminderSubscriptions(); -const arrivalTimes: Map = new Map(); - -function processReminders() { - // move current arrival times to prev - for (const [_k, v] of arrivalTimes) { - v.prev = v.curr; - v.curr = null; - } - - const stpids = Object.keys(cachedPredsByStopId); - // determine arrival time updates for each stop - for (const stpid of stpids) { - for (const vehicle of cachedPredsByStopId[stpid]) { - const rtid = vehicle.rt; - const prediction = vehicle.prdctdn === 'DUE' ? 0 : parseInt(vehicle.prdctdn); - const key = encodeEvent({ stpid: stpid, rtid: rtid } as Event); - let time = arrivalTimes.get(key); - if (time === undefined) { - arrivalTimes.set(key, { curr: null, prev: null }); - time = arrivalTimes.get(key)!; - } - if (time.curr === null || prediction < time.curr) - time.curr = prediction; - } - } - - // send push notifications as needed - const notifications = reminderSubscriptions.process(arrivalTimes); - for (const [eventKey, tokens] of notifications.reminder) { - const event = decodeEvent(eventKey); - const stopName = stopIdToName[event.stpid] ?? event.stpid; - sendToAll( - { - notification: { - title: 'Bus Arrival Reminder', - body: `${event.rtid} is ${arrivalTimes.get(eventKey)?.curr} minute(s) away from ${stopName}` - } - }, - tokens - ); - } - for (const [eventKey, tokens] of notifications.atTheStop) { - const event = decodeEvent(eventKey); - const stopName = stopIdToName[event.stpid] ?? event.stpid; - sendToAll( - { - notification: { title: 'Bus Arriving', body: `${event.rtid} is almost at ${stopName}` }, - }, - tokens - ); - } - for (const [eventKey, tokens] of notifications.delayed) { - const event = decodeEvent(eventKey); - const stopName = stopIdToName[event.stpid] ?? event.stpid; - const arrivalTime = arrivalTimes.get(eventKey); - const delay = arrivalTime?.curr !== null && arrivalTime?.prev ? `${arrivalTime.curr - arrivalTime.prev}` : `some`; - sendToAll( - { - notification: { - title: `Bus Delayed`, - body: `The ${event.rtid} bus en route to ${stopName} got delayed by ${delay} minute(s).` - } - }, - tokens - ); - } - for (const [eventKey, tokens] of notifications.disappeared) { - const event = decodeEvent(eventKey); - const stopName = stopIdToName[event.stpid] ?? event.stpid; - sendToAll( - { - notification: { - title: `Bus Disappeared`, - body: `The ${event.rtid} bus en route to ${stopName} disappeared! Set a new reminder if desired.` - } - }, - tokens - ); - } - -} - -function sendToAll(msg: any, tokens: Set) { - console.log(`sending a message to ${tokens.size} devices`); - const group = new Set(); - for (const token of tokens) { - if (group.size === 500) { - sendToAllHelper(msg, group); - group.clear(); - } - group.add(token); - } - sendToAllHelper(msg, group); -} - -// REQUIRES: tokens.size <= 500 -function sendToAllHelper(msg: any, tokens: Set) { - const payload = { tokens: Array.from(tokens), ...msg }; - getMessaging().sendEachForMulticast(payload) - .then((res) => { - if (res.failureCount > 0) { - console.log(`${res.failureCount} messages failed to send!`); - res.responses.forEach((res, idx) => { - if (!res.success) { - console.log(`message send ${idx} failed`); - console.log(res.error); - } - }) - } - }); -} - -export { processReminders, reminderSubscriptions, Event, RegistrationToken }; diff --git a/src/legacy/v3-reminders.ts b/src/legacy/v3-reminders.ts deleted file mode 100644 index b01d078..0000000 --- a/src/legacy/v3-reminders.ts +++ /dev/null @@ -1,660 +0,0 @@ -import express from "express"; -import dotenv from "dotenv"; -import { - Transfer, - StopID, - TimetableLeg -} from "./raptor/types"; -import { RaptorAlgorithm } from "./raptor/RaptorAlgorithm"; -import { RaptorAlgorithmFactory } from "./raptor/RaptorAlgorithmFactory"; -import { DepartAfterQuery } from "./query/DepartAfterQuery"; -import { JourneyFactory } from "./results/JourneyFactory"; -import { earliestArrival, leastChanges, leastWalking } from "./results/filter/MultipleCriteriaFilter"; - -import * as metadata from "./assets/route-data.json"; -import * as valid_assets from "./assets/valid_assets.json"; -import * as path from "node:path"; -import { MaxPriorityQueue } from '@datastructures-js/priority-queue'; - - -import { - curBusPositions, - cachedPredsByStopId, - cachedRoutes, - cachedPredsByVid, - cachedStopLocations, - curRouteSelections, - stopIdToName, - tatripidToRt, - getAllBusPredictions, - routeTimingCache, - cachedGraph, - updateBusPositions, - getSelectableRoutes, - rebuildGraph -} from './busService'; -import axios from "axios"; - -// Simple Bus Color System -interface BusRoute { - routeId: string; - color: string; - image: string; -} - -class BusColorManager { - private readonly routes: BusRoute[] = [ - { routeId: "BB", color: "#2F773F", image: "bus_BB.png" }, - { routeId: "CN", color: "#643076", image: "bus_CN.png" }, - { routeId: "CS", color: "#3559B8", image: "bus_CS.png" }, - { routeId: "CSX", color: "#1C2256", image: "bus_CSX.png" }, - { routeId: "DD", color: "#A9C534", image: "bus_DD.png" }, - { routeId: "MX", color: "#5EC7DE", image: "bus_MX.png" }, - { routeId: "NE", color: "#C55188", image: "bus_NE.png" }, - { routeId: "NW", color: "#AE3636", image: "bus_NW.png" }, - { routeId: "NX", color: "#DA4343", image: "bus_NX.png" }, - { routeId: "OS", color: "#E8A43C", image: "bus_OS.png" }, - { routeId: "NES", color: "#C55188", image: "bus_NES.png" }, - { routeId: "WS", color: "#BA5231", image: "bus_WS.png" }, - { routeId: "WX", color: "#E8663E", image: "bus_WX.png" } - ]; - - public getRouteColor(routeId: string): string | null { - const route = this.routes.find(r => r.routeId === routeId); - return route ? route.color : null; - } - - public getRouteImage(routeId: string): string | null { - const route = this.routes.find(r => r.routeId === routeId); - return route ? route.image : null; - } - - public getAllRoutes(): BusRoute[] { - return [...this.routes]; - } - - public getRouteInfo(routeId: string): BusRoute | null { - return this.routes.find(r => r.routeId === routeId) || null; - } -} - -// Initialize bus color manager -const busColorManager = new BusColorManager(); - -dotenv.config(); -const router = express.Router(); -const routeImages: { [k: string]: string } = metadata.routeImages; - -setInterval(updateBusPositions, 7500); -setInterval(getSelectableRoutes, 60000); -setInterval(rebuildGraph, 10 * 1000); -setInterval(processReminders, 10 * 1000); -setInterval(() => reminderSubscriptions.describe(), 60000); -getSelectableRoutes(); -rebuildGraph(); - -import * as process from "node:process"; -import { getMessaging } from "firebase-admin/messaging"; -import { processReminders, reminderSubscriptions, Event, RegistrationToken } from "./reminderService"; - - -dotenv.config(); - -const API_KEY = process.env.MBUS_API_KEY; -router.get('/getBusPredictions1/:busId', (req, res) => { - axios.get(`https://mbus.ltp.umich.edu/bustime/api/v3/getpredictions?requestType=getpredictions&locale=en&vid=${req.params.busId}&top=4&tmres=s&rtpidatafeed=bustime&key=${API_KEY}&format=json&xtime=1626028950462`).then(apiRes => { - res.send(apiRes.data); - }).catch(err => { - console.log(err); - res.sendStatus(500); - - }); -}); - -router.get('/getBusPositions', (req, res) => { - res.send(curBusPositions); -}); - -router.get('/getVehiclePositions', (req, res) => { - res.send(curBusPositions); -}); - -router.get('/getSelectableRoutes', (req, res) => { - res.send(curRouteSelections); -}); - -router.get('/getAllRoutes', (req, res) => { - res.send({ routes: cachedRoutes }); -}); - -router.get('/getrouteCache', (req, res) => { - res.send({ routes: routeTimingCache }); -}); - -router.get('/getVehicleImage/:route', (req, res) => { - const { route } = req.params; - - const dirname = import.meta.dirname; - const assetPath = path.join(dirname, 'assets'); - const imagePath = path.join(assetPath, 'main2025'); - - if (!route || !(route in routeImages)) { - res.sendFile(path.join(assetPath, 'bus_CN.png')); - return res.sendStatus(400); - } - - res.sendFile(path.join(imagePath, routeImages[route])); -}); - -router.get('/getRouteInfoVersion', (req, res) => { - res.send(JSON.stringify({ version: metadata.metadata.version })); -}); - -router.get('/getRouteInformation', (req, res) => { - const infoToSend = { - routeIdToName: metadata.routeIdToName, - routeImages: metadata.routeImages, - metadata: metadata.metadata, - routeColors: busColorManager.getAllRoutes().map(route => ({ - routeId: route.routeId, - color: route.color, - image: route.image - })) - } - res.send(infoToSend); -}); - -router.get('/getStartupInfo', (req, res) => { - res.json({ - // updating this will disable older versions of the app - min_supported_version: "1.0.0", - why_update_message: { - title: "New Update Available", - subtitle: "Please update to the latest version for the best experience." - }, - // adding data here will show a persistant message on launch - persistant_message: { - title: "Update on missing bus predictions", - subtitle: "2/9 9:03 PM: The university has confirmed to us that they're actively working on fixing the issue with missing bus predictions. Thank you for your patience." - }, - // adding data here will show a one-time message on launch (not yet implemented) - one_time_message: { - title: "", - subtitle: "" - }, - // updating this will make bus images redownload on frontend - bus_image_version: "1", - }); -});; - -router.get('/getBusPredictions/:busId', (req, res) => { - const busId = req.params.busId; - const preds = cachedPredsByVid[busId]; - - if (!preds) { - return res.json({ - "bustime-response": { "prd": [] } - }); - } - res.json({ - "bustime-response": { "prd": preds } - }); -}); - -router.get('/getStopPredictions/:stopId', (req, res) => { - const stopId = req.params.stopId; - const preds = cachedPredsByStopId[stopId]; - - if (!preds) { - return res.json({ - "bustime-response": { "prd": [] } - }); - } - - res.json({ - "bustime-response": { "prd": preds } - }); -}); - -router.get('/getAllPredictions', async (req, res) => { - try { - const predictions = await getAllBusPredictions(); - res.send(predictions); - } catch (err) { - console.log(err); - res.sendStatus(500); - } -}); - -router.get('/getAllStops', (req, res) => { - const stopsList = Object.entries(cachedStopLocations).map(([stpid, stopInfo]) => ({ - stpid, - ...stopInfo, - })); - res.json(Object.values(stopsList)); -}); - -router.get('/getBuildingLocations', (req, res) => { - res.sendFile(path.join(import.meta.dirname, 'assets', 'building-data.json')); -}); - -router.get('/get-startup-messages', (req, res) => { - res.send(JSON.stringify(message)); -}); - -// Simple bus color endpoints -router.get('/getRouteColors', (req, res) => { - const routes = busColorManager.getAllRoutes(); - res.json({ - routes: routes.map(route => ({ - routeId: route.routeId, - color: route.color, - image: route.image - })) - }); -}); - -router.get('/getRouteColor/:routeId', (req, res) => { - const { routeId } = req.params; - const routeInfo = busColorManager.getRouteInfo(routeId); - - if (!routeInfo) { - return res.status(404).json({ error: `Route '${routeId}' not found` }); - } - - res.json({ - routeId: routeInfo.routeId, - color: routeInfo.color, - image: routeInfo.image - }); -}); - -// Simple endpoint for frontend, gets everything needed for UI -router.get('/getFrontendData', (req, res) => { - try { - const routes = busColorManager.getAllRoutes(); - - const response = { - routes: routes.map(route => ({ - routeId: route.routeId, - name: (metadata.routeIdToName as any)[route.routeId] || route.routeId, - image: route.image, - color: route.color, - imageUrl: `/mbus/api/v3/getVehicleImage/${route.routeId}` - })), - metadata: { - ...metadata.metadata, - lastUpdated: new Date().toISOString() - } - }; - - res.json(response); - } catch (error) { - res.status(500).json({ error: 'Failed to get frontend data' }); - } -}); - - -router.get('/nearest-stops', (req, res) => { - try { - const { lat, lon, k = '2' } = req.query; - - const originLat = parseFloat(lat as string); - const originLon = parseFloat(lon as string); - const numStops = parseInt(k as string); - - if (isNaN(originLat) || isNaN(originLon)) { - return res.status(400).json({ error: 'Invalid or missing lat/lon' }); - } - if (isNaN(numStops) || numStops <= 0) { - return res.status(400).json({ error: 'Parameter k must be a positive integer' }); - } - - const heap = new MaxPriorityQueue<{ stpid: string; name: string; lat: number; lon: number; distance: number }>({ - compare: (a, b) => a.distance - b.distance - }); - - for (const [stpid, stop] of Object.entries(cachedStopLocations)) { - const latDiff = (stop.lat - originLat) * 111320; - const lonDiff = (stop.lon - originLon) * 111320 * Math.cos(originLat * Math.PI / 180); - const distance = Math.sqrt(latDiff ** 2 + lonDiff ** 2); - - const stopWithDist = { - stpid, - name: stop.name, - lat: stop.lat, - lon: stop.lon, - distance - }; - - if (heap.size() < numStops) { - heap.enqueue(stopWithDist); - } else if (distance < heap.front().distance) { - heap.dequeue(); - heap.enqueue(stopWithDist); - } - } - - const nearestStops = heap.toArray().sort((a, b) => a.distance - b.distance); - res.json({ nearestStops }); - } catch (error) { - console.error('Error in /nearest-stops:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -function optimizeWalkingFastest(journey: any, cachedGraph: any): any { - if (!journey) return journey; - - const optimizedLegs: any[] = []; - const WALKING_SPEED_KMH = 4; - const WALKING_SPEED_MS = WALKING_SPEED_KMH * 1000 / 3600; - - function distanceMeters(lat1: number, lon1: number, lat2: number, lon2: number): number { - const dx = (lat2 - lat1) * 111320; - const dy = (lon2 - lon1) * 111320 * Math.cos(lat1 * Math.PI / 180); - return Math.sqrt(dx * dx + dy * dy); - } - - for (let i = 0; i < journey.legs.length; i++) { - const leg = journey.legs[i]; - - // Look for a transfer followed by a bus trip - if (leg.trip && i > 0 && "duration" in journey.legs[i - 1]) { - const transfer = journey.legs[i - 1] as Transfer; - const tripLeg = leg as TimetableLeg; - const trip = tripLeg.trip; - - const originalBoardStop = tripLeg.stopTimes[0].stop; - const boardIdx = trip.stopTimes.findIndex(st => st.stop === originalBoardStop); - const walkStartTime = transfer.startTime; - - let bestStop = originalBoardStop; - let bestIdx = boardIdx; - let bestDistance = Number.MAX_SAFE_INTEGER; - - const originLoc = cachedStopLocations[transfer.origin]; - if (!originLoc) { - optimizedLegs.push(leg); - continue; - } - - for (let j = boardIdx; j < trip.stopTimes.length; j++) { - const candidate = trip.stopTimes[j]; - const stopLoc = cachedStopLocations[candidate.stop]; - if (!stopLoc) continue; - - const dist = distanceMeters(originLoc.lat, originLoc.lon, stopLoc.lat, stopLoc.lon); - const walkArrival = walkStartTime + dist / WALKING_SPEED_MS; - const busArrival = candidate.arrivalTime - (cachedGraph.interchange[candidate.stop] ?? 0); - - if (walkArrival <= busArrival && dist < bestDistance) { - bestStop = candidate.stop; - bestIdx = j; - bestDistance = dist; - } - } - - if (bestStop !== originalBoardStop) { - console.log(`Optimized walking to ${bestStop} instead of ${originalBoardStop}, saving ${Math.round(bestDistance)} meters`); - // Update transfer - transfer.destination = bestStop; - transfer.duration = Math.round(bestDistance / WALKING_SPEED_MS); - - // Trim trip leg to start from bestStop - tripLeg.stopTimes = trip.stopTimes.slice(bestIdx); - - // Refresh trip leg duration - if (tripLeg.stopTimes.length > 1) { - const firstStop = tripLeg.stopTimes[0]; - const lastStop = tripLeg.stopTimes[tripLeg.stopTimes.length - 1]; - (tripLeg as any).duration = lastStop.arrivalTime - firstStop.departureTime; - } - } - } - - optimizedLegs.push(leg); - } - - return { ...journey, legs: optimizedLegs }; -} - -router.get('/plan-journey', async (req, res) => { - try { - const originStopId = 'VIRTUAL_ORIGIN'; - const destStopId = 'VIRTUAL_DESTINATION'; - - const { originLat, originLon, destLat, destLon, walkingPenalty: walkingPenaltyParam } = req.query; - if (!originLat || !originLon || !destLat || !destLon) { - return res.status(400).json({ error: 'Origin and destination coordinates are required' }); - } - - const now = new Date(); - const currentTime = now.getUTCHours() * 3600 + now.getUTCMinutes() * 60 + now.getUTCSeconds(); - - if (!cachedGraph || !cachedGraph.trips || cachedGraph.trips.length === 0) { - await rebuildGraph(); // try to build - if (!cachedGraph) { - return res.status(404).json({ error: 'No routes available at this time' }); - } - } - - // Clear all transfers from the virtual origin - cachedGraph.transfers[originStopId] = []; - cachedGraph.transfers[destStopId] = []; - // Clear all transfers to virtual destination - Object.keys(cachedGraph.transfers).forEach(stopId => { - cachedGraph.transfers[stopId] = cachedGraph.transfers[stopId].filter( - t => t.destination !== destStopId - ); - }); - - // Update the times for the virtual trips - const vOriginTrip = cachedGraph.trips.find(t => t.tripId === 'VIRTUAL_ORIGIN_TRIP'); - if (vOriginTrip) { - vOriginTrip.stopTimes[0].arrivalTime = currentTime; - vOriginTrip.stopTimes[0].departureTime = currentTime; - } - const vDestTrip = cachedGraph.trips.find(t => t.tripId === 'VIRTUAL_DESTINATION_TRIP'); - if (vDestTrip) { - vDestTrip.stopTimes[0].arrivalTime = currentTime; - vDestTrip.stopTimes[0].departureTime = currentTime; - } - - // Calculate transfers from origin to all real stops - const originLatNum = parseFloat(originLat as string); - const originLonNum = parseFloat(originLon as string); - const destLatNum = parseFloat(destLat as string); - const destLonNum = parseFloat(destLon as string); - - const WALKING_SPEED_KMH = 4; - const WALKING_SPEED_MS = WALKING_SPEED_KMH * 1000 / 3600; - - // Add transfers from origin to all real stops - Object.keys(cachedStopLocations).forEach(stopId => { - const stopLocation = cachedStopLocations[stopId]; - if (stopLocation) { - const latDiff = (stopLocation.lat - originLatNum) * 111320; - const lonDiff = (stopLocation.lon - originLonNum) * 111320 * Math.cos(originLatNum * Math.PI / 180); - const distance = Math.sqrt(latDiff * latDiff + lonDiff * lonDiff); - - let walkingTimeSeconds = distance / WALKING_SPEED_MS; - const transferDuration = Math.round(walkingTimeSeconds); - - const transfer: Transfer = { - origin: originStopId, - destination: stopId, - duration: transferDuration, - startTime: currentTime, - endTime: Number.MAX_SAFE_INTEGER - }; - cachedGraph.transfers[originStopId].push(transfer); - } - }); - - // Add transfers from all real stops to destination - Object.keys(cachedStopLocations).forEach(stopId => { - const stopLocation = cachedStopLocations[stopId]; - if (stopLocation) { - const latDiff = (destLatNum - stopLocation.lat) * 111320; - const lonDiff = (destLonNum - stopLocation.lon) * 111320 * Math.cos(stopLocation.lat * Math.PI / 180); - const distance = Math.sqrt(latDiff * latDiff + lonDiff * lonDiff); - - let walkingTimeSeconds = distance / WALKING_SPEED_MS; - const transferDuration = Math.round(walkingTimeSeconds); - - const transfer: Transfer = { - origin: stopId, - destination: destStopId, - duration: transferDuration, - startTime: currentTime, - endTime: Number.MAX_SAFE_INTEGER - }; - cachedGraph.transfers[stopId].push(transfer); - } - }); - - // Direct transfer from VIRTUAL_ORIGIN to VIRTUAL_DESTINATION - const directLatDiff = (destLatNum - originLatNum) * 111320; - const directLonDiff = (destLonNum - originLonNum) * 111320 * Math.cos(originLatNum * Math.PI / 180); - const directDistance = Math.sqrt(directLatDiff * directLatDiff + directLonDiff * directLonDiff); - - let directWalkingTimeSeconds = directDistance / WALKING_SPEED_MS; - - const directTransferDuration = Math.round(directWalkingTimeSeconds); - //console.log(`Walking Distance: ${directTransferDuration}`); - - const directTransfer: Transfer = { - origin: originStopId, - destination: destStopId, - duration: directTransferDuration, - startTime: currentTime, - endTime: Number.MAX_SAFE_INTEGER - }; - cachedGraph.transfers[originStopId].push(directTransfer); - - RaptorAlgorithm.setDebug(false); - const raptor = RaptorAlgorithmFactory.create(cachedGraph.trips, cachedGraph.transfers, cachedGraph.interchange); - let walkingPenalty = 1; // default no penalty - if (walkingPenaltyParam !== undefined) { - const parsed = parseFloat(walkingPenaltyParam as string); - if (!isNaN(parsed) && parsed > 0) { - walkingPenalty = parsed; - } - } - raptor.setWalkingPenalty(walkingPenalty); - - const resultsFactory = new JourneyFactory(); - const journeyPlanner = new DepartAfterQuery(raptor, resultsFactory); - const journeys = journeyPlanner.plan( - originStopId as StopID, - destStopId as StopID, - currentTime - ); - - const formatJourney = (journey: any) => { - if (!journey) return null; - return { - ...journey, - legs: journey.legs.map((leg: any) => { - const formattedLeg: any = { - ...leg, - origin_id: leg.origin, - origin: leg.origin === 'VIRTUAL_ORIGIN' ? 'Start' : (leg.origin === 'VIRTUAL_DESTINATION' ? 'End' : (stopIdToName[leg.origin] || leg.origin)), - destination_id: leg.destination, - destination: leg.destination === 'VIRTUAL_ORIGIN' ? 'Start' : (leg.destination === 'VIRTUAL_DESTINATION' ? 'End' : (stopIdToName[leg.destination] || leg.destination)) - }; - if (leg.trip && leg.trip.tripId) { - // Add duration for bus legs - if (leg.stopTimes && leg.stopTimes.length > 0) { - const firstStop = leg.stopTimes[0]; - const lastStop = leg.stopTimes[leg.stopTimes.length - 1]; - formattedLeg.duration = lastStop.arrivalTime - firstStop.departureTime; - formattedLeg.rt = firstStop.rt; - formattedLeg.vid = leg.vid; - - } - } else if (typeof leg.duration === 'number') { - // Add duration for transfer legs - formattedLeg.duration = leg.duration; - } - return formattedLeg; - }) - }; - }; - - let fastest = journeys.length > 0 ? journeys.reduce((best, j) => earliestArrival(best, j) ? j : best, journeys[0]) : null; - // if (fastest) { - // fastest = optimizeWalkingFastest(fastest, cachedGraph); - // } - const leastTransfers = journeys.length > 0 ? journeys.reduce((best, j) => leastChanges(best, j) ? j : best, journeys[0]) : null; - const leastWalk = journeys.length > 0 ? journeys.reduce((best, j) => leastWalking(best, j) ? j : best, journeys[0]) : null; - const uniqueJourneys = [fastest, leastTransfers, leastWalk] - .filter((j, i, arr) => j && arr.findIndex(x => x === j) === i) - .map(formatJourney); - res.json({ journeys: uniqueJourneys.slice(0, 3) }); - } catch (error) { - console.error('Error planning journey:', error); - res.status(500).json({ error: 'Failed to plan journey' }); - } -}); - -// Notifications / Reminders - -// Expects {token: string, stpid: string, rtid: string, thresh: number} in the body -router.post('/setReminder', (req, res) => { - const token = req.body.token as RegistrationToken; - const stpid: string = req.body.stpid; - const rtid: string = req.body.rtid; - const thresh: number = req.body.thresh; - reminderSubscriptions.add({ stpid, rtid } as Event, thresh, token); - res.sendStatus(200); -}); - -// Expects {token: string, stpid: string, rtid: string} in the body -router.post('/unsetReminder', (req, res) => { - const token = req.body.token as RegistrationToken; - const stpid: string = req.body.stpid; - const rtid: string = req.body.rtid; - reminderSubscriptions.remove({ stpid, rtid } as Event, token); - res.sendStatus(200); -}); - -// Expects {oldTok: string, newTok: string} in the body -// Upon responding with 200, future calls to /setReminder, /unsetReminder, and /activeReminders -// will need the new token -router.post('/swapToken', (req, res) => { - const oldTok = req.body.oldTok as RegistrationToken; - const newTok = req.body.newTok as RegistrationToken; - reminderSubscriptions.swapToken(oldTok, newTok); - res.sendStatus(200); -}); - -// Responds with { reminders: Array<{ stpid: string, rtid: string, thresh: number | null }> } -router.get('/activeReminders/:registrationToken', (req, res) => { - const token = req.params.registrationToken as RegistrationToken; - res.send({ reminders: reminderSubscriptions.activeRemindersFor(token) }); - res.status(200); -}); - -// testing purposes -router.post('/notifyMeLater', (req, res) => { - console.log("got request"); - const registrationToken = req.body.token; - if (registrationToken === undefined) { - console.log("got request with no token"); - console.log(req.body); - res.send("registration token missing"); - res.status(400); - return; - } - setTimeout(() => { - console.log("sending push notification.."); - getMessaging() - .send({ notification: { title: "hi", body: "hello world!" }, token: registrationToken }) - .catch((e) => console.log("Failed to send message: ", e)); - }, 10000); - res.sendStatus(200); -}); - -export default router; diff --git a/src/legacy/v3.ts b/src/legacy/v3.ts deleted file mode 100644 index 8de143a..0000000 --- a/src/legacy/v3.ts +++ /dev/null @@ -1,561 +0,0 @@ - -import * as walking from '../walking/walkingMap'; -import * as metadata from "../assets/route-data.json"; -import * as path from "node:path"; -import * as fs from 'fs'; - -import express from "express"; -import dotenv from "dotenv"; -import axios from "axios"; - -import { McRaptorAlgorithm, Journey, JourneyLeg } from "../raptor/McRaptorAlgorithm"; -import { MaxPriorityQueue } from '@datastructures-js/priority-queue'; - -import { - curBusPositions, - cachedPredsByStopId, - cachedRoutes, - cachedPredsByVid, - cachedStopLocations, - curRouteSelections, - stopIdToName, - tatripidToRt, - getAllBusPredictions, - routeTimingCache, - cachedGraph, - updateBusPositions, - getSelectableRoutes, - rebuildGraph, -} from './busService'; - -// Simple Bus Color System -interface BusRoute { - routeId: string; - color: string; - image: string; -} - -class BusColorManager { - private readonly routes: BusRoute[] = [ - { routeId: "BB", color: "#2F773F", image: "bus_BB.png" }, - { routeId: "CN", color: "#643076", image: "bus_CN.png" }, - { routeId: "CS", color: "#3559B8", image: "bus_CS.png" }, - { routeId: "CSX", color: "#1C2256", image: "bus_CSX.png" }, - { routeId: "DD", color: "#A9C534", image: "bus_DD.png" }, - { routeId: "MX", color: "#5EC7DE", image: "bus_MX.png" }, - { routeId: "NE", color: "#C55188", image: "bus_NE.png" }, - { routeId: "NW", color: "#AE3636", image: "bus_NW.png" }, - { routeId: "NX", color: "#DA4343", image: "bus_NX.png" }, - { routeId: "OS", color: "#E8A43C", image: "bus_OS.png" }, - { routeId: "NES", color: "#C55188", image: "bus_NES.png" }, - { routeId: "WS", color: "#BA5231", image: "bus_WS.png" }, - { routeId: "WX", color: "#E8663E", image: "bus_WX.png" } - ]; - - public getRouteColor(routeId: string): string | null { - const route = this.routes.find(r => r.routeId === routeId); - return route ? route.color : null; - } - - public getRouteImage(routeId: string): string | null { - const route = this.routes.find(r => r.routeId === routeId); - return route ? route.image : null; - } - - public getAllRoutes(): BusRoute[] { - return [...this.routes]; - } - - public getRouteInfo(routeId: string): BusRoute | null { - return this.routes.find(r => r.routeId === routeId) || null; - } -} - - -// Initialize bus color manager -const busColorManager = new BusColorManager(); - -dotenv.config(); -const router = express.Router(); -const routeImages: { [k: string]: string } = metadata.routeImages; - -setInterval(updateBusPositions, 7500); -setInterval(getSelectableRoutes, 60000); -setInterval(rebuildGraph, 10 * 1000); -getSelectableRoutes(); -rebuildGraph(); - -import * as process from "node:process"; - -const message = { - id: "gradamatation", - title: "Congrats Grads 🥳", - message: - "Congrats to everyone who is gradamatating! Enjoy some grad hats on the buses, and don't forget to celebrate!", - buildVersion: "99", -}; - -dotenv.config(); - -const API_KEY = process.env.MBUS_API_KEY; -router.get('/getBusPredictions1/:busId', (req, res) => { - axios.get(`https://mbus.ltp.umich.edu/bustime/api/v3/getpredictions?requestType=getpredictions&locale=en&vid=${req.params.busId}&top=4&tmres=s&rtpidatafeed=bustime&key=${API_KEY}&format=json&xtime=1626028950462`).then(apiRes => { - res.send(apiRes.data); - }).catch(err => { - console.log(err); - res.sendStatus(500); - - }); -}); - -router.get('/getBusPositions', (req, res) => { - res.send(curBusPositions); -}); - -router.get('/getVehiclePositions', (req, res) => { - res.send(curBusPositions); -}); - -router.get('/getSelectableRoutes', (req, res) => { - res.send(curRouteSelections); -}); - -router.get('/getAllRoutes', (req, res) => { - res.send({ routes: cachedRoutes }); -}); - -router.get('/getrouteCache', (req, res) => { - res.send({ routes: routeTimingCache }); -}); - -router.get('/getVehicleImage/:route', (req, res) => { - const { route } = req.params; - - const dirname = import.meta.dirname; - const assetPath = path.join(dirname, 'assets'); - const imagePath = path.join(assetPath, 'main2025'); - - if (!route || !(route in routeImages)) { - res.status(400).sendFile(path.join(imagePath, 'bus_CN.png')); - return; - } - res.sendFile(path.join(imagePath, routeImages[route]), (err) => { - if (err) { - console.error(`Error sending requested image for route ${route}: ${err.message}`); - if (!res.headersSent) res.status(404).send('Image file not found on server.'); - } - }); -}); - -router.get('/getRouteInfoVersion', (req, res) => { - res.send(JSON.stringify({ version: metadata.metadata.version })); -}); - -router.get('/getRouteInformation', (req, res) => { - const infoToSend = { - routeIdToName: metadata.routeIdToName, - routeImages: metadata.routeImages, - metadata: metadata.metadata, - routeColors: busColorManager.getAllRoutes().map(route => ({ - routeId: route.routeId, - color: route.color, - image: route.image - })) - } - res.send(infoToSend); -}); - -router.get('/getStartupInfo', (req, res) => { - res.json({ - // updating this will disable older versions of the app - min_supported_version: "1.0.0", - why_update_message: { - title: "New Update Available", - subtitle: "Please update to the latest version for the best experience." - }, - // adding data here will show a persistant message on launch - persistant_message: { - title: "", - subtitle: "" - }, - // adding data here will show a one-time message on launch (not yet implemented) - one_time_message: { - title: "", - subtitle: "" - }, - // updating this will make bus images redownload on frontend - bus_image_version: "1", - }); -});; - -router.get('/getBusPredictions/:busId', (req, res) => { - const busId = req.params.busId; - const preds = cachedPredsByVid[busId]; - - if (!preds) { - return res.json({ - "bustime-response": { "prd": [] } - }); - } - res.json({ - "bustime-response": { "prd": preds } - }); -}); - -router.get('/getStopPredictions/:stopId', (req, res) => { - const stopId = req.params.stopId; - const preds = cachedPredsByStopId[stopId]; - - if (!preds) { - return res.json({ - "bustime-response": { "prd": [] } - }); - } - - res.json({ - "bustime-response": { "prd": preds } - }); -}); - -router.get('/getAllPredictions', async (req, res) => { - try { - const predictions = await getAllBusPredictions(); - res.send(predictions); - } catch (err) { - console.log(err); - res.sendStatus(500); - } -}); - -router.get('/getAllStops', (req, res) => { - const stopsList = Object.entries(cachedStopLocations).map(([stpid, stopInfo]) => ({ - stpid, - ...stopInfo, - })); - res.json(Object.values(stopsList)); -}); - -router.get('/getBuildingLocations', (req, res) => { - res.sendFile(path.join(import.meta.dirname, 'assets', 'building-data.json')); -}); - -router.get('/get-startup-messages', (req, res) => { - res.send(JSON.stringify(message)); -}); - -// Simple bus color endpoints -router.get('/getRouteColors', (req, res) => { - const routes = busColorManager.getAllRoutes(); - res.json({ - routes: routes.map(route => ({ - routeId: route.routeId, - color: route.color, - image: route.image - })) - }); -}); - -router.get('/getRouteColor/:routeId', (req, res) => { - const { routeId } = req.params; - const routeInfo = busColorManager.getRouteInfo(routeId); - - if (!routeInfo) { - return res.status(404).json({ error: `Route '${routeId}' not found` }); - } - - res.json({ - routeId: routeInfo.routeId, - color: routeInfo.color, - image: routeInfo.image - }); -}); - -// Simple endpoint for frontend, gets everything needed for UI -router.get('/getFrontendData', (req, res) => { - try { - const routes = busColorManager.getAllRoutes(); - - const response = { - routes: routes.map(route => ({ - routeId: route.routeId, - name: (metadata.routeIdToName as any)[route.routeId] || route.routeId, - image: route.image, - color: route.color, - imageUrl: `/mbus/api/v3/getVehicleImage/${route.routeId}` - })), - metadata: { - ...metadata.metadata, - lastUpdated: new Date().toISOString() - } - }; - - res.json(response); - } catch (error) { - res.status(500).json({ error: 'Failed to get frontend data' }); - } -}); - - -router.get('/nearest-stops', (req, res) => { - try { - const { lat, lon, k = '2' } = req.query; - - const originLat = parseFloat(lat as string); - const originLon = parseFloat(lon as string); - const numStops = parseInt(k as string); - - if (isNaN(originLat) || isNaN(originLon)) { - return res.status(400).json({ error: 'Invalid or missing lat/lon' }); - } - if (isNaN(numStops) || numStops <= 0) { - return res.status(400).json({ error: 'Parameter k must be a positive integer' }); - } - - const heap = new MaxPriorityQueue<{ stpid: string; name: string; lat: number; lon: number; distance: number }>({ - compare: (a, b) => a.distance - b.distance - }); - - for (const [stpid, stop] of Object.entries(cachedStopLocations)) { - const latDiff = (stop.lat - originLat) * 111320; - const lonDiff = (stop.lon - originLon) * 111320 * Math.cos(originLat * Math.PI / 180); - const distance = Math.sqrt(latDiff ** 2 + lonDiff ** 2); - - const stopWithDist = { - stpid, - name: stop.name, - lat: stop.lat, - lon: stop.lon, - distance - }; - - if (heap.size() < numStops) { - heap.enqueue(stopWithDist); - } else if (distance < heap.front()!.distance) { - heap.dequeue(); - heap.enqueue(stopWithDist); - } - } - - const nearestStops = heap.toArray().sort((a, b) => a.distance - b.distance); - res.json({ nearestStops }); - } catch (error) { - console.error('Error in /nearest-stops:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -router.get('/plan-journey', async (req, res) => { - try { - const originStopId = 'VIRTUAL_ORIGIN'; - const destStopId = 'VIRTUAL_DESTINATION'; - - const { originLat, originLon, destLat, destLon, walkingPenalty: walkingPenaltyParam} = req.query; - if (!originLat || !originLon || !destLat || !destLon) { - return res.status(400).json({ error: 'Origin and destination coordinates are required' }); - } - - const oLat = parseFloat(originLat as string); - const oLon = parseFloat(originLon as string); - const dLat = parseFloat(destLat as string); - const dLon = parseFloat(destLon as string); - - const now = new Date(); - const currentTime = now.getUTCHours() * 3600 + now.getUTCMinutes() * 60 + now.getUTCSeconds(); - - if (!cachedGraph || !cachedGraph.trips || cachedGraph.trips.length === 0) { - await rebuildGraph(); - if (!cachedGraph) { - return res.status(404).json({ error: 'No routes available at this time' }); - } - } - - // Clear all transfers for virtual stops - cachedGraph.transfers[originStopId] = []; - cachedGraph.transfers[destStopId] = []; - Object.keys(cachedGraph.transfers).forEach(stopId => { - cachedGraph.transfers[stopId] = cachedGraph.transfers[stopId].filter( - t => t.destination !== destStopId - ); - }); - - const vOriginTrip = cachedGraph.trips.find(t => t.tripId === 'VIRTUAL_ORIGIN_TRIP'); - if (vOriginTrip) { - vOriginTrip.stopTimes[0].arrivalTime = currentTime; - vOriginTrip.stopTimes[0].departureTime = currentTime; - } - const vDestTrip = cachedGraph.trips.find(t => t.tripId === 'VIRTUAL_DESTINATION_TRIP'); - if (vDestTrip) { - vDestTrip.stopTimes[0].arrivalTime = currentTime; - vDestTrip.stopTimes[0].departureTime = currentTime; - } - - // Get walking times from origin to all stops - // Get walking times from all stops to dest - const walksFromOrigin = walking.getWalkingDistancesFrom(oLat, oLon, dLat, dLon); - const walksToDest = walking.getWalkingDistancesFrom(dLat, dLon); - - walksFromOrigin.forEach(walk => { - if (walk.stopId === "DIRECT_WALK") { - cachedGraph.transfers[originStopId].push({ - origin: originStopId, - destination: destStopId, - duration: walk.duration, - startTime: currentTime, - endTime: Number.MAX_SAFE_INTEGER - }); - } else { - cachedGraph.transfers[originStopId].push({ - origin: originStopId, - destination: walk.stopId, - duration: walk.duration, - startTime: currentTime, - endTime: Number.MAX_SAFE_INTEGER - }); - } - }); - - walksToDest.forEach(walk => { - cachedGraph.transfers[walk.stopId].push({ - origin: walk.stopId, - destination: destStopId, - duration: walk.duration, - startTime: currentTime, - endTime: Number.MAX_SAFE_INTEGER - }); - }); - - const mcRaptor = new McRaptorAlgorithm(cachedGraph.trips, cachedGraph.transfers, cachedGraph.interchange); - - let walkingPenalty = 1; - if (walkingPenaltyParam !== undefined) { - const parsed = parseFloat(walkingPenaltyParam as string); - if (!isNaN(parsed) && parsed > 0) { - walkingPenalty = parsed; - } - } - mcRaptor.setWalkingPenalty(walkingPenalty); - - let rangeInSeconds = 60*45; - const { range } = req.query; - if (range !== undefined) { - const parsedRange = parseInt(range as string); - if (!isNaN(parsedRange) && parsedRange > 0) { - rangeInSeconds = parsedRange * 60; - } - } - - let journeys: Journey[]; - if(range !== undefined) { - journeys = mcRaptor.getOptimizedJourneysInRange(originStopId, destStopId, currentTime, rangeInSeconds); - } else { - journeys = mcRaptor.getOptimizedJourneys(originStopId, destStopId, currentTime); - } - const processLeg = async (leg: JourneyLeg) => { - const isWalk = !leg.trip; - - const formattedLeg: any = { - origin_id: leg.origin, - origin: leg.origin === 'VIRTUAL_ORIGIN' ? 'Start' : (leg.origin === 'VIRTUAL_DESTINATION' ? 'End' : (stopIdToName[leg.origin] || leg.origin)), - destination_id: leg.destination, - destination: leg.destination === 'VIRTUAL_DESTINATION' ? 'End' : (stopIdToName[leg.destination] || leg.destination), - destinationName: leg.destination === 'VIRTUAL_DESTINATION' ? 'End' : (stopIdToName[leg.destination] || leg.destination), - startTime: Math.round(leg.startTime), - endTime: Math.round(leg.endTime), - duration: Math.round(leg.duration), - mode: isWalk ? 'walk' : 'bus', - originID: leg.originID, - destinationID: leg.destinationID, - stopTimes: leg.stopTimes, - trip: leg.trip, - rt: leg.rt - }; - - if (leg.trip) { - formattedLeg.tripId = leg.trip.tripId; - formattedLeg.vid = leg.trip.vid; - if (!formattedLeg.rt) { - const firstStop = leg.trip.stopTimes[0]; - formattedLeg.rt = firstStop.rt || tatripidToRt[leg.trip.tripId] || 'UNKNOWN'; - } - } - - if (isWalk) { - const cached = walking.getCachedWalk(leg.origin, leg.destination); - - if (cached) { - Object.assign(formattedLeg, cached); - } else { - const l1 = leg.origin === 'VIRTUAL_ORIGIN' ? { lat: oLat, lon: oLon } : cachedStopLocations[leg.origin]; - const l2 = leg.destination === 'VIRTUAL_DESTINATION' ? { lat: dLat, lon: dLon } : cachedStopLocations[leg.destination]; - - if (l1 && l2) { - try { - const data = await walking.getWalkingResponse(l1.lat, l1.lon, l2.lat, l2.lon); - data.duration = Math.round(data.duration); - Object.assign(formattedLeg, data); - } catch (e) { - formattedLeg.path_coords = []; - } - } - } - } - - return formattedLeg; - }; - - const processJourneys = async (journeys: Journey[]) => { - return Promise.all(journeys.map(async (journey: Journey) => { - if (!journey) return null; - - const legs = await Promise.all(journey.legs.map(processLeg)); - - return { - legs, - departureTime: journey.criteria.arrivalTime - (legs.reduce((acc, leg) => acc + leg.duration, 0)), - arrivalTime: journey.criteria.arrivalTime, - criteria: journey.criteria - }; - })); - }; - const processedList = await processJourneys(journeys); - const sortedJourneys = processedList - .filter((j: any) => j !== null) - .sort((a: any, b: any) => - a.arrivalTime - b.arrivalTime || - a.criteria.walkingDistance - b.criteria.walkingDistance - ); - res.json({ - journeys: sortedJourneys - }); - - } catch (error) { - console.error('Error planning journey:', error); - res.status(500).json({ error: 'Failed to plan journey' }); - } -}); - - -router.get('/save-graph', async (req, res) => { - if (process.env.DEV_SAVE !== 'true') { - return res.status(403).json({ error: 'Endpoint only available in DEV mode' }); - } - - try { - const filePath = path.resolve(process.cwd(), 'saved_graph.json'); - const fullState = { - graph: cachedGraph, - stopLocations: cachedStopLocations, - stopNames: stopIdToName, - predsByVid: cachedPredsByVid, - predsByStopId: cachedPredsByStopId - }; - const data = JSON.stringify(fullState, null, 2); - fs.writeFileSync(filePath, data); - res.json({ message: `Graph and state saved to ${filePath}` }); - } catch (error) { - console.error('Error saving graph:', error); - res.status(500).json({ error: 'Failed to save graph' }); - } -}); - -export default router; diff --git a/src/routes/api.ts b/src/routes/api.ts index 8f24271..cc03750 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -567,7 +567,12 @@ export function swapToken(req: express.Request, res: express.Response) { } router.post('/swapToken', swapToken); -type ActiveReminderInfo = { stpid: string, rtid: string, thresh: number | null, eta: number | null }; +export interface ActiveReminderInfo { + stpid: string + rtid: string + thresh: number | null + eta: number | null +}; /** * @param req - Express request, token is path encoded diff --git a/src/services/reminder.ts b/src/services/reminder.ts index a274244..8b87c78 100644 --- a/src/services/reminder.ts +++ b/src/services/reminder.ts @@ -61,7 +61,7 @@ function prdctdnToNum(prdctdn: string): number { } /** result of ReminderSubscriptons.process */ -type RemindersToTrigger = { +export type RemindersToTrigger = { /** can be sent in bulk based off of route, stop, and threshold (or route, stop, and prdctdn) */ reminder: Map, Set>, /** can be sent in bulk based off of what route and stop */ @@ -74,7 +74,7 @@ type RemindersToTrigger = { }; /** Subscriptions go through a pipeline, see types above for details. */ -class ReminderSubscriptions { +export class ReminderSubscriptions { subscriptions: Array<{ token: RegistrationToken, subscription: PreThreshold | PostThreshold }> diff --git a/src/services/reminderTypes.ts b/src/services/reminderTypes.ts index eae8a53..5f4f54c 100644 --- a/src/services/reminderTypes.ts +++ b/src/services/reminderTypes.ts @@ -13,8 +13,8 @@ export function fromKey(key: Key): T { return JSON.parse(key); } -/** internal */ -type CoreEvent = { +/** @internal */ +export type CoreEvent = { stpid: string, rtid: string, } From 747818a3f6617aa016b88af07cca18427b5e0dd8 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Wed, 22 Jul 2026 13:13:57 -0700 Subject: [PATCH 25/36] feat: test stubs, output to file, flesh out tsdoc comments --- src/app.ts | 9 ++- src/routes/api.ts | 16 ++-- src/routes/documented.ts | 158 ++++++++++++++++++++++++++++----------- test/documented.test.ts | 78 +++++++++++++++++++ 4 files changed, 207 insertions(+), 54 deletions(-) create mode 100644 test/documented.test.ts diff --git a/src/app.ts b/src/app.ts index f743a3d..c5508a6 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,12 +1,12 @@ import express from "express"; import mbus from "./routes/api" -import { addRouter, dumpReflectionInfo, reflection } from "./routes/documented"; +import * as documented from "./routes/documented"; const app = express(); app.use(express.json()); -addRouter(app, "/mbus/api/v3", mbus); +documented.addRouter(documented.globalContext, app, "/mbus/api/v3", mbus); app.use("/docs", express.static("docs")); const PORT = process.env.PORT || 3000; @@ -14,6 +14,7 @@ const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); - if (reflection) - dumpReflectionInfo(); + if (documented.ENABLED) { + documented.outputDocsFor(documented.globalContext); + } }); \ No newline at end of file diff --git a/src/routes/api.ts b/src/routes/api.ts index 0131ee4..bb953c5 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -9,7 +9,7 @@ import * as journeyService from '../services/journey'; import * as reminderService from '../services/reminder'; import * as graphBuilder from '../services/graphBuilder'; import { startBackgroundJobs } from '../jobs'; -import { addGetRoute, addPostRoute, emptyFormat, makeFailureResponse, makeSuccessResponse } from "./documented"; +import * as documented from "./documented"; /** * Express router for the MBus API v3. @@ -488,12 +488,12 @@ router.get('/get-key-stops', getKeyStops); // Notifications / Reminders const SetReminderBody = z.object({ token: z.string(), stpid: z.string(), rtid: z.string(), thresh: z.number() }); -addPostRoute( - router, '/setReminder', { ...emptyFormat, reqBody: SetReminderBody }, +documented.addPostRoute( + documented.globalContext, router, '/setReminder', { ...documented.emptyFormat, reqBody: SetReminderBody }, (_, __, { token, stpid, rtid, thresh }) => { const info = reminderService.infoToUseForRoute(rtid); if (info === null) { - return makeFailureResponse(400, `Invalid route ${rtid}`); + return documented.makeFailureResponse(400, `Invalid route ${rtid}`); } const { reminderSubscriptions, predsByStopId } = info; reminderSubscriptions.add( @@ -503,7 +503,7 @@ addPostRoute( predsByStopId, Date.now(), ); - return makeSuccessResponse(200, {}); + return documented.makeSuccessResponse(200, {}); } ); @@ -564,8 +564,8 @@ const ActiveReminder = z.object({ eta: z.number().nullable(), }).meta({ id: "Reminder" }); -addGetRoute( - router, '/activeReminders/:token', +documented.addGetRoute( + documented.globalContext, router, '/activeReminders/:token', { params: z.object({ token: Token }), query: z.object(), @@ -589,7 +589,7 @@ addGetRoute( .rideReminderSubscriptions .activeRemindersFor(token) .map(subscriptionInfo); - return makeSuccessResponse(200, { reminders: universityReminders.concat(rideReminders) }); + return documented.makeSuccessResponse(200, { reminders: universityReminders.concat(rideReminders) }); }, { summary: "active reminders", diff --git a/src/routes/documented.ts b/src/routes/documented.ts index 675f54b..5f77bd3 100644 --- a/src/routes/documented.ts +++ b/src/routes/documented.ts @@ -1,43 +1,73 @@ /** * Wrappers around stuff you would otherwise do with express but with reflection - * capabilities used for openapi specification generation. + * capabilities used for openapi specification generation and built-in request + * format validation. * * The `req` and `res` objects aren't provided to the passed in handler * functions, if you're doing something more complicated just use the router * directly for now. * * Nested routing not supported yet, but should probably be added since api.ts - * is getting long (or we could separate the functions from the route defintions?). + * is getting long (or we could separate the functions from the route + * defintions). * * Extra functionality can be added as needed. * - * EXAMPLES: + * # Getting Started + * + * ## Defining Routes + * + * Make sure you know how to use Zod, then look into {@link addRouter}, + * {@link addGetRoute}, and {@link addPostRoute}. It would also be useful to + * take a look at {@link HandlerReturn} + remember the existence of + * {@link emptyFormat} and {@link globalContext}. + * + * ## Getting OpenAPI Specs + * + * Look into setting the environment variables `DOCUMENTED` (to anything truthy) + * and `DOCUMENTED_OUTPUT_FILE` (or it will log to the console). Also look at + * {@link globalContext}, {@link docsFor}, and {@link outputDocsFor} * - * TODO: post support [done?] * TODO: add examples - * TODO: support empty request and response bodies + * * TODO: make actually testable? + * * TODO: add tests? + * @module */ +import * as fs from 'node:fs/promises'; + +import dotenv from 'dotenv'; import express from 'express'; import z from 'zod'; import { JSONSchema, ToJSONSchemaParams } from 'zod/v4/core'; -/** is reflection enabled? */ -export const reflection = true; +dotenv.config(); + +export const ENABLED = process.env.DOCUMENTED && true; +const OUTPUT_FILE = process.env.DOCUMENTED_OUTPUT_FILE ?? null // === interface for people defining apis === -export function addRouter(app: express.Express, route: string, router: express.Router) { - if (reflection) { - info.routers.push({ route, router }); +/** + * Wrapper around `express.Express.use`, instead something like + * `app.use("/api", router)` you'd call + * `addRouter(someContext, app, "/api", router)`. + */ +export function addRouter(ctx: Context, app: express.Express, route: string, router: express.Router) { + if (ENABLED) { + ctx.routers.push({ route, router }); } app.use(route, router); } /** - * feel free to add more codes here and to the make[A-Za-z]*Response functions as you need them + * You should genrally use either {@link makeSuccessResponse} or + * {@link makeFailureResponse} to construct this. + * + * Feel free to add more codes here and to the make[A-Za-z]*Response functions + * as you need them. */ export type HandlerReturn = { success: true, status: 200 | 201 | 202 | 203 | 205, json: T @@ -59,30 +89,46 @@ export function makeFailureResponse(status: 400 | 401 | 403 | 404 | 500, erro return { success: false, status, error }; } -/** z.object({ example: z.(...), hello: z.number(), ... }) */ +/** + * A type representing a Zod object (i.e. `z.object(...)`) used normally. + */ export type StandardZodObject = z.ZodObject>; +/** + * Meant to be used along with the spread operator to fill out format fields + * that aren't cared about. + */ export const emptyFormat = { params: z.object(), query: z.object(), reqBody: z.unknown(), resBody: z.unknown(), }; /** - * wrapper around router.get with built in validation and schema recording + * Wrapper around router.get with built in validation and schema recording + * + * The `req` and `res` objects aren't provided to the passed in handler, if + * you're doing something more complicated (e.g. using headers) just use the + * router directly for now, the functionality needed could be incorporated in + * the future. * - * the `req` and `res` objects aren't provided to the passed in handler, if - * you're doing something more complicated just use the router directly for - * now, the functionality needed will be incorporated + * @typeParam P - path parameters as a zod object + * @typeParam Q - query parameters as a zod object + * @typeParam RB - response body as a zod type * - * type parameters - * - P: path params - * - Q: query params - * - RB: response body + * @param ctx - which context to place this route in, see `globalContext` + * @param router - express router + * @param path - path the route happens, same format as used in express but + * avoid features not supported in openapi (e.g. advanced path matchers) + * @param format - zod schemas of the parts of the request and response, use + * `emptyFormat` to fill in default values + * @param handler - route handler + * @param docs - information that should end up in the openapi spec */ export function addGetRoute< P extends StandardZodObject, Q extends StandardZodObject, RB extends z.ZodType >( + ctx: Context, router: express.Router, path: string, format: { params: P, query: Q, resBody: RB }, @@ -96,8 +142,8 @@ export function addGetRoute< ) { const { params: paramsSchema, query: querySchema, resBody: resBodySchema } = format; - if (reflection) { - info.routes.push({ + if (ENABLED) { + ctx.routes.push({ router, method: 'get', pathSuffix: path, params: paramsSchema.shape, @@ -140,17 +186,22 @@ export function addGetRoute< } /** - * wrapper around router.post with built in validation and schema recording + * Wrapper around router.post with built in validation and schema recording, + * more details can be found in {@link addGetRoute}. * - * the `req` and `res` objects aren't provided to the passed in handler, if - * you're doing something more complicated just use the router directly for - * now, the functionality needed will be incorporated + * @typeParam P - path params + * @typeParam Q - query params + * @typeParam B - request body + * @typeParam RB - response body * - * type parameters - * - P: path params - * - Q: query params - * - B: request body - * - RB: response body + * @param ctx - which context to place this route in, see `globalContext` + * @param router - express router + * @param path - path the route happens, same format as used in express but + * avoid features not supported in openapi (e.g. advanced path matchers) + * @param format - zod schemas of the parts of the request and response, use + * `emptyFormat` to fill in default values + * @param handler - route handler + * @param docs - information that should end up in the openapi spec */ export function addPostRoute< P extends StandardZodObject, @@ -158,6 +209,7 @@ export function addPostRoute< B extends z.ZodType, RB extends z.ZodType, >( + ctx: Context, router: express.Router, path: string, format: { params: P, query: Q, reqBody: B, resBody: RB }, @@ -171,8 +223,8 @@ export function addPostRoute< ) { const { params: paramsSchema, query: querySchema, reqBody: reqBodySchema, resBody: resBodySchema } = format; - if (reflection) { - info.routes.push({ + if (ENABLED) { + ctx.routes.push({ router, method: 'post', pathSuffix: path, params: paramsSchema.shape, query: querySchema.shape, @@ -220,15 +272,22 @@ export function addPostRoute< // === end of interface for api defining === -const info: ReflectionInfoRaw = { +/** + * The context you should probably be using for everything unless writing a + * test. + */ +export const globalContext: Context = { routers: [], routes: [] }; +/** Where api route info is aggregated */ +export type Context = ReflectionInfoRaw; + /** - * doc descriptions passed as data in summary and description fields + * @internal */ -interface ReflectionInfoRaw { +export interface ReflectionInfoRaw { routers: Array<{ route: string, router: express.Router }>, /** full routes along with req+res schemas, routes are incomplete until info is finalized */ routes: Array<{ @@ -275,9 +334,9 @@ interface OpenAPIPathCommon { }, }; -interface OpenAPIGetPath extends OpenAPIPathCommon { }; +export interface OpenAPIGetPath extends OpenAPIPathCommon { }; -interface OpenAPIPostPath extends OpenAPIPathCommon { +export interface OpenAPIPostPath extends OpenAPIPathCommon { requestBody?: { content: { "application/json": { @@ -289,7 +348,7 @@ interface OpenAPIPostPath extends OpenAPIPathCommon { }; /** the subset of the openapi format(s) we are concerned with generating */ -interface OpenAPI { +export interface OpenAPI { openapi: "3.1.2", info: { title: string, @@ -451,9 +510,24 @@ function makeOpenAPI(info: ReflectionInfo): OpenAPI { } } -export function dumpReflectionInfo() { - const finalized = finalize(info); +/** Get the OpenAPI spec as a structured object. */ +export function docsFor(ctx: Context) { + const finalized = finalize(ctx); const openAPI = makeOpenAPI(finalized); - console.log(JSON.stringify(openAPI, null, 4)); + return openAPI; +} + +/** + * Output the OpenAPI spec to the file specified by the environment, or to the + * console if this isn't set. + */ +export async function outputDocsFor(ctx: Context) { + console.log('outputting docs...'); + const openAPI = docsFor(ctx); + const output = JSON.stringify(openAPI, null, 4); + if (OUTPUT_FILE) + await fs.writeFile(OUTPUT_FILE, output); + else + console.log(output); } diff --git a/test/documented.test.ts b/test/documented.test.ts new file mode 100644 index 0000000..59ddd5e --- /dev/null +++ b/test/documented.test.ts @@ -0,0 +1,78 @@ +import { expect, it } from "vitest"; + +it('should handle path params (GET)', () => { + // correct value goes through + // incorrect value is caught + // shows up in docs + expect(true).toBe(false); +}) + +it('should handle query params (GET)', () => { + // correct value goes through + // incorrect value is caught + // shows up in docs + expect(true).toBe(false); +}) + +it('should handle response bodies (GET)', () => { + // correct value goes through + // incorrect value is caught + // shows up in docs + // can be empty + expect(true).toBe(false); +}) + +it('should handle path params (POST)', () => { + // correct value goes through + // incorrect value is caught + // shows up in docs + expect(true).toBe(false); +}) + +it('should handle query params (POST)', () => { + // correct value goes through + // incorrect value is caught + // shows up in docs + expect(true).toBe(false); +}) + +it('should handle request bodies (POST)', () => { + // correct value goes through + // incorrect value is caught + // shows up in docs + // can be empty + expect(true).toBe(false); +}) + +it('should handle response bodies (POST)', () => { + // correct value goes through + // incorrect value is caught + // shows up in docs + // can be empty + expect(true).toBe(false); +}) + +it('should handle zod coerce types', () => { + // type is correct + // docs don't error + expect(true).toBe(false); +}); + +it('should handle zod pipe/transform types', () => { + // type is correct + // docs don't error + expect(true).toBe(false); +}); + +it('should surface type descriptions & names', () => { + expect(true).toBe(false); +}); + +it('should surface route descriptions & names', () => { + expect(true).toBe(false); +}); + +it('should have a stable output', () => { + expect(true).toBe(false); +}); + From d9663d1b6edd17c65ed6a667d74069959919f4c3 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Wed, 22 Jul 2026 14:12:18 -0700 Subject: [PATCH 26/36] feat: generate and upload openapi specs in ci --- .github/workflows/ci.yml | 29 ++++++++++++++++++++++++++++- src/routes/documented.ts | 13 +++++++++---- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64f2ccf..1eb31a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: - name: Build Docs run: npx typedoc --entryPointStrategy expand ./src --treatWarningsAsErrors working-directory: ${{ github.workspace }} - - name: Sync files + - name: Sync Files uses: SamKirkland/FTP-Deploy-Action@v4.4.0 with: server: ${{ secrets.FTP_SERVER }} @@ -68,3 +68,30 @@ jobs: password: ${{ secrets.FTP_PASSWORD }} local-dir: ${{ github.workspace }}/docs/ server-dir: ${{ github.ref }}/typedoc/ + + OpenAPI: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v6 + - name: NPM Install + run: npm i + working-directory: ${{ github.workspace }} + - name: Build Spec + run: | + export DOCUMENTED=1 + export DOCUMENTED_OUTPUT_FILE=openapi/spec.json + export DOCUMENTED_EXIT_ON_OUTPUT=1 + mkdir openapi + npm start + working-directory: ${{ github.workspace }} + - name: Sync Files + uses: SamKirkland/FTP-Deploy-Action@v4.4.0 + with: + server: ${{ secrets.FTP_SERVER }} + port: ${{ secrets.FTP_PORT }} + username: ${{ secrets.FTP_USERNAME }} + password: ${{ secrets.FTP_PASSWORD }} + local-dir: ${{ github.workspace }}/openapi/ + server-dir: ${{ github.ref }}/openapi/ + diff --git a/src/routes/documented.ts b/src/routes/documented.ts index 5f77bd3..7a72ddb 100644 --- a/src/routes/documented.ts +++ b/src/routes/documented.ts @@ -24,9 +24,10 @@ * * ## Getting OpenAPI Specs * - * Look into setting the environment variables `DOCUMENTED` (to anything truthy) - * and `DOCUMENTED_OUTPUT_FILE` (or it will log to the console). Also look at - * {@link globalContext}, {@link docsFor}, and {@link outputDocsFor} + * Look into setting the environment variables `DOCUMENTED` (to anything + * truthy), `DOCUMENTED_OUTPUT_FILE` (or it will log to the console), and + * `DOCUMENTED_EXIT_ON_OUTPUT`. Also look at {@link globalContext}, + * {@link docsFor}, and {@link outputDocsFor}. * * TODO: add examples * @@ -42,11 +43,13 @@ import dotenv from 'dotenv'; import express from 'express'; import z from 'zod'; import { JSONSchema, ToJSONSchemaParams } from 'zod/v4/core'; +import { exit } from 'node:process'; dotenv.config(); export const ENABLED = process.env.DOCUMENTED && true; const OUTPUT_FILE = process.env.DOCUMENTED_OUTPUT_FILE ?? null +const EXIT_ON_OUTPUT = process.env.DOCUMENTED_EXIT_ON_OUTPUT && true; // === interface for people defining apis === @@ -519,7 +522,7 @@ export function docsFor(ctx: Context) { /** * Output the OpenAPI spec to the file specified by the environment, or to the - * console if this isn't set. + * console if this isn't set. Will also exit the process if configured to do so. */ export async function outputDocsFor(ctx: Context) { console.log('outputting docs...'); @@ -529,5 +532,7 @@ export async function outputDocsFor(ctx: Context) { await fs.writeFile(OUTPUT_FILE, output); else console.log(output); + if (EXIT_ON_OUTPUT) + exit(0); } From aece55aebc47de1b9499efbf4fb078aa6f5f6352 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Thu, 23 Jul 2026 13:57:10 -0700 Subject: [PATCH 27/36] improve testability, start adding tests, confirm ci failure when docs never generate --- src/routes/documented.ts | 64 +++++++++--- test/documented.test.ts | 205 +++++++++++++++++++++++++++++++++------ 2 files changed, 224 insertions(+), 45 deletions(-) diff --git a/src/routes/documented.ts b/src/routes/documented.ts index 7a72ddb..b1e8c44 100644 --- a/src/routes/documented.ts +++ b/src/routes/documented.ts @@ -22,6 +22,17 @@ * take a look at {@link HandlerReturn} + remember the existence of * {@link emptyFormat} and {@link globalContext}. * + * ### Be Careful With Zod Transformations + * The OpenAPI spec will be populated with the output formats of the schemas you + * define routes with. This gives z.coerce the correct behavior and also will + * work with carefully constructed z.pipe chains, but a z.transform without + * further validation through pipe will not work. Uses of z.coerce, z.pipe, and + * z.transform should generally conform to a parse string into primative type + * pattern, and only be used for path and query parameters. Response schemas + * are not guaranteed to be used (currently they aren't but this may change in + * the future) so avoid *any* kind of data transformation in them unless it is + * confirmed that response schemas will be used. + * * ## Getting OpenAPI Specs * * Look into setting the environment variables `DOCUMENTED` (to anything @@ -44,6 +55,7 @@ import express from 'express'; import z from 'zod'; import { JSONSchema, ToJSONSchemaParams } from 'zod/v4/core'; import { exit } from 'node:process'; +import router from './api'; dotenv.config(); @@ -57,6 +69,9 @@ const EXIT_ON_OUTPUT = process.env.DOCUMENTED_EXIT_ON_OUTPUT && true; * Wrapper around `express.Express.use`, instead something like * `app.use("/api", router)` you'd call * `addRouter(someContext, app, "/api", router)`. + * + * @param route should not have a trailing slash (notably '/' would be + * incorrect, pass '' instead) */ export function addRouter(ctx: Context, app: express.Express, route: string, router: express.Router) { if (ENABLED) { @@ -93,9 +108,10 @@ export function makeFailureResponse(status: 400 | 401 | 403 | 404 | 500, erro } /** - * A type representing a Zod object (i.e. `z.object(...)`) used normally. + * A type representing a Zod object (i.e. `z.object(...)`) where string fields + * like those from query & path parms can get parsed. */ -export type StandardZodObject = z.ZodObject>; +export type StandardZodObject = z.ZodObject>>; /** * Meant to be used along with the spread operator to fill out format fields @@ -125,6 +141,9 @@ export const emptyFormat = { * `emptyFormat` to fill in default values * @param handler - route handler * @param docs - information that should end up in the openapi spec + * + * @returns the handler function that got passed to `router.get` internally, + * to be used in testing */ export function addGetRoute< P extends StandardZodObject, @@ -156,12 +175,12 @@ export function addGetRoute< }); } - router.get(path, (req: express.Request, res: express.Response | { error: string }>) => { + router.get(path, (req: ExpressRequest, res: express.Response | { error: string }>) => { const { status, json } = determineResponse(req); res.status(status).json(json); }); - const determineResponse = (req: express.Request): { status: number, json: z.infer | { error: string } } => { + const determineResponse = (req: ExpressRequest): { status: number, json: z.infer | { error: string } } => { let params = paramsSchema.safeParse(req.params); if (params.error) { return { status: 400, json: { error: "invalid path params: " + params.error.message } }; @@ -186,6 +205,7 @@ export function addGetRoute< } } } + return determineResponse; } /** @@ -205,6 +225,9 @@ export function addGetRoute< * `emptyFormat` to fill in default values * @param handler - route handler * @param docs - information that should end up in the openapi spec + * + * @returns the handler function that got passed to `router.post` internally, + * to be used in testing */ export function addPostRoute< P extends StandardZodObject, @@ -242,7 +265,7 @@ export function addPostRoute< res.status(status).json(json); }); - const determineResponse = (req: express.Request): { status: number, json: z.infer | { error: string } } => { + const determineResponse = (req: ExpressRequest): { status: number, json: z.infer | { error: string } } => { let params = paramsSchema.safeParse(req.params); if (params.error) { return { status: 400, json: { error: "invalid path params: " + params.error.message } }; @@ -271,6 +294,7 @@ export function addPostRoute< } } } + return determineResponse; } // === end of interface for api defining === @@ -279,10 +303,14 @@ export function addPostRoute< * The context you should probably be using for everything unless writing a * test. */ -export const globalContext: Context = { - routers: [], - routes: [] -}; +export const globalContext: Context = newContext(); + +/** + * Returns an independent context. + */ +export function newContext() { + return { routers: [], routes: [] }; +} /** Where api route info is aggregated */ export type Context = ReflectionInfoRaw; @@ -363,6 +391,15 @@ export interface OpenAPI { paths: Record*/>, } +/** + * the parts of an express request that are relevant for mocking during tests + */ +export interface ExpressRequest { + params: express.Request['params'] + query: express.Request['query'] + body: express.Request['body'] +} + function finalize(info: ReflectionInfoRaw): ReflectionInfo { // replace $def with components/schemas const fixSchema = (s: T, shouldStripDefs: boolean): T => { @@ -379,20 +416,19 @@ function finalize(info: ReflectionInfoRaw): ReflectionInfo { const stripExtraKeys = (s: T, shouldStripDefs: boolean): T => { if (typeof s !== 'object' || !s) return s; if (shouldStripDefs && '$defs' in s) { - s['$defs'] = undefined; + delete s['$defs']; } - if ('$schema' in s) s['$schema'] = undefined; - if ('id' in s) s['id'] = undefined; + if ('$schema' in s) delete s['$schema']; + if ('id' in s) delete s['id']; for (const v of Object.values(s)) { stripExtraKeys(v, shouldStripDefs); } return s; } - // TODO: try output first then fallback to input const schemaOpts: ToJSONSchemaParams = { // reused: 'ref', - io: 'input', + io: 'output', } const resultRoutes: ReflectionInfo['routes'] = []; diff --git a/test/documented.test.ts b/test/documented.test.ts index 59ddd5e..65fc75c 100644 --- a/test/documented.test.ts +++ b/test/documented.test.ts @@ -1,78 +1,221 @@ import { expect, it } from "vitest"; +import * as d from '@/routes/documented'; + +import express from 'express'; +import z from 'zod'; it('should handle path params (GET)', () => { - // correct value goes through - // incorrect value is caught - // shows up in docs - expect(true).toBe(false); -}) + testCase( + 'get', + '/root', '/path/{item}', + { ...d.emptyFormat, params: z.object({ item: z.string() }) }, + { params: { item: 'five' }, query: {}, body: {} }, + { params: { wrong: 'five' }, query: {}, body: {} }, + (correct) => expect(correct) + .toMatchInlineSnapshot(` + { + "params": { + "item": "five", + }, + "query": {}, + } + `), + (incorrect) => expect(incorrect) + .toMatchInlineSnapshot(` + "invalid path params: [ + { + "expected": "string", + "code": "invalid_type", + "path": [ + "item" + ], + "message": "Invalid input: expected string, received undefined" + } + ]" + `), + (spec) => expect(spec.paths['/root/path/{item}'].get?.parameters[0]) + .toMatchInlineSnapshot(` + { + "in": "path", + "name": "item", + "required": true, + "schema": { + "type": "string", + }, + } + `), + ); +}); it('should handle query params (GET)', () => { - // correct value goes through - // incorrect value is caught - // shows up in docs - expect(true).toBe(false); -}) + testCase( + 'get', '', '/api', + { query: z.object({ field: z.number() })}, + { query: { field: "-2" } }, + { query: { field: "no" } }, + (json) => expect(json).toMatchInlineSnapshot(), + (error) => expect(error).toMatchInlineSnapshot(), + (spec) => expect(spec.paths['/api'].get?.parameters[0]).toMatchInlineSnapshot() + ) +}); it('should handle response bodies (GET)', () => { // correct value goes through // incorrect value is caught // shows up in docs // can be empty - expect(true).toBe(false); -}) + expect(true).toBe(true); +}); it('should handle path params (POST)', () => { - // correct value goes through - // incorrect value is caught - // shows up in docs - expect(true).toBe(false); -}) + testCase( + 'post', + '/root', '/path/{item}', + { ...d.emptyFormat, params: z.object({ item: z.string() }) }, + { params: { item: 'five' }, query: {}, body: {} }, + { params: { wrong: 'five' }, query: {}, body: {} }, + (correct) => expect(correct) + .toMatchInlineSnapshot(` + { + "body": {}, + "params": { + "item": "five", + }, + "query": {}, + } + `), + (incorrect) => expect(incorrect) + .toMatchInlineSnapshot(` + "invalid path params: [ + { + "expected": "string", + "code": "invalid_type", + "path": [ + "item" + ], + "message": "Invalid input: expected string, received undefined" + } + ]" + `), + (spec) => expect(spec.paths['/root/path/{item}'].post?.parameters[0]) + .toMatchInlineSnapshot(` + { + "in": "path", + "name": "item", + "required": true, + "schema": { + "type": "string", + }, + } + `), + ); +}); it('should handle query params (POST)', () => { - // correct value goes through - // incorrect value is caught - // shows up in docs - expect(true).toBe(false); -}) + testCase( + 'post', '', '/api', + { query: z.object({ field: z.number() })}, + { query: { field: "-2" } }, + { query: { field: "no" } }, + (json) => expect(json).toMatchInlineSnapshot(), + (error) => expect(error).toMatchInlineSnapshot(), + (spec) => expect(spec.paths['/api'].post?.parameters[0]).toMatchInlineSnapshot() + ) +}); it('should handle request bodies (POST)', () => { // correct value goes through // incorrect value is caught // shows up in docs // can be empty - expect(true).toBe(false); -}) + expect(true).toBe(true); +}); it('should handle response bodies (POST)', () => { // correct value goes through // incorrect value is caught // shows up in docs // can be empty - expect(true).toBe(false); -}) + expect(true).toBe(true); +}); + +function testCase( + mode: 'get' | 'post', + base: string, + suffix: string, + format: Partial, + correct: Partial, + incorrect: Partial, + validateCorrect: (json: unknown) => unknown, + validateIncorrect: (error: string) => unknown, + validateSpec: (spec: d.OpenAPI) => unknown, +) { + const app = express(); + const router = express.Router(); + + const ctx = d.newContext(); + d.addRouter(ctx, app, base, router); + const handler = mode === 'get' + ? d.addGetRoute( + ctx, router, suffix, + { ...d.emptyFormat, ...format }, + (params, query) => d.makeSuccessResponse(200, { params, query }) + ) + : d.addPostRoute( + ctx, router, suffix, + { ...d.emptyFormat, ...format }, + (params, query, body) => d.makeSuccessResponse(200, { params, query, body }) + ); + + const defaultResponse: d.ExpressRequest = { query: {}, params: {}, body: {} }; + // correct value goes through? + { + const res = handler({ ...defaultResponse, ...correct }); + expect(res.status).toBe(200); + validateCorrect(res.json); + } + + // incorrect value is caught? + { + const res = handler({ ...defaultResponse, ...incorrect }); + expect(res.status).toBe(400); + validateIncorrect( + typeof res.json === 'object' && res.json && 'error' in res.json && typeof res.json.error === 'string' + ? res.json.error + : '' + ); + } + + // shows up in docs? + const spec = d.docsFor(ctx); + validateSpec(spec); +} + +it('should be able to handle GET & POST to the same path', () => { + expect(true).toBe(true); +}); it('should handle zod coerce types', () => { // type is correct // docs don't error - expect(true).toBe(false); + expect(true).toBe(true); }); it('should handle zod pipe/transform types', () => { // type is correct // docs don't error - expect(true).toBe(false); + expect(true).toBe(true); }); it('should surface type descriptions & names', () => { - expect(true).toBe(false); + expect(true).toBe(true); }); it('should surface route descriptions & names', () => { - expect(true).toBe(false); + expect(true).toBe(true); }); it('should have a stable output', () => { - expect(true).toBe(false); + expect(true).toBe(true); }); From ce516d9382b04a6b468c93fd67c70d32dd413e17 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Thu, 23 Jul 2026 14:37:10 -0700 Subject: [PATCH 28/36] feat(documented): improve error ergonomics when calling finalize --- src/routes/documented.ts | 94 +++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/src/routes/documented.ts b/src/routes/documented.ts index b1e8c44..3774984 100644 --- a/src/routes/documented.ts +++ b/src/routes/documented.ts @@ -436,52 +436,56 @@ function finalize(info: ReflectionInfoRaw): ReflectionInfo { const model: Record = {}; for (const route of info.routes) { - const basePath = info.routers.find((r) => r.router == route.router)?.route; - if (basePath == undefined) { - throw new Error('route has missing base path'); - } - const path = (basePath + route.pathSuffix).replace(/:([A-Za-z0-9_]+)/, "{$1}"); - const finalParams: Record = {}; - for (const param in route.params) { - const zodSchema = route.params[param]; - model[path + ' params ' + param] = zodSchema; - finalParams[param] = fixSchema(zodSchema.toJSONSchema(schemaOpts), true); - } - const finalQuery: Record = {}; - for (const key in route.query) { - const zodSchema = route.query[key]; - model[path + '?' + key] = zodSchema; - finalQuery[key] = fixSchema(zodSchema.toJSONSchema(schemaOpts), true); - } - const common = { - path, - params: finalParams, - query: finalQuery, - resBody: route.resBody === null ? null : fixSchema(route.resBody.toJSONSchema(schemaOpts), true), - summary: route.summary, - description: route.description, - }; - switch (route.method) { - case 'get': - resultRoutes.push({ ...common, method: 'get' }); - break; - case 'post': - resultRoutes.push({ - ...common, - method: 'post', - reqBody: route.reqBody === null - ? null - : fixSchema(route.reqBody.toJSONSchema(schemaOpts), true), - }); - if (route.reqBody) - model[path + ' reqBody'] = route.reqBody; - break; - default: - // TODO: use eslint exhaustiveness checking - const _: never = route; + try { + const basePath = info.routers.find((r) => r.router == route.router)?.route; + if (basePath == undefined) { + throw new Error('route has missing base path'); + } + const path = (basePath + route.pathSuffix).replace(/:([A-Za-z0-9_]+)/, "{$1}"); + const finalParams: Record = {}; + for (const param in route.params) { + const zodSchema = route.params[param]; + model[path + ' params ' + param] = zodSchema; + finalParams[param] = fixSchema(zodSchema.toJSONSchema(schemaOpts), true); + } + const finalQuery: Record = {}; + for (const key in route.query) { + const zodSchema = route.query[key]; + model[path + '?' + key] = zodSchema; + finalQuery[key] = fixSchema(zodSchema.toJSONSchema(schemaOpts), true); + } + const common = { + path, + params: finalParams, + query: finalQuery, + resBody: route.resBody === null ? null : fixSchema(route.resBody.toJSONSchema(schemaOpts), true), + summary: route.summary, + description: route.description, + }; + switch (route.method) { + case 'get': + resultRoutes.push({ ...common, method: 'get' }); + break; + case 'post': + resultRoutes.push({ + ...common, + method: 'post', + reqBody: route.reqBody === null + ? null + : fixSchema(route.reqBody.toJSONSchema(schemaOpts), true), + }); + if (route.reqBody) + model[path + ' reqBody'] = route.reqBody; + break; + default: + // TODO: use eslint exhaustiveness checking + const _: never = route; + } + if (route.resBody) + model[path + ' resBody'] = route.resBody; + } catch (e) { + throw new Error(`couldn't finalize "${route.pathSuffix}": ${e}`); } - if (route.resBody) - model[path + ' resBody'] = route.resBody; } return { routes: resultRoutes, From adb40a1c684bd36ba6371a33067ccb936911ebd2 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Thu, 23 Jul 2026 22:26:52 -0700 Subject: [PATCH 29/36] feat(documented): make request errors less verbose --- src/routes/documented.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/routes/documented.ts b/src/routes/documented.ts index 3774984..178b95e 100644 --- a/src/routes/documented.ts +++ b/src/routes/documented.ts @@ -56,6 +56,7 @@ import z from 'zod'; import { JSONSchema, ToJSONSchemaParams } from 'zod/v4/core'; import { exit } from 'node:process'; import router from './api'; +import { ZodIssue } from 'zod/v3'; dotenv.config(); @@ -183,11 +184,14 @@ export function addGetRoute< const determineResponse = (req: ExpressRequest): { status: number, json: z.infer | { error: string } } => { let params = paramsSchema.safeParse(req.params); if (params.error) { - return { status: 400, json: { error: "invalid path params: " + params.error.message } }; + return { + status: 400, + json: formatError('invalid path params', params.error) + }; } let query = querySchema.safeParse(req.query); if (query.error) { - return { status: 400, json: { error: "invalid query params: " + query.error.message } }; + return { status: 400, json: formatError('invalid query params', query.error) }; } try { const result = handler(params.data, query.data); @@ -268,15 +272,15 @@ export function addPostRoute< const determineResponse = (req: ExpressRequest): { status: number, json: z.infer | { error: string } } => { let params = paramsSchema.safeParse(req.params); if (params.error) { - return { status: 400, json: { error: "invalid path params: " + params.error.message } }; + return { status: 400, json: formatError('invalid path params', params.error) }; } let query = querySchema.safeParse(req.query); if (query.error) { - return { status: 400, json: { error: "invalid query params: " + query.error.message } }; + return { status: 400, json: formatError('invalid query params', query.error) }; } let body = reqBodySchema.safeParse(req.body); if (body.error) { - return { status: 400, json: { error: "invalid body: " + body.error.message } }; + return { status: 400, json: formatError('invalid body', body.error) }; } try { const result = handler(params.data, query.data, body.data); @@ -297,6 +301,13 @@ export function addPostRoute< return determineResponse; } +function formatError(title: string, error: z.ZodError) { + const issuesText = error.issues + .map(i => `- ${i.path.length ? (i.path.join('.') + ': ') : ''}${i.message}`) + .join('\n'); + return { error: `${title}:\n${issuesText}` }; +} + // === end of interface for api defining === /** From b421b93a5dd5a3d53f58ae419be9ce2c3cfc956f Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Thu, 23 Jul 2026 22:38:22 -0700 Subject: [PATCH 30/36] fix(documented): refine type of query & path params Make sure the passed schemas can accept strings as input as that is what express gives as `path` and `query`. Update module docs. --- src/routes/documented.ts | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/routes/documented.ts b/src/routes/documented.ts index 178b95e..5bad2da 100644 --- a/src/routes/documented.ts +++ b/src/routes/documented.ts @@ -24,14 +24,11 @@ * * ### Be Careful With Zod Transformations * The OpenAPI spec will be populated with the output formats of the schemas you - * define routes with. This gives z.coerce the correct behavior and also will - * work with carefully constructed z.pipe chains, but a z.transform without - * further validation through pipe will not work. Uses of z.coerce, z.pipe, and - * z.transform should generally conform to a parse string into primative type - * pattern, and only be used for path and query parameters. Response schemas - * are not guaranteed to be used (currently they aren't but this may change in - * the future) so avoid *any* kind of data transformation in them unless it is - * confirmed that response schemas will be used. + * define routes with. This makes coerce work well with path/query formats but + * might be problematic with client generation if using transformations to + * non-primative types. Only use coerce, pipe, and transform with the parts of + * the request, not the response (the resBody schema is never used to validate, + * only to get a type). * * ## Getting OpenAPI Specs * @@ -112,13 +109,15 @@ export function makeFailureResponse(status: 400 | 401 | 403 | 404 | 500, erro * A type representing a Zod object (i.e. `z.object(...)`) where string fields * like those from query & path parms can get parsed. */ -export type StandardZodObject = z.ZodObject>>; +export type StringlyZodObject = z.ZodObject>>; /** * Meant to be used along with the spread operator to fill out format fields * that aren't cared about. */ -export const emptyFormat = { +export const emptyFormat: { + params: StringlyZodObject, query: StringlyZodObject, reqBody: z.ZodType, resBody: z.ZodType, +} = { params: z.object(), query: z.object(), reqBody: z.unknown(), resBody: z.unknown(), }; @@ -147,8 +146,8 @@ export const emptyFormat = { * to be used in testing */ export function addGetRoute< - P extends StandardZodObject, - Q extends StandardZodObject, + P extends StringlyZodObject, + Q extends StringlyZodObject, RB extends z.ZodType >( ctx: Context, @@ -234,8 +233,8 @@ export function addGetRoute< * to be used in testing */ export function addPostRoute< - P extends StandardZodObject, - Q extends StandardZodObject, + P extends StringlyZodObject, + Q extends StringlyZodObject, B extends z.ZodType, RB extends z.ZodType, >( From 1bdc13368ef80cc1b2536a390a7dbf7d84953160 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Thu, 23 Jul 2026 22:42:00 -0700 Subject: [PATCH 31/36] test(documented): complete the test suite --- test/documented.test.ts | 272 +++++++++++++++++++++++++++++----------- 1 file changed, 199 insertions(+), 73 deletions(-) diff --git a/test/documented.test.ts b/test/documented.test.ts index 65fc75c..8534ebd 100644 --- a/test/documented.test.ts +++ b/test/documented.test.ts @@ -8,7 +8,7 @@ it('should handle path params (GET)', () => { testCase( 'get', '/root', '/path/{item}', - { ...d.emptyFormat, params: z.object({ item: z.string() }) }, + { ...d.emptyFormat, params: z.strictObject({ item: z.string() }) }, { params: { item: 'five' }, query: {}, body: {} }, { params: { wrong: 'five' }, query: {}, body: {} }, (correct) => expect(correct) @@ -22,16 +22,9 @@ it('should handle path params (GET)', () => { `), (incorrect) => expect(incorrect) .toMatchInlineSnapshot(` - "invalid path params: [ - { - "expected": "string", - "code": "invalid_type", - "path": [ - "item" - ], - "message": "Invalid input: expected string, received undefined" - } - ]" + "invalid path params: + - item: Invalid input: expected string, received undefined + - Unrecognized key: "wrong"" `), (spec) => expect(spec.paths['/root/path/{item}'].get?.parameters[0]) .toMatchInlineSnapshot(` @@ -50,21 +43,60 @@ it('should handle path params (GET)', () => { it('should handle query params (GET)', () => { testCase( 'get', '', '/api', - { query: z.object({ field: z.number() })}, + { query: z.object({ field: z.coerce.number() })}, { query: { field: "-2" } }, { query: { field: "no" } }, - (json) => expect(json).toMatchInlineSnapshot(), - (error) => expect(error).toMatchInlineSnapshot(), - (spec) => expect(spec.paths['/api'].get?.parameters[0]).toMatchInlineSnapshot() + (json) => expect(json).toMatchInlineSnapshot(` + { + "params": {}, + "query": { + "field": -2, + }, + } + `), + (error) => expect(error).toMatchInlineSnapshot(` + "invalid query params: + - field: Invalid input: expected number, received NaN" + `), + (spec) => expect(spec.paths['/api'].get?.parameters[0]).toMatchInlineSnapshot(` + { + "in": "query", + "name": "field", + "required": true, + "schema": { + "type": "number", + }, + } + `) ) }); it('should handle response bodies (GET)', () => { - // correct value goes through - // incorrect value is caught // shows up in docs + testCase( + 'get', '/4', '/34', + { resBody: z.number() }, + {}, {}, + (json) => expect(json).toMatchInlineSnapshot(` + { + "params": {}, + "query": {}, + } + `), + null, + (spec) => expect(spec.paths['/4/34'].get?.responses["2XX"].content?.["application/json"].schema) + .toMatchInlineSnapshot(` + { + "type": "number", + } + `) + ); // can be empty - expect(true).toBe(true); + testCase( + 'get', '', '/h', {}, {}, {}, null, null, + (spec) => expect(spec.paths['/h'].get!.responses["2XX"].content) + .toMatchInlineSnapshot(`undefined`) + ); }); it('should handle path params (POST)', () => { @@ -86,16 +118,8 @@ it('should handle path params (POST)', () => { `), (incorrect) => expect(incorrect) .toMatchInlineSnapshot(` - "invalid path params: [ - { - "expected": "string", - "code": "invalid_type", - "path": [ - "item" - ], - "message": "Invalid input: expected string, received undefined" - } - ]" + "invalid path params: + - item: Invalid input: expected string, received undefined" `), (spec) => expect(spec.paths['/root/path/{item}'].post?.parameters[0]) .toMatchInlineSnapshot(` @@ -114,29 +138,160 @@ it('should handle path params (POST)', () => { it('should handle query params (POST)', () => { testCase( 'post', '', '/api', - { query: z.object({ field: z.number() })}, + { query: z.object({ field: z.coerce.number() })}, { query: { field: "-2" } }, { query: { field: "no" } }, - (json) => expect(json).toMatchInlineSnapshot(), - (error) => expect(error).toMatchInlineSnapshot(), - (spec) => expect(spec.paths['/api'].post?.parameters[0]).toMatchInlineSnapshot() + (json) => expect(json).toMatchInlineSnapshot(` + { + "body": {}, + "params": {}, + "query": { + "field": -2, + }, + } + `), + (error) => expect(error).toMatchInlineSnapshot(` + "invalid query params: + - field: Invalid input: expected number, received NaN" + `), + (spec) => expect(spec.paths['/api'].post?.parameters[0]).toMatchInlineSnapshot(` + { + "in": "query", + "name": "field", + "required": true, + "schema": { + "type": "number", + }, + } + `) ) }); it('should handle request bodies (POST)', () => { - // correct value goes through - // incorrect value is caught - // shows up in docs - // can be empty - expect(true).toBe(true); + testCase( + 'post', '/a/b', '/c', + { reqBody: z.object({ field: z.boolean() })}, + { body: { field: true } }, + { body: { field: [] } }, + (json) => expect(json).toMatchInlineSnapshot(` + { + "body": { + "field": true, + }, + "params": {}, + "query": {}, + } + `), + (error) => expect(error).toMatchInlineSnapshot(` + "invalid body: + - field: Invalid input: expected boolean, received array" + `), + (spec) => expect(spec.paths['/a/b/c'].post?.parameters[0]).toMatchInlineSnapshot(`undefined`) + ) }); it('should handle response bodies (POST)', () => { - // correct value goes through - // incorrect value is caught // shows up in docs + testCase( + 'post', '/4', '/34', + { resBody: z.number() }, + {}, {}, + (json) => expect(json).toMatchInlineSnapshot(` + { + "body": {}, + "params": {}, + "query": {}, + } + `), + null, + (spec) => expect(spec.paths['/4/34'].post?.responses["2XX"].content?.["application/json"].schema) + .toMatchInlineSnapshot(` + { + "type": "number", + } + `) + ); // can be empty - expect(true).toBe(true); + testCase( + 'post', '', '/h', {}, {}, {}, null, null, + (spec) => expect(spec.paths['/h'].post!.responses["2XX"].content) + .toMatchInlineSnapshot(`undefined`) + ); +}); + + +it('should be able to handle GET & POST to the same path', () => { + const app = express(); + const router = express.Router(); + + const ctx = d.newContext(); + d.addRouter(ctx, app, '', router); + + d.addGetRoute(ctx, router, '/rt', d.emptyFormat, () => d.makeSuccessResponse(200, {}), {}); + d.addPostRoute(ctx, router, '/rt', d.emptyFormat, () => d.makeSuccessResponse(200, {}), {}); + + const path = d.docsFor(ctx).paths['/rt']; + expect(path.get).toBeDefined(); + expect(path.post).toBeDefined(); + expect(path.get).toEqual(path.post); +}); + +it('should handle zod transform types in the request', () => { + const app = express(); + const router = express.Router(); + + const ctx = d.newContext(); + d.addRouter(ctx, app, '', router); + + const handler = d.addPostRoute( + ctx, router, '/rt', + { ...d.emptyFormat, reqBody: z.string().transform((s) => s.toLowerCase()) }, + (_, __, body) => d.makeSuccessResponse(200, body) + ); + const res = handler({ params: {}, query: {}, body: 'HI' }); + expect(res.json).toMatchInlineSnapshot(`"hi"`); +}); + +it('should surface type descriptions & names', () => { + const app = express(); + const router = express.Router(); + + const ctx = d.newContext(); + d.addRouter(ctx, app, '', router); + d.addGetRoute( + ctx, router, '/pan', + { ...d.emptyFormat, resBody: z.object().meta({ id: 'Obj', description: 'obj' }) }, + () => d.makeSuccessResponse(200, {}) + ); + const spec = d.docsFor(ctx); + expect(spec.components.schemas).toMatchInlineSnapshot(` + { + "Obj": { + "additionalProperties": false, + "description": "obj", + "properties": {}, + "type": "object", + }, + } + `); +}); + +it('should surface route descriptions & names', () => { + const app = express(); + const router = express.Router(); + + const ctx = d.newContext(); + d.addRouter(ctx, app, '', router); + d.addGetRoute( + ctx, router, '/pan', + d.emptyFormat, + () => d.makeSuccessResponse(200, {}), + { summary: 'summary', description: 'description' } + ); + const spec = d.docsFor(ctx); + const path = spec.paths['/pan'].get; + expect(path).toHaveProperty('description', 'description'); + expect(path).toHaveProperty('summary', 'summary'); }); function testCase( @@ -146,8 +301,8 @@ function testCase( format: Partial, correct: Partial, incorrect: Partial, - validateCorrect: (json: unknown) => unknown, - validateIncorrect: (error: string) => unknown, + validateCorrect: null | ((json: unknown) => unknown), + validateIncorrect: null | ((error: string) => unknown), validateSpec: (spec: d.OpenAPI) => unknown, ) { const app = express(); @@ -169,14 +324,14 @@ function testCase( const defaultResponse: d.ExpressRequest = { query: {}, params: {}, body: {} }; // correct value goes through? - { + if (validateCorrect) { const res = handler({ ...defaultResponse, ...correct }); expect(res.status).toBe(200); validateCorrect(res.json); } // incorrect value is caught? - { + if (validateIncorrect) { const res = handler({ ...defaultResponse, ...incorrect }); expect(res.status).toBe(400); validateIncorrect( @@ -190,32 +345,3 @@ function testCase( const spec = d.docsFor(ctx); validateSpec(spec); } - -it('should be able to handle GET & POST to the same path', () => { - expect(true).toBe(true); -}); - -it('should handle zod coerce types', () => { - // type is correct - // docs don't error - expect(true).toBe(true); -}); - -it('should handle zod pipe/transform types', () => { - // type is correct - // docs don't error - expect(true).toBe(true); -}); - -it('should surface type descriptions & names', () => { - expect(true).toBe(true); -}); - -it('should surface route descriptions & names', () => { - expect(true).toBe(true); -}); - -it('should have a stable output', () => { - expect(true).toBe(true); -}); - From 7ee8ee7246c3b5b457a7ede427ef19691452e394 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Thu, 23 Jul 2026 23:14:33 -0700 Subject: [PATCH 32/36] fix(api): remove z.transform from /reminders/:token --- src/routes/api.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/routes/api.ts b/src/routes/api.ts index bb953c5..187ed83 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -556,7 +556,7 @@ export function swapToken(req: express.Request, res: express.Response) { } router.post('/swapToken', swapToken); -const Token = z.string().transform(reminderService.registrationToken).meta({ id: "Token" }) +const Token = z.string().meta({ id: "Token" }) const ActiveReminder = z.object({ stpid: z.string(), rtid: z.string(), @@ -572,6 +572,7 @@ documented.addGetRoute( resBody: z.object({ reminders: z.array(ActiveReminder) }), }, ({ token }, _) => { + const regTok = reminderService.registrationToken(token); const subscriptionInfo = (r: reminderService.PreThreshold | reminderService.PostThreshold) => { return { stpid: r.event.stpid, @@ -583,11 +584,11 @@ documented.addGetRoute( console.log(`Got request for active reminders of ${token}`); const universityReminders = reminderService .universityReminderSubscriptions - .activeRemindersFor(token) + .activeRemindersFor(regTok) .map(subscriptionInfo); const rideReminders = reminderService .rideReminderSubscriptions - .activeRemindersFor(token) + .activeRemindersFor(regTok) .map(subscriptionInfo); return documented.makeSuccessResponse(200, { reminders: universityReminders.concat(rideReminders) }); }, From c69f3d3f7bc9167b1346ce9611e49da4f3664384 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Thu, 23 Jul 2026 23:56:43 -0700 Subject: [PATCH 33/36] fix: check if documented is enabled before tests --- .github/workflows/ci.yml | 1 + src/routes/documented.ts | 2 ++ test/documented.test.ts | 8 +++++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1eb31a7..a863850 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ jobs: working-directory: ${{ github.workspace }} - name: Test Suite run: | + export DOCUMENTED=1 export MBUS_URL=https://mbus.bustime.mock.mb.thething.fyi/ export RIDE_URL=https://ride.bustime.mock.mb.thething.fyi/ npm start & diff --git a/src/routes/documented.ts b/src/routes/documented.ts index 5bad2da..653215f 100644 --- a/src/routes/documented.ts +++ b/src/routes/documented.ts @@ -565,6 +565,7 @@ function makeOpenAPI(info: ReflectionInfo): OpenAPI { /** Get the OpenAPI spec as a structured object. */ export function docsFor(ctx: Context) { + if (!ENABLED) throw new Error('documented must be enabled'); const finalized = finalize(ctx); const openAPI = makeOpenAPI(finalized); return openAPI; @@ -575,6 +576,7 @@ export function docsFor(ctx: Context) { * console if this isn't set. Will also exit the process if configured to do so. */ export async function outputDocsFor(ctx: Context) { + if (!ENABLED) throw new Error('documented must be enabled'); console.log('outputting docs...'); const openAPI = docsFor(ctx); const output = JSON.stringify(openAPI, null, 4); diff --git a/test/documented.test.ts b/test/documented.test.ts index 8534ebd..f84151c 100644 --- a/test/documented.test.ts +++ b/test/documented.test.ts @@ -1,9 +1,15 @@ -import { expect, it } from "vitest"; +import { beforeAll, expect, it } from "vitest"; import * as d from '@/routes/documented'; import express from 'express'; import z from 'zod'; +beforeAll(() => { + if (!d.ENABLED) { + throw new Error('documented must be enabled'); + } +}); + it('should handle path params (GET)', () => { testCase( 'get', From e4994c164ea0e021c2d8ddafcf6fc03c6ea77350 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Sat, 25 Jul 2026 07:00:40 -0700 Subject: [PATCH 34/36] fix(api): restrict success status codes The previous approach of supporting many status codes and having a 2XX entry in the generated OpenAPI spec doesn't work well with swagger_parser. Switch to using only the 200 status code and having an entry for 200 instead. --- src/routes/api.ts | 4 ++-- src/routes/documented.ts | 20 +++++++++++++------- test/documented.test.ts | 22 +++++++++++----------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/routes/api.ts b/src/routes/api.ts index 187ed83..1bd8f8d 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -503,7 +503,7 @@ documented.addPostRoute( predsByStopId, Date.now(), ); - return documented.makeSuccessResponse(200, {}); + return documented.makeSuccessResponse({}); } ); @@ -590,7 +590,7 @@ documented.addGetRoute( .rideReminderSubscriptions .activeRemindersFor(regTok) .map(subscriptionInfo); - return documented.makeSuccessResponse(200, { reminders: universityReminders.concat(rideReminders) }); + return documented.makeSuccessResponse({ reminders: universityReminders.concat(rideReminders) }); }, { summary: "active reminders", diff --git a/src/routes/documented.ts b/src/routes/documented.ts index 653215f..c082c09 100644 --- a/src/routes/documented.ts +++ b/src/routes/documented.ts @@ -22,6 +22,12 @@ * take a look at {@link HandlerReturn} + remember the existence of * {@link emptyFormat} and {@link globalContext}. * + * If a Zod schema is reused / important enough to get its own variable, make + * sure to at the very least add `.meta({ id: 'unique name' })` to it so that + * API/docs consumers can also take advantage of this. Other schemas can also + * have this even if they are not variables. Note that id must be unique, and + * accidentally setting `name` instead won't have the intended outcome. + * * ### Be Careful With Zod Transformations * The OpenAPI spec will be populated with the output formats of the schemas you * define routes with. This makes coerce work well with path/query formats but @@ -82,11 +88,11 @@ export function addRouter(ctx: Context, app: express.Express, route: string, rou * You should genrally use either {@link makeSuccessResponse} or * {@link makeFailureResponse} to construct this. * - * Feel free to add more codes here and to the make[A-Za-z]*Response functions - * as you need them. + * Success responses were limited to just 200 because the response body of a + * 2XX catch-all entry isn't reflected in swagger_parser's output. */ export type HandlerReturn = { - success: true, status: 200 | 201 | 202 | 203 | 205, json: T + success: true, status: 200, json: T } | { success: false, status: 400 | 401 | 403 | 404 | 500, error: string }; @@ -94,8 +100,8 @@ export type HandlerReturn = { /** * helper function that should avoid weird typechecker issues */ -export function makeSuccessResponse(status: 200 | 201 | 202 | 203 | 205, json: T): HandlerReturn { - return { success: true, status, json }; +export function makeSuccessResponse(json: T): HandlerReturn { + return { success: true, status: 200, json }; } /** @@ -364,7 +370,7 @@ interface OpenAPIPathCommon { required: boolean, }>, responses: { - "2XX": { + "200": { description: "success", content?: { "application/json": { @@ -519,7 +525,7 @@ function makeOpenAPI(info: ReflectionInfo): OpenAPI { ? undefined : { 'application/json': { schema: route.resBody } }; const responses: OpenAPIGetPath['responses'] = { - '2XX': { + '200': { description: 'success', content, } diff --git a/test/documented.test.ts b/test/documented.test.ts index f84151c..0e23b21 100644 --- a/test/documented.test.ts +++ b/test/documented.test.ts @@ -90,7 +90,7 @@ it('should handle response bodies (GET)', () => { } `), null, - (spec) => expect(spec.paths['/4/34'].get?.responses["2XX"].content?.["application/json"].schema) + (spec) => expect(spec.paths['/4/34'].get?.responses["200"].content?.["application/json"].schema) .toMatchInlineSnapshot(` { "type": "number", @@ -100,7 +100,7 @@ it('should handle response bodies (GET)', () => { // can be empty testCase( 'get', '', '/h', {}, {}, {}, null, null, - (spec) => expect(spec.paths['/h'].get!.responses["2XX"].content) + (spec) => expect(spec.paths['/h'].get!.responses["200"].content) .toMatchInlineSnapshot(`undefined`) ); }); @@ -210,7 +210,7 @@ it('should handle response bodies (POST)', () => { } `), null, - (spec) => expect(spec.paths['/4/34'].post?.responses["2XX"].content?.["application/json"].schema) + (spec) => expect(spec.paths['/4/34'].post?.responses["200"].content?.["application/json"].schema) .toMatchInlineSnapshot(` { "type": "number", @@ -220,7 +220,7 @@ it('should handle response bodies (POST)', () => { // can be empty testCase( 'post', '', '/h', {}, {}, {}, null, null, - (spec) => expect(spec.paths['/h'].post!.responses["2XX"].content) + (spec) => expect(spec.paths['/h'].post!.responses["200"].content) .toMatchInlineSnapshot(`undefined`) ); }); @@ -233,8 +233,8 @@ it('should be able to handle GET & POST to the same path', () => { const ctx = d.newContext(); d.addRouter(ctx, app, '', router); - d.addGetRoute(ctx, router, '/rt', d.emptyFormat, () => d.makeSuccessResponse(200, {}), {}); - d.addPostRoute(ctx, router, '/rt', d.emptyFormat, () => d.makeSuccessResponse(200, {}), {}); + d.addGetRoute(ctx, router, '/rt', d.emptyFormat, () => d.makeSuccessResponse({}), {}); + d.addPostRoute(ctx, router, '/rt', d.emptyFormat, () => d.makeSuccessResponse({}), {}); const path = d.docsFor(ctx).paths['/rt']; expect(path.get).toBeDefined(); @@ -252,7 +252,7 @@ it('should handle zod transform types in the request', () => { const handler = d.addPostRoute( ctx, router, '/rt', { ...d.emptyFormat, reqBody: z.string().transform((s) => s.toLowerCase()) }, - (_, __, body) => d.makeSuccessResponse(200, body) + (_, __, body) => d.makeSuccessResponse(body) ); const res = handler({ params: {}, query: {}, body: 'HI' }); expect(res.json).toMatchInlineSnapshot(`"hi"`); @@ -267,7 +267,7 @@ it('should surface type descriptions & names', () => { d.addGetRoute( ctx, router, '/pan', { ...d.emptyFormat, resBody: z.object().meta({ id: 'Obj', description: 'obj' }) }, - () => d.makeSuccessResponse(200, {}) + () => d.makeSuccessResponse({}) ); const spec = d.docsFor(ctx); expect(spec.components.schemas).toMatchInlineSnapshot(` @@ -291,7 +291,7 @@ it('should surface route descriptions & names', () => { d.addGetRoute( ctx, router, '/pan', d.emptyFormat, - () => d.makeSuccessResponse(200, {}), + () => d.makeSuccessResponse({}), { summary: 'summary', description: 'description' } ); const spec = d.docsFor(ctx); @@ -320,12 +320,12 @@ function testCase( ? d.addGetRoute( ctx, router, suffix, { ...d.emptyFormat, ...format }, - (params, query) => d.makeSuccessResponse(200, { params, query }) + (params, query) => d.makeSuccessResponse({ params, query }) ) : d.addPostRoute( ctx, router, suffix, { ...d.emptyFormat, ...format }, - (params, query, body) => d.makeSuccessResponse(200, { params, query, body }) + (params, query, body) => d.makeSuccessResponse({ params, query, body }) ); const defaultResponse: d.ExpressRequest = { query: {}, params: {}, body: {} }; From 0fe71f0f35388e8235f2fa260d56c668fd26dbcb Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Sat, 25 Jul 2026 10:12:27 -0700 Subject: [PATCH 35/36] docs(documented): add example, add note about tuples --- src/routes/documented.ts | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/routes/documented.ts b/src/routes/documented.ts index c082c09..0ae946d 100644 --- a/src/routes/documented.ts +++ b/src/routes/documented.ts @@ -15,13 +15,49 @@ * * # Getting Started * - * ## Defining Routes + * ```typescript + * // have express app + * const app = express(); + * + * // cool router (mandatory probably) + * const router = express.Router(); + * + * // use local context if you want + * const ctx = newContext(); + * + * // add router to app (app.use(router, '/api')) + * addRouter(globalContext, app, router, '/api') + * + * // add route (/api/double) + * addGetRoute( + * globalContext, router, '/double', + * // Zod schemas for each part of the request & response, defaults=emptyFormat + * // since query params end up as strings, z.coerce.number() is needed not z.number() + * { ...emptyFormat, query: z.object({ x: z.coerce.number() }), resBody: z.number() }, + * // handling logic goes here, first arg is ignored b/c it is the path params + * (_, { x }) => { + * // x is already a number as opposed to any/unknown + * makeSuccessResponse(x * 2); + * }, + * // documentation goes here + * { name: 'double a number', description: 'f: R -> R, x |-> 2x'} + * ); + * + * // get docs + * const spec: OpenAPI = docsFor(globalContext); + * // output docs + * outputDocsFor(globalContext); + * ``` * + * ## Defining Routes + * * Make sure you know how to use Zod, then look into {@link addRouter}, * {@link addGetRoute}, and {@link addPostRoute}. It would also be useful to * take a look at {@link HandlerReturn} + remember the existence of * {@link emptyFormat} and {@link globalContext}. * + * `z.tuple` isn't handled well by swagger_parser, prefer objects instead. + * * If a Zod schema is reused / important enough to get its own variable, make * sure to at the very least add `.meta({ id: 'unique name' })` to it so that * API/docs consumers can also take advantage of this. Other schemas can also From 78ac5c0c6ed619e2b98e1b42efe33f6023ab4b31 Mon Sep 17 00:00:00 2001 From: Edward Zhang Date: Sat, 25 Jul 2026 15:34:06 -0700 Subject: [PATCH 36/36] fix(documented): support schemas with "id" field --- src/routes/documented.ts | 1 - test/documented.test.ts | 23 ++++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/routes/documented.ts b/src/routes/documented.ts index 0ae946d..2fd9035 100644 --- a/src/routes/documented.ts +++ b/src/routes/documented.ts @@ -471,7 +471,6 @@ function finalize(info: ReflectionInfoRaw): ReflectionInfo { delete s['$defs']; } if ('$schema' in s) delete s['$schema']; - if ('id' in s) delete s['id']; for (const v of Object.values(s)) { stripExtraKeys(v, shouldStripDefs); } diff --git a/test/documented.test.ts b/test/documented.test.ts index 0e23b21..51a9641 100644 --- a/test/documented.test.ts +++ b/test/documented.test.ts @@ -14,7 +14,7 @@ it('should handle path params (GET)', () => { testCase( 'get', '/root', '/path/{item}', - { ...d.emptyFormat, params: z.strictObject({ item: z.string() }) }, + { params: z.strictObject({ item: z.string() }) }, { params: { item: 'five' }, query: {}, body: {} }, { params: { wrong: 'five' }, query: {}, body: {} }, (correct) => expect(correct) @@ -46,6 +46,27 @@ it('should handle path params (GET)', () => { ); }); +it('should accept params named id', () => { + testCase( + 'get', + '', '/test', + { params: z.object({ id: z.string() }) }, + {}, {}, null, null, + (spec) => expect(spec.paths['/test'].get?.parameters[0].name).toEqual("id") + ); +}); + +it('should work with schemas containing id', () => { + const B = z.object({ id: z.number() }).meta({ id: 'B'}); + testCase( + 'post', + '', '/test', + { reqBody: B, resBody: B }, + {}, {}, null, null, + (spec) => expect(spec.components.schemas.B.properties).toHaveProperty('id'), + ); +}); + it('should handle query params (GET)', () => { testCase( 'get', '', '/api',
                                                                                                                                                                                                                                                                                                            mbus-backend
                                                                                                                                                                                                                                                                                                              Preparing search index...