@emeryld/rrroutes-server 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,13 +16,13 @@ The server package depends on `@emeryld/rrroutes-contract` and `zod` internally.
16
16
 
17
17
  ```ts
18
18
  import express from 'express';
19
- import { createRouteServer } from '@emeryld/rrroutes-server';
19
+ import { createRRRoute } from '@emeryld/rrroutes-server';
20
20
  import { registry } from '../routes';
21
21
  import { controllers } from './controllers';
22
22
 
23
23
  const router = express.Router();
24
24
 
25
- const server = createRouteServer(router, {
25
+ const server = createRRRoute(router, {
26
26
  baseUrl: '/api',
27
27
  buildCtx: async (req) => ({ user: await loadUser(req) }),
28
28
  fromCfg: {
@@ -32,6 +32,8 @@ const server = createRouteServer(router, {
32
32
 
33
33
  server.registerControllers(registry, controllers);
34
34
  server.warnMissingControllers(registry, console);
35
+
36
+ `buildCtx` can optionally include `routesLogger` to route handler debug events to a per-request logger.
35
37
  ```
36
38
 
37
39
  ## Scripts
package/dist/index.cjs CHANGED
@@ -24,7 +24,7 @@ __export(index_exports, {
24
24
  asLeafAuth: () => asLeafAuth,
25
25
  bindAll: () => bindAll,
26
26
  bindExpressRoutes: () => bindExpressRoutes,
27
- createRouteServer: () => createRouteServer,
27
+ createRRRoute: () => createRRRoute,
28
28
  defineControllers: () => defineControllers,
29
29
  getCtx: () => getCtx,
30
30
  keyOf: () => keyOf,
@@ -94,6 +94,31 @@ function adaptCtxMw(mw) {
94
94
  }
95
95
  };
96
96
  }
97
+ function adaptAfterMw(mw) {
98
+ const adapted = adaptCtxMw(mw);
99
+ return (req, res, next) => {
100
+ if (!handlerInvokedNext(res)) {
101
+ next();
102
+ return;
103
+ }
104
+ adapted(req, res, next);
105
+ };
106
+ }
107
+ function mapAfterMiddleware(mws) {
108
+ return (mws ?? []).map((mw) => adaptAfterMw(mw));
109
+ }
110
+ function logHandlerDebugWithRoutesLogger(logger, event) {
111
+ if (!logger || event.type !== "handler") return;
112
+ const payload = [
113
+ `[rrroutes-server][handler:${event.stage}] ${event.method} ${event.path}`,
114
+ event
115
+ ];
116
+ if (event.stage === "error") {
117
+ (logger.error ?? logger.warn ?? logger.debug ?? logger.info ?? logger.log ?? logger.system)?.call(logger, ...payload);
118
+ return;
119
+ }
120
+ (logger.debug ?? logger.verbose ?? logger.info ?? logger.log ?? logger.system)?.call(logger, ...payload);
121
+ }
97
122
  var defaultSend = (res, data) => {
98
123
  res.json(data);
99
124
  };
@@ -126,17 +151,19 @@ function collectRoutesFromStack(appOrRouter) {
126
151
  }
127
152
  return result;
128
153
  }
129
- function createRouteServer(router, config) {
154
+ function createRRRoute(router, config) {
130
155
  const validateOutput = config.validateOutput ?? true;
131
156
  const send = config.send ?? defaultSend;
132
157
  const logger = config.logger;
133
- const { emit: emitDebug, mode: debugMode } = createServerDebugEmitter(config.debug);
134
- const isVerboseDebug = debugMode === "complete";
135
- const decorateDebugEvent = (event, details) => {
136
- if (!isVerboseDebug || !details) return event;
158
+ const { emit: defaultEmitDebug, mode: defaultDebugMode } = createServerDebugEmitter(config.debug);
159
+ const decorateDebugEvent = (isVerbose, event, details) => {
160
+ if (!isVerbose || !details) return event;
137
161
  return { ...event, ...details };
138
162
  };
139
- const globalMws = (config.global ?? []).map((mw) => adaptCtxMw(mw));
163
+ const globalBeforeMws = [...config.global ?? [], ...config.globalMiddleware?.before ?? []].map(
164
+ (mw) => adaptCtxMw(mw)
165
+ );
166
+ const globalAfterMws = mapAfterMiddleware(config.globalMiddleware?.after);
140
167
  const registered = getRegisteredRouteStore(router);
141
168
  const buildDerived = (leaf) => {
142
169
  const derived = [];
@@ -156,8 +183,23 @@ function createRouteServer(router, config) {
156
183
  const methodUpper = method.toUpperCase();
157
184
  const path = leaf.path;
158
185
  const key = keyOf(leaf);
159
- const debugName = def.debugName;
160
- const emit = (event) => emitDebug(event, debugName);
186
+ const defDebug = def.debug;
187
+ let debugName = def.debugName;
188
+ let routeDebugEmitter;
189
+ if (defDebug) {
190
+ const { debugName: overrideName, ...rest } = defDebug;
191
+ const hasOverrides = Object.values(rest).some((value) => value !== void 0);
192
+ if (hasOverrides) {
193
+ routeDebugEmitter = createServerDebugEmitter(
194
+ rest
195
+ );
196
+ }
197
+ debugName = overrideName ?? debugName;
198
+ }
199
+ const activeEmit = routeDebugEmitter?.emit ?? defaultEmitDebug;
200
+ const activeDebugMode = routeDebugEmitter?.mode ?? defaultDebugMode;
201
+ const emit = (event) => activeEmit(event, debugName);
202
+ const isVerboseDebug = activeDebugMode === "complete";
161
203
  emit({ type: "register", method: methodUpper, path });
162
204
  const routeSpecific = (def?.use ?? []).map((mw) => adaptCtxMw(mw));
163
205
  const derived = buildDerived(leaf);
@@ -191,14 +233,9 @@ function createRouteServer(router, config) {
191
233
  next(err);
192
234
  }
193
235
  };
194
- const before = [
195
- ...[ctxMw],
196
- ...globalMws,
197
- ...derived,
198
- ...routeSpecific
199
- ];
236
+ const before = [ctxMw, ...globalBeforeMws, ...derived, ...routeSpecific];
200
237
  const wrapped = async (req, res, next) => {
201
- const requestUrl = req.originalUrl ?? path;
238
+ const requestUrl = req.originalUrl.split("?")[0] ?? path;
202
239
  const startedAt = Date.now();
203
240
  emit({ type: "request", stage: "start", method: methodUpper, path, url: requestUrl });
204
241
  let params;
@@ -213,9 +250,18 @@ function createRouteServer(router, config) {
213
250
  setAfterHandlerNext(res, true);
214
251
  next(err);
215
252
  };
253
+ let ctxRoutesLogger;
254
+ const emitWithCtx = (event, details) => {
255
+ const decorated = decorateDebugEvent(isVerboseDebug, event, details);
256
+ if (decorated.type === "handler") {
257
+ logHandlerDebugWithRoutesLogger(ctxRoutesLogger, decorated);
258
+ }
259
+ emit(decorated);
260
+ };
216
261
  try {
217
262
  logger?.info?.(`${methodUpper}@${path} (${requestUrl})`);
218
263
  const ctx = res.locals[CTX_SYMBOL];
264
+ ctxRoutesLogger = ctx.routesLogger;
219
265
  params = leaf.cfg.paramsSchema ? leaf.cfg.paramsSchema.parse(req.params) : Object.keys(req.params || {}).length ? req.params : void 0;
220
266
  try {
221
267
  query = leaf.cfg.querySchema ? leaf.cfg.querySchema.parse(req.query) : Object.keys(req.query || {}).length ? req.query : void 0;
@@ -235,17 +281,15 @@ function createRouteServer(router, config) {
235
281
  body
236
282
  });
237
283
  const handlerStartedAt = Date.now();
238
- emit(
239
- decorateDebugEvent(
240
- {
241
- type: "handler",
242
- stage: "start",
243
- method: methodUpper,
244
- path,
245
- url: requestUrl
246
- },
247
- isVerboseDebug ? { params, query, body } : void 0
248
- )
284
+ emitWithCtx(
285
+ {
286
+ type: "handler",
287
+ stage: "start",
288
+ method: methodUpper,
289
+ path,
290
+ url: requestUrl
291
+ },
292
+ isVerboseDebug ? { params, query, body } : void 0
249
293
  );
250
294
  let result;
251
295
  try {
@@ -258,38 +302,34 @@ function createRouteServer(router, config) {
258
302
  query,
259
303
  body
260
304
  });
261
- emit(
262
- decorateDebugEvent(
263
- {
264
- type: "handler",
265
- stage: "success",
266
- method: methodUpper,
267
- path,
268
- url: requestUrl,
269
- durationMs: Date.now() - handlerStartedAt
270
- },
271
- isVerboseDebug ? {
272
- params,
273
- query,
274
- body,
275
- ...result !== void 0 ? { output: result } : {}
276
- } : void 0
277
- )
305
+ emitWithCtx(
306
+ {
307
+ type: "handler",
308
+ stage: "success",
309
+ method: methodUpper,
310
+ path,
311
+ url: requestUrl,
312
+ durationMs: Date.now() - handlerStartedAt
313
+ },
314
+ isVerboseDebug ? {
315
+ params,
316
+ query,
317
+ body,
318
+ ...result !== void 0 ? { output: result } : {}
319
+ } : void 0
278
320
  );
279
321
  } catch (e) {
280
- emit(
281
- decorateDebugEvent(
282
- {
283
- type: "handler",
284
- stage: "error",
285
- method: methodUpper,
286
- path,
287
- url: requestUrl,
288
- durationMs: Date.now() - handlerStartedAt,
289
- error: e
290
- },
291
- isVerboseDebug ? { params, query, body } : void 0
292
- )
322
+ emitWithCtx(
323
+ {
324
+ type: "handler",
325
+ stage: "error",
326
+ method: methodUpper,
327
+ path,
328
+ url: requestUrl,
329
+ durationMs: Date.now() - handlerStartedAt,
330
+ error: e
331
+ },
332
+ isVerboseDebug ? { params, query, body } : void 0
293
333
  );
294
334
  logger?.error?.("Handler error", e);
295
335
  throw e;
@@ -305,53 +345,40 @@ function createRouteServer(router, config) {
305
345
  next();
306
346
  }
307
347
  }
308
- emit(
309
- decorateDebugEvent(
310
- {
311
- type: "request",
312
- stage: "success",
313
- method: methodUpper,
314
- path,
315
- url: requestUrl,
316
- durationMs: Date.now() - startedAt
317
- },
318
- isVerboseDebug ? {
319
- params,
320
- query,
321
- body,
322
- ...hasResponsePayload ? { output: responsePayload } : {}
323
- } : void 0
324
- )
348
+ emitWithCtx(
349
+ {
350
+ type: "request",
351
+ stage: "success",
352
+ method: methodUpper,
353
+ path,
354
+ url: requestUrl,
355
+ durationMs: Date.now() - startedAt
356
+ },
357
+ isVerboseDebug ? {
358
+ params,
359
+ query,
360
+ body,
361
+ ...hasResponsePayload ? { output: responsePayload } : {}
362
+ } : void 0
325
363
  );
326
364
  } catch (err) {
327
- emit(
328
- decorateDebugEvent(
329
- {
330
- type: "request",
331
- stage: "error",
332
- method: methodUpper,
333
- path,
334
- url: requestUrl,
335
- durationMs: Date.now() - startedAt,
336
- error: err
337
- },
338
- isVerboseDebug ? { params, query, body } : void 0
339
- )
365
+ emitWithCtx(
366
+ {
367
+ type: "request",
368
+ stage: "error",
369
+ method: methodUpper,
370
+ path,
371
+ url: requestUrl,
372
+ durationMs: Date.now() - startedAt,
373
+ error: err
374
+ },
375
+ isVerboseDebug ? { params, query, body } : void 0
340
376
  );
341
377
  logger?.error?.("Route error", err);
342
378
  next(err);
343
379
  }
344
380
  };
345
- const after = (def?.after ?? []).map((mw) => {
346
- const adapted = adaptCtxMw(mw);
347
- return (req, res, next) => {
348
- if (!handlerInvokedNext(res)) {
349
- next();
350
- return;
351
- }
352
- adapted(req, res, next);
353
- };
354
- });
381
+ const after = [...mapAfterMiddleware(def?.after), ...globalAfterMws];
355
382
  router[method](path, ...before, wrapped, ...after);
356
383
  registered.add(key);
357
384
  }
@@ -388,12 +415,12 @@ function createRouteServer(router, config) {
388
415
  };
389
416
  }
390
417
  function bindExpressRoutes(router, registry, controllers, config) {
391
- const server = createRouteServer(router, config);
418
+ const server = createRRRoute(router, config);
392
419
  server.registerControllers(registry, controllers);
393
420
  return router;
394
421
  }
395
422
  function bindAll(router, registry, controllers, config) {
396
- const server = createRouteServer(router, config);
423
+ const server = createRRRoute(router, config);
397
424
  server.registerControllers(registry, controllers);
398
425
  return router;
399
426
  }
@@ -416,7 +443,7 @@ function warnMissingControllers(router, registry, logger) {
416
443
  asLeafAuth,
417
444
  bindAll,
418
445
  bindExpressRoutes,
419
- createRouteServer,
446
+ createRRRoute,
420
447
  defineControllers,
421
448
  getCtx,
422
449
  keyOf,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/routesV3.server.ts"],"sourcesContent":["export * from './routesV3.server';\n","/**\n * routesV3.server.ts\n * -----------------------------------------------------------------------------\n * Bind an Express router/app to a `finalize(...)` registry of AnyLeafs.\n * - Fully typed handlers (params/query/body/output)\n * - Zod parsing + optional output validation\n * - buildCtx runs as a middleware *first*, before all other middlewares\n * - Global, per-route, and cfg-derived middlewares (auth, uploads)\n * - Helper to warn about unimplemented routes\n * - DX helpers to use `ctx` in any middleware with proper types\n */\n\nimport { AnyLeaf, FileField, HttpMethod, InferBody, InferOutput, InferParams, InferQuery, MethodCfg } from '@emeryld/rrroutes-contract';\nimport type * as express from 'express';\nimport type { RequestHandler, Router } from 'express';\nimport type { ZodType } from 'zod';\n\n\n\n/** Shape expected from optional logger implementations. */\nexport type LoggerLike = {\n info?: (...args: any[]) => void;\n warn?: (...args: any[]) => void;\n error?: (...args: any[]) => void;\n debug?: (...args: any[]) => void;\n verbose?: (...args: any[]) => void;\n system?: (...args: any[]) => void;\n log?: (...args: any[]) => void;\n};\n\n// Debug logging --------------------------------------------------------------\nexport type RouteServerDebugMode = 'minimal' | 'complete';\n\ntype RouteServerDebugEventBase = {\n /** Optional logical name assigned via `RouteDef.debugName`. */\n name?: string;\n};\n\nexport type RouteServerDebugEvent =\n | (RouteServerDebugEventBase & {\n type: 'request';\n stage: 'start' | 'success' | 'error';\n method: Uppercase<HttpMethod>;\n path: string;\n url: string;\n durationMs?: number;\n params?: unknown;\n query?: unknown;\n body?: unknown;\n output?: unknown;\n error?: unknown;\n })\n | (RouteServerDebugEventBase & {\n type: 'register';\n method: Uppercase<HttpMethod>;\n path: string;\n })\n | (RouteServerDebugEventBase & {\n type: 'buildCtx';\n stage: 'start' | 'success' | 'error';\n method: Uppercase<HttpMethod>;\n path: string;\n url: string;\n durationMs?: number;\n error?: unknown;\n })\n | (RouteServerDebugEventBase & {\n type: 'handler';\n stage: 'start' | 'success' | 'error';\n method: Uppercase<HttpMethod>;\n path: string;\n url: string;\n durationMs?: number;\n params?: unknown;\n query?: unknown;\n body?: unknown;\n output?: unknown;\n error?: unknown;\n });\n\nexport type RouteServerDebugLogger = (event: RouteServerDebugEvent) => void;\n\n/**\n * Configure server-side debug logging.\n * - Use booleans or `'minimal'/'complete'` for quick toggles.\n * - Pass a custom logger function to redirect structured events.\n * - Provide a map to enable specific event types, opt into verbose payload logging, or restrict logs via `only`.\n */\nexport type RouteServerDebugOptions<Names extends string = string> = RouteServerDebugToggleOptions<Names>;\n\n/**\n * Fine-grained toggle map for server debug logging.\n * Enable individual event types, opt into verbose payload logging, override the logger, or restrict to named routes.\n * Use `RouteDef.debugName` to set the name that `only` will match against.\n */\nexport type RouteServerDebugToggleOptions<Names extends string = string> = Partial<\n Record<RouteServerDebugEvent['type'], boolean>\n> & {\n verbose?: boolean;\n logger?: RouteServerDebugLogger;\n only?: Names[];\n};\n\nconst noopServerDebug: RouteServerDebugLogger = () => {};\n\nconst defaultServerDebug: RouteServerDebugLogger = (event: RouteServerDebugEvent) => {\n if (typeof console === 'undefined') return;\n const fn = console.debug ?? console.log;\n fn?.call(console, '[rrroutes-server]', event);\n};\n\nconst serverDebugEventTypes: RouteServerDebugEvent['type'][] = [\n 'register',\n 'request',\n 'buildCtx',\n 'handler',\n];\n\ntype ServerDebugEmitter<Names extends string> = {\n emit: (event: RouteServerDebugEvent, name?: Names) => void;\n mode: RouteServerDebugMode;\n};\n\nconst noopServerEmit = () => {};\n\nfunction createServerDebugEmitter<Names extends string>(\n option?: RouteServerDebugOptions<Names>,\n): ServerDebugEmitter<Names> {\n const disabled: ServerDebugEmitter<Names> = { emit: noopServerEmit, mode: 'minimal' };\n if (!option) return disabled;\n\n if (typeof option === 'object') {\n const toggles = option as RouteServerDebugToggleOptions<Names>;\n const verbose = Boolean(toggles.verbose);\n const enabledTypes = serverDebugEventTypes.filter((type) => toggles[type]);\n if (enabledTypes.length === 0) {\n return { emit: noopServerEmit, mode: verbose ? 'complete' : 'minimal' };\n }\n const whitelist = new Set<RouteServerDebugEvent['type']>(enabledTypes);\n const onlySet =\n toggles.only && toggles.only.length > 0 ? new Set<Names>(toggles.only) : undefined;\n const logger = toggles.logger ?? defaultServerDebug;\n const emit: ServerDebugEmitter<Names>['emit'] = (event, name) => {\n if (!whitelist.has(event.type)) return;\n if (onlySet && (!name || !onlySet.has(name))) return;\n logger(name ? { ...event, name } : event);\n };\n return { emit, mode: verbose ? 'complete' : 'minimal' };\n }\n\n return disabled;\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Keys + leaf helpers (derive keys from byKey to avoid template-literal pitfalls)\n// ──────────────────────────────────────────────────────────────────────────────\n\n/** Keys like \"GET /v1/foo\" that *actually* exist in the registry */\nexport type KeysOfRegistry<R extends { byKey: Record<string, AnyLeaf> }> = keyof R['byKey'] &\n string;\n\ntype MethodFromKey<K extends string> = K extends `${infer M} ${string}` ? Lowercase<M> : never;\ntype PathFromKey<K extends string> = K extends `${string} ${infer P}` ? P : never;\n\n/** Given a registry and a key, pick the exact leaf for that method+path */\nexport type LeafFromKey<R extends { all: readonly AnyLeaf[] }, K extends string> = Extract<\n R['all'][number],\n { method: MethodFromKey<K> & HttpMethod; path: PathFromKey<K> }\n>;\n\n/** Optional-ify types if your core returns `never` when a schema isn't defined */\ntype Maybe<T> = [T] extends [never] ? undefined : T;\n\n/** Typed params argument exposed to handlers. */\nexport type ArgParams<L extends AnyLeaf> = Maybe<InferParams<L>>;\n/** Typed query argument exposed to handlers. */\nexport type ArgQuery<L extends AnyLeaf> = Maybe<InferQuery<L>>;\n/** Typed body argument exposed to handlers. */\nexport type ArgBody<L extends AnyLeaf> = Maybe<InferBody<L>>;\n\n/**\n * Convenience to compute a `\"METHOD /path\"` key from a leaf.\n * @param leaf Leaf describing the route.\n * @returns Uppercase method + path key.\n */\nexport const keyOf = (leaf: AnyLeaf) => `${leaf.method.toUpperCase()} ${leaf.path}` as const;\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Context typing & DX helpers (so ctx is usable in *any* middleware)\n// ──────────────────────────────────────────────────────────────────────────────\n\n/**\n * Unique symbol used to stash ctx on res.locals.\n * (Symbols are safer than string keys against collisions.)\n */\nexport const CTX_SYMBOL: unique symbol = Symbol.for('typedLeaves.ctx');\n\nconst AFTER_HANDLER_NEXT_SYMBOL: unique symbol = Symbol.for('typedLeaves.afterHandlerNext');\n\nfunction setAfterHandlerNext(res: express.Response, value: boolean) {\n (res.locals as any)[AFTER_HANDLER_NEXT_SYMBOL] = value;\n}\n\nfunction handlerInvokedNext(res: express.Response): boolean {\n return Boolean((res.locals as any)[AFTER_HANDLER_NEXT_SYMBOL]);\n}\n\n/** Response type that *has* a ctx on locals for DX in middlewares */\nexport type ResponseWithCtx<Ctx> =\n // Replace locals with an intersection that guarantees CTX_SYMBOL exists\n Omit<express.Response, 'locals'> & {\n locals: express.Response['locals'] & { [CTX_SYMBOL]: Ctx };\n };\n\n/** A middleware signature that can *use* ctx via `res.locals[CTX_SYMBOL]` */\nexport type CtxRequestHandler<Ctx> = (args: {\n req: express.Request;\n res: express.Response;\n next: express.NextFunction;\n ctx: Ctx;\n}) => any;\n\n/**\n * Safely read ctx from any Response.\n * @param res Express response whose locals contain the ctx symbol.\n * @returns Strongly typed context object.\n */\nexport function getCtx<Ctx = unknown>(res: express.Response): Ctx {\n return (res.locals as any)[CTX_SYMBOL] as Ctx;\n}\n\n/**\n * Wrap a ctx-typed middleware to a plain RequestHandler (for arrays, etc.).\n * @param mw Middleware that expects a typed response with ctx available.\n * @returns Standard Express request handler.\n */\nfunction adaptCtxMw<Ctx>(mw: CtxRequestHandler<Ctx>): RequestHandler {\n return (req, res, next) => {\n try {\n const result = mw({ req, res, next, ctx: getCtx<Ctx>(res) });\n if (result && typeof (result as Promise<unknown>).then === 'function') {\n return (result as Promise<unknown>).catch((err) => next(err));\n }\n return result as any;\n } catch (err) {\n next(err as any);\n return undefined;\n }\n };\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Controller types — object form only (simpler, clearer typings)\n// ──────────────────────────────────────────────────────────────────────────────\n\n/** Typed route handler for a specific leaf */\nexport type Handler<L extends AnyLeaf, Ctx = unknown> = (args: {\n req: express.Request;\n res: express.Response;\n next: express.NextFunction;\n ctx: Ctx;\n params: ArgParams<L>;\n query: ArgQuery<L>;\n body: ArgBody<L>;\n}) => Promise<InferOutput<L>> | InferOutput<L>;\n\n/** Route definition for one key */\nexport type RouteDef<L extends AnyLeaf, Ctx = unknown, Names extends string = string> = {\n /** Middlewares before the handler (run after buildCtx/global/derived) */\n use?: Array<CtxRequestHandler<Ctx>>;\n /** Middlewares after the handler *if* it calls next() */\n after?: Array<CtxRequestHandler<Ctx>>;\n /** Your business logic */\n handler: Handler<L, Ctx>;\n /**\n * Optional logical name used for debug filtering. Pair with `debug.only` so only named routes emit logs.\n */\n debugName?: Names;\n};\n\n/** Map of registry keys -> route defs */\nexport type ControllerMap<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n> = {\n [P in KeysOfRegistry<R>]: RouteDef<LeafFromKey<R, P>, Ctx, Names>;\n};\n\nexport type PartialControllerMap<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n> = Partial<ControllerMap<R, Ctx, Names>>;\n\n// ──────────────────────────────────────────────────────────────────────────────\n/** Options + derivation helpers */\n// ──────────────────────────────────────────────────────────────────────────────\n\nexport type RouteServerConfig<Ctx = unknown, Names extends string = string> = {\n /**\n * Build a request-scoped context. We wrap this in a middleware that runs\n * *first* (before global/derived/route middlewares), and stash it on\n * `res.locals[CTX_SYMBOL]` so *all* later middlewares can use it.\n */\n buildCtx: (req: express.Request, res: express.Response) => Ctx | Promise<Ctx>;\n\n /**\n * Global middlewares for every bound route (run *after* buildCtx).\n * You can write them as ctx-aware middlewares for great DX.\n */\n global?: Array<CtxRequestHandler<Ctx>>;\n\n /**\n * Derive middleware from MethodCfg.\n * - `auth` runs when cfg.authenticated === true (or `when` overrides)\n * - `upload` runs when cfg.bodyFiles has entries\n */\n fromCfg?: {\n auth?: RequestHandler | ((leaf: AnyLeaf) => RequestHandler);\n when?: (cfg: MethodCfg, leaf: AnyLeaf) => { auth?: boolean } | void;\n upload?: (files: FileField[] | undefined, leaf: AnyLeaf) => RequestHandler[];\n };\n\n /** Validate handler return values with outputSchema (default: true) */\n validateOutput?: boolean;\n\n /** Custom responder (default: res.json(data)) */\n send?: (res: express.Response, data: unknown) => void;\n\n /** Optional logger hooks */\n logger?: LoggerLike;\n\n /**\n * Optional debug logging for the request lifecycle.\n * Supports booleans/modes/loggers, or a toggle map with per-event enabling, verbose payload logging,\n * and `only` filters tied to `RouteDef.debugName`.\n */\n debug?: RouteServerDebugOptions<Names>;\n};\n\n/** Default JSON responder (typed to avoid implicit-any diagnostics) */\nconst defaultSend: (res: express.Response, data: unknown) => void = (res, data) => {\n res.json(data as any);\n};\n\n/**\n * Normalize `auth` into a RequestHandler (avoids union-narrowing issues).\n * @param auth Static middleware or factory returning one for the current leaf.\n * @param leaf Leaf being registered.\n * @returns Request handler or undefined when no auth is required.\n */\nfunction resolveAuth(\n auth: RequestHandler | ((leaf: AnyLeaf) => RequestHandler) | undefined,\n leaf: AnyLeaf,\n): RequestHandler | undefined {\n if (!auth) return undefined;\n return (auth as (l: AnyLeaf) => RequestHandler).length === 1\n ? (auth as (l: AnyLeaf) => RequestHandler)(leaf)\n : (auth as RequestHandler);\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Core builder\n// ──────────────────────────────────────────────────────────────────────────────\n\nconst REGISTERED_ROUTES_SYMBOL = Symbol.for('routesV3.registeredRoutes');\n\ntype RegisteredRouteStore = Set<string>;\n\n/**\n * Retrieve or initialize the shared store of registered route keys.\n * @param router Express router/application that carries previously registered keys.\n * @returns Set of string keys describing registered routes.\n */\nfunction getRegisteredRouteStore(router: Router): RegisteredRouteStore {\n const existing = (router as any)[REGISTERED_ROUTES_SYMBOL] as RegisteredRouteStore | undefined;\n if (existing) return existing;\n const store: RegisteredRouteStore = new Set();\n (router as any)[REGISTERED_ROUTES_SYMBOL] = store;\n return store;\n}\n\n/**\n * Inspect the Express layer stack to discover already-registered routes.\n * @param appOrRouter Express application or router to inspect.\n * @returns All keys in the form `\"METHOD /path\"` found on the stack.\n */\nfunction collectRoutesFromStack(appOrRouter: Router): string[] {\n const result: string[] = [];\n const stack: any[] =\n (appOrRouter as any).stack ??\n ((appOrRouter as any)._router ? (appOrRouter as any)._router.stack : undefined) ??\n [];\n\n if (!Array.isArray(stack)) return result;\n\n for (const layer of stack) {\n const route = layer && layer.route;\n if (!route) continue;\n\n const paths = Array.isArray(route.path) ? route.path : [route.path];\n const methodEntries = Object.entries(route.methods ?? {}).filter(([, enabled]) => enabled);\n\n for (const path of paths) {\n for (const [method] of methodEntries) {\n result.push(`${method.toUpperCase()} ${path}`);\n }\n }\n }\n\n return result;\n}\n\n/** Runtime helpers returned by `createRouteServer`. */\nexport type RouteServer<Ctx = unknown, Names extends string = string> = {\n router: Router;\n register<L extends AnyLeaf>(leaf: L, def: RouteDef<L, Ctx, Names>): void;\n registerControllers<R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> }>(\n registry: R,\n controllers: PartialControllerMap<R, Ctx, Names>,\n ): void;\n warnMissingControllers<R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> }>(\n registry: R,\n logger: { warn: (...args: any[]) => void },\n ): void;\n getRegisteredKeys(): string[];\n};\n\n/**\n * Create an Express binding helper that keeps routes and controllers in sync.\n * @param router Express router or app to register handlers on.\n * @param config Optional configuration controlling ctx building, auth, uploads, etc.\n * @returns Object with helpers to register controllers and inspect registered keys.\n */\nexport function createRouteServer<Ctx = unknown, Names extends string = string>(\n router: Router,\n config: RouteServerConfig<Ctx, Names>,\n): RouteServer<Ctx, Names> {\n const validateOutput = config.validateOutput ?? true;\n const send = config.send ?? defaultSend;\n const logger = config.logger;\n const { emit: emitDebug, mode: debugMode } = createServerDebugEmitter<Names>(config.debug);\n const isVerboseDebug = debugMode === 'complete';\n const decorateDebugEvent = <T extends RouteServerDebugEvent>(\n event: T,\n details?: Partial<RouteServerDebugEvent>,\n ): RouteServerDebugEvent => {\n if (!isVerboseDebug || !details) return event;\n return { ...event, ...details } as RouteServerDebugEvent;\n };\n\n const globalMws = (config.global ?? []).map((mw) => adaptCtxMw<Ctx>(mw));\n const registered = getRegisteredRouteStore(router);\n\n const buildDerived = (leaf: AnyLeaf): RequestHandler[] => {\n const derived: RequestHandler[] = [];\n const decision = config.fromCfg?.when?.(leaf.cfg, leaf) ?? {};\n const needsAuth = typeof decision.auth === 'boolean' ? decision.auth : !!leaf.cfg.authenticated;\n\n if (needsAuth && config.fromCfg?.auth) {\n const authMw = resolveAuth(config.fromCfg.auth, leaf);\n if (authMw) derived.push(authMw);\n }\n\n if (\n config.fromCfg?.upload &&\n Array.isArray(leaf.cfg.bodyFiles) &&\n leaf.cfg.bodyFiles.length > 0\n ) {\n derived.push(...config.fromCfg.upload(leaf.cfg.bodyFiles, leaf));\n }\n\n return derived;\n };\n\n /** Register a single leaf/controller pair on the underlying router. */\n function register<L extends AnyLeaf>(leaf: L, def: RouteDef<L, Ctx, Names>) {\n const method = leaf.method as HttpMethod;\n const methodUpper = method.toUpperCase() as Uppercase<HttpMethod>;\n const path = leaf.path as string;\n const key = keyOf(leaf);\n const debugName = def.debugName as Names | undefined;\n const emit = (event: RouteServerDebugEvent) => emitDebug(event, debugName);\n emit({ type: 'register', method: methodUpper, path });\n\n const routeSpecific = (def?.use ?? []).map((mw) => adaptCtxMw<Ctx>(mw));\n const derived = buildDerived(leaf);\n const ctxMw: RequestHandler = async (req, res, next) => {\n const requestUrl = req.originalUrl ?? path;\n const startedAt = Date.now();\n emit({ type: 'buildCtx', stage: 'start', method: methodUpper, path, url: requestUrl });\n try {\n const ctx = await config.buildCtx(req, res);\n (res.locals as any)[CTX_SYMBOL] = ctx;\n emit({\n type: 'buildCtx',\n stage: 'success',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n });\n next();\n } catch (err) {\n emit({\n type: 'buildCtx',\n stage: 'error',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n error: err,\n });\n logger?.error?.('buildCtx error', err);\n next(err as any);\n }\n };\n const before: RequestHandler[] = [\n ...[ctxMw],\n ...globalMws,\n ...derived,\n ...routeSpecific,\n ];\n\n const wrapped: RequestHandler = async (req, res, next) => {\n const requestUrl = req.originalUrl ?? path;\n const startedAt = Date.now();\n emit({ type: 'request', stage: 'start', method: methodUpper, path, url: requestUrl });\n let params: ArgParams<L> | undefined;\n let query: ArgQuery<L> | undefined;\n let body: ArgBody<L> | undefined;\n let responsePayload: InferOutput<L> | undefined;\n let hasResponsePayload = false;\n setAfterHandlerNext(res, false);\n let handlerCalledNext = false;\n const downstreamNext: express.NextFunction = (err?: any) => {\n handlerCalledNext = true;\n setAfterHandlerNext(res, true);\n next(err);\n };\n\n try {\n logger?.info?.(`${methodUpper}@${path} (${requestUrl})`);\n\n const ctx = (res.locals as any)[CTX_SYMBOL] as Ctx;\n\n params = (\n leaf.cfg.paramsSchema\n ? (leaf.cfg.paramsSchema as ZodType).parse(req.params)\n : Object.keys(req.params || {}).length\n ? (req.params as any)\n : undefined\n ) as ArgParams<L>;\n\n try {\n query = leaf.cfg.querySchema\n ? (leaf.cfg.querySchema as ZodType).parse(req.query)\n : Object.keys(req.query || {}).length\n ? (req.query as any)\n : undefined;\n } catch (e) {\n logger?.error?.('Query parsing error', {\n path,\n method: methodUpper,\n error: e,\n raw: JSON.stringify(req.query),\n });\n throw e;\n }\n\n body = (\n leaf.cfg.bodySchema\n ? (leaf.cfg.bodySchema as ZodType).parse(req.body)\n : req.body !== undefined\n ? (req.body as any)\n : undefined\n ) as ArgBody<L>;\n\n logger?.verbose?.(`${methodUpper}@${path} (${requestUrl})`, {\n params,\n query,\n body,\n });\n\n const handlerStartedAt = Date.now();\n emit(\n decorateDebugEvent(\n {\n type: 'handler',\n stage: 'start',\n method: methodUpper,\n path,\n url: requestUrl,\n },\n isVerboseDebug ? { params, query, body } : undefined,\n ),\n );\n\n let result;\n try {\n result = await def.handler({\n req,\n res,\n next: downstreamNext,\n ctx,\n params: params as ArgParams<L>,\n query: query as ArgQuery<L>,\n body: body as ArgBody<L>,\n });\n emit(\n decorateDebugEvent(\n {\n type: 'handler',\n stage: 'success',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - handlerStartedAt,\n },\n isVerboseDebug\n ? {\n params,\n query,\n body,\n ...(result !== undefined ? { output: result } : {}),\n }\n : undefined,\n ),\n );\n } catch (e) {\n emit(\n decorateDebugEvent(\n {\n type: 'handler',\n stage: 'error',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - handlerStartedAt,\n error: e,\n },\n isVerboseDebug ? { params, query, body } : undefined,\n ),\n );\n logger?.error?.('Handler error', e);\n throw e;\n }\n\n if (!res.headersSent) {\n if (result !== undefined) {\n const out =\n validateOutput && leaf.cfg.outputSchema\n ? (leaf.cfg.outputSchema as ZodType).parse(result)\n : result;\n responsePayload = out as InferOutput<L>;\n hasResponsePayload = true;\n logger?.verbose?.(`${methodUpper}@${path} result`, out);\n send(res, out);\n } else if (!handlerCalledNext) {\n next();\n }\n }\n\n emit(\n decorateDebugEvent(\n {\n type: 'request',\n stage: 'success',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug\n ? {\n params,\n query,\n body,\n ...(hasResponsePayload ? { output: responsePayload } : {}),\n }\n : undefined,\n ),\n );\n } catch (err) {\n emit(\n decorateDebugEvent(\n {\n type: 'request',\n stage: 'error',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n error: err,\n },\n isVerboseDebug ? { params, query, body } : undefined,\n ),\n );\n logger?.error?.('Route error', err);\n next(err as any);\n }\n };\n\n const after = (def?.after ?? []).map((mw) => {\n const adapted = adaptCtxMw<Ctx>(mw);\n return (req: express.Request, res: express.Response, next: express.NextFunction) => {\n if (!handlerInvokedNext(res)) {\n next();\n return;\n }\n adapted(req, res, next);\n };\n });\n (router as any)[method](path, ...before, wrapped, ...after);\n registered.add(key);\n }\n\n /**\n * Register controller definitions for the provided keys.\n * @param registry Finalized registry of leaves.\n * @param controllers Partial controller map keyed by `\"METHOD /path\"`.\n */\n function registerControllers<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n >(registry: R, controllers: PartialControllerMap<R, Ctx, Names>) {\n (Object.keys(controllers) as Array<KeysOfRegistry<R>>).forEach((key) => {\n const leaf = registry.byKey[key] as unknown as LeafFromKey<R, typeof key> | undefined;\n if (!leaf) {\n logger?.warn?.(`No leaf found for controller key: ${key}. Not registering route.`);\n return;\n }\n const def = controllers[key];\n if (!def) return;\n register(leaf as LeafFromKey<R, typeof key>, def);\n });\n }\n\n /**\n * Warn about leaves that do not have a registered controller.\n * @param registry Finalized registry of leaves.\n * @param warnLogger Logger used for warning output.\n */\n function warnMissing<R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> }>(\n registry: R,\n warnLogger: { warn: (...args: any[]) => void },\n ) {\n const registeredFromStore = new Set<string>(Array.from(registered));\n if (registeredFromStore.size === 0) {\n collectRoutesFromStack(router).forEach((key) => registeredFromStore.add(key));\n }\n for (const leaf of registry.all) {\n const key = keyOf(leaf);\n if (!registeredFromStore.has(key)) {\n warnLogger.warn(`No controller registered for route: ${key}`);\n }\n }\n }\n\n return {\n router,\n register,\n registerControllers,\n warnMissingControllers: warnMissing,\n getRegisteredKeys: () => Array.from(registered),\n };\n}\n\n/**\n * Bind only the controllers that are present in the provided map.\n * @param router Express router or app.\n * @param registry Finalized registry produced by `finalize(...)`.\n * @param controllers Partial map of controllers keyed by `\"METHOD /path\"`.\n * @param config Optional route server configuration.\n * @returns The same router instance for chaining.\n */\nexport function bindExpressRoutes<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n>(\n router: Router,\n registry: R,\n controllers: PartialControllerMap<R, Ctx, Names>,\n config: RouteServerConfig<Ctx, Names>,\n) {\n const server = createRouteServer<Ctx, Names>(router, config);\n server.registerControllers(registry, controllers);\n return router;\n}\n\n/**\n * Bind controllers for every leaf. Missing entries fail at compile time.\n * @param router Express router or app.\n * @param registry Finalized registry produced by `finalize(...)`.\n * @param controllers Complete map of controllers keyed by `\"METHOD /path\"`.\n * @param config Optional route server configuration.\n * @returns The same router instance for chaining.\n */\nexport function bindAll<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n>(\n router: Router,\n registry: R,\n controllers: { [K in KeysOfRegistry<R>]: RouteDef<LeafFromKey<R, K>, Ctx, Names> },\n config: RouteServerConfig<Ctx, Names>,\n) {\n const server = createRouteServer<Ctx, Names>(router, config);\n server.registerControllers(registry, controllers);\n return router;\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// DX helpers\n// ──────────────────────────────────────────────────────────────────────────────\n\n/**\n * Helper for great IntelliSense when authoring controller maps.\n * @returns Function that enforces key names while preserving partial flexibility.\n */\nexport const defineControllers =\n <\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n >() =>\n <M extends PartialControllerMap<R, Ctx, Names>>(m: M) =>\n m;\n\n/**\n * Wrap a plain RequestHandler as an auth factory compatible with `fromCfg.auth`.\n * @param mw Middleware invoked for any leaf that requires authentication.\n * @param _leaf Leaf metadata (ignored, but provided to match factory signature).\n * @returns Factory that ignores the leaf and returns the same middleware.\n */\nexport const asLeafAuth =\n (mw: RequestHandler) =>\n (_leaf: AnyLeaf): RequestHandler =>\n mw;\n\n/**\n * Warn about leaves that don't have controllers.\n * Call this during startup to surface missing routes.\n * @param router Express router or app to inspect.\n * @param registry Finalized registry produced by `finalize(...)`.\n * @param logger Logger where warnings are emitted.\n */\nexport function warnMissingControllers<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n>(router: Router, registry: R, logger: { warn: (...args: any[]) => void }) {\n const registeredStore = (router as any)[REGISTERED_ROUTES_SYMBOL] as Set<string> | undefined;\n const initial = registeredStore ? Array.from(registeredStore) : collectRoutesFromStack(router);\n const registeredKeys = new Set<string>(initial);\n\n for (const leaf of registry.all) {\n const k = keyOf(leaf);\n if (!registeredKeys.has(k)) {\n logger.warn(`No controller registered for route: ${k}`);\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/routesV3.server.ts"],"sourcesContent":["export * from './routesV3.server';\n","/**\n * routesV3.server.ts\n * -----------------------------------------------------------------------------\n * Bind an Express router/app to a `finalize(...)` registry of AnyLeafs.\n * - Fully typed handlers (params/query/body/output)\n * - Zod parsing + optional output validation\n * - buildCtx runs as a middleware *first*, before all other middlewares\n * - Global, per-route, and cfg-derived middlewares (auth, uploads)\n * - Helper to warn about unimplemented routes\n * - DX helpers to use `ctx` in any middleware with proper types\n */\n\nimport { AnyLeaf, FileField, HttpMethod, InferBody, InferOutput, InferParams, InferQuery, MethodCfg } from '@emeryld/rrroutes-contract';\nimport type * as express from 'express';\nimport type { RequestHandler, Router } from 'express';\nimport type { ZodType } from 'zod';\n\n\n\n/** Shape expected from optional logger implementations. */\nexport type LoggerLike = {\n info?: (...args: any[]) => void;\n warn?: (...args: any[]) => void;\n error?: (...args: any[]) => void;\n debug?: (...args: any[]) => void;\n verbose?: (...args: any[]) => void;\n system?: (...args: any[]) => void;\n log?: (...args: any[]) => void;\n};\n\ntype RoutesLoggerCarrier = {\n routesLogger?: LoggerLike;\n};\n\ntype CtxWithRoutesLogger<Ctx> = Ctx & RoutesLoggerCarrier;\n\n// Debug logging --------------------------------------------------------------\nexport type RouteServerDebugMode = 'minimal' | 'complete';\n\ntype RouteServerDebugEventBase = {\n /** Optional logical name assigned via `RouteDef.debug?.debugName` (or `RouteDef.debugName`). */\n name?: string;\n};\n\nexport type RouteServerDebugEvent =\n | (RouteServerDebugEventBase & {\n type: 'request';\n stage: 'start' | 'success' | 'error';\n method: Uppercase<HttpMethod>;\n path: string;\n url: string;\n durationMs?: number;\n params?: unknown;\n query?: unknown;\n body?: unknown;\n output?: unknown;\n error?: unknown;\n })\n | (RouteServerDebugEventBase & {\n type: 'register';\n method: Uppercase<HttpMethod>;\n path: string;\n })\n | (RouteServerDebugEventBase & {\n type: 'buildCtx';\n stage: 'start' | 'success' | 'error';\n method: Uppercase<HttpMethod>;\n path: string;\n url: string;\n durationMs?: number;\n error?: unknown;\n })\n | (RouteServerDebugEventBase & {\n type: 'handler';\n stage: 'start' | 'success' | 'error';\n method: Uppercase<HttpMethod>;\n path: string;\n url: string;\n durationMs?: number;\n params?: unknown;\n query?: unknown;\n body?: unknown;\n output?: unknown;\n error?: unknown;\n });\n\nexport type RouteServerDebugLogger = (event: RouteServerDebugEvent) => void;\n\n/**\n * Configure server-side debug logging.\n * - Use booleans or `'minimal'/'complete'` for quick toggles.\n * - Pass a custom logger function to redirect structured events.\n * - Provide a map to enable specific event types, opt into verbose payload logging, or restrict logs via `only`.\n */\nexport type RouteServerDebugOptions<Names extends string = string> = RouteServerDebugToggleOptions<Names>;\n\n/**\n * Fine-grained toggle map for server debug logging.\n * Enable individual event types, opt into verbose payload logging, override the logger, or restrict to named routes.\n * Use `RouteDef.debug?.debugName` (or the deprecated `RouteDef.debugName`) to set the name that `only` will match against.\n */\nexport type RouteServerDebugToggleOptions<Names extends string = string> = Partial<\n Record<RouteServerDebugEvent['type'], boolean>\n> & {\n verbose?: boolean;\n logger?: RouteServerDebugLogger;\n only?: Names[];\n};\n\n/**\n * Per-route debug overrides. Same toggles as `RouteServerDebugOptions`, but limited to a single route\n * and therefore replaces the `only` filter with a local `debugName`.\n */\nexport type RouteDefDebugOptions<Names extends string = string> = Omit<\n RouteServerDebugToggleOptions<Names>,\n 'only'\n> & {\n debugName?: Names;\n};\n\nconst noopServerDebug: RouteServerDebugLogger = () => {};\n\nconst defaultServerDebug: RouteServerDebugLogger = (event: RouteServerDebugEvent) => {\n if (typeof console === 'undefined') return;\n const fn = console.debug ?? console.log;\n fn?.call(console, '[rrroutes-server]', event);\n};\n\nconst serverDebugEventTypes: RouteServerDebugEvent['type'][] = [\n 'register',\n 'request',\n 'buildCtx',\n 'handler',\n];\n\ntype ServerDebugEmitter<Names extends string> = {\n emit: (event: RouteServerDebugEvent, name?: Names) => void;\n mode: RouteServerDebugMode;\n};\n\nconst noopServerEmit = () => {};\n\nfunction createServerDebugEmitter<Names extends string>(\n option?: RouteServerDebugOptions<Names>,\n): ServerDebugEmitter<Names> {\n const disabled: ServerDebugEmitter<Names> = { emit: noopServerEmit, mode: 'minimal' };\n if (!option) return disabled;\n\n if (typeof option === 'object') {\n const toggles = option as RouteServerDebugToggleOptions<Names>;\n const verbose = Boolean(toggles.verbose);\n const enabledTypes = serverDebugEventTypes.filter((type) => toggles[type]);\n if (enabledTypes.length === 0) {\n return { emit: noopServerEmit, mode: verbose ? 'complete' : 'minimal' };\n }\n const whitelist = new Set<RouteServerDebugEvent['type']>(enabledTypes);\n const onlySet =\n toggles.only && toggles.only.length > 0 ? new Set<Names>(toggles.only) : undefined;\n const logger = toggles.logger ?? defaultServerDebug;\n const emit: ServerDebugEmitter<Names>['emit'] = (event, name) => {\n if (!whitelist.has(event.type)) return;\n if (onlySet && (!name || !onlySet.has(name))) return;\n logger(name ? { ...event, name } : event);\n };\n return { emit, mode: verbose ? 'complete' : 'minimal' };\n }\n\n return disabled;\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Keys + leaf helpers (derive keys from byKey to avoid template-literal pitfalls)\n// ──────────────────────────────────────────────────────────────────────────────\n\n/** Keys like \"GET /v1/foo\" that *actually* exist in the registry */\nexport type KeysOfRegistry<R extends { byKey: Record<string, AnyLeaf> }> = keyof R['byKey'] &\n string;\n\ntype MethodFromKey<K extends string> = K extends `${infer M} ${string}` ? Lowercase<M> : never;\ntype PathFromKey<K extends string> = K extends `${string} ${infer P}` ? P : never;\n\n/** Given a registry and a key, pick the exact leaf for that method+path */\nexport type LeafFromKey<R extends { all: readonly AnyLeaf[] }, K extends string> = Extract<\n R['all'][number],\n { method: MethodFromKey<K> & HttpMethod; path: PathFromKey<K> }\n>;\n\n/** Optional-ify types if your core returns `never` when a schema isn't defined */\ntype Maybe<T> = [T] extends [never] ? undefined : T;\n\n/** Typed params argument exposed to handlers. */\nexport type ArgParams<L extends AnyLeaf> = Maybe<InferParams<L>>;\n/** Typed query argument exposed to handlers. */\nexport type ArgQuery<L extends AnyLeaf> = Maybe<InferQuery<L>>;\n/** Typed body argument exposed to handlers. */\nexport type ArgBody<L extends AnyLeaf> = Maybe<InferBody<L>>;\n\n/**\n * Convenience to compute a `\"METHOD /path\"` key from a leaf.\n * @param leaf Leaf describing the route.\n * @returns Uppercase method + path key.\n */\nexport const keyOf = (leaf: AnyLeaf) => `${leaf.method.toUpperCase()} ${leaf.path}` as const;\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Context typing & DX helpers (so ctx is usable in *any* middleware)\n// ──────────────────────────────────────────────────────────────────────────────\n\n/**\n * Unique symbol used to stash ctx on res.locals.\n * (Symbols are safer than string keys against collisions.)\n */\nexport const CTX_SYMBOL: unique symbol = Symbol.for('typedLeaves.ctx');\n\nconst AFTER_HANDLER_NEXT_SYMBOL: unique symbol = Symbol.for('typedLeaves.afterHandlerNext');\n\nfunction setAfterHandlerNext(res: express.Response, value: boolean) {\n (res.locals as any)[AFTER_HANDLER_NEXT_SYMBOL] = value;\n}\n\nfunction handlerInvokedNext(res: express.Response): boolean {\n return Boolean((res.locals as any)[AFTER_HANDLER_NEXT_SYMBOL]);\n}\n\n/** Response type that *has* a ctx on locals for DX in middlewares */\nexport type ResponseWithCtx<Ctx> =\n // Replace locals with an intersection that guarantees CTX_SYMBOL exists\n Omit<express.Response, 'locals'> & {\n locals: express.Response['locals'] & { [CTX_SYMBOL]: CtxWithRoutesLogger<Ctx> };\n };\n\n/** A middleware signature that can *use* ctx via `res.locals[CTX_SYMBOL]` */\nexport type CtxRequestHandler<Ctx> = (args: {\n req: express.Request;\n res: express.Response;\n next: express.NextFunction;\n ctx: CtxWithRoutesLogger<Ctx>;\n}) => any;\n\n/**\n * Safely read ctx from any Response.\n * @param res Express response whose locals contain the ctx symbol.\n * @returns Strongly typed context object.\n */\nexport function getCtx<Ctx = unknown>(res: express.Response): CtxWithRoutesLogger<Ctx> {\n return (res.locals as any)[CTX_SYMBOL] as CtxWithRoutesLogger<Ctx>;\n}\n\n/**\n * Wrap a ctx-typed middleware to a plain RequestHandler (for arrays, etc.).\n * @param mw Middleware that expects a typed response with ctx available.\n * @returns Standard Express request handler.\n */\nfunction adaptCtxMw<Ctx>(mw: CtxRequestHandler<Ctx>): RequestHandler {\n return (req, res, next) => {\n try {\n const result = mw({ req, res, next, ctx: getCtx<Ctx>(res) });\n if (result && typeof (result as Promise<unknown>).then === 'function') {\n return (result as Promise<unknown>).catch((err) => next(err));\n }\n return result as any;\n } catch (err) {\n next(err as any);\n return undefined;\n }\n };\n}\n\nfunction adaptAfterMw<Ctx>(mw: CtxRequestHandler<Ctx>): RequestHandler {\n const adapted = adaptCtxMw<Ctx>(mw);\n return (req: express.Request, res: express.Response, next: express.NextFunction) => {\n if (!handlerInvokedNext(res)) {\n next();\n return;\n }\n adapted(req, res, next);\n };\n}\n\nfunction mapAfterMiddleware<Ctx>(mws?: Array<CtxRequestHandler<Ctx>>): RequestHandler[] {\n return (mws ?? []).map((mw) => adaptAfterMw<Ctx>(mw));\n}\n\nfunction logHandlerDebugWithRoutesLogger(\n logger: LoggerLike | undefined,\n event: RouteServerDebugEvent,\n) {\n if (!logger || event.type !== 'handler') return;\n const payload: [string, RouteServerDebugEvent] = [\n `[rrroutes-server][handler:${event.stage}] ${event.method} ${event.path}`,\n event,\n ];\n if (event.stage === 'error') {\n (\n logger.error ??\n logger.warn ??\n logger.debug ??\n logger.info ??\n logger.log ??\n logger.system\n )?.call(logger, ...payload);\n return;\n }\n (\n logger.debug ??\n logger.verbose ??\n logger.info ??\n logger.log ??\n logger.system\n )?.call(logger, ...payload);\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Controller types — object form only (simpler, clearer typings)\n// ──────────────────────────────────────────────────────────────────────────────\n\n/** Typed route handler for a specific leaf */\nexport type Handler<L extends AnyLeaf, Ctx = unknown> = (args: {\n req: express.Request;\n res: express.Response;\n next: express.NextFunction;\n ctx: CtxWithRoutesLogger<Ctx>;\n params: ArgParams<L>;\n query: ArgQuery<L>;\n body: ArgBody<L>;\n}) => Promise<InferOutput<L>> | InferOutput<L>;\n\n/** Route definition for one key */\nexport type RouteDef<L extends AnyLeaf, Ctx = unknown, Names extends string = string> = {\n /** Middlewares before the handler (run after buildCtx/global/derived) */\n use?: Array<CtxRequestHandler<Ctx>>;\n /** Middlewares after the handler *if* it calls next() */\n after?: Array<CtxRequestHandler<Ctx>>;\n /** Your business logic */\n handler: Handler<L, Ctx>;\n /**\n * Optional per-route debug overrides. When provided, these replace the global debug options.\n */\n debug?: RouteDefDebugOptions<Names>;\n /**\n * Optional logical name used for debug filtering. Prefer `debug.debugName`; this field remains for backwards compatibility.\n */\n debugName?: Names;\n};\n\n/** Map of registry keys -> route defs */\nexport type ControllerMap<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n> = {\n [P in KeysOfRegistry<R>]: RouteDef<LeafFromKey<R, P>, Ctx, Names>;\n};\n\nexport type PartialControllerMap<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n> = Partial<ControllerMap<R, Ctx, Names>>;\n\n// ──────────────────────────────────────────────────────────────────────────────\n/** Options + derivation helpers */\n// ──────────────────────────────────────────────────────────────────────────────\n\nexport type RouteServerConfig<Ctx = unknown, Names extends string = string> = {\n /**\n * Build a request-scoped context. We wrap this in a middleware that runs\n * *first* (before global/derived/route middlewares), and stash it on\n * `res.locals[CTX_SYMBOL]` so *all* later middlewares can use it.\n * You can optionally include `routesLogger` to override handler debug logging per request.\n */\n buildCtx: (\n req: express.Request,\n res: express.Response,\n ) => CtxWithRoutesLogger<Ctx> | Promise<CtxWithRoutesLogger<Ctx>>;\n\n /**\n * Global middlewares for every bound route (run *after* buildCtx). Prefer `globalMiddleware.before`.\n */\n global?: Array<CtxRequestHandler<Ctx>>;\n\n /**\n * Grouped global middlewares that run *before* or *after* the handler for every route.\n */\n globalMiddleware?: {\n before?: Array<CtxRequestHandler<Ctx>>;\n after?: Array<CtxRequestHandler<Ctx>>;\n };\n\n /**\n * Derive middleware from MethodCfg.\n * - `auth` runs when cfg.authenticated === true (or `when` overrides)\n * - `upload` runs when cfg.bodyFiles has entries\n */\n fromCfg?: {\n auth?: RequestHandler | ((leaf: AnyLeaf) => RequestHandler);\n when?: (cfg: MethodCfg, leaf: AnyLeaf) => { auth?: boolean } | void;\n upload?: (files: FileField[] | undefined, leaf: AnyLeaf) => RequestHandler[];\n };\n\n /** Validate handler return values with outputSchema (default: true) */\n validateOutput?: boolean;\n\n /** Custom responder (default: res.json(data)) */\n send?: (res: express.Response, data: unknown) => void;\n\n /** Optional logger hooks */\n logger?: LoggerLike;\n\n /**\n * Optional debug logging for the request lifecycle.\n * Supports booleans/modes/loggers, or a toggle map with per-event enabling, verbose payload logging,\n * and `only` filters tied to `RouteDef.debug?.debugName` (or the deprecated `RouteDef.debugName`).\n */\n debug?: RouteServerDebugOptions<Names>;\n};\n\n/** Default JSON responder (typed to avoid implicit-any diagnostics) */\nconst defaultSend: (res: express.Response, data: unknown) => void = (res, data) => {\n res.json(data as any);\n};\n\n/**\n * Normalize `auth` into a RequestHandler (avoids union-narrowing issues).\n * @param auth Static middleware or factory returning one for the current leaf.\n * @param leaf Leaf being registered.\n * @returns Request handler or undefined when no auth is required.\n */\nfunction resolveAuth(\n auth: RequestHandler | ((leaf: AnyLeaf) => RequestHandler) | undefined,\n leaf: AnyLeaf,\n): RequestHandler | undefined {\n if (!auth) return undefined;\n return (auth as (l: AnyLeaf) => RequestHandler).length === 1\n ? (auth as (l: AnyLeaf) => RequestHandler)(leaf)\n : (auth as RequestHandler);\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Core builder\n// ──────────────────────────────────────────────────────────────────────────────\n\nconst REGISTERED_ROUTES_SYMBOL = Symbol.for('routesV3.registeredRoutes');\n\ntype RegisteredRouteStore = Set<string>;\n\n/**\n * Retrieve or initialize the shared store of registered route keys.\n * @param router Express router/application that carries previously registered keys.\n * @returns Set of string keys describing registered routes.\n */\nfunction getRegisteredRouteStore(router: Router): RegisteredRouteStore {\n const existing = (router as any)[REGISTERED_ROUTES_SYMBOL] as RegisteredRouteStore | undefined;\n if (existing) return existing;\n const store: RegisteredRouteStore = new Set();\n (router as any)[REGISTERED_ROUTES_SYMBOL] = store;\n return store;\n}\n\n/**\n * Inspect the Express layer stack to discover already-registered routes.\n * @param appOrRouter Express application or router to inspect.\n * @returns All keys in the form `\"METHOD /path\"` found on the stack.\n */\nfunction collectRoutesFromStack(appOrRouter: Router): string[] {\n const result: string[] = [];\n const stack: any[] =\n (appOrRouter as any).stack ??\n ((appOrRouter as any)._router ? (appOrRouter as any)._router.stack : undefined) ??\n [];\n\n if (!Array.isArray(stack)) return result;\n\n for (const layer of stack) {\n const route = layer && layer.route;\n if (!route) continue;\n\n const paths = Array.isArray(route.path) ? route.path : [route.path];\n const methodEntries = Object.entries(route.methods ?? {}).filter(([, enabled]) => enabled);\n\n for (const path of paths) {\n for (const [method] of methodEntries) {\n result.push(`${method.toUpperCase()} ${path}`);\n }\n }\n }\n\n return result;\n}\n\n/** Runtime helpers returned by `createRRRoute`. */\nexport type RouteServer<Ctx = unknown, Names extends string = string> = {\n router: Router;\n register<L extends AnyLeaf>(leaf: L, def: RouteDef<L, Ctx, Names>): void;\n registerControllers<R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> }>(\n registry: R,\n controllers: PartialControllerMap<R, Ctx, Names>,\n ): void;\n warnMissingControllers<R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> }>(\n registry: R,\n logger: { warn: (...args: any[]) => void },\n ): void;\n getRegisteredKeys(): string[];\n};\n\n/**\n * Create an Express binding helper that keeps routes and controllers in sync.\n * @param router Express router or app to register handlers on.\n * @param config Optional configuration controlling ctx building, auth, uploads, etc.\n * @returns Object with helpers to register controllers and inspect registered keys.\n */\nexport function createRRRoute<Ctx = unknown, Names extends string = string>(\n router: Router,\n config: RouteServerConfig<Ctx, Names>,\n): RouteServer<Ctx, Names> {\n const validateOutput = config.validateOutput ?? true;\n const send = config.send ?? defaultSend;\n const logger = config.logger;\n const { emit: defaultEmitDebug, mode: defaultDebugMode } =\n createServerDebugEmitter<Names>(config.debug);\n const decorateDebugEvent = <T extends RouteServerDebugEvent>(\n isVerbose: boolean,\n event: T,\n details?: Partial<RouteServerDebugEvent>,\n ): RouteServerDebugEvent => {\n if (!isVerbose || !details) return event;\n return { ...event, ...details } as RouteServerDebugEvent;\n };\n\n const globalBeforeMws = [...(config.global ?? []), ...(config.globalMiddleware?.before ?? [])].map(\n (mw) => adaptCtxMw<Ctx>(mw),\n );\n const globalAfterMws = mapAfterMiddleware<Ctx>(config.globalMiddleware?.after);\n const registered = getRegisteredRouteStore(router);\n\n const buildDerived = (leaf: AnyLeaf): RequestHandler[] => {\n const derived: RequestHandler[] = [];\n const decision = config.fromCfg?.when?.(leaf.cfg, leaf) ?? {};\n const needsAuth = typeof decision.auth === 'boolean' ? decision.auth : !!leaf.cfg.authenticated;\n\n if (needsAuth && config.fromCfg?.auth) {\n const authMw = resolveAuth(config.fromCfg.auth, leaf);\n if (authMw) derived.push(authMw);\n }\n\n if (\n config.fromCfg?.upload &&\n Array.isArray(leaf.cfg.bodyFiles) &&\n leaf.cfg.bodyFiles.length > 0\n ) {\n derived.push(...config.fromCfg.upload(leaf.cfg.bodyFiles, leaf));\n }\n\n return derived;\n };\n\n /** Register a single leaf/controller pair on the underlying router. */\n function register<L extends AnyLeaf>(leaf: L, def: RouteDef<L, Ctx, Names>) {\n const method = leaf.method as HttpMethod;\n const methodUpper = method.toUpperCase() as Uppercase<HttpMethod>;\n const path = leaf.path as string;\n const key = keyOf(leaf);\n const defDebug = def.debug;\n let debugName = def.debugName as Names | undefined;\n let routeDebugEmitter: ServerDebugEmitter<Names> | undefined;\n if (defDebug) {\n const { debugName: overrideName, ...rest } = defDebug;\n const hasOverrides = Object.values(rest).some((value) => value !== undefined);\n if (hasOverrides) {\n routeDebugEmitter = createServerDebugEmitter<Names>(\n rest as RouteServerDebugOptions<Names>,\n );\n }\n debugName = (overrideName ?? debugName) as Names | undefined;\n }\n const activeEmit = routeDebugEmitter?.emit ?? defaultEmitDebug;\n const activeDebugMode = routeDebugEmitter?.mode ?? defaultDebugMode;\n const emit = (event: RouteServerDebugEvent) => activeEmit(event, debugName);\n const isVerboseDebug = activeDebugMode === 'complete';\n emit({ type: 'register', method: methodUpper, path });\n\n const routeSpecific = (def?.use ?? []).map((mw) => adaptCtxMw<Ctx>(mw));\n const derived = buildDerived(leaf);\n const ctxMw: RequestHandler = async (req, res, next) => {\n const requestUrl = req.originalUrl ?? path;\n const startedAt = Date.now();\n emit({ type: 'buildCtx', stage: 'start', method: methodUpper, path, url: requestUrl });\n try {\n const ctx = await config.buildCtx(req, res);\n (res.locals as any)[CTX_SYMBOL] = ctx;\n emit({\n type: 'buildCtx',\n stage: 'success',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n });\n next();\n } catch (err) {\n emit({\n type: 'buildCtx',\n stage: 'error',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n error: err,\n });\n logger?.error?.('buildCtx error', err);\n next(err as any);\n }\n };\n const before: RequestHandler[] = [ctxMw, ...globalBeforeMws, ...derived, ...routeSpecific];\n\n const wrapped: RequestHandler = async (req, res, next) => {\n const requestUrl = req.originalUrl.split('?')[0] ?? path;\n const startedAt = Date.now();\n emit({ type: 'request', stage: 'start', method: methodUpper, path, url: requestUrl });\n let params: ArgParams<L> | undefined;\n let query: ArgQuery<L> | undefined;\n let body: ArgBody<L> | undefined;\n let responsePayload: InferOutput<L> | undefined;\n let hasResponsePayload = false;\n setAfterHandlerNext(res, false);\n let handlerCalledNext = false;\n const downstreamNext: express.NextFunction = (err?: any) => {\n handlerCalledNext = true;\n setAfterHandlerNext(res, true);\n next(err);\n };\n let ctxRoutesLogger: LoggerLike | undefined;\n const emitWithCtx = (\n event: RouteServerDebugEvent,\n details?: Partial<RouteServerDebugEvent>,\n ) => {\n const decorated = decorateDebugEvent(isVerboseDebug, event, details);\n if (decorated.type === 'handler') {\n logHandlerDebugWithRoutesLogger(ctxRoutesLogger, decorated);\n }\n emit(decorated);\n };\n\n try {\n logger?.info?.(`${methodUpper}@${path} (${requestUrl})`);\n\n const ctx = (res.locals as any)[CTX_SYMBOL] as CtxWithRoutesLogger<Ctx>;\n ctxRoutesLogger = ctx.routesLogger;\n\n params = (\n leaf.cfg.paramsSchema\n ? (leaf.cfg.paramsSchema as ZodType).parse(req.params)\n : Object.keys(req.params || {}).length\n ? (req.params as any)\n : undefined\n ) as ArgParams<L>;\n\n try {\n query = leaf.cfg.querySchema\n ? (leaf.cfg.querySchema as ZodType).parse(req.query)\n : Object.keys(req.query || {}).length\n ? (req.query as any)\n : undefined;\n } catch (e) {\n logger?.error?.('Query parsing error', {\n path,\n method: methodUpper,\n error: e,\n raw: JSON.stringify(req.query),\n });\n throw e;\n }\n\n body = (\n leaf.cfg.bodySchema\n ? (leaf.cfg.bodySchema as ZodType).parse(req.body)\n : req.body !== undefined\n ? (req.body as any)\n : undefined\n ) as ArgBody<L>;\n\n logger?.verbose?.(`${methodUpper}@${path} (${requestUrl})`, {\n params,\n query,\n body,\n });\n\n const handlerStartedAt = Date.now();\n emitWithCtx(\n {\n type: 'handler',\n stage: 'start',\n method: methodUpper,\n path,\n url: requestUrl,\n },\n isVerboseDebug ? { params, query, body } : undefined,\n );\n\n let result;\n try {\n result = await def.handler({\n req,\n res,\n next: downstreamNext,\n ctx,\n params: params as ArgParams<L>,\n query: query as ArgQuery<L>,\n body: body as ArgBody<L>,\n });\n emitWithCtx(\n {\n type: 'handler',\n stage: 'success',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - handlerStartedAt,\n },\n isVerboseDebug\n ? {\n params,\n query,\n body,\n ...(result !== undefined ? { output: result } : {}),\n }\n : undefined,\n );\n } catch (e) {\n emitWithCtx(\n {\n type: 'handler',\n stage: 'error',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - handlerStartedAt,\n error: e,\n },\n isVerboseDebug ? { params, query, body } : undefined,\n );\n logger?.error?.('Handler error', e);\n throw e;\n }\n\n if (!res.headersSent) {\n if (result !== undefined) {\n const out =\n validateOutput && leaf.cfg.outputSchema\n ? (leaf.cfg.outputSchema as ZodType).parse(result)\n : result;\n responsePayload = out as InferOutput<L>;\n hasResponsePayload = true;\n logger?.verbose?.(`${methodUpper}@${path} result`, out);\n send(res, out);\n } else if (!handlerCalledNext) {\n next();\n }\n }\n\n emitWithCtx(\n {\n type: 'request',\n stage: 'success',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug\n ? {\n params,\n query,\n body,\n ...(hasResponsePayload ? { output: responsePayload } : {}),\n }\n : undefined,\n );\n } catch (err) {\n emitWithCtx(\n {\n type: 'request',\n stage: 'error',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n error: err,\n },\n isVerboseDebug ? { params, query, body } : undefined,\n );\n logger?.error?.('Route error', err);\n next(err as any);\n }\n };\n\n const after = [...mapAfterMiddleware<Ctx>(def?.after), ...globalAfterMws];\n (router as any)[method](path, ...before, wrapped, ...after);\n registered.add(key);\n }\n\n /**\n * Register controller definitions for the provided keys.\n * @param registry Finalized registry of leaves.\n * @param controllers Partial controller map keyed by `\"METHOD /path\"`.\n */\n function registerControllers<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n >(registry: R, controllers: PartialControllerMap<R, Ctx, Names>) {\n (Object.keys(controllers) as Array<KeysOfRegistry<R>>).forEach((key) => {\n const leaf = registry.byKey[key] as unknown as LeafFromKey<R, typeof key> | undefined;\n if (!leaf) {\n logger?.warn?.(`No leaf found for controller key: ${key}. Not registering route.`);\n return;\n }\n const def = controllers[key];\n if (!def) return;\n register(leaf as LeafFromKey<R, typeof key>, def);\n });\n }\n\n /**\n * Warn about leaves that do not have a registered controller.\n * @param registry Finalized registry of leaves.\n * @param warnLogger Logger used for warning output.\n */\n function warnMissing<R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> }>(\n registry: R,\n warnLogger: { warn: (...args: any[]) => void },\n ) {\n const registeredFromStore = new Set<string>(Array.from(registered));\n if (registeredFromStore.size === 0) {\n collectRoutesFromStack(router).forEach((key) => registeredFromStore.add(key));\n }\n for (const leaf of registry.all) {\n const key = keyOf(leaf);\n if (!registeredFromStore.has(key)) {\n warnLogger.warn(`No controller registered for route: ${key}`);\n }\n }\n }\n\n return {\n router,\n register,\n registerControllers,\n warnMissingControllers: warnMissing,\n getRegisteredKeys: () => Array.from(registered),\n };\n}\n\n/**\n * Bind only the controllers that are present in the provided map.\n * @param router Express router or app.\n * @param registry Finalized registry produced by `finalize(...)`.\n * @param controllers Partial map of controllers keyed by `\"METHOD /path\"`.\n * @param config Optional route server configuration.\n * @returns The same router instance for chaining.\n */\nexport function bindExpressRoutes<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n>(\n router: Router,\n registry: R,\n controllers: PartialControllerMap<R, Ctx, Names>,\n config: RouteServerConfig<Ctx, Names>,\n) {\n const server = createRRRoute<Ctx, Names>(router, config);\n server.registerControllers(registry, controllers);\n return router;\n}\n\n/**\n * Bind controllers for every leaf. Missing entries fail at compile time.\n * @param router Express router or app.\n * @param registry Finalized registry produced by `finalize(...)`.\n * @param controllers Complete map of controllers keyed by `\"METHOD /path\"`.\n * @param config Optional route server configuration.\n * @returns The same router instance for chaining.\n */\nexport function bindAll<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n>(\n router: Router,\n registry: R,\n controllers: { [K in KeysOfRegistry<R>]: RouteDef<LeafFromKey<R, K>, Ctx, Names> },\n config: RouteServerConfig<Ctx, Names>,\n) {\n const server = createRRRoute<Ctx, Names>(router, config);\n server.registerControllers(registry, controllers);\n return router;\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// DX helpers\n// ──────────────────────────────────────────────────────────────────────────────\n\n/**\n * Helper for great IntelliSense when authoring controller maps.\n * @returns Function that enforces key names while preserving partial flexibility.\n */\nexport const defineControllers =\n <\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n >() =>\n <M extends PartialControllerMap<R, Ctx, Names>>(m: M) =>\n m;\n\n/**\n * Wrap a plain RequestHandler as an auth factory compatible with `fromCfg.auth`.\n * @param mw Middleware invoked for any leaf that requires authentication.\n * @param _leaf Leaf metadata (ignored, but provided to match factory signature).\n * @returns Factory that ignores the leaf and returns the same middleware.\n */\nexport const asLeafAuth =\n (mw: RequestHandler) =>\n (_leaf: AnyLeaf): RequestHandler =>\n mw;\n\n/**\n * Warn about leaves that don't have controllers.\n * Call this during startup to surface missing routes.\n * @param router Express router or app to inspect.\n * @param registry Finalized registry produced by `finalize(...)`.\n * @param logger Logger where warnings are emitted.\n */\nexport function warnMissingControllers<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n>(router: Router, registry: R, logger: { warn: (...args: any[]) => void }) {\n const registeredStore = (router as any)[REGISTERED_ROUTES_SYMBOL] as Set<string> | undefined;\n const initial = registeredStore ? Array.from(registeredStore) : collectRoutesFromStack(router);\n const registeredKeys = new Set<string>(initial);\n\n for (const leaf of registry.all) {\n const k = keyOf(leaf);\n if (!registeredKeys.has(k)) {\n logger.warn(`No controller registered for route: ${k}`);\n }\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -60,6 +60,31 @@ function adaptCtxMw(mw) {
60
60
  }
61
61
  };
62
62
  }
63
+ function adaptAfterMw(mw) {
64
+ const adapted = adaptCtxMw(mw);
65
+ return (req, res, next) => {
66
+ if (!handlerInvokedNext(res)) {
67
+ next();
68
+ return;
69
+ }
70
+ adapted(req, res, next);
71
+ };
72
+ }
73
+ function mapAfterMiddleware(mws) {
74
+ return (mws ?? []).map((mw) => adaptAfterMw(mw));
75
+ }
76
+ function logHandlerDebugWithRoutesLogger(logger, event) {
77
+ if (!logger || event.type !== "handler") return;
78
+ const payload = [
79
+ `[rrroutes-server][handler:${event.stage}] ${event.method} ${event.path}`,
80
+ event
81
+ ];
82
+ if (event.stage === "error") {
83
+ (logger.error ?? logger.warn ?? logger.debug ?? logger.info ?? logger.log ?? logger.system)?.call(logger, ...payload);
84
+ return;
85
+ }
86
+ (logger.debug ?? logger.verbose ?? logger.info ?? logger.log ?? logger.system)?.call(logger, ...payload);
87
+ }
63
88
  var defaultSend = (res, data) => {
64
89
  res.json(data);
65
90
  };
@@ -92,17 +117,19 @@ function collectRoutesFromStack(appOrRouter) {
92
117
  }
93
118
  return result;
94
119
  }
95
- function createRouteServer(router, config) {
120
+ function createRRRoute(router, config) {
96
121
  const validateOutput = config.validateOutput ?? true;
97
122
  const send = config.send ?? defaultSend;
98
123
  const logger = config.logger;
99
- const { emit: emitDebug, mode: debugMode } = createServerDebugEmitter(config.debug);
100
- const isVerboseDebug = debugMode === "complete";
101
- const decorateDebugEvent = (event, details) => {
102
- if (!isVerboseDebug || !details) return event;
124
+ const { emit: defaultEmitDebug, mode: defaultDebugMode } = createServerDebugEmitter(config.debug);
125
+ const decorateDebugEvent = (isVerbose, event, details) => {
126
+ if (!isVerbose || !details) return event;
103
127
  return { ...event, ...details };
104
128
  };
105
- const globalMws = (config.global ?? []).map((mw) => adaptCtxMw(mw));
129
+ const globalBeforeMws = [...config.global ?? [], ...config.globalMiddleware?.before ?? []].map(
130
+ (mw) => adaptCtxMw(mw)
131
+ );
132
+ const globalAfterMws = mapAfterMiddleware(config.globalMiddleware?.after);
106
133
  const registered = getRegisteredRouteStore(router);
107
134
  const buildDerived = (leaf) => {
108
135
  const derived = [];
@@ -122,8 +149,23 @@ function createRouteServer(router, config) {
122
149
  const methodUpper = method.toUpperCase();
123
150
  const path = leaf.path;
124
151
  const key = keyOf(leaf);
125
- const debugName = def.debugName;
126
- const emit = (event) => emitDebug(event, debugName);
152
+ const defDebug = def.debug;
153
+ let debugName = def.debugName;
154
+ let routeDebugEmitter;
155
+ if (defDebug) {
156
+ const { debugName: overrideName, ...rest } = defDebug;
157
+ const hasOverrides = Object.values(rest).some((value) => value !== void 0);
158
+ if (hasOverrides) {
159
+ routeDebugEmitter = createServerDebugEmitter(
160
+ rest
161
+ );
162
+ }
163
+ debugName = overrideName ?? debugName;
164
+ }
165
+ const activeEmit = routeDebugEmitter?.emit ?? defaultEmitDebug;
166
+ const activeDebugMode = routeDebugEmitter?.mode ?? defaultDebugMode;
167
+ const emit = (event) => activeEmit(event, debugName);
168
+ const isVerboseDebug = activeDebugMode === "complete";
127
169
  emit({ type: "register", method: methodUpper, path });
128
170
  const routeSpecific = (def?.use ?? []).map((mw) => adaptCtxMw(mw));
129
171
  const derived = buildDerived(leaf);
@@ -157,14 +199,9 @@ function createRouteServer(router, config) {
157
199
  next(err);
158
200
  }
159
201
  };
160
- const before = [
161
- ...[ctxMw],
162
- ...globalMws,
163
- ...derived,
164
- ...routeSpecific
165
- ];
202
+ const before = [ctxMw, ...globalBeforeMws, ...derived, ...routeSpecific];
166
203
  const wrapped = async (req, res, next) => {
167
- const requestUrl = req.originalUrl ?? path;
204
+ const requestUrl = req.originalUrl.split("?")[0] ?? path;
168
205
  const startedAt = Date.now();
169
206
  emit({ type: "request", stage: "start", method: methodUpper, path, url: requestUrl });
170
207
  let params;
@@ -179,9 +216,18 @@ function createRouteServer(router, config) {
179
216
  setAfterHandlerNext(res, true);
180
217
  next(err);
181
218
  };
219
+ let ctxRoutesLogger;
220
+ const emitWithCtx = (event, details) => {
221
+ const decorated = decorateDebugEvent(isVerboseDebug, event, details);
222
+ if (decorated.type === "handler") {
223
+ logHandlerDebugWithRoutesLogger(ctxRoutesLogger, decorated);
224
+ }
225
+ emit(decorated);
226
+ };
182
227
  try {
183
228
  logger?.info?.(`${methodUpper}@${path} (${requestUrl})`);
184
229
  const ctx = res.locals[CTX_SYMBOL];
230
+ ctxRoutesLogger = ctx.routesLogger;
185
231
  params = leaf.cfg.paramsSchema ? leaf.cfg.paramsSchema.parse(req.params) : Object.keys(req.params || {}).length ? req.params : void 0;
186
232
  try {
187
233
  query = leaf.cfg.querySchema ? leaf.cfg.querySchema.parse(req.query) : Object.keys(req.query || {}).length ? req.query : void 0;
@@ -201,17 +247,15 @@ function createRouteServer(router, config) {
201
247
  body
202
248
  });
203
249
  const handlerStartedAt = Date.now();
204
- emit(
205
- decorateDebugEvent(
206
- {
207
- type: "handler",
208
- stage: "start",
209
- method: methodUpper,
210
- path,
211
- url: requestUrl
212
- },
213
- isVerboseDebug ? { params, query, body } : void 0
214
- )
250
+ emitWithCtx(
251
+ {
252
+ type: "handler",
253
+ stage: "start",
254
+ method: methodUpper,
255
+ path,
256
+ url: requestUrl
257
+ },
258
+ isVerboseDebug ? { params, query, body } : void 0
215
259
  );
216
260
  let result;
217
261
  try {
@@ -224,38 +268,34 @@ function createRouteServer(router, config) {
224
268
  query,
225
269
  body
226
270
  });
227
- emit(
228
- decorateDebugEvent(
229
- {
230
- type: "handler",
231
- stage: "success",
232
- method: methodUpper,
233
- path,
234
- url: requestUrl,
235
- durationMs: Date.now() - handlerStartedAt
236
- },
237
- isVerboseDebug ? {
238
- params,
239
- query,
240
- body,
241
- ...result !== void 0 ? { output: result } : {}
242
- } : void 0
243
- )
271
+ emitWithCtx(
272
+ {
273
+ type: "handler",
274
+ stage: "success",
275
+ method: methodUpper,
276
+ path,
277
+ url: requestUrl,
278
+ durationMs: Date.now() - handlerStartedAt
279
+ },
280
+ isVerboseDebug ? {
281
+ params,
282
+ query,
283
+ body,
284
+ ...result !== void 0 ? { output: result } : {}
285
+ } : void 0
244
286
  );
245
287
  } catch (e) {
246
- emit(
247
- decorateDebugEvent(
248
- {
249
- type: "handler",
250
- stage: "error",
251
- method: methodUpper,
252
- path,
253
- url: requestUrl,
254
- durationMs: Date.now() - handlerStartedAt,
255
- error: e
256
- },
257
- isVerboseDebug ? { params, query, body } : void 0
258
- )
288
+ emitWithCtx(
289
+ {
290
+ type: "handler",
291
+ stage: "error",
292
+ method: methodUpper,
293
+ path,
294
+ url: requestUrl,
295
+ durationMs: Date.now() - handlerStartedAt,
296
+ error: e
297
+ },
298
+ isVerboseDebug ? { params, query, body } : void 0
259
299
  );
260
300
  logger?.error?.("Handler error", e);
261
301
  throw e;
@@ -271,53 +311,40 @@ function createRouteServer(router, config) {
271
311
  next();
272
312
  }
273
313
  }
274
- emit(
275
- decorateDebugEvent(
276
- {
277
- type: "request",
278
- stage: "success",
279
- method: methodUpper,
280
- path,
281
- url: requestUrl,
282
- durationMs: Date.now() - startedAt
283
- },
284
- isVerboseDebug ? {
285
- params,
286
- query,
287
- body,
288
- ...hasResponsePayload ? { output: responsePayload } : {}
289
- } : void 0
290
- )
314
+ emitWithCtx(
315
+ {
316
+ type: "request",
317
+ stage: "success",
318
+ method: methodUpper,
319
+ path,
320
+ url: requestUrl,
321
+ durationMs: Date.now() - startedAt
322
+ },
323
+ isVerboseDebug ? {
324
+ params,
325
+ query,
326
+ body,
327
+ ...hasResponsePayload ? { output: responsePayload } : {}
328
+ } : void 0
291
329
  );
292
330
  } catch (err) {
293
- emit(
294
- decorateDebugEvent(
295
- {
296
- type: "request",
297
- stage: "error",
298
- method: methodUpper,
299
- path,
300
- url: requestUrl,
301
- durationMs: Date.now() - startedAt,
302
- error: err
303
- },
304
- isVerboseDebug ? { params, query, body } : void 0
305
- )
331
+ emitWithCtx(
332
+ {
333
+ type: "request",
334
+ stage: "error",
335
+ method: methodUpper,
336
+ path,
337
+ url: requestUrl,
338
+ durationMs: Date.now() - startedAt,
339
+ error: err
340
+ },
341
+ isVerboseDebug ? { params, query, body } : void 0
306
342
  );
307
343
  logger?.error?.("Route error", err);
308
344
  next(err);
309
345
  }
310
346
  };
311
- const after = (def?.after ?? []).map((mw) => {
312
- const adapted = adaptCtxMw(mw);
313
- return (req, res, next) => {
314
- if (!handlerInvokedNext(res)) {
315
- next();
316
- return;
317
- }
318
- adapted(req, res, next);
319
- };
320
- });
347
+ const after = [...mapAfterMiddleware(def?.after), ...globalAfterMws];
321
348
  router[method](path, ...before, wrapped, ...after);
322
349
  registered.add(key);
323
350
  }
@@ -354,12 +381,12 @@ function createRouteServer(router, config) {
354
381
  };
355
382
  }
356
383
  function bindExpressRoutes(router, registry, controllers, config) {
357
- const server = createRouteServer(router, config);
384
+ const server = createRRRoute(router, config);
358
385
  server.registerControllers(registry, controllers);
359
386
  return router;
360
387
  }
361
388
  function bindAll(router, registry, controllers, config) {
362
- const server = createRouteServer(router, config);
389
+ const server = createRRRoute(router, config);
363
390
  server.registerControllers(registry, controllers);
364
391
  return router;
365
392
  }
@@ -381,7 +408,7 @@ export {
381
408
  asLeafAuth,
382
409
  bindAll,
383
410
  bindExpressRoutes,
384
- createRouteServer,
411
+ createRRRoute,
385
412
  defineControllers,
386
413
  getCtx,
387
414
  keyOf,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/routesV3.server.ts"],"sourcesContent":["/**\n * routesV3.server.ts\n * -----------------------------------------------------------------------------\n * Bind an Express router/app to a `finalize(...)` registry of AnyLeafs.\n * - Fully typed handlers (params/query/body/output)\n * - Zod parsing + optional output validation\n * - buildCtx runs as a middleware *first*, before all other middlewares\n * - Global, per-route, and cfg-derived middlewares (auth, uploads)\n * - Helper to warn about unimplemented routes\n * - DX helpers to use `ctx` in any middleware with proper types\n */\n\nimport { AnyLeaf, FileField, HttpMethod, InferBody, InferOutput, InferParams, InferQuery, MethodCfg } from '@emeryld/rrroutes-contract';\nimport type * as express from 'express';\nimport type { RequestHandler, Router } from 'express';\nimport type { ZodType } from 'zod';\n\n\n\n/** Shape expected from optional logger implementations. */\nexport type LoggerLike = {\n info?: (...args: any[]) => void;\n warn?: (...args: any[]) => void;\n error?: (...args: any[]) => void;\n debug?: (...args: any[]) => void;\n verbose?: (...args: any[]) => void;\n system?: (...args: any[]) => void;\n log?: (...args: any[]) => void;\n};\n\n// Debug logging --------------------------------------------------------------\nexport type RouteServerDebugMode = 'minimal' | 'complete';\n\ntype RouteServerDebugEventBase = {\n /** Optional logical name assigned via `RouteDef.debugName`. */\n name?: string;\n};\n\nexport type RouteServerDebugEvent =\n | (RouteServerDebugEventBase & {\n type: 'request';\n stage: 'start' | 'success' | 'error';\n method: Uppercase<HttpMethod>;\n path: string;\n url: string;\n durationMs?: number;\n params?: unknown;\n query?: unknown;\n body?: unknown;\n output?: unknown;\n error?: unknown;\n })\n | (RouteServerDebugEventBase & {\n type: 'register';\n method: Uppercase<HttpMethod>;\n path: string;\n })\n | (RouteServerDebugEventBase & {\n type: 'buildCtx';\n stage: 'start' | 'success' | 'error';\n method: Uppercase<HttpMethod>;\n path: string;\n url: string;\n durationMs?: number;\n error?: unknown;\n })\n | (RouteServerDebugEventBase & {\n type: 'handler';\n stage: 'start' | 'success' | 'error';\n method: Uppercase<HttpMethod>;\n path: string;\n url: string;\n durationMs?: number;\n params?: unknown;\n query?: unknown;\n body?: unknown;\n output?: unknown;\n error?: unknown;\n });\n\nexport type RouteServerDebugLogger = (event: RouteServerDebugEvent) => void;\n\n/**\n * Configure server-side debug logging.\n * - Use booleans or `'minimal'/'complete'` for quick toggles.\n * - Pass a custom logger function to redirect structured events.\n * - Provide a map to enable specific event types, opt into verbose payload logging, or restrict logs via `only`.\n */\nexport type RouteServerDebugOptions<Names extends string = string> = RouteServerDebugToggleOptions<Names>;\n\n/**\n * Fine-grained toggle map for server debug logging.\n * Enable individual event types, opt into verbose payload logging, override the logger, or restrict to named routes.\n * Use `RouteDef.debugName` to set the name that `only` will match against.\n */\nexport type RouteServerDebugToggleOptions<Names extends string = string> = Partial<\n Record<RouteServerDebugEvent['type'], boolean>\n> & {\n verbose?: boolean;\n logger?: RouteServerDebugLogger;\n only?: Names[];\n};\n\nconst noopServerDebug: RouteServerDebugLogger = () => {};\n\nconst defaultServerDebug: RouteServerDebugLogger = (event: RouteServerDebugEvent) => {\n if (typeof console === 'undefined') return;\n const fn = console.debug ?? console.log;\n fn?.call(console, '[rrroutes-server]', event);\n};\n\nconst serverDebugEventTypes: RouteServerDebugEvent['type'][] = [\n 'register',\n 'request',\n 'buildCtx',\n 'handler',\n];\n\ntype ServerDebugEmitter<Names extends string> = {\n emit: (event: RouteServerDebugEvent, name?: Names) => void;\n mode: RouteServerDebugMode;\n};\n\nconst noopServerEmit = () => {};\n\nfunction createServerDebugEmitter<Names extends string>(\n option?: RouteServerDebugOptions<Names>,\n): ServerDebugEmitter<Names> {\n const disabled: ServerDebugEmitter<Names> = { emit: noopServerEmit, mode: 'minimal' };\n if (!option) return disabled;\n\n if (typeof option === 'object') {\n const toggles = option as RouteServerDebugToggleOptions<Names>;\n const verbose = Boolean(toggles.verbose);\n const enabledTypes = serverDebugEventTypes.filter((type) => toggles[type]);\n if (enabledTypes.length === 0) {\n return { emit: noopServerEmit, mode: verbose ? 'complete' : 'minimal' };\n }\n const whitelist = new Set<RouteServerDebugEvent['type']>(enabledTypes);\n const onlySet =\n toggles.only && toggles.only.length > 0 ? new Set<Names>(toggles.only) : undefined;\n const logger = toggles.logger ?? defaultServerDebug;\n const emit: ServerDebugEmitter<Names>['emit'] = (event, name) => {\n if (!whitelist.has(event.type)) return;\n if (onlySet && (!name || !onlySet.has(name))) return;\n logger(name ? { ...event, name } : event);\n };\n return { emit, mode: verbose ? 'complete' : 'minimal' };\n }\n\n return disabled;\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Keys + leaf helpers (derive keys from byKey to avoid template-literal pitfalls)\n// ──────────────────────────────────────────────────────────────────────────────\n\n/** Keys like \"GET /v1/foo\" that *actually* exist in the registry */\nexport type KeysOfRegistry<R extends { byKey: Record<string, AnyLeaf> }> = keyof R['byKey'] &\n string;\n\ntype MethodFromKey<K extends string> = K extends `${infer M} ${string}` ? Lowercase<M> : never;\ntype PathFromKey<K extends string> = K extends `${string} ${infer P}` ? P : never;\n\n/** Given a registry and a key, pick the exact leaf for that method+path */\nexport type LeafFromKey<R extends { all: readonly AnyLeaf[] }, K extends string> = Extract<\n R['all'][number],\n { method: MethodFromKey<K> & HttpMethod; path: PathFromKey<K> }\n>;\n\n/** Optional-ify types if your core returns `never` when a schema isn't defined */\ntype Maybe<T> = [T] extends [never] ? undefined : T;\n\n/** Typed params argument exposed to handlers. */\nexport type ArgParams<L extends AnyLeaf> = Maybe<InferParams<L>>;\n/** Typed query argument exposed to handlers. */\nexport type ArgQuery<L extends AnyLeaf> = Maybe<InferQuery<L>>;\n/** Typed body argument exposed to handlers. */\nexport type ArgBody<L extends AnyLeaf> = Maybe<InferBody<L>>;\n\n/**\n * Convenience to compute a `\"METHOD /path\"` key from a leaf.\n * @param leaf Leaf describing the route.\n * @returns Uppercase method + path key.\n */\nexport const keyOf = (leaf: AnyLeaf) => `${leaf.method.toUpperCase()} ${leaf.path}` as const;\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Context typing & DX helpers (so ctx is usable in *any* middleware)\n// ──────────────────────────────────────────────────────────────────────────────\n\n/**\n * Unique symbol used to stash ctx on res.locals.\n * (Symbols are safer than string keys against collisions.)\n */\nexport const CTX_SYMBOL: unique symbol = Symbol.for('typedLeaves.ctx');\n\nconst AFTER_HANDLER_NEXT_SYMBOL: unique symbol = Symbol.for('typedLeaves.afterHandlerNext');\n\nfunction setAfterHandlerNext(res: express.Response, value: boolean) {\n (res.locals as any)[AFTER_HANDLER_NEXT_SYMBOL] = value;\n}\n\nfunction handlerInvokedNext(res: express.Response): boolean {\n return Boolean((res.locals as any)[AFTER_HANDLER_NEXT_SYMBOL]);\n}\n\n/** Response type that *has* a ctx on locals for DX in middlewares */\nexport type ResponseWithCtx<Ctx> =\n // Replace locals with an intersection that guarantees CTX_SYMBOL exists\n Omit<express.Response, 'locals'> & {\n locals: express.Response['locals'] & { [CTX_SYMBOL]: Ctx };\n };\n\n/** A middleware signature that can *use* ctx via `res.locals[CTX_SYMBOL]` */\nexport type CtxRequestHandler<Ctx> = (args: {\n req: express.Request;\n res: express.Response;\n next: express.NextFunction;\n ctx: Ctx;\n}) => any;\n\n/**\n * Safely read ctx from any Response.\n * @param res Express response whose locals contain the ctx symbol.\n * @returns Strongly typed context object.\n */\nexport function getCtx<Ctx = unknown>(res: express.Response): Ctx {\n return (res.locals as any)[CTX_SYMBOL] as Ctx;\n}\n\n/**\n * Wrap a ctx-typed middleware to a plain RequestHandler (for arrays, etc.).\n * @param mw Middleware that expects a typed response with ctx available.\n * @returns Standard Express request handler.\n */\nfunction adaptCtxMw<Ctx>(mw: CtxRequestHandler<Ctx>): RequestHandler {\n return (req, res, next) => {\n try {\n const result = mw({ req, res, next, ctx: getCtx<Ctx>(res) });\n if (result && typeof (result as Promise<unknown>).then === 'function') {\n return (result as Promise<unknown>).catch((err) => next(err));\n }\n return result as any;\n } catch (err) {\n next(err as any);\n return undefined;\n }\n };\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Controller types — object form only (simpler, clearer typings)\n// ──────────────────────────────────────────────────────────────────────────────\n\n/** Typed route handler for a specific leaf */\nexport type Handler<L extends AnyLeaf, Ctx = unknown> = (args: {\n req: express.Request;\n res: express.Response;\n next: express.NextFunction;\n ctx: Ctx;\n params: ArgParams<L>;\n query: ArgQuery<L>;\n body: ArgBody<L>;\n}) => Promise<InferOutput<L>> | InferOutput<L>;\n\n/** Route definition for one key */\nexport type RouteDef<L extends AnyLeaf, Ctx = unknown, Names extends string = string> = {\n /** Middlewares before the handler (run after buildCtx/global/derived) */\n use?: Array<CtxRequestHandler<Ctx>>;\n /** Middlewares after the handler *if* it calls next() */\n after?: Array<CtxRequestHandler<Ctx>>;\n /** Your business logic */\n handler: Handler<L, Ctx>;\n /**\n * Optional logical name used for debug filtering. Pair with `debug.only` so only named routes emit logs.\n */\n debugName?: Names;\n};\n\n/** Map of registry keys -> route defs */\nexport type ControllerMap<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n> = {\n [P in KeysOfRegistry<R>]: RouteDef<LeafFromKey<R, P>, Ctx, Names>;\n};\n\nexport type PartialControllerMap<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n> = Partial<ControllerMap<R, Ctx, Names>>;\n\n// ──────────────────────────────────────────────────────────────────────────────\n/** Options + derivation helpers */\n// ──────────────────────────────────────────────────────────────────────────────\n\nexport type RouteServerConfig<Ctx = unknown, Names extends string = string> = {\n /**\n * Build a request-scoped context. We wrap this in a middleware that runs\n * *first* (before global/derived/route middlewares), and stash it on\n * `res.locals[CTX_SYMBOL]` so *all* later middlewares can use it.\n */\n buildCtx: (req: express.Request, res: express.Response) => Ctx | Promise<Ctx>;\n\n /**\n * Global middlewares for every bound route (run *after* buildCtx).\n * You can write them as ctx-aware middlewares for great DX.\n */\n global?: Array<CtxRequestHandler<Ctx>>;\n\n /**\n * Derive middleware from MethodCfg.\n * - `auth` runs when cfg.authenticated === true (or `when` overrides)\n * - `upload` runs when cfg.bodyFiles has entries\n */\n fromCfg?: {\n auth?: RequestHandler | ((leaf: AnyLeaf) => RequestHandler);\n when?: (cfg: MethodCfg, leaf: AnyLeaf) => { auth?: boolean } | void;\n upload?: (files: FileField[] | undefined, leaf: AnyLeaf) => RequestHandler[];\n };\n\n /** Validate handler return values with outputSchema (default: true) */\n validateOutput?: boolean;\n\n /** Custom responder (default: res.json(data)) */\n send?: (res: express.Response, data: unknown) => void;\n\n /** Optional logger hooks */\n logger?: LoggerLike;\n\n /**\n * Optional debug logging for the request lifecycle.\n * Supports booleans/modes/loggers, or a toggle map with per-event enabling, verbose payload logging,\n * and `only` filters tied to `RouteDef.debugName`.\n */\n debug?: RouteServerDebugOptions<Names>;\n};\n\n/** Default JSON responder (typed to avoid implicit-any diagnostics) */\nconst defaultSend: (res: express.Response, data: unknown) => void = (res, data) => {\n res.json(data as any);\n};\n\n/**\n * Normalize `auth` into a RequestHandler (avoids union-narrowing issues).\n * @param auth Static middleware or factory returning one for the current leaf.\n * @param leaf Leaf being registered.\n * @returns Request handler or undefined when no auth is required.\n */\nfunction resolveAuth(\n auth: RequestHandler | ((leaf: AnyLeaf) => RequestHandler) | undefined,\n leaf: AnyLeaf,\n): RequestHandler | undefined {\n if (!auth) return undefined;\n return (auth as (l: AnyLeaf) => RequestHandler).length === 1\n ? (auth as (l: AnyLeaf) => RequestHandler)(leaf)\n : (auth as RequestHandler);\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Core builder\n// ──────────────────────────────────────────────────────────────────────────────\n\nconst REGISTERED_ROUTES_SYMBOL = Symbol.for('routesV3.registeredRoutes');\n\ntype RegisteredRouteStore = Set<string>;\n\n/**\n * Retrieve or initialize the shared store of registered route keys.\n * @param router Express router/application that carries previously registered keys.\n * @returns Set of string keys describing registered routes.\n */\nfunction getRegisteredRouteStore(router: Router): RegisteredRouteStore {\n const existing = (router as any)[REGISTERED_ROUTES_SYMBOL] as RegisteredRouteStore | undefined;\n if (existing) return existing;\n const store: RegisteredRouteStore = new Set();\n (router as any)[REGISTERED_ROUTES_SYMBOL] = store;\n return store;\n}\n\n/**\n * Inspect the Express layer stack to discover already-registered routes.\n * @param appOrRouter Express application or router to inspect.\n * @returns All keys in the form `\"METHOD /path\"` found on the stack.\n */\nfunction collectRoutesFromStack(appOrRouter: Router): string[] {\n const result: string[] = [];\n const stack: any[] =\n (appOrRouter as any).stack ??\n ((appOrRouter as any)._router ? (appOrRouter as any)._router.stack : undefined) ??\n [];\n\n if (!Array.isArray(stack)) return result;\n\n for (const layer of stack) {\n const route = layer && layer.route;\n if (!route) continue;\n\n const paths = Array.isArray(route.path) ? route.path : [route.path];\n const methodEntries = Object.entries(route.methods ?? {}).filter(([, enabled]) => enabled);\n\n for (const path of paths) {\n for (const [method] of methodEntries) {\n result.push(`${method.toUpperCase()} ${path}`);\n }\n }\n }\n\n return result;\n}\n\n/** Runtime helpers returned by `createRouteServer`. */\nexport type RouteServer<Ctx = unknown, Names extends string = string> = {\n router: Router;\n register<L extends AnyLeaf>(leaf: L, def: RouteDef<L, Ctx, Names>): void;\n registerControllers<R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> }>(\n registry: R,\n controllers: PartialControllerMap<R, Ctx, Names>,\n ): void;\n warnMissingControllers<R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> }>(\n registry: R,\n logger: { warn: (...args: any[]) => void },\n ): void;\n getRegisteredKeys(): string[];\n};\n\n/**\n * Create an Express binding helper that keeps routes and controllers in sync.\n * @param router Express router or app to register handlers on.\n * @param config Optional configuration controlling ctx building, auth, uploads, etc.\n * @returns Object with helpers to register controllers and inspect registered keys.\n */\nexport function createRouteServer<Ctx = unknown, Names extends string = string>(\n router: Router,\n config: RouteServerConfig<Ctx, Names>,\n): RouteServer<Ctx, Names> {\n const validateOutput = config.validateOutput ?? true;\n const send = config.send ?? defaultSend;\n const logger = config.logger;\n const { emit: emitDebug, mode: debugMode } = createServerDebugEmitter<Names>(config.debug);\n const isVerboseDebug = debugMode === 'complete';\n const decorateDebugEvent = <T extends RouteServerDebugEvent>(\n event: T,\n details?: Partial<RouteServerDebugEvent>,\n ): RouteServerDebugEvent => {\n if (!isVerboseDebug || !details) return event;\n return { ...event, ...details } as RouteServerDebugEvent;\n };\n\n const globalMws = (config.global ?? []).map((mw) => adaptCtxMw<Ctx>(mw));\n const registered = getRegisteredRouteStore(router);\n\n const buildDerived = (leaf: AnyLeaf): RequestHandler[] => {\n const derived: RequestHandler[] = [];\n const decision = config.fromCfg?.when?.(leaf.cfg, leaf) ?? {};\n const needsAuth = typeof decision.auth === 'boolean' ? decision.auth : !!leaf.cfg.authenticated;\n\n if (needsAuth && config.fromCfg?.auth) {\n const authMw = resolveAuth(config.fromCfg.auth, leaf);\n if (authMw) derived.push(authMw);\n }\n\n if (\n config.fromCfg?.upload &&\n Array.isArray(leaf.cfg.bodyFiles) &&\n leaf.cfg.bodyFiles.length > 0\n ) {\n derived.push(...config.fromCfg.upload(leaf.cfg.bodyFiles, leaf));\n }\n\n return derived;\n };\n\n /** Register a single leaf/controller pair on the underlying router. */\n function register<L extends AnyLeaf>(leaf: L, def: RouteDef<L, Ctx, Names>) {\n const method = leaf.method as HttpMethod;\n const methodUpper = method.toUpperCase() as Uppercase<HttpMethod>;\n const path = leaf.path as string;\n const key = keyOf(leaf);\n const debugName = def.debugName as Names | undefined;\n const emit = (event: RouteServerDebugEvent) => emitDebug(event, debugName);\n emit({ type: 'register', method: methodUpper, path });\n\n const routeSpecific = (def?.use ?? []).map((mw) => adaptCtxMw<Ctx>(mw));\n const derived = buildDerived(leaf);\n const ctxMw: RequestHandler = async (req, res, next) => {\n const requestUrl = req.originalUrl ?? path;\n const startedAt = Date.now();\n emit({ type: 'buildCtx', stage: 'start', method: methodUpper, path, url: requestUrl });\n try {\n const ctx = await config.buildCtx(req, res);\n (res.locals as any)[CTX_SYMBOL] = ctx;\n emit({\n type: 'buildCtx',\n stage: 'success',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n });\n next();\n } catch (err) {\n emit({\n type: 'buildCtx',\n stage: 'error',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n error: err,\n });\n logger?.error?.('buildCtx error', err);\n next(err as any);\n }\n };\n const before: RequestHandler[] = [\n ...[ctxMw],\n ...globalMws,\n ...derived,\n ...routeSpecific,\n ];\n\n const wrapped: RequestHandler = async (req, res, next) => {\n const requestUrl = req.originalUrl ?? path;\n const startedAt = Date.now();\n emit({ type: 'request', stage: 'start', method: methodUpper, path, url: requestUrl });\n let params: ArgParams<L> | undefined;\n let query: ArgQuery<L> | undefined;\n let body: ArgBody<L> | undefined;\n let responsePayload: InferOutput<L> | undefined;\n let hasResponsePayload = false;\n setAfterHandlerNext(res, false);\n let handlerCalledNext = false;\n const downstreamNext: express.NextFunction = (err?: any) => {\n handlerCalledNext = true;\n setAfterHandlerNext(res, true);\n next(err);\n };\n\n try {\n logger?.info?.(`${methodUpper}@${path} (${requestUrl})`);\n\n const ctx = (res.locals as any)[CTX_SYMBOL] as Ctx;\n\n params = (\n leaf.cfg.paramsSchema\n ? (leaf.cfg.paramsSchema as ZodType).parse(req.params)\n : Object.keys(req.params || {}).length\n ? (req.params as any)\n : undefined\n ) as ArgParams<L>;\n\n try {\n query = leaf.cfg.querySchema\n ? (leaf.cfg.querySchema as ZodType).parse(req.query)\n : Object.keys(req.query || {}).length\n ? (req.query as any)\n : undefined;\n } catch (e) {\n logger?.error?.('Query parsing error', {\n path,\n method: methodUpper,\n error: e,\n raw: JSON.stringify(req.query),\n });\n throw e;\n }\n\n body = (\n leaf.cfg.bodySchema\n ? (leaf.cfg.bodySchema as ZodType).parse(req.body)\n : req.body !== undefined\n ? (req.body as any)\n : undefined\n ) as ArgBody<L>;\n\n logger?.verbose?.(`${methodUpper}@${path} (${requestUrl})`, {\n params,\n query,\n body,\n });\n\n const handlerStartedAt = Date.now();\n emit(\n decorateDebugEvent(\n {\n type: 'handler',\n stage: 'start',\n method: methodUpper,\n path,\n url: requestUrl,\n },\n isVerboseDebug ? { params, query, body } : undefined,\n ),\n );\n\n let result;\n try {\n result = await def.handler({\n req,\n res,\n next: downstreamNext,\n ctx,\n params: params as ArgParams<L>,\n query: query as ArgQuery<L>,\n body: body as ArgBody<L>,\n });\n emit(\n decorateDebugEvent(\n {\n type: 'handler',\n stage: 'success',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - handlerStartedAt,\n },\n isVerboseDebug\n ? {\n params,\n query,\n body,\n ...(result !== undefined ? { output: result } : {}),\n }\n : undefined,\n ),\n );\n } catch (e) {\n emit(\n decorateDebugEvent(\n {\n type: 'handler',\n stage: 'error',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - handlerStartedAt,\n error: e,\n },\n isVerboseDebug ? { params, query, body } : undefined,\n ),\n );\n logger?.error?.('Handler error', e);\n throw e;\n }\n\n if (!res.headersSent) {\n if (result !== undefined) {\n const out =\n validateOutput && leaf.cfg.outputSchema\n ? (leaf.cfg.outputSchema as ZodType).parse(result)\n : result;\n responsePayload = out as InferOutput<L>;\n hasResponsePayload = true;\n logger?.verbose?.(`${methodUpper}@${path} result`, out);\n send(res, out);\n } else if (!handlerCalledNext) {\n next();\n }\n }\n\n emit(\n decorateDebugEvent(\n {\n type: 'request',\n stage: 'success',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug\n ? {\n params,\n query,\n body,\n ...(hasResponsePayload ? { output: responsePayload } : {}),\n }\n : undefined,\n ),\n );\n } catch (err) {\n emit(\n decorateDebugEvent(\n {\n type: 'request',\n stage: 'error',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n error: err,\n },\n isVerboseDebug ? { params, query, body } : undefined,\n ),\n );\n logger?.error?.('Route error', err);\n next(err as any);\n }\n };\n\n const after = (def?.after ?? []).map((mw) => {\n const adapted = adaptCtxMw<Ctx>(mw);\n return (req: express.Request, res: express.Response, next: express.NextFunction) => {\n if (!handlerInvokedNext(res)) {\n next();\n return;\n }\n adapted(req, res, next);\n };\n });\n (router as any)[method](path, ...before, wrapped, ...after);\n registered.add(key);\n }\n\n /**\n * Register controller definitions for the provided keys.\n * @param registry Finalized registry of leaves.\n * @param controllers Partial controller map keyed by `\"METHOD /path\"`.\n */\n function registerControllers<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n >(registry: R, controllers: PartialControllerMap<R, Ctx, Names>) {\n (Object.keys(controllers) as Array<KeysOfRegistry<R>>).forEach((key) => {\n const leaf = registry.byKey[key] as unknown as LeafFromKey<R, typeof key> | undefined;\n if (!leaf) {\n logger?.warn?.(`No leaf found for controller key: ${key}. Not registering route.`);\n return;\n }\n const def = controllers[key];\n if (!def) return;\n register(leaf as LeafFromKey<R, typeof key>, def);\n });\n }\n\n /**\n * Warn about leaves that do not have a registered controller.\n * @param registry Finalized registry of leaves.\n * @param warnLogger Logger used for warning output.\n */\n function warnMissing<R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> }>(\n registry: R,\n warnLogger: { warn: (...args: any[]) => void },\n ) {\n const registeredFromStore = new Set<string>(Array.from(registered));\n if (registeredFromStore.size === 0) {\n collectRoutesFromStack(router).forEach((key) => registeredFromStore.add(key));\n }\n for (const leaf of registry.all) {\n const key = keyOf(leaf);\n if (!registeredFromStore.has(key)) {\n warnLogger.warn(`No controller registered for route: ${key}`);\n }\n }\n }\n\n return {\n router,\n register,\n registerControllers,\n warnMissingControllers: warnMissing,\n getRegisteredKeys: () => Array.from(registered),\n };\n}\n\n/**\n * Bind only the controllers that are present in the provided map.\n * @param router Express router or app.\n * @param registry Finalized registry produced by `finalize(...)`.\n * @param controllers Partial map of controllers keyed by `\"METHOD /path\"`.\n * @param config Optional route server configuration.\n * @returns The same router instance for chaining.\n */\nexport function bindExpressRoutes<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n>(\n router: Router,\n registry: R,\n controllers: PartialControllerMap<R, Ctx, Names>,\n config: RouteServerConfig<Ctx, Names>,\n) {\n const server = createRouteServer<Ctx, Names>(router, config);\n server.registerControllers(registry, controllers);\n return router;\n}\n\n/**\n * Bind controllers for every leaf. Missing entries fail at compile time.\n * @param router Express router or app.\n * @param registry Finalized registry produced by `finalize(...)`.\n * @param controllers Complete map of controllers keyed by `\"METHOD /path\"`.\n * @param config Optional route server configuration.\n * @returns The same router instance for chaining.\n */\nexport function bindAll<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n>(\n router: Router,\n registry: R,\n controllers: { [K in KeysOfRegistry<R>]: RouteDef<LeafFromKey<R, K>, Ctx, Names> },\n config: RouteServerConfig<Ctx, Names>,\n) {\n const server = createRouteServer<Ctx, Names>(router, config);\n server.registerControllers(registry, controllers);\n return router;\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// DX helpers\n// ──────────────────────────────────────────────────────────────────────────────\n\n/**\n * Helper for great IntelliSense when authoring controller maps.\n * @returns Function that enforces key names while preserving partial flexibility.\n */\nexport const defineControllers =\n <\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n >() =>\n <M extends PartialControllerMap<R, Ctx, Names>>(m: M) =>\n m;\n\n/**\n * Wrap a plain RequestHandler as an auth factory compatible with `fromCfg.auth`.\n * @param mw Middleware invoked for any leaf that requires authentication.\n * @param _leaf Leaf metadata (ignored, but provided to match factory signature).\n * @returns Factory that ignores the leaf and returns the same middleware.\n */\nexport const asLeafAuth =\n (mw: RequestHandler) =>\n (_leaf: AnyLeaf): RequestHandler =>\n mw;\n\n/**\n * Warn about leaves that don't have controllers.\n * Call this during startup to surface missing routes.\n * @param router Express router or app to inspect.\n * @param registry Finalized registry produced by `finalize(...)`.\n * @param logger Logger where warnings are emitted.\n */\nexport function warnMissingControllers<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n>(router: Router, registry: R, logger: { warn: (...args: any[]) => void }) {\n const registeredStore = (router as any)[REGISTERED_ROUTES_SYMBOL] as Set<string> | undefined;\n const initial = registeredStore ? Array.from(registeredStore) : collectRoutesFromStack(router);\n const registeredKeys = new Set<string>(initial);\n\n for (const leaf of registry.all) {\n const k = keyOf(leaf);\n if (!registeredKeys.has(k)) {\n logger.warn(`No controller registered for route: ${k}`);\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/routesV3.server.ts"],"sourcesContent":["/**\n * routesV3.server.ts\n * -----------------------------------------------------------------------------\n * Bind an Express router/app to a `finalize(...)` registry of AnyLeafs.\n * - Fully typed handlers (params/query/body/output)\n * - Zod parsing + optional output validation\n * - buildCtx runs as a middleware *first*, before all other middlewares\n * - Global, per-route, and cfg-derived middlewares (auth, uploads)\n * - Helper to warn about unimplemented routes\n * - DX helpers to use `ctx` in any middleware with proper types\n */\n\nimport { AnyLeaf, FileField, HttpMethod, InferBody, InferOutput, InferParams, InferQuery, MethodCfg } from '@emeryld/rrroutes-contract';\nimport type * as express from 'express';\nimport type { RequestHandler, Router } from 'express';\nimport type { ZodType } from 'zod';\n\n\n\n/** Shape expected from optional logger implementations. */\nexport type LoggerLike = {\n info?: (...args: any[]) => void;\n warn?: (...args: any[]) => void;\n error?: (...args: any[]) => void;\n debug?: (...args: any[]) => void;\n verbose?: (...args: any[]) => void;\n system?: (...args: any[]) => void;\n log?: (...args: any[]) => void;\n};\n\ntype RoutesLoggerCarrier = {\n routesLogger?: LoggerLike;\n};\n\ntype CtxWithRoutesLogger<Ctx> = Ctx & RoutesLoggerCarrier;\n\n// Debug logging --------------------------------------------------------------\nexport type RouteServerDebugMode = 'minimal' | 'complete';\n\ntype RouteServerDebugEventBase = {\n /** Optional logical name assigned via `RouteDef.debug?.debugName` (or `RouteDef.debugName`). */\n name?: string;\n};\n\nexport type RouteServerDebugEvent =\n | (RouteServerDebugEventBase & {\n type: 'request';\n stage: 'start' | 'success' | 'error';\n method: Uppercase<HttpMethod>;\n path: string;\n url: string;\n durationMs?: number;\n params?: unknown;\n query?: unknown;\n body?: unknown;\n output?: unknown;\n error?: unknown;\n })\n | (RouteServerDebugEventBase & {\n type: 'register';\n method: Uppercase<HttpMethod>;\n path: string;\n })\n | (RouteServerDebugEventBase & {\n type: 'buildCtx';\n stage: 'start' | 'success' | 'error';\n method: Uppercase<HttpMethod>;\n path: string;\n url: string;\n durationMs?: number;\n error?: unknown;\n })\n | (RouteServerDebugEventBase & {\n type: 'handler';\n stage: 'start' | 'success' | 'error';\n method: Uppercase<HttpMethod>;\n path: string;\n url: string;\n durationMs?: number;\n params?: unknown;\n query?: unknown;\n body?: unknown;\n output?: unknown;\n error?: unknown;\n });\n\nexport type RouteServerDebugLogger = (event: RouteServerDebugEvent) => void;\n\n/**\n * Configure server-side debug logging.\n * - Use booleans or `'minimal'/'complete'` for quick toggles.\n * - Pass a custom logger function to redirect structured events.\n * - Provide a map to enable specific event types, opt into verbose payload logging, or restrict logs via `only`.\n */\nexport type RouteServerDebugOptions<Names extends string = string> = RouteServerDebugToggleOptions<Names>;\n\n/**\n * Fine-grained toggle map for server debug logging.\n * Enable individual event types, opt into verbose payload logging, override the logger, or restrict to named routes.\n * Use `RouteDef.debug?.debugName` (or the deprecated `RouteDef.debugName`) to set the name that `only` will match against.\n */\nexport type RouteServerDebugToggleOptions<Names extends string = string> = Partial<\n Record<RouteServerDebugEvent['type'], boolean>\n> & {\n verbose?: boolean;\n logger?: RouteServerDebugLogger;\n only?: Names[];\n};\n\n/**\n * Per-route debug overrides. Same toggles as `RouteServerDebugOptions`, but limited to a single route\n * and therefore replaces the `only` filter with a local `debugName`.\n */\nexport type RouteDefDebugOptions<Names extends string = string> = Omit<\n RouteServerDebugToggleOptions<Names>,\n 'only'\n> & {\n debugName?: Names;\n};\n\nconst noopServerDebug: RouteServerDebugLogger = () => {};\n\nconst defaultServerDebug: RouteServerDebugLogger = (event: RouteServerDebugEvent) => {\n if (typeof console === 'undefined') return;\n const fn = console.debug ?? console.log;\n fn?.call(console, '[rrroutes-server]', event);\n};\n\nconst serverDebugEventTypes: RouteServerDebugEvent['type'][] = [\n 'register',\n 'request',\n 'buildCtx',\n 'handler',\n];\n\ntype ServerDebugEmitter<Names extends string> = {\n emit: (event: RouteServerDebugEvent, name?: Names) => void;\n mode: RouteServerDebugMode;\n};\n\nconst noopServerEmit = () => {};\n\nfunction createServerDebugEmitter<Names extends string>(\n option?: RouteServerDebugOptions<Names>,\n): ServerDebugEmitter<Names> {\n const disabled: ServerDebugEmitter<Names> = { emit: noopServerEmit, mode: 'minimal' };\n if (!option) return disabled;\n\n if (typeof option === 'object') {\n const toggles = option as RouteServerDebugToggleOptions<Names>;\n const verbose = Boolean(toggles.verbose);\n const enabledTypes = serverDebugEventTypes.filter((type) => toggles[type]);\n if (enabledTypes.length === 0) {\n return { emit: noopServerEmit, mode: verbose ? 'complete' : 'minimal' };\n }\n const whitelist = new Set<RouteServerDebugEvent['type']>(enabledTypes);\n const onlySet =\n toggles.only && toggles.only.length > 0 ? new Set<Names>(toggles.only) : undefined;\n const logger = toggles.logger ?? defaultServerDebug;\n const emit: ServerDebugEmitter<Names>['emit'] = (event, name) => {\n if (!whitelist.has(event.type)) return;\n if (onlySet && (!name || !onlySet.has(name))) return;\n logger(name ? { ...event, name } : event);\n };\n return { emit, mode: verbose ? 'complete' : 'minimal' };\n }\n\n return disabled;\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Keys + leaf helpers (derive keys from byKey to avoid template-literal pitfalls)\n// ──────────────────────────────────────────────────────────────────────────────\n\n/** Keys like \"GET /v1/foo\" that *actually* exist in the registry */\nexport type KeysOfRegistry<R extends { byKey: Record<string, AnyLeaf> }> = keyof R['byKey'] &\n string;\n\ntype MethodFromKey<K extends string> = K extends `${infer M} ${string}` ? Lowercase<M> : never;\ntype PathFromKey<K extends string> = K extends `${string} ${infer P}` ? P : never;\n\n/** Given a registry and a key, pick the exact leaf for that method+path */\nexport type LeafFromKey<R extends { all: readonly AnyLeaf[] }, K extends string> = Extract<\n R['all'][number],\n { method: MethodFromKey<K> & HttpMethod; path: PathFromKey<K> }\n>;\n\n/** Optional-ify types if your core returns `never` when a schema isn't defined */\ntype Maybe<T> = [T] extends [never] ? undefined : T;\n\n/** Typed params argument exposed to handlers. */\nexport type ArgParams<L extends AnyLeaf> = Maybe<InferParams<L>>;\n/** Typed query argument exposed to handlers. */\nexport type ArgQuery<L extends AnyLeaf> = Maybe<InferQuery<L>>;\n/** Typed body argument exposed to handlers. */\nexport type ArgBody<L extends AnyLeaf> = Maybe<InferBody<L>>;\n\n/**\n * Convenience to compute a `\"METHOD /path\"` key from a leaf.\n * @param leaf Leaf describing the route.\n * @returns Uppercase method + path key.\n */\nexport const keyOf = (leaf: AnyLeaf) => `${leaf.method.toUpperCase()} ${leaf.path}` as const;\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Context typing & DX helpers (so ctx is usable in *any* middleware)\n// ──────────────────────────────────────────────────────────────────────────────\n\n/**\n * Unique symbol used to stash ctx on res.locals.\n * (Symbols are safer than string keys against collisions.)\n */\nexport const CTX_SYMBOL: unique symbol = Symbol.for('typedLeaves.ctx');\n\nconst AFTER_HANDLER_NEXT_SYMBOL: unique symbol = Symbol.for('typedLeaves.afterHandlerNext');\n\nfunction setAfterHandlerNext(res: express.Response, value: boolean) {\n (res.locals as any)[AFTER_HANDLER_NEXT_SYMBOL] = value;\n}\n\nfunction handlerInvokedNext(res: express.Response): boolean {\n return Boolean((res.locals as any)[AFTER_HANDLER_NEXT_SYMBOL]);\n}\n\n/** Response type that *has* a ctx on locals for DX in middlewares */\nexport type ResponseWithCtx<Ctx> =\n // Replace locals with an intersection that guarantees CTX_SYMBOL exists\n Omit<express.Response, 'locals'> & {\n locals: express.Response['locals'] & { [CTX_SYMBOL]: CtxWithRoutesLogger<Ctx> };\n };\n\n/** A middleware signature that can *use* ctx via `res.locals[CTX_SYMBOL]` */\nexport type CtxRequestHandler<Ctx> = (args: {\n req: express.Request;\n res: express.Response;\n next: express.NextFunction;\n ctx: CtxWithRoutesLogger<Ctx>;\n}) => any;\n\n/**\n * Safely read ctx from any Response.\n * @param res Express response whose locals contain the ctx symbol.\n * @returns Strongly typed context object.\n */\nexport function getCtx<Ctx = unknown>(res: express.Response): CtxWithRoutesLogger<Ctx> {\n return (res.locals as any)[CTX_SYMBOL] as CtxWithRoutesLogger<Ctx>;\n}\n\n/**\n * Wrap a ctx-typed middleware to a plain RequestHandler (for arrays, etc.).\n * @param mw Middleware that expects a typed response with ctx available.\n * @returns Standard Express request handler.\n */\nfunction adaptCtxMw<Ctx>(mw: CtxRequestHandler<Ctx>): RequestHandler {\n return (req, res, next) => {\n try {\n const result = mw({ req, res, next, ctx: getCtx<Ctx>(res) });\n if (result && typeof (result as Promise<unknown>).then === 'function') {\n return (result as Promise<unknown>).catch((err) => next(err));\n }\n return result as any;\n } catch (err) {\n next(err as any);\n return undefined;\n }\n };\n}\n\nfunction adaptAfterMw<Ctx>(mw: CtxRequestHandler<Ctx>): RequestHandler {\n const adapted = adaptCtxMw<Ctx>(mw);\n return (req: express.Request, res: express.Response, next: express.NextFunction) => {\n if (!handlerInvokedNext(res)) {\n next();\n return;\n }\n adapted(req, res, next);\n };\n}\n\nfunction mapAfterMiddleware<Ctx>(mws?: Array<CtxRequestHandler<Ctx>>): RequestHandler[] {\n return (mws ?? []).map((mw) => adaptAfterMw<Ctx>(mw));\n}\n\nfunction logHandlerDebugWithRoutesLogger(\n logger: LoggerLike | undefined,\n event: RouteServerDebugEvent,\n) {\n if (!logger || event.type !== 'handler') return;\n const payload: [string, RouteServerDebugEvent] = [\n `[rrroutes-server][handler:${event.stage}] ${event.method} ${event.path}`,\n event,\n ];\n if (event.stage === 'error') {\n (\n logger.error ??\n logger.warn ??\n logger.debug ??\n logger.info ??\n logger.log ??\n logger.system\n )?.call(logger, ...payload);\n return;\n }\n (\n logger.debug ??\n logger.verbose ??\n logger.info ??\n logger.log ??\n logger.system\n )?.call(logger, ...payload);\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Controller types — object form only (simpler, clearer typings)\n// ──────────────────────────────────────────────────────────────────────────────\n\n/** Typed route handler for a specific leaf */\nexport type Handler<L extends AnyLeaf, Ctx = unknown> = (args: {\n req: express.Request;\n res: express.Response;\n next: express.NextFunction;\n ctx: CtxWithRoutesLogger<Ctx>;\n params: ArgParams<L>;\n query: ArgQuery<L>;\n body: ArgBody<L>;\n}) => Promise<InferOutput<L>> | InferOutput<L>;\n\n/** Route definition for one key */\nexport type RouteDef<L extends AnyLeaf, Ctx = unknown, Names extends string = string> = {\n /** Middlewares before the handler (run after buildCtx/global/derived) */\n use?: Array<CtxRequestHandler<Ctx>>;\n /** Middlewares after the handler *if* it calls next() */\n after?: Array<CtxRequestHandler<Ctx>>;\n /** Your business logic */\n handler: Handler<L, Ctx>;\n /**\n * Optional per-route debug overrides. When provided, these replace the global debug options.\n */\n debug?: RouteDefDebugOptions<Names>;\n /**\n * Optional logical name used for debug filtering. Prefer `debug.debugName`; this field remains for backwards compatibility.\n */\n debugName?: Names;\n};\n\n/** Map of registry keys -> route defs */\nexport type ControllerMap<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n> = {\n [P in KeysOfRegistry<R>]: RouteDef<LeafFromKey<R, P>, Ctx, Names>;\n};\n\nexport type PartialControllerMap<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n> = Partial<ControllerMap<R, Ctx, Names>>;\n\n// ──────────────────────────────────────────────────────────────────────────────\n/** Options + derivation helpers */\n// ──────────────────────────────────────────────────────────────────────────────\n\nexport type RouteServerConfig<Ctx = unknown, Names extends string = string> = {\n /**\n * Build a request-scoped context. We wrap this in a middleware that runs\n * *first* (before global/derived/route middlewares), and stash it on\n * `res.locals[CTX_SYMBOL]` so *all* later middlewares can use it.\n * You can optionally include `routesLogger` to override handler debug logging per request.\n */\n buildCtx: (\n req: express.Request,\n res: express.Response,\n ) => CtxWithRoutesLogger<Ctx> | Promise<CtxWithRoutesLogger<Ctx>>;\n\n /**\n * Global middlewares for every bound route (run *after* buildCtx). Prefer `globalMiddleware.before`.\n */\n global?: Array<CtxRequestHandler<Ctx>>;\n\n /**\n * Grouped global middlewares that run *before* or *after* the handler for every route.\n */\n globalMiddleware?: {\n before?: Array<CtxRequestHandler<Ctx>>;\n after?: Array<CtxRequestHandler<Ctx>>;\n };\n\n /**\n * Derive middleware from MethodCfg.\n * - `auth` runs when cfg.authenticated === true (or `when` overrides)\n * - `upload` runs when cfg.bodyFiles has entries\n */\n fromCfg?: {\n auth?: RequestHandler | ((leaf: AnyLeaf) => RequestHandler);\n when?: (cfg: MethodCfg, leaf: AnyLeaf) => { auth?: boolean } | void;\n upload?: (files: FileField[] | undefined, leaf: AnyLeaf) => RequestHandler[];\n };\n\n /** Validate handler return values with outputSchema (default: true) */\n validateOutput?: boolean;\n\n /** Custom responder (default: res.json(data)) */\n send?: (res: express.Response, data: unknown) => void;\n\n /** Optional logger hooks */\n logger?: LoggerLike;\n\n /**\n * Optional debug logging for the request lifecycle.\n * Supports booleans/modes/loggers, or a toggle map with per-event enabling, verbose payload logging,\n * and `only` filters tied to `RouteDef.debug?.debugName` (or the deprecated `RouteDef.debugName`).\n */\n debug?: RouteServerDebugOptions<Names>;\n};\n\n/** Default JSON responder (typed to avoid implicit-any diagnostics) */\nconst defaultSend: (res: express.Response, data: unknown) => void = (res, data) => {\n res.json(data as any);\n};\n\n/**\n * Normalize `auth` into a RequestHandler (avoids union-narrowing issues).\n * @param auth Static middleware or factory returning one for the current leaf.\n * @param leaf Leaf being registered.\n * @returns Request handler or undefined when no auth is required.\n */\nfunction resolveAuth(\n auth: RequestHandler | ((leaf: AnyLeaf) => RequestHandler) | undefined,\n leaf: AnyLeaf,\n): RequestHandler | undefined {\n if (!auth) return undefined;\n return (auth as (l: AnyLeaf) => RequestHandler).length === 1\n ? (auth as (l: AnyLeaf) => RequestHandler)(leaf)\n : (auth as RequestHandler);\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Core builder\n// ──────────────────────────────────────────────────────────────────────────────\n\nconst REGISTERED_ROUTES_SYMBOL = Symbol.for('routesV3.registeredRoutes');\n\ntype RegisteredRouteStore = Set<string>;\n\n/**\n * Retrieve or initialize the shared store of registered route keys.\n * @param router Express router/application that carries previously registered keys.\n * @returns Set of string keys describing registered routes.\n */\nfunction getRegisteredRouteStore(router: Router): RegisteredRouteStore {\n const existing = (router as any)[REGISTERED_ROUTES_SYMBOL] as RegisteredRouteStore | undefined;\n if (existing) return existing;\n const store: RegisteredRouteStore = new Set();\n (router as any)[REGISTERED_ROUTES_SYMBOL] = store;\n return store;\n}\n\n/**\n * Inspect the Express layer stack to discover already-registered routes.\n * @param appOrRouter Express application or router to inspect.\n * @returns All keys in the form `\"METHOD /path\"` found on the stack.\n */\nfunction collectRoutesFromStack(appOrRouter: Router): string[] {\n const result: string[] = [];\n const stack: any[] =\n (appOrRouter as any).stack ??\n ((appOrRouter as any)._router ? (appOrRouter as any)._router.stack : undefined) ??\n [];\n\n if (!Array.isArray(stack)) return result;\n\n for (const layer of stack) {\n const route = layer && layer.route;\n if (!route) continue;\n\n const paths = Array.isArray(route.path) ? route.path : [route.path];\n const methodEntries = Object.entries(route.methods ?? {}).filter(([, enabled]) => enabled);\n\n for (const path of paths) {\n for (const [method] of methodEntries) {\n result.push(`${method.toUpperCase()} ${path}`);\n }\n }\n }\n\n return result;\n}\n\n/** Runtime helpers returned by `createRRRoute`. */\nexport type RouteServer<Ctx = unknown, Names extends string = string> = {\n router: Router;\n register<L extends AnyLeaf>(leaf: L, def: RouteDef<L, Ctx, Names>): void;\n registerControllers<R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> }>(\n registry: R,\n controllers: PartialControllerMap<R, Ctx, Names>,\n ): void;\n warnMissingControllers<R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> }>(\n registry: R,\n logger: { warn: (...args: any[]) => void },\n ): void;\n getRegisteredKeys(): string[];\n};\n\n/**\n * Create an Express binding helper that keeps routes and controllers in sync.\n * @param router Express router or app to register handlers on.\n * @param config Optional configuration controlling ctx building, auth, uploads, etc.\n * @returns Object with helpers to register controllers and inspect registered keys.\n */\nexport function createRRRoute<Ctx = unknown, Names extends string = string>(\n router: Router,\n config: RouteServerConfig<Ctx, Names>,\n): RouteServer<Ctx, Names> {\n const validateOutput = config.validateOutput ?? true;\n const send = config.send ?? defaultSend;\n const logger = config.logger;\n const { emit: defaultEmitDebug, mode: defaultDebugMode } =\n createServerDebugEmitter<Names>(config.debug);\n const decorateDebugEvent = <T extends RouteServerDebugEvent>(\n isVerbose: boolean,\n event: T,\n details?: Partial<RouteServerDebugEvent>,\n ): RouteServerDebugEvent => {\n if (!isVerbose || !details) return event;\n return { ...event, ...details } as RouteServerDebugEvent;\n };\n\n const globalBeforeMws = [...(config.global ?? []), ...(config.globalMiddleware?.before ?? [])].map(\n (mw) => adaptCtxMw<Ctx>(mw),\n );\n const globalAfterMws = mapAfterMiddleware<Ctx>(config.globalMiddleware?.after);\n const registered = getRegisteredRouteStore(router);\n\n const buildDerived = (leaf: AnyLeaf): RequestHandler[] => {\n const derived: RequestHandler[] = [];\n const decision = config.fromCfg?.when?.(leaf.cfg, leaf) ?? {};\n const needsAuth = typeof decision.auth === 'boolean' ? decision.auth : !!leaf.cfg.authenticated;\n\n if (needsAuth && config.fromCfg?.auth) {\n const authMw = resolveAuth(config.fromCfg.auth, leaf);\n if (authMw) derived.push(authMw);\n }\n\n if (\n config.fromCfg?.upload &&\n Array.isArray(leaf.cfg.bodyFiles) &&\n leaf.cfg.bodyFiles.length > 0\n ) {\n derived.push(...config.fromCfg.upload(leaf.cfg.bodyFiles, leaf));\n }\n\n return derived;\n };\n\n /** Register a single leaf/controller pair on the underlying router. */\n function register<L extends AnyLeaf>(leaf: L, def: RouteDef<L, Ctx, Names>) {\n const method = leaf.method as HttpMethod;\n const methodUpper = method.toUpperCase() as Uppercase<HttpMethod>;\n const path = leaf.path as string;\n const key = keyOf(leaf);\n const defDebug = def.debug;\n let debugName = def.debugName as Names | undefined;\n let routeDebugEmitter: ServerDebugEmitter<Names> | undefined;\n if (defDebug) {\n const { debugName: overrideName, ...rest } = defDebug;\n const hasOverrides = Object.values(rest).some((value) => value !== undefined);\n if (hasOverrides) {\n routeDebugEmitter = createServerDebugEmitter<Names>(\n rest as RouteServerDebugOptions<Names>,\n );\n }\n debugName = (overrideName ?? debugName) as Names | undefined;\n }\n const activeEmit = routeDebugEmitter?.emit ?? defaultEmitDebug;\n const activeDebugMode = routeDebugEmitter?.mode ?? defaultDebugMode;\n const emit = (event: RouteServerDebugEvent) => activeEmit(event, debugName);\n const isVerboseDebug = activeDebugMode === 'complete';\n emit({ type: 'register', method: methodUpper, path });\n\n const routeSpecific = (def?.use ?? []).map((mw) => adaptCtxMw<Ctx>(mw));\n const derived = buildDerived(leaf);\n const ctxMw: RequestHandler = async (req, res, next) => {\n const requestUrl = req.originalUrl ?? path;\n const startedAt = Date.now();\n emit({ type: 'buildCtx', stage: 'start', method: methodUpper, path, url: requestUrl });\n try {\n const ctx = await config.buildCtx(req, res);\n (res.locals as any)[CTX_SYMBOL] = ctx;\n emit({\n type: 'buildCtx',\n stage: 'success',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n });\n next();\n } catch (err) {\n emit({\n type: 'buildCtx',\n stage: 'error',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n error: err,\n });\n logger?.error?.('buildCtx error', err);\n next(err as any);\n }\n };\n const before: RequestHandler[] = [ctxMw, ...globalBeforeMws, ...derived, ...routeSpecific];\n\n const wrapped: RequestHandler = async (req, res, next) => {\n const requestUrl = req.originalUrl.split('?')[0] ?? path;\n const startedAt = Date.now();\n emit({ type: 'request', stage: 'start', method: methodUpper, path, url: requestUrl });\n let params: ArgParams<L> | undefined;\n let query: ArgQuery<L> | undefined;\n let body: ArgBody<L> | undefined;\n let responsePayload: InferOutput<L> | undefined;\n let hasResponsePayload = false;\n setAfterHandlerNext(res, false);\n let handlerCalledNext = false;\n const downstreamNext: express.NextFunction = (err?: any) => {\n handlerCalledNext = true;\n setAfterHandlerNext(res, true);\n next(err);\n };\n let ctxRoutesLogger: LoggerLike | undefined;\n const emitWithCtx = (\n event: RouteServerDebugEvent,\n details?: Partial<RouteServerDebugEvent>,\n ) => {\n const decorated = decorateDebugEvent(isVerboseDebug, event, details);\n if (decorated.type === 'handler') {\n logHandlerDebugWithRoutesLogger(ctxRoutesLogger, decorated);\n }\n emit(decorated);\n };\n\n try {\n logger?.info?.(`${methodUpper}@${path} (${requestUrl})`);\n\n const ctx = (res.locals as any)[CTX_SYMBOL] as CtxWithRoutesLogger<Ctx>;\n ctxRoutesLogger = ctx.routesLogger;\n\n params = (\n leaf.cfg.paramsSchema\n ? (leaf.cfg.paramsSchema as ZodType).parse(req.params)\n : Object.keys(req.params || {}).length\n ? (req.params as any)\n : undefined\n ) as ArgParams<L>;\n\n try {\n query = leaf.cfg.querySchema\n ? (leaf.cfg.querySchema as ZodType).parse(req.query)\n : Object.keys(req.query || {}).length\n ? (req.query as any)\n : undefined;\n } catch (e) {\n logger?.error?.('Query parsing error', {\n path,\n method: methodUpper,\n error: e,\n raw: JSON.stringify(req.query),\n });\n throw e;\n }\n\n body = (\n leaf.cfg.bodySchema\n ? (leaf.cfg.bodySchema as ZodType).parse(req.body)\n : req.body !== undefined\n ? (req.body as any)\n : undefined\n ) as ArgBody<L>;\n\n logger?.verbose?.(`${methodUpper}@${path} (${requestUrl})`, {\n params,\n query,\n body,\n });\n\n const handlerStartedAt = Date.now();\n emitWithCtx(\n {\n type: 'handler',\n stage: 'start',\n method: methodUpper,\n path,\n url: requestUrl,\n },\n isVerboseDebug ? { params, query, body } : undefined,\n );\n\n let result;\n try {\n result = await def.handler({\n req,\n res,\n next: downstreamNext,\n ctx,\n params: params as ArgParams<L>,\n query: query as ArgQuery<L>,\n body: body as ArgBody<L>,\n });\n emitWithCtx(\n {\n type: 'handler',\n stage: 'success',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - handlerStartedAt,\n },\n isVerboseDebug\n ? {\n params,\n query,\n body,\n ...(result !== undefined ? { output: result } : {}),\n }\n : undefined,\n );\n } catch (e) {\n emitWithCtx(\n {\n type: 'handler',\n stage: 'error',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - handlerStartedAt,\n error: e,\n },\n isVerboseDebug ? { params, query, body } : undefined,\n );\n logger?.error?.('Handler error', e);\n throw e;\n }\n\n if (!res.headersSent) {\n if (result !== undefined) {\n const out =\n validateOutput && leaf.cfg.outputSchema\n ? (leaf.cfg.outputSchema as ZodType).parse(result)\n : result;\n responsePayload = out as InferOutput<L>;\n hasResponsePayload = true;\n logger?.verbose?.(`${methodUpper}@${path} result`, out);\n send(res, out);\n } else if (!handlerCalledNext) {\n next();\n }\n }\n\n emitWithCtx(\n {\n type: 'request',\n stage: 'success',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n },\n isVerboseDebug\n ? {\n params,\n query,\n body,\n ...(hasResponsePayload ? { output: responsePayload } : {}),\n }\n : undefined,\n );\n } catch (err) {\n emitWithCtx(\n {\n type: 'request',\n stage: 'error',\n method: methodUpper,\n path,\n url: requestUrl,\n durationMs: Date.now() - startedAt,\n error: err,\n },\n isVerboseDebug ? { params, query, body } : undefined,\n );\n logger?.error?.('Route error', err);\n next(err as any);\n }\n };\n\n const after = [...mapAfterMiddleware<Ctx>(def?.after), ...globalAfterMws];\n (router as any)[method](path, ...before, wrapped, ...after);\n registered.add(key);\n }\n\n /**\n * Register controller definitions for the provided keys.\n * @param registry Finalized registry of leaves.\n * @param controllers Partial controller map keyed by `\"METHOD /path\"`.\n */\n function registerControllers<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n >(registry: R, controllers: PartialControllerMap<R, Ctx, Names>) {\n (Object.keys(controllers) as Array<KeysOfRegistry<R>>).forEach((key) => {\n const leaf = registry.byKey[key] as unknown as LeafFromKey<R, typeof key> | undefined;\n if (!leaf) {\n logger?.warn?.(`No leaf found for controller key: ${key}. Not registering route.`);\n return;\n }\n const def = controllers[key];\n if (!def) return;\n register(leaf as LeafFromKey<R, typeof key>, def);\n });\n }\n\n /**\n * Warn about leaves that do not have a registered controller.\n * @param registry Finalized registry of leaves.\n * @param warnLogger Logger used for warning output.\n */\n function warnMissing<R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> }>(\n registry: R,\n warnLogger: { warn: (...args: any[]) => void },\n ) {\n const registeredFromStore = new Set<string>(Array.from(registered));\n if (registeredFromStore.size === 0) {\n collectRoutesFromStack(router).forEach((key) => registeredFromStore.add(key));\n }\n for (const leaf of registry.all) {\n const key = keyOf(leaf);\n if (!registeredFromStore.has(key)) {\n warnLogger.warn(`No controller registered for route: ${key}`);\n }\n }\n }\n\n return {\n router,\n register,\n registerControllers,\n warnMissingControllers: warnMissing,\n getRegisteredKeys: () => Array.from(registered),\n };\n}\n\n/**\n * Bind only the controllers that are present in the provided map.\n * @param router Express router or app.\n * @param registry Finalized registry produced by `finalize(...)`.\n * @param controllers Partial map of controllers keyed by `\"METHOD /path\"`.\n * @param config Optional route server configuration.\n * @returns The same router instance for chaining.\n */\nexport function bindExpressRoutes<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n>(\n router: Router,\n registry: R,\n controllers: PartialControllerMap<R, Ctx, Names>,\n config: RouteServerConfig<Ctx, Names>,\n) {\n const server = createRRRoute<Ctx, Names>(router, config);\n server.registerControllers(registry, controllers);\n return router;\n}\n\n/**\n * Bind controllers for every leaf. Missing entries fail at compile time.\n * @param router Express router or app.\n * @param registry Finalized registry produced by `finalize(...)`.\n * @param controllers Complete map of controllers keyed by `\"METHOD /path\"`.\n * @param config Optional route server configuration.\n * @returns The same router instance for chaining.\n */\nexport function bindAll<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n>(\n router: Router,\n registry: R,\n controllers: { [K in KeysOfRegistry<R>]: RouteDef<LeafFromKey<R, K>, Ctx, Names> },\n config: RouteServerConfig<Ctx, Names>,\n) {\n const server = createRRRoute<Ctx, Names>(router, config);\n server.registerControllers(registry, controllers);\n return router;\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// DX helpers\n// ──────────────────────────────────────────────────────────────────────────────\n\n/**\n * Helper for great IntelliSense when authoring controller maps.\n * @returns Function that enforces key names while preserving partial flexibility.\n */\nexport const defineControllers =\n <\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n Ctx = unknown,\n Names extends string = string,\n >() =>\n <M extends PartialControllerMap<R, Ctx, Names>>(m: M) =>\n m;\n\n/**\n * Wrap a plain RequestHandler as an auth factory compatible with `fromCfg.auth`.\n * @param mw Middleware invoked for any leaf that requires authentication.\n * @param _leaf Leaf metadata (ignored, but provided to match factory signature).\n * @returns Factory that ignores the leaf and returns the same middleware.\n */\nexport const asLeafAuth =\n (mw: RequestHandler) =>\n (_leaf: AnyLeaf): RequestHandler =>\n mw;\n\n/**\n * Warn about leaves that don't have controllers.\n * Call this during startup to surface missing routes.\n * @param router Express router or app to inspect.\n * @param registry Finalized registry produced by `finalize(...)`.\n * @param logger Logger where warnings are emitted.\n */\nexport function warnMissingControllers<\n R extends { all: readonly AnyLeaf[]; byKey: Record<string, AnyLeaf> },\n>(router: Router, registry: R, logger: { warn: (...args: any[]) => void }) {\n const registeredStore = (router as any)[REGISTERED_ROUTES_SYMBOL] as Set<string> | undefined;\n const initial = registeredStore ? Array.from(registeredStore) : collectRoutesFromStack(router);\n const registeredKeys = new Set<string>(initial);\n\n for (const leaf of registry.all) {\n const k = keyOf(leaf);\n if (!registeredKeys.has(k)) {\n logger.warn(`No controller registered for route: ${k}`);\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -22,9 +22,13 @@ export type LoggerLike = {
22
22
  system?: (...args: any[]) => void;
23
23
  log?: (...args: any[]) => void;
24
24
  };
25
+ type RoutesLoggerCarrier = {
26
+ routesLogger?: LoggerLike;
27
+ };
28
+ type CtxWithRoutesLogger<Ctx> = Ctx & RoutesLoggerCarrier;
25
29
  export type RouteServerDebugMode = 'minimal' | 'complete';
26
30
  type RouteServerDebugEventBase = {
27
- /** Optional logical name assigned via `RouteDef.debugName`. */
31
+ /** Optional logical name assigned via `RouteDef.debug?.debugName` (or `RouteDef.debugName`). */
28
32
  name?: string;
29
33
  };
30
34
  export type RouteServerDebugEvent = (RouteServerDebugEventBase & {
@@ -75,13 +79,20 @@ export type RouteServerDebugOptions<Names extends string = string> = RouteServer
75
79
  /**
76
80
  * Fine-grained toggle map for server debug logging.
77
81
  * Enable individual event types, opt into verbose payload logging, override the logger, or restrict to named routes.
78
- * Use `RouteDef.debugName` to set the name that `only` will match against.
82
+ * Use `RouteDef.debug?.debugName` (or the deprecated `RouteDef.debugName`) to set the name that `only` will match against.
79
83
  */
80
84
  export type RouteServerDebugToggleOptions<Names extends string = string> = Partial<Record<RouteServerDebugEvent['type'], boolean>> & {
81
85
  verbose?: boolean;
82
86
  logger?: RouteServerDebugLogger;
83
87
  only?: Names[];
84
88
  };
89
+ /**
90
+ * Per-route debug overrides. Same toggles as `RouteServerDebugOptions`, but limited to a single route
91
+ * and therefore replaces the `only` filter with a local `debugName`.
92
+ */
93
+ export type RouteDefDebugOptions<Names extends string = string> = Omit<RouteServerDebugToggleOptions<Names>, 'only'> & {
94
+ debugName?: Names;
95
+ };
85
96
  /** Keys like "GET /v1/foo" that *actually* exist in the registry */
86
97
  export type KeysOfRegistry<R extends {
87
98
  byKey: Record<string, AnyLeaf>;
@@ -117,7 +128,7 @@ export declare const CTX_SYMBOL: unique symbol;
117
128
  /** Response type that *has* a ctx on locals for DX in middlewares */
118
129
  export type ResponseWithCtx<Ctx> = Omit<express.Response, 'locals'> & {
119
130
  locals: express.Response['locals'] & {
120
- [CTX_SYMBOL]: Ctx;
131
+ [CTX_SYMBOL]: CtxWithRoutesLogger<Ctx>;
121
132
  };
122
133
  };
123
134
  /** A middleware signature that can *use* ctx via `res.locals[CTX_SYMBOL]` */
@@ -125,20 +136,20 @@ export type CtxRequestHandler<Ctx> = (args: {
125
136
  req: express.Request;
126
137
  res: express.Response;
127
138
  next: express.NextFunction;
128
- ctx: Ctx;
139
+ ctx: CtxWithRoutesLogger<Ctx>;
129
140
  }) => any;
130
141
  /**
131
142
  * Safely read ctx from any Response.
132
143
  * @param res Express response whose locals contain the ctx symbol.
133
144
  * @returns Strongly typed context object.
134
145
  */
135
- export declare function getCtx<Ctx = unknown>(res: express.Response): Ctx;
146
+ export declare function getCtx<Ctx = unknown>(res: express.Response): CtxWithRoutesLogger<Ctx>;
136
147
  /** Typed route handler for a specific leaf */
137
148
  export type Handler<L extends AnyLeaf, Ctx = unknown> = (args: {
138
149
  req: express.Request;
139
150
  res: express.Response;
140
151
  next: express.NextFunction;
141
- ctx: Ctx;
152
+ ctx: CtxWithRoutesLogger<Ctx>;
142
153
  params: ArgParams<L>;
143
154
  query: ArgQuery<L>;
144
155
  body: ArgBody<L>;
@@ -152,7 +163,11 @@ export type RouteDef<L extends AnyLeaf, Ctx = unknown, Names extends string = st
152
163
  /** Your business logic */
153
164
  handler: Handler<L, Ctx>;
154
165
  /**
155
- * Optional logical name used for debug filtering. Pair with `debug.only` so only named routes emit logs.
166
+ * Optional per-route debug overrides. When provided, these replace the global debug options.
167
+ */
168
+ debug?: RouteDefDebugOptions<Names>;
169
+ /**
170
+ * Optional logical name used for debug filtering. Prefer `debug.debugName`; this field remains for backwards compatibility.
156
171
  */
157
172
  debugName?: Names;
158
173
  };
@@ -173,13 +188,20 @@ export type RouteServerConfig<Ctx = unknown, Names extends string = string> = {
173
188
  * Build a request-scoped context. We wrap this in a middleware that runs
174
189
  * *first* (before global/derived/route middlewares), and stash it on
175
190
  * `res.locals[CTX_SYMBOL]` so *all* later middlewares can use it.
191
+ * You can optionally include `routesLogger` to override handler debug logging per request.
176
192
  */
177
- buildCtx: (req: express.Request, res: express.Response) => Ctx | Promise<Ctx>;
193
+ buildCtx: (req: express.Request, res: express.Response) => CtxWithRoutesLogger<Ctx> | Promise<CtxWithRoutesLogger<Ctx>>;
178
194
  /**
179
- * Global middlewares for every bound route (run *after* buildCtx).
180
- * You can write them as ctx-aware middlewares for great DX.
195
+ * Global middlewares for every bound route (run *after* buildCtx). Prefer `globalMiddleware.before`.
181
196
  */
182
197
  global?: Array<CtxRequestHandler<Ctx>>;
198
+ /**
199
+ * Grouped global middlewares that run *before* or *after* the handler for every route.
200
+ */
201
+ globalMiddleware?: {
202
+ before?: Array<CtxRequestHandler<Ctx>>;
203
+ after?: Array<CtxRequestHandler<Ctx>>;
204
+ };
183
205
  /**
184
206
  * Derive middleware from MethodCfg.
185
207
  * - `auth` runs when cfg.authenticated === true (or `when` overrides)
@@ -201,11 +223,11 @@ export type RouteServerConfig<Ctx = unknown, Names extends string = string> = {
201
223
  /**
202
224
  * Optional debug logging for the request lifecycle.
203
225
  * Supports booleans/modes/loggers, or a toggle map with per-event enabling, verbose payload logging,
204
- * and `only` filters tied to `RouteDef.debugName`.
226
+ * and `only` filters tied to `RouteDef.debug?.debugName` (or the deprecated `RouteDef.debugName`).
205
227
  */
206
228
  debug?: RouteServerDebugOptions<Names>;
207
229
  };
208
- /** Runtime helpers returned by `createRouteServer`. */
230
+ /** Runtime helpers returned by `createRRRoute`. */
209
231
  export type RouteServer<Ctx = unknown, Names extends string = string> = {
210
232
  router: Router;
211
233
  register<L extends AnyLeaf>(leaf: L, def: RouteDef<L, Ctx, Names>): void;
@@ -227,7 +249,7 @@ export type RouteServer<Ctx = unknown, Names extends string = string> = {
227
249
  * @param config Optional configuration controlling ctx building, auth, uploads, etc.
228
250
  * @returns Object with helpers to register controllers and inspect registered keys.
229
251
  */
230
- export declare function createRouteServer<Ctx = unknown, Names extends string = string>(router: Router, config: RouteServerConfig<Ctx, Names>): RouteServer<Ctx, Names>;
252
+ export declare function createRRRoute<Ctx = unknown, Names extends string = string>(router: Router, config: RouteServerConfig<Ctx, Names>): RouteServer<Ctx, Names>;
231
253
  /**
232
254
  * Bind only the controllers that are present in the provided map.
233
255
  * @param router Express router or app.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emeryld/rrroutes-server",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",