@emeryld/rrroutes-server 1.6.3 → 2.0.0
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 +0 -1
- package/dist/index.cjs +14 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +14 -83
- package/dist/index.js.map +1 -1
- package/dist/routesV3.server.d.ts +1 -13
- package/dist/sockets/socket.server.index.d.ts +19 -21
- package/package.json +2 -2
- package/dist/sockets/socket.server.auth.d.ts +0 -28
package/README.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,21 +15,12 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
20
|
// src/index.ts
|
|
31
21
|
var index_exports = {};
|
|
32
22
|
__export(index_exports, {
|
|
33
23
|
CTX_SYMBOL: () => CTX_SYMBOL,
|
|
34
|
-
asLeafAuth: () => asLeafAuth,
|
|
35
24
|
bindAll: () => bindAll,
|
|
36
25
|
bindExpressRoutes: () => bindExpressRoutes,
|
|
37
26
|
createRRRoute: () => createRRRoute,
|
|
@@ -44,11 +33,6 @@ __export(index_exports, {
|
|
|
44
33
|
module.exports = __toCommonJS(index_exports);
|
|
45
34
|
|
|
46
35
|
// src/routesV3.server.ts
|
|
47
|
-
var defaultServerDebug = (event) => {
|
|
48
|
-
if (typeof console === "undefined") return;
|
|
49
|
-
const fn = console.debug ?? console.log;
|
|
50
|
-
fn?.call(console, "[rrroutes-server]", event);
|
|
51
|
-
};
|
|
52
36
|
var serverDebugEventTypes = [
|
|
53
37
|
"register",
|
|
54
38
|
"request",
|
|
@@ -69,9 +53,9 @@ function createServerDebugEmitter(option) {
|
|
|
69
53
|
}
|
|
70
54
|
const whitelist = new Set(enabledTypes);
|
|
71
55
|
const onlySet = toggles.only && toggles.only.length > 0 ? new Set(toggles.only) : void 0;
|
|
72
|
-
const logger = toggles.logger
|
|
56
|
+
const logger = toggles.logger;
|
|
73
57
|
const emit = (event, name) => {
|
|
74
|
-
if (!whitelist.has(event.type)) return;
|
|
58
|
+
if (!whitelist.has(event.type) || !logger) return;
|
|
75
59
|
if (onlySet && (!name || !onlySet.has(name))) return;
|
|
76
60
|
logger(name ? { ...event, name } : event);
|
|
77
61
|
};
|
|
@@ -133,10 +117,6 @@ function logHandlerDebugWithRoutesLogger(logger, event) {
|
|
|
133
117
|
var defaultSend = (res, data) => {
|
|
134
118
|
res.json(data);
|
|
135
119
|
};
|
|
136
|
-
function resolveAuth(auth, leaf) {
|
|
137
|
-
if (!auth) return void 0;
|
|
138
|
-
return auth.length === 1 ? auth(leaf) : auth;
|
|
139
|
-
}
|
|
140
120
|
var REGISTERED_ROUTES_SYMBOL = Symbol.for("routesV3.registeredRoutes");
|
|
141
121
|
function getRegisteredRouteStore(router) {
|
|
142
122
|
const existing = router[REGISTERED_ROUTES_SYMBOL];
|
|
@@ -178,12 +158,6 @@ function createRRRoute(router, config) {
|
|
|
178
158
|
const registered = getRegisteredRouteStore(router);
|
|
179
159
|
const buildDerived = (leaf) => {
|
|
180
160
|
const derived = [];
|
|
181
|
-
const decision = config.fromCfg?.when?.(leaf.cfg, leaf) ?? {};
|
|
182
|
-
const needsAuth = typeof decision.auth === "boolean" ? decision.auth : !!leaf.cfg.authenticated;
|
|
183
|
-
if (needsAuth && config.fromCfg?.auth) {
|
|
184
|
-
const authMw = resolveAuth(config.fromCfg.auth, leaf);
|
|
185
|
-
if (authMw) derived.push(authMw);
|
|
186
|
-
}
|
|
187
161
|
if (config.fromCfg?.upload && Array.isArray(leaf.cfg.bodyFiles) && leaf.cfg.bodyFiles.length > 0) {
|
|
188
162
|
derived.push(...config.fromCfg.upload(leaf.cfg.bodyFiles, leaf));
|
|
189
163
|
}
|
|
@@ -433,7 +407,6 @@ function bindAll(router, registry, controllers, config) {
|
|
|
433
407
|
return router;
|
|
434
408
|
}
|
|
435
409
|
var defineControllers = () => (m) => m;
|
|
436
|
-
var asLeafAuth = (mw) => (_leaf) => mw;
|
|
437
410
|
function warnMissingControllers(router, registry, logger) {
|
|
438
411
|
const registeredStore = router[REGISTERED_ROUTES_SYMBOL];
|
|
439
412
|
const initial = registeredStore ? Array.from(registeredStore) : collectRoutesFromStack(router);
|
|
@@ -446,44 +419,6 @@ function warnMissingControllers(router, registry, logger) {
|
|
|
446
419
|
}
|
|
447
420
|
}
|
|
448
421
|
|
|
449
|
-
// src/sockets/socket.server.auth.ts
|
|
450
|
-
var _josePromise = null;
|
|
451
|
-
var getJose = () => _josePromise ?? (_josePromise = import("jose"));
|
|
452
|
-
function createAuthMiddleware(io, cfg) {
|
|
453
|
-
const jwksUrl = cfg.type === "jwks" ? new URL(cfg.jwksUri) : void 0;
|
|
454
|
-
async function verify(token) {
|
|
455
|
-
const jose = await getJose();
|
|
456
|
-
const opts = { algorithms: cfg.algorithms ?? ["RS256", "HS256"] };
|
|
457
|
-
if (cfg.type === "jwks") {
|
|
458
|
-
const { payload } = await jose.jwtVerify(token, jose.createRemoteJWKSet(jwksUrl), opts);
|
|
459
|
-
return payload;
|
|
460
|
-
} else {
|
|
461
|
-
const key = cfg.secret;
|
|
462
|
-
const { payload } = await jose.jwtVerify(token, key, opts);
|
|
463
|
-
return payload;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
const defaultGetToken = (req, socket) => {
|
|
467
|
-
const auth = req.headers["authorization"];
|
|
468
|
-
const bearer = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice(7) : void 0;
|
|
469
|
-
return bearer || socket.handshake.auth?.token;
|
|
470
|
-
};
|
|
471
|
-
return async (socket, next) => {
|
|
472
|
-
try {
|
|
473
|
-
const token = (cfg.getToken ?? defaultGetToken)(socket.request, socket);
|
|
474
|
-
if (!token) return next(new Error("unauthorized: missing token"));
|
|
475
|
-
const raw = await verify(token);
|
|
476
|
-
const user = cfg.mapUser ? cfg.mapUser(raw) : raw;
|
|
477
|
-
socket.data.user = user;
|
|
478
|
-
const scopes = (cfg.selectScopes ? cfg.selectScopes(user) : void 0) ?? (user?.scopes ?? user?.roles ?? []);
|
|
479
|
-
socket.data.scopes = Array.isArray(scopes) ? scopes : [];
|
|
480
|
-
next();
|
|
481
|
-
} catch {
|
|
482
|
-
next(new Error("unauthorized: invalid token"));
|
|
483
|
-
}
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
|
|
487
422
|
// src/sockets/socket.server.index.ts
|
|
488
423
|
function createSocketConnections(io, events, opts) {
|
|
489
424
|
const debug = opts?.debug ?? {};
|
|
@@ -494,17 +429,6 @@ function createSocketConnections(io, events, opts) {
|
|
|
494
429
|
};
|
|
495
430
|
const getSchema = (k) => events[k].message;
|
|
496
431
|
const toArray = (rooms) => rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms];
|
|
497
|
-
if (opts?.auth) {
|
|
498
|
-
const mw = createAuthMiddleware(io, opts.auth);
|
|
499
|
-
io.use(async (socket, next) => {
|
|
500
|
-
try {
|
|
501
|
-
await mw(socket, next);
|
|
502
|
-
} catch (e) {
|
|
503
|
-
dbg("auth_error", { type: "auth_error", socketId: socket?.id, err: String(e?.message ?? e) });
|
|
504
|
-
next(e);
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
432
|
const registrations = /* @__PURE__ */ new Map();
|
|
509
433
|
const addRegistration = (eventName, reg) => {
|
|
510
434
|
let set = registrations.get(eventName);
|
|
@@ -542,17 +466,19 @@ function createSocketConnections(io, events, opts) {
|
|
|
542
466
|
const joinHandler = async ({ rooms }) => {
|
|
543
467
|
const list = toArray(rooms);
|
|
544
468
|
for (const r of list) {
|
|
545
|
-
await
|
|
546
|
-
|
|
547
|
-
|
|
469
|
+
if (!opts?.rooms?.onRoomJoin || await opts.rooms.onRoomJoin({ room: r, socket }) == true) {
|
|
470
|
+
await socket.join(r);
|
|
471
|
+
dbg("room_join", { type: "room_join", rooms: [r], socketId: socket.id });
|
|
472
|
+
}
|
|
548
473
|
}
|
|
549
474
|
};
|
|
550
475
|
const leaveHandler = async ({ rooms }) => {
|
|
551
476
|
const list = toArray(rooms);
|
|
552
477
|
for (const r of list) {
|
|
553
|
-
await
|
|
554
|
-
|
|
555
|
-
|
|
478
|
+
if (!opts?.rooms?.onRoomLeave || await opts.rooms.onRoomLeave({ room: r, socket }) == true) {
|
|
479
|
+
dbg("room_leave", { type: "room_leave", rooms: [r], socketId: socket.id });
|
|
480
|
+
await socket.leave(r);
|
|
481
|
+
}
|
|
556
482
|
}
|
|
557
483
|
};
|
|
558
484
|
socket.on(roomJoinEvent, joinHandler);
|
|
@@ -562,29 +488,24 @@ function createSocketConnections(io, events, opts) {
|
|
|
562
488
|
socket.off(roomLeaveEvent, leaveHandler);
|
|
563
489
|
});
|
|
564
490
|
socket.on(pingEvent, (msg = {}, ack) => {
|
|
565
|
-
const started = Date.now();
|
|
566
491
|
const parsed = hb.pingSchema.safeParse(msg?.payload);
|
|
567
492
|
if (!parsed.success) {
|
|
568
493
|
socket.emit(`${pingEvent}:error`, { issues: parsed.error.issues });
|
|
569
494
|
return;
|
|
570
495
|
}
|
|
571
496
|
dbg("ping", { type: "ping", socketId: socket.id, payload: parsed.data });
|
|
572
|
-
const pong = {
|
|
573
|
-
serverNow: (/* @__PURE__ */ new Date()).toISOString(),
|
|
574
|
-
sinceMs: Date.now() - started,
|
|
575
|
-
payload: hb.makePongPayload({ socket, ping: parsed.data }),
|
|
576
|
-
clientEcho: parsed.data
|
|
577
|
-
};
|
|
497
|
+
const pong = hb.makePongPayload({ socket, ping: parsed.data });
|
|
578
498
|
if (hb.pongSchema) {
|
|
579
499
|
const check = hb.pongSchema.safeParse(pong);
|
|
580
500
|
if (!check.success) {
|
|
581
501
|
socket.emit(`${pongEvent}:error`, { issues: check.error.issues });
|
|
502
|
+
dbg("pong", { type: "pong", socketId: socket.id, issues: check.error.issues, error: true });
|
|
582
503
|
return;
|
|
583
504
|
}
|
|
584
505
|
}
|
|
585
506
|
socket.emit(pongEvent, pong);
|
|
586
|
-
dbg("pong", { type: "pong", socketId: socket.id,
|
|
587
|
-
if (typeof ack === "function") ack({ serverNow:
|
|
507
|
+
dbg("pong", { type: "pong", socketId: socket.id, payload: pong, error: false });
|
|
508
|
+
if (typeof ack === "function") ack({ serverNow: (/* @__PURE__ */ new Date()).toISOString() });
|
|
588
509
|
});
|
|
589
510
|
});
|
|
590
511
|
const conn = {
|
|
@@ -699,7 +620,6 @@ function createSocketConnections(io, events, opts) {
|
|
|
699
620
|
// Annotate the CommonJS export names for ESM import in node:
|
|
700
621
|
0 && (module.exports = {
|
|
701
622
|
CTX_SYMBOL,
|
|
702
|
-
asLeafAuth,
|
|
703
623
|
bindAll,
|
|
704
624
|
bindExpressRoutes,
|
|
705
625
|
createRRRoute,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/routesV3.server.ts","../src/sockets/socket.server.auth.ts","../src/sockets/socket.server.index.ts"],"sourcesContent":["export * from './routesV3.server';\nexport * from './sockets/socket.server.index';","/**\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 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\n logger?.info?.(`${methodUpper}@${path} (${requestUrl})`);\n const ctx = (res.locals as any)[CTX_SYMBOL] as CtxWithRoutesLogger<Ctx>;\n const ctxRoutesLogger = ctx?.routesLogger;\n const emitWithCtx = (\n event: RouteServerDebugEvent,\n details?: Partial<RouteServerDebugEvent>,\n ) => {\n const decorated = decorateDebugEvent(isVerboseDebug, event, details);\n if (decorated.type === 'handler' && ctxRoutesLogger) {\n logHandlerDebugWithRoutesLogger(ctxRoutesLogger, decorated);\n }else{\n emit(decorated);\n }\n };\n try {\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 },\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 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 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","// REMOVE this runtime import:\n// import { jwtVerify, createRemoteJWKSet, type JWTPayload, type JWTVerifyOptions } from 'jose';\n// import { KeyLike } from 'crypto';\n\n// Keep types only (erased at compile)\nimport type { JWTPayload, JWTVerifyOptions } from 'jose';\nimport type { KeyLike } from 'crypto';\nimport { IncomingMessage } from 'http';\nimport { Server, Socket } from 'socket.io';\n\ntype Jose = typeof import('jose');\nlet _josePromise: Promise<Jose> | null = null;\nconst getJose = () => (_josePromise ??= import('jose'));\n\ntype JwtLocalSecret = { type: 'local'; secret: Uint8Array; algorithms?: JWTVerifyOptions['algorithms'] };\ntype JwtJwks = { type: 'jwks'; jwksUri: string; algorithms?: JWTVerifyOptions['algorithms'] };\nexport type BaseAuthConfig = JwtLocalSecret | JwtJwks;\n\nexport type AuthCustomization = {\n mapUser?: (payload: JWTPayload) => unknown;\n selectScopes?: (user: unknown) => string[] | undefined;\n getToken?: (req: IncomingMessage, socket: Socket) => string | undefined;\n};\n\nexport type AuthConfig = BaseAuthConfig & AuthCustomization;\n\nexport type AuthUser = JWTPayload & {\n sub: string;\n email?: string;\n roles?: string[];\n scopes?: string[];\n};\n\nexport function createAuthMiddleware(io: Server, cfg: AuthConfig) {\n const jwksUrl = cfg.type === 'jwks' ? new URL(cfg.jwksUri) : undefined;\n\n async function verify(token: string): Promise<JWTPayload> {\n const jose = await getJose();\n const opts: JWTVerifyOptions = { algorithms: cfg.algorithms ?? ['RS256', 'HS256'] };\n\n if (cfg.type === 'jwks') {\n const { payload } = await jose.jwtVerify(token, jose.createRemoteJWKSet(jwksUrl!), opts);\n return payload;\n } else {\n const key: KeyLike | Uint8Array = cfg.secret;\n const { payload } = await jose.jwtVerify(token, key, opts);\n return payload;\n }\n }\n\n const defaultGetToken = (req: IncomingMessage, socket: Socket): string | undefined => {\n const auth = req.headers['authorization'];\n const bearer = typeof auth === 'string' && auth.startsWith('Bearer ') ? auth.slice(7) : undefined;\n return bearer || (socket.handshake.auth as any)?.token;\n };\n\n return async (socket: Socket, next: (err?: any) => void) => {\n try {\n const token = (cfg.getToken ?? defaultGetToken)(socket.request, socket);\n if (!token) return next(new Error('unauthorized: missing token'));\n\n const raw = await verify(token);\n\n const user = cfg.mapUser ? cfg.mapUser(raw) : (raw as AuthUser);\n (socket.data as any).user = user;\n\n const scopes =\n (cfg.selectScopes ? cfg.selectScopes(user) : undefined) ??\n ((user as any)?.scopes ?? (user as any)?.roles ?? []);\n\n (socket.data as any).scopes = Array.isArray(scopes) ? scopes : [];\n next();\n } catch {\n next(new Error('unauthorized: invalid token'));\n }\n };\n}\n","// socket.server.index.ts\nimport { Server, Socket } from 'socket.io';\nimport { z } from 'zod';\nimport type { SocketEvent } from '@emeryld/rrroutes-contract';\nimport { createAuthMiddleware, type AuthConfig } from './socket.server.auth';\n\ntype EventMap = Record<string, SocketEvent>;\ntype Payload<T extends EventMap, K extends keyof T> = z.infer<T[K]['message']>;\n\nexport type HandlerCtx = {\n sentAt: Date;\n socket: Socket;\n socketId: string;\n nsp: string;\n rooms: string[];\n user?: unknown;\n scopes?: string[];\n ack?: (data?: unknown) => void; \n};\n\nexport interface SocketConnection<T extends EventMap> {\n on<K extends keyof T & string>(\n eventName: K,\n handler: (payload: Payload<T, K>, ctx: HandlerCtx) => void | Promise<void>\n ): () => void;\n\n off<K extends keyof T & string>(eventName: K): void;\n\n emit<K extends keyof T & string>(\n eventName: K,\n payload: Payload<T, K>,\n toRooms?: string[] | string,\n metadata?: Record<string, unknown>,\n onAck?: (ack: unknown) => void\n ): void;\n}\n\nexport type SocketServerDebugEvent =\n | { type: 'register'; event: string }\n | { type: 'unregister'; event: string }\n | { type: 'receive'; event: string; socketId: string; nsp: string; rooms: string[]; raw?: unknown }\n | { type: 'validation_error'; event: string; issues: any[] }\n | { type: 'handler_success'; event: string }\n | { type: 'handler_error'; event: string; error: unknown }\n | { type: 'emit'; event: string; rooms: string[]; envelope?: unknown }\n | { type: 'room_join'; rooms: string[]; socketId: string }\n | { type: 'room_leave'; rooms: string[]; socketId: string }\n | { type: 'auth_ok'; socketId: string; sub?: string }\n | { type: 'auth_error'; socketId?: string; err: string }\n | { type: 'ping'; socketId: string; payload?: Record<string, unknown> }\n | { type: 'pong'; socketId: string; sinceMs: number; payload?: Record<string, unknown> };\n\nexport type SocketDebugOptions = {\n verbose?: boolean;\n only?: string[];\n logger?: (e: SocketServerDebugEvent) => void;\n} & {\n [K in SocketServerDebugEvent['type']]?: boolean;\n};\n\nexport type BuiltInRoomHooks = {\n onRoomJoin?: (args: { room: string; socket: Socket }) => void | Promise<void>;\n onRoomLeave?: (args: { room: string; socket: Socket }) => void | Promise<void>;\n roomJoinEvent?: string;\n roomLeaveEvent?: string;\n};\n\n/** === NEW: enforced heartbeat config with schemas and callback */\nexport type HeartbeatServerOptions = {\n pingEvent?: string; // default 'sys:ping'\n pongEvent?: string; // default 'sys:pong'\n\n /** Validate incoming ping payload: event will be { payload: <ping> }. */\n pingSchema: z.ZodTypeAny;\n\n /** Build the pong payload you emit. It will receive the validated ping and socket. */\n makePongPayload: (args: { socket: Socket; ping: unknown }) => Record<string, unknown> | undefined;\n\n /** Optionally validate the outgoing pong object before emit. */\n pongSchema?: z.ZodTypeAny;\n};\n\nexport function createSocketConnections<T extends EventMap>(\n io: Server,\n events: T,\n opts?: {\n debug?: SocketDebugOptions;\n rooms?: BuiltInRoomHooks;\n auth?: AuthConfig;\n /** NEW: required heartbeat config */\n heartbeat: HeartbeatServerOptions;\n }\n): SocketConnection<T> {\n const debug = opts?.debug ?? {};\n const dbg = (name: string, e: SocketServerDebugEvent) => {\n if (!debug.logger || !debug[e.type]) return;\n if (debug.only && !debug.only.includes(name) && e.type === 'receive') return;\n debug.logger(e);\n };\n\n const getSchema = <K extends keyof T & string>(k: K) => events[k].message;\n const toArray = (rooms?: string[] | string): string[] =>\n rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms];\n\n // auth middleware\n if (opts?.auth) {\n const mw = createAuthMiddleware(io, opts.auth);\n io.use(async (socket, next) => {\n try {\n await mw(socket, next);\n } catch (e: any) {\n dbg('auth_error', { type: 'auth_error', socketId: socket?.id, err: String(e?.message ?? e) });\n next(e);\n }\n });\n }\n\n // registrations\n const registrations = new Map<\n string,\n Set<{\n connectionListener: (socket: Socket) => void;\n socketListeners: WeakMap<Socket, (raw: unknown) => void>;\n }>\n >();\n\n const addRegistration = (eventName: string, reg: {\n connectionListener: (socket: Socket) => void;\n socketListeners: WeakMap<Socket, (raw: unknown) => void>;\n }) => {\n let set = registrations.get(eventName);\n if (!set) {\n set = new Set();\n registrations.set(eventName, set);\n }\n set.add(reg);\n };\n\n const removeAllForEvent = (eventName: string) => {\n const set = registrations.get(eventName);\n if (!set) return;\n for (const reg of set) {\n io.off('connection', reg.connectionListener);\n for (const socket of io.sockets.sockets.values()) {\n const wrapped = reg.socketListeners.get(socket);\n if (wrapped) socket.off(String(eventName), wrapped);\n }\n }\n registrations.delete(eventName);\n dbg(eventName, { type: 'unregister', event: eventName });\n };\n\n const roomJoinEvent = opts?.rooms?.roomJoinEvent ?? 'room:join';\n const roomLeaveEvent = opts?.rooms?.roomLeaveEvent ?? 'room:leave';\n\n // === NEW: enforce heartbeat presence\n const hb = opts?.heartbeat;\n if (!hb) {\n throw new Error('createSocketConnections: heartbeat config is required');\n }\n const pingEvent = hb.pingEvent ?? 'sys:ping';\n const pongEvent = hb.pongEvent ?? 'sys:pong';\n\n io.on('connection', (socket) => {\n if ((socket.data as any)?.user) {\n dbg('auth_ok', { type: 'auth_ok', socketId: socket.id, sub: (socket.data as any).user?.sub });\n }\n\n // built-in room handlers\n const joinHandler = async ({ rooms }: { rooms?: string[] }) => {\n const list = toArray(rooms);\n for (const r of list) {\n await socket.join(r);\n dbg('room_join', { type: 'room_join', rooms: [r], socketId: socket.id });\n if (opts?.rooms?.onRoomJoin) await opts.rooms.onRoomJoin({ room: r, socket });\n }\n };\n const leaveHandler = async ({ rooms }: { rooms?: string[] }) => {\n const list = toArray(rooms);\n for (const r of list) {\n await socket.leave(r);\n dbg('room_leave', { type: 'room_leave', rooms: [r], socketId: socket.id });\n if (opts?.rooms?.onRoomLeave) await opts.rooms.onRoomLeave({ room: r, socket });\n }\n };\n socket.on(roomJoinEvent, joinHandler);\n socket.on(roomLeaveEvent, leaveHandler);\n socket.once('disconnect', () => {\n socket.off(roomJoinEvent, joinHandler);\n socket.off(roomLeaveEvent, leaveHandler);\n });\n\n // === NEW: app-level heartbeat with schema validation\n socket.on(pingEvent, (msg: { payload?: unknown } = {}, ack?: (x: { serverNow: string }) => void) => {\n const started = Date.now();\n const parsed = hb.pingSchema.safeParse(msg?.payload);\n if (!parsed.success) {\n socket.emit(`${pingEvent}:error`, { issues: parsed.error.issues });\n return;\n }\n\n dbg('ping', { type: 'ping', socketId: socket.id, payload: parsed.data as any });\n\n const pong = {\n serverNow: new Date().toISOString(),\n sinceMs: Date.now() - started,\n payload: hb.makePongPayload({ socket, ping: parsed.data }),\n clientEcho: parsed.data,\n };\n\n if (hb.pongSchema) {\n const check = hb.pongSchema.safeParse(pong);\n if (!check.success) {\n socket.emit(`${pongEvent}:error`, { issues: check.error.issues });\n return;\n }\n }\n\n socket.emit(pongEvent, pong);\n dbg('pong', { type: 'pong', socketId: socket.id, sinceMs: pong.sinceMs, payload: pong.payload });\n\n if (typeof ack === 'function') ack({ serverNow: pong.serverNow });\n });\n });\n\n const conn = {\n on<K extends keyof T & string>(\n eventName: K,\n handler: (payload: Payload<T, K>, ctx: HandlerCtx) => void | Promise<void>\n ): () => void {\n const socketListeners = new WeakMap<Socket, (raw: unknown) => void>();\n\nconst connectionListener = (socket: Socket) => {\n const wrapped = async (raw: unknown, maybeAck?: (data?: unknown) => void) => {\n const schema = getSchema(eventName);\n const parsed = schema.safeParse(raw);\n\n const ctx: HandlerCtx = {\n sentAt: new Date(),\n socket,\n socketId: socket.id,\n nsp: socket.nsp.name,\n rooms: Array.from(socket.rooms),\n user: (socket.data as any)?.user,\n scopes: (socket.data as any)?.scopes,\n ack: typeof maybeAck === 'function' ? (d?: unknown) => { try { maybeAck(d); } catch { /* noop */ } } : undefined, // NEW\n };\n\n dbg(String(eventName), {\n type: 'receive',\n event: String(eventName),\n socketId: ctx.socketId,\n nsp: ctx.nsp,\n rooms: ctx.rooms,\n raw: debug.verbose ? raw : undefined,\n });\n\n if (!parsed.success) {\n socket.emit(`${String(eventName)}:error`, { eventName, sentAt: ctx.sentAt, issues: parsed.error.issues });\n dbg(String(eventName), { type: 'validation_error', event: String(eventName), issues: parsed.error.issues });\n return;\n }\n\n try {\n await handler(parsed.data as Payload<T, K>, ctx);\n dbg(String(eventName), { type: 'handler_success', event: String(eventName) });\n } catch (error) {\n dbg(String(eventName), { type: 'handler_error', event: String(eventName), error });\n throw error;\n }\n };\n\n socketListeners.set(socket, wrapped);\n socket.on(String(eventName), wrapped);\n socket.once('disconnect', () => {\n const w = socketListeners.get(socket);\n if (w) socket.off(String(eventName), w);\n });\n};\n\n\n io.on('connection', connectionListener);\n addRegistration(String(eventName), { connectionListener, socketListeners });\n dbg(String(eventName), { type: 'register', event: String(eventName) });\n\n return () => {\n const set = registrations.get(String(eventName));\n if (!set) return;\n for (const reg of Array.from(set)) {\n if (reg.connectionListener === connectionListener) {\n io.off('connection', reg.connectionListener);\n for (const socket of io.sockets.sockets.values()) {\n const wrapped = reg.socketListeners.get(socket);\n if (wrapped) socket.off(String(eventName), wrapped);\n }\n set.delete(reg);\n break;\n }\n }\n if (set.size === 0) registrations.delete(String(eventName));\n dbg(String(eventName), { type: 'unregister', event: String(eventName) });\n };\n },\n\n off<K extends keyof T & string>(eventName: K): void {\n removeAllForEvent(String(eventName));\n },\n\nemit<K extends keyof T & string>(\n eventName: K,\n payload: Payload<T, K>,\n rooms?: string[] | string,\n metadata?: Record<string, unknown>,\n onAck?: (ack: unknown) => void, // NEW\n): void {\n const schema = getSchema(eventName);\n const check = schema.safeParse(payload);\n if (!check.success) throw new Error(`Invalid payload for \"${String(eventName)}\": ${check.error.message}`);\n\n const targets = toArray(rooms);\n const envelope = {\n eventName,\n sentAt: new Date(),\n sentTo: targets,\n data: check.data as Payload<T, K>,\n metadata: metadata ?? {},\n };\n\n // If exactly one target socket, use per-socket emit with ack\n if (targets.length === 1) {\n const sid = targets[0]!;\n const sock = io.sockets.sockets.get(sid as any);\n if (sock) {\n if (onAck) sock.emit(String(eventName), envelope, (ack: unknown) => { try { onAck(ack); } catch { /* noop */ } });\n else sock.emit(String(eventName), envelope);\n dbg(String(eventName), { type: 'emit', event: String(eventName), rooms: targets, envelope: debug.verbose ? envelope : undefined });\n return;\n }\n }\n\n // Fallback: broadcast, no ack possible\n if (targets.length === 0) io.emit(String(eventName), envelope);\n else io.to(targets).emit(String(eventName), envelope);\n\n dbg(String(eventName), { type: 'emit', event: String(eventName), rooms: targets, envelope: debug.verbose ? envelope : undefined });\n}\n,\n } as const satisfies SocketConnection<T>;\n\n return conn;\n}\n\nexport type { EventMap, Payload };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/routesV3.server.ts","../src/sockets/socket.server.index.ts"],"sourcesContent":["export * from './routesV3.server';\nexport * from './sockets/socket.server.index';","/**\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 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 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;\n const emit: ServerDebugEmitter<Names>['emit'] = (event, name) => {\n if (!whitelist.has(event.type) || !logger) 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 * - `upload` runs when cfg.bodyFiles has entries\n */\n fromCfg?: {\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// 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 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\n logger?.info?.(`${methodUpper}@${path} (${requestUrl})`);\n const ctx = (res.locals as any)[CTX_SYMBOL] as CtxWithRoutesLogger<Ctx>;\n const ctxRoutesLogger = ctx?.routesLogger;\n const emitWithCtx = (\n event: RouteServerDebugEvent,\n details?: Partial<RouteServerDebugEvent>,\n ) => {\n const decorated = decorateDebugEvent(isVerboseDebug, event, details);\n if (decorated.type === 'handler' && ctxRoutesLogger) {\n logHandlerDebugWithRoutesLogger(ctxRoutesLogger, decorated);\n }else{\n emit(decorated);\n }\n };\n try {\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 },\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 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 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 * 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","// socket.server.index.ts\nimport { Server, Socket } from 'socket.io';\nimport { z, ZodType } from 'zod';\nimport type { SocketEvent } from '@emeryld/rrroutes-contract';\n\ntype EventMap = Record<string, SocketEvent>;\ntype Payload<T extends EventMap, K extends keyof T> = z.infer<T[K]['message']>;\n\nexport type HandlerCtx = {\n sentAt: Date;\n socket: Socket;\n socketId: string;\n nsp: string;\n rooms: string[];\n user?: unknown;\n scopes?: string[];\n ack?: (data?: unknown) => void; \n};\n\nexport interface SocketConnection<T extends EventMap> {\n on<K extends keyof T & string>(\n eventName: K,\n handler: (payload: Payload<T, K>, ctx: HandlerCtx) => void | Promise<void>\n ): () => void;\n\n off<K extends keyof T & string>(eventName: K): void;\n\n emit<K extends keyof T & string>(\n eventName: K,\n payload: Payload<T, K>,\n toRooms?: string[] | string,\n metadata?: Record<string, unknown>,\n onAck?: (ack: unknown) => void\n ): void;\n}\n\nexport type SocketServerDebugEvent<Ping extends ZodType=ZodType, Pong extends ZodType=ZodType> =\n | { type: 'register'; event: string }\n | { type: 'unregister'; event: string }\n | { type: 'receive'; event: string; socketId: string; nsp: string; rooms: string[]; raw?: unknown }\n | { type: 'validation_error'; event: string; issues: any[] }\n | { type: 'handler_success'; event: string }\n | { type: 'handler_error'; event: string; error: unknown }\n | { type: 'emit'; event: string; rooms: string[]; envelope?: unknown }\n | { type: 'room_join'; rooms: string[]; socketId: string }\n | { type: 'room_leave'; rooms: string[]; socketId: string }\n | { type: 'auth_ok'; socketId: string; sub?: string }\n | { type: 'auth_error'; socketId?: string; err: string }\n | { type: 'ping'; socketId: string; payload?: NoInfer<z.infer<Ping>> }\n | { type: 'pong'; socketId: string; payload?: NoInfer<z.infer<Pong>>; issues?: any[], error:boolean };\n\nexport type SocketDebugOptions<Ping extends ZodType, Pong extends ZodType> = {\n verbose?: boolean;\n only?: string[];\n logger?: (e: SocketServerDebugEvent<Ping, Pong>) => void;\n} & {\n [K in SocketServerDebugEvent<Ping, Pong>['type']]?: boolean;\n};\n\nexport type BuiltInRoomHooks = {\n onRoomJoin?: (args: { room: string; socket: Socket }) => boolean | Promise<boolean>;\n onRoomLeave?: (args: { room: string; socket: Socket }) => boolean | Promise<boolean>;\n roomJoinEvent?: string;\n roomLeaveEvent?: string;\n};\n\n/** === NEW: enforced heartbeat config with schemas and callback */\nexport type HeartbeatServerOptions<Ping extends ZodType, Pong extends ZodType> = {\n pingEvent?: string; // default 'sys:ping'\n pongEvent?: string; // default 'sys:pong'\n\n /** Validate incoming ping payload: event will be { payload: <ping> }. */\n pingSchema: Ping;\n\n /** Build the pong payload you emit. It will receive the validated ping and socket. */\n makePongPayload: (args: { socket: Socket; ping: z.infer<Ping> }) => NoInfer<z.infer<Pong>>;\n\n /** Optionally validate the outgoing pong object before emit. */\n pongSchema?: Pong;\n};\n\nexport function createSocketConnections<Ping extends ZodType, Pong extends ZodType, T extends EventMap>(\n io: Server,\n events: T,\n opts?: {\n debug?: SocketDebugOptions<Ping, Pong>;\n rooms?: BuiltInRoomHooks;\n heartbeat: HeartbeatServerOptions<Ping, Pong>;\n }\n): SocketConnection<T> {\n const debug = opts?.debug ?? {};\n const dbg = (name: string, e: SocketServerDebugEvent<Ping, Pong>) => {\n if (!debug.logger || !debug[e.type]) return;\n if (debug.only && !debug.only.includes(name) && e.type === 'receive') return;\n debug.logger(e);\n };\n\n const getSchema = <K extends keyof T & string>(k: K) => events[k].message;\n const toArray = (rooms?: string[] | string): string[] =>\n rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms];\n\n // registrations\n const registrations = new Map<\n string,\n Set<{\n connectionListener: (socket: Socket) => void;\n socketListeners: WeakMap<Socket, (raw: unknown) => void>;\n }>\n >();\n\n const addRegistration = (eventName: string, reg: {\n connectionListener: (socket: Socket) => void;\n socketListeners: WeakMap<Socket, (raw: unknown) => void>;\n }) => {\n let set = registrations.get(eventName);\n if (!set) {\n set = new Set();\n registrations.set(eventName, set);\n }\n set.add(reg);\n };\n\n const removeAllForEvent = (eventName: string) => {\n const set = registrations.get(eventName);\n if (!set) return;\n for (const reg of set) {\n io.off('connection', reg.connectionListener);\n for (const socket of io.sockets.sockets.values()) {\n const wrapped = reg.socketListeners.get(socket);\n if (wrapped) socket.off(String(eventName), wrapped);\n }\n }\n registrations.delete(eventName);\n dbg(eventName, { type: 'unregister', event: eventName });\n };\n\n const roomJoinEvent = opts?.rooms?.roomJoinEvent ?? 'room:join';\n const roomLeaveEvent = opts?.rooms?.roomLeaveEvent ?? 'room:leave';\n\n const hb = opts?.heartbeat;\n if (!hb) {\n throw new Error('createSocketConnections: heartbeat config is required');\n }\n const pingEvent = hb.pingEvent ?? 'sys:ping';\n const pongEvent = hb.pongEvent ?? 'sys:pong';\n\n io.on('connection', (socket) => {\n if ((socket.data as any)?.user) {\n dbg('auth_ok', { type: 'auth_ok', socketId: socket.id, sub: (socket.data as any).user?.sub });\n }\n\n // built-in room handlers\n const joinHandler = async ({ rooms }: { rooms?: string[] }) => {\n const list = toArray(rooms);\n for (const r of list) {\n \n if (!opts?.rooms?.onRoomJoin ||( await opts.rooms.onRoomJoin({ room: r, socket }))==true){\n await socket.join(r);\n dbg('room_join', { type: 'room_join', rooms: [r], socketId: socket.id });\n } \n }\n };\n const leaveHandler = async ({ rooms }: { rooms?: string[] }) => {\n const list = toArray(rooms);\n for (const r of list) {\n \n \n if (!opts?.rooms?.onRoomLeave || ( await opts.rooms.onRoomLeave({ room: r, socket }))==true){\n dbg('room_leave', { type: 'room_leave', rooms: [r], socketId: socket.id });\nawait socket.leave(r);\n }\n }\n };\n socket.on(roomJoinEvent, joinHandler);\n socket.on(roomLeaveEvent, leaveHandler);\n socket.once('disconnect', () => {\n socket.off(roomJoinEvent, joinHandler);\n socket.off(roomLeaveEvent, leaveHandler);\n });\n\n socket.on(pingEvent, (msg: { payload?: unknown } = {}, ack?: (x: { serverNow: string }) => void) => {\n\n const parsed = hb.pingSchema.safeParse(msg?.payload);\n if (!parsed.success) {\n socket.emit(`${pingEvent}:error`, { issues: parsed.error.issues });\n return;\n }\n\n dbg('ping', { type: 'ping', socketId: socket.id, payload: parsed.data });\n\n const pong = hb.makePongPayload({ socket, ping: parsed.data })\n\n if (hb.pongSchema) {\n const check = hb.pongSchema.safeParse(pong);\n if (!check.success) {\n socket.emit(`${pongEvent}:error`, { issues: check.error.issues });\n dbg('pong', { type: 'pong', socketId: socket.id, issues: check.error.issues, error:true });\n return;\n }\n }\n\n socket.emit(pongEvent, pong);\n dbg('pong', { type: 'pong', socketId: socket.id, payload: pong , error:false});\n\n if (typeof ack === 'function') ack({ serverNow: new Date().toISOString() });\n });\n });\n\n const conn = {\n on<K extends keyof T & string>(\n eventName: K,\n handler: (payload: Payload<T, K>, ctx: HandlerCtx) => void | Promise<void>\n ): () => void {\n const socketListeners = new WeakMap<Socket, (raw: unknown) => void>();\n\nconst connectionListener = (socket: Socket) => {\n const wrapped = async (raw: unknown, maybeAck?: (data?: unknown) => void) => {\n const schema = getSchema(eventName);\n const parsed = schema.safeParse(raw);\n\n const ctx: HandlerCtx = {\n sentAt: new Date(),\n socket,\n socketId: socket.id,\n nsp: socket.nsp.name,\n rooms: Array.from(socket.rooms),\n user: (socket.data )?.user,\n scopes: (socket.data )?.scopes,\n ack: typeof maybeAck === 'function' ? (d?: unknown) => { try { maybeAck(d); } catch { /* noop */ } } : undefined, // NEW\n };\n\n dbg(String(eventName), {\n type: 'receive',\n event: String(eventName),\n socketId: ctx.socketId,\n nsp: ctx.nsp,\n rooms: ctx.rooms,\n raw: debug.verbose ? raw : undefined,\n });\n\n if (!parsed.success) {\n socket.emit(`${String(eventName)}:error`, { eventName, sentAt: ctx.sentAt, issues: parsed.error.issues });\n dbg(String(eventName), { type: 'validation_error', event: String(eventName), issues: parsed.error.issues });\n return;\n }\n\n try {\n await handler(parsed.data as Payload<T, K>, ctx);\n dbg(String(eventName), { type: 'handler_success', event: String(eventName) });\n } catch (error) {\n dbg(String(eventName), { type: 'handler_error', event: String(eventName), error });\n throw error;\n }\n };\n\n socketListeners.set(socket, wrapped);\n socket.on(String(eventName), wrapped);\n socket.once('disconnect', () => {\n const w = socketListeners.get(socket);\n if (w) socket.off(String(eventName), w);\n });\n};\n\n\n io.on('connection', connectionListener);\n addRegistration(String(eventName), { connectionListener, socketListeners });\n dbg(String(eventName), { type: 'register', event: String(eventName) });\n\n return () => {\n const set = registrations.get(String(eventName));\n if (!set) return;\n for (const reg of Array.from(set)) {\n if (reg.connectionListener === connectionListener) {\n io.off('connection', reg.connectionListener);\n for (const socket of io.sockets.sockets.values()) {\n const wrapped = reg.socketListeners.get(socket);\n if (wrapped) socket.off(String(eventName), wrapped);\n }\n set.delete(reg);\n break;\n }\n }\n if (set.size === 0) registrations.delete(String(eventName));\n dbg(String(eventName), { type: 'unregister', event: String(eventName) });\n };\n },\n\n off<K extends keyof T & string>(eventName: K): void {\n removeAllForEvent(String(eventName));\n },\n\nemit<K extends keyof T & string>(\n eventName: K,\n payload: Payload<T, K>,\n rooms?: string[] | string,\n metadata?: Record<string, unknown>,\n onAck?: (ack: unknown) => void, \n): void {\n const schema = getSchema(eventName);\n const check = schema.safeParse(payload);\n if (!check.success) throw new Error(`Invalid payload for \"${String(eventName)}\": ${check.error.message}`);\n\n const targets = toArray(rooms);\n const envelope = {\n eventName,\n sentAt: new Date(),\n sentTo: targets,\n data: check.data as Payload<T, K>,\n metadata: metadata ?? {},\n };\n\n // If exactly one target socket, use per-socket emit with ack\n if (targets.length === 1) {\n const sid = targets[0]!;\n const sock = io.sockets.sockets.get(sid as any);\n if (sock) {\n if (onAck) sock.emit(String(eventName), envelope, (ack: unknown) => { try { onAck(ack); } catch { /* noop */ } });\n else sock.emit(String(eventName), envelope);\n dbg(String(eventName), { type: 'emit', event: String(eventName), rooms: targets, envelope: debug.verbose ? envelope : undefined });\n return;\n }\n }\n\n // Fallback: broadcast, no ack possible\n if (targets.length === 0) io.emit(String(eventName), envelope);\n else io.to(targets).emit(String(eventName), envelope);\n\n dbg(String(eventName), { type: 'emit', event: String(eventName), rooms: targets, envelope: debug.verbose ? envelope : undefined });\n}\n,\n } as const satisfies SocketConnection<T>;\n\n return conn;\n}\n\nexport type { EventMap, Payload };\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
// src/routesV3.server.ts
|
|
2
|
-
var defaultServerDebug = (event) => {
|
|
3
|
-
if (typeof console === "undefined") return;
|
|
4
|
-
const fn = console.debug ?? console.log;
|
|
5
|
-
fn?.call(console, "[rrroutes-server]", event);
|
|
6
|
-
};
|
|
7
2
|
var serverDebugEventTypes = [
|
|
8
3
|
"register",
|
|
9
4
|
"request",
|
|
@@ -24,9 +19,9 @@ function createServerDebugEmitter(option) {
|
|
|
24
19
|
}
|
|
25
20
|
const whitelist = new Set(enabledTypes);
|
|
26
21
|
const onlySet = toggles.only && toggles.only.length > 0 ? new Set(toggles.only) : void 0;
|
|
27
|
-
const logger = toggles.logger
|
|
22
|
+
const logger = toggles.logger;
|
|
28
23
|
const emit = (event, name) => {
|
|
29
|
-
if (!whitelist.has(event.type)) return;
|
|
24
|
+
if (!whitelist.has(event.type) || !logger) return;
|
|
30
25
|
if (onlySet && (!name || !onlySet.has(name))) return;
|
|
31
26
|
logger(name ? { ...event, name } : event);
|
|
32
27
|
};
|
|
@@ -88,10 +83,6 @@ function logHandlerDebugWithRoutesLogger(logger, event) {
|
|
|
88
83
|
var defaultSend = (res, data) => {
|
|
89
84
|
res.json(data);
|
|
90
85
|
};
|
|
91
|
-
function resolveAuth(auth, leaf) {
|
|
92
|
-
if (!auth) return void 0;
|
|
93
|
-
return auth.length === 1 ? auth(leaf) : auth;
|
|
94
|
-
}
|
|
95
86
|
var REGISTERED_ROUTES_SYMBOL = Symbol.for("routesV3.registeredRoutes");
|
|
96
87
|
function getRegisteredRouteStore(router) {
|
|
97
88
|
const existing = router[REGISTERED_ROUTES_SYMBOL];
|
|
@@ -133,12 +124,6 @@ function createRRRoute(router, config) {
|
|
|
133
124
|
const registered = getRegisteredRouteStore(router);
|
|
134
125
|
const buildDerived = (leaf) => {
|
|
135
126
|
const derived = [];
|
|
136
|
-
const decision = config.fromCfg?.when?.(leaf.cfg, leaf) ?? {};
|
|
137
|
-
const needsAuth = typeof decision.auth === "boolean" ? decision.auth : !!leaf.cfg.authenticated;
|
|
138
|
-
if (needsAuth && config.fromCfg?.auth) {
|
|
139
|
-
const authMw = resolveAuth(config.fromCfg.auth, leaf);
|
|
140
|
-
if (authMw) derived.push(authMw);
|
|
141
|
-
}
|
|
142
127
|
if (config.fromCfg?.upload && Array.isArray(leaf.cfg.bodyFiles) && leaf.cfg.bodyFiles.length > 0) {
|
|
143
128
|
derived.push(...config.fromCfg.upload(leaf.cfg.bodyFiles, leaf));
|
|
144
129
|
}
|
|
@@ -388,7 +373,6 @@ function bindAll(router, registry, controllers, config) {
|
|
|
388
373
|
return router;
|
|
389
374
|
}
|
|
390
375
|
var defineControllers = () => (m) => m;
|
|
391
|
-
var asLeafAuth = (mw) => (_leaf) => mw;
|
|
392
376
|
function warnMissingControllers(router, registry, logger) {
|
|
393
377
|
const registeredStore = router[REGISTERED_ROUTES_SYMBOL];
|
|
394
378
|
const initial = registeredStore ? Array.from(registeredStore) : collectRoutesFromStack(router);
|
|
@@ -401,44 +385,6 @@ function warnMissingControllers(router, registry, logger) {
|
|
|
401
385
|
}
|
|
402
386
|
}
|
|
403
387
|
|
|
404
|
-
// src/sockets/socket.server.auth.ts
|
|
405
|
-
var _josePromise = null;
|
|
406
|
-
var getJose = () => _josePromise ?? (_josePromise = import("jose"));
|
|
407
|
-
function createAuthMiddleware(io, cfg) {
|
|
408
|
-
const jwksUrl = cfg.type === "jwks" ? new URL(cfg.jwksUri) : void 0;
|
|
409
|
-
async function verify(token) {
|
|
410
|
-
const jose = await getJose();
|
|
411
|
-
const opts = { algorithms: cfg.algorithms ?? ["RS256", "HS256"] };
|
|
412
|
-
if (cfg.type === "jwks") {
|
|
413
|
-
const { payload } = await jose.jwtVerify(token, jose.createRemoteJWKSet(jwksUrl), opts);
|
|
414
|
-
return payload;
|
|
415
|
-
} else {
|
|
416
|
-
const key = cfg.secret;
|
|
417
|
-
const { payload } = await jose.jwtVerify(token, key, opts);
|
|
418
|
-
return payload;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
const defaultGetToken = (req, socket) => {
|
|
422
|
-
const auth = req.headers["authorization"];
|
|
423
|
-
const bearer = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice(7) : void 0;
|
|
424
|
-
return bearer || socket.handshake.auth?.token;
|
|
425
|
-
};
|
|
426
|
-
return async (socket, next) => {
|
|
427
|
-
try {
|
|
428
|
-
const token = (cfg.getToken ?? defaultGetToken)(socket.request, socket);
|
|
429
|
-
if (!token) return next(new Error("unauthorized: missing token"));
|
|
430
|
-
const raw = await verify(token);
|
|
431
|
-
const user = cfg.mapUser ? cfg.mapUser(raw) : raw;
|
|
432
|
-
socket.data.user = user;
|
|
433
|
-
const scopes = (cfg.selectScopes ? cfg.selectScopes(user) : void 0) ?? (user?.scopes ?? user?.roles ?? []);
|
|
434
|
-
socket.data.scopes = Array.isArray(scopes) ? scopes : [];
|
|
435
|
-
next();
|
|
436
|
-
} catch {
|
|
437
|
-
next(new Error("unauthorized: invalid token"));
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
|
|
442
388
|
// src/sockets/socket.server.index.ts
|
|
443
389
|
function createSocketConnections(io, events, opts) {
|
|
444
390
|
const debug = opts?.debug ?? {};
|
|
@@ -449,17 +395,6 @@ function createSocketConnections(io, events, opts) {
|
|
|
449
395
|
};
|
|
450
396
|
const getSchema = (k) => events[k].message;
|
|
451
397
|
const toArray = (rooms) => rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms];
|
|
452
|
-
if (opts?.auth) {
|
|
453
|
-
const mw = createAuthMiddleware(io, opts.auth);
|
|
454
|
-
io.use(async (socket, next) => {
|
|
455
|
-
try {
|
|
456
|
-
await mw(socket, next);
|
|
457
|
-
} catch (e) {
|
|
458
|
-
dbg("auth_error", { type: "auth_error", socketId: socket?.id, err: String(e?.message ?? e) });
|
|
459
|
-
next(e);
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
398
|
const registrations = /* @__PURE__ */ new Map();
|
|
464
399
|
const addRegistration = (eventName, reg) => {
|
|
465
400
|
let set = registrations.get(eventName);
|
|
@@ -497,17 +432,19 @@ function createSocketConnections(io, events, opts) {
|
|
|
497
432
|
const joinHandler = async ({ rooms }) => {
|
|
498
433
|
const list = toArray(rooms);
|
|
499
434
|
for (const r of list) {
|
|
500
|
-
await
|
|
501
|
-
|
|
502
|
-
|
|
435
|
+
if (!opts?.rooms?.onRoomJoin || await opts.rooms.onRoomJoin({ room: r, socket }) == true) {
|
|
436
|
+
await socket.join(r);
|
|
437
|
+
dbg("room_join", { type: "room_join", rooms: [r], socketId: socket.id });
|
|
438
|
+
}
|
|
503
439
|
}
|
|
504
440
|
};
|
|
505
441
|
const leaveHandler = async ({ rooms }) => {
|
|
506
442
|
const list = toArray(rooms);
|
|
507
443
|
for (const r of list) {
|
|
508
|
-
await
|
|
509
|
-
|
|
510
|
-
|
|
444
|
+
if (!opts?.rooms?.onRoomLeave || await opts.rooms.onRoomLeave({ room: r, socket }) == true) {
|
|
445
|
+
dbg("room_leave", { type: "room_leave", rooms: [r], socketId: socket.id });
|
|
446
|
+
await socket.leave(r);
|
|
447
|
+
}
|
|
511
448
|
}
|
|
512
449
|
};
|
|
513
450
|
socket.on(roomJoinEvent, joinHandler);
|
|
@@ -517,29 +454,24 @@ function createSocketConnections(io, events, opts) {
|
|
|
517
454
|
socket.off(roomLeaveEvent, leaveHandler);
|
|
518
455
|
});
|
|
519
456
|
socket.on(pingEvent, (msg = {}, ack) => {
|
|
520
|
-
const started = Date.now();
|
|
521
457
|
const parsed = hb.pingSchema.safeParse(msg?.payload);
|
|
522
458
|
if (!parsed.success) {
|
|
523
459
|
socket.emit(`${pingEvent}:error`, { issues: parsed.error.issues });
|
|
524
460
|
return;
|
|
525
461
|
}
|
|
526
462
|
dbg("ping", { type: "ping", socketId: socket.id, payload: parsed.data });
|
|
527
|
-
const pong = {
|
|
528
|
-
serverNow: (/* @__PURE__ */ new Date()).toISOString(),
|
|
529
|
-
sinceMs: Date.now() - started,
|
|
530
|
-
payload: hb.makePongPayload({ socket, ping: parsed.data }),
|
|
531
|
-
clientEcho: parsed.data
|
|
532
|
-
};
|
|
463
|
+
const pong = hb.makePongPayload({ socket, ping: parsed.data });
|
|
533
464
|
if (hb.pongSchema) {
|
|
534
465
|
const check = hb.pongSchema.safeParse(pong);
|
|
535
466
|
if (!check.success) {
|
|
536
467
|
socket.emit(`${pongEvent}:error`, { issues: check.error.issues });
|
|
468
|
+
dbg("pong", { type: "pong", socketId: socket.id, issues: check.error.issues, error: true });
|
|
537
469
|
return;
|
|
538
470
|
}
|
|
539
471
|
}
|
|
540
472
|
socket.emit(pongEvent, pong);
|
|
541
|
-
dbg("pong", { type: "pong", socketId: socket.id,
|
|
542
|
-
if (typeof ack === "function") ack({ serverNow:
|
|
473
|
+
dbg("pong", { type: "pong", socketId: socket.id, payload: pong, error: false });
|
|
474
|
+
if (typeof ack === "function") ack({ serverNow: (/* @__PURE__ */ new Date()).toISOString() });
|
|
543
475
|
});
|
|
544
476
|
});
|
|
545
477
|
const conn = {
|
|
@@ -653,7 +585,6 @@ function createSocketConnections(io, events, opts) {
|
|
|
653
585
|
}
|
|
654
586
|
export {
|
|
655
587
|
CTX_SYMBOL,
|
|
656
|
-
asLeafAuth,
|
|
657
588
|
bindAll,
|
|
658
589
|
bindExpressRoutes,
|
|
659
590
|
createRRRoute,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/routesV3.server.ts","../src/sockets/socket.server.auth.ts","../src/sockets/socket.server.index.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 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\n logger?.info?.(`${methodUpper}@${path} (${requestUrl})`);\n const ctx = (res.locals as any)[CTX_SYMBOL] as CtxWithRoutesLogger<Ctx>;\n const ctxRoutesLogger = ctx?.routesLogger;\n const emitWithCtx = (\n event: RouteServerDebugEvent,\n details?: Partial<RouteServerDebugEvent>,\n ) => {\n const decorated = decorateDebugEvent(isVerboseDebug, event, details);\n if (decorated.type === 'handler' && ctxRoutesLogger) {\n logHandlerDebugWithRoutesLogger(ctxRoutesLogger, decorated);\n }else{\n emit(decorated);\n }\n };\n try {\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 },\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 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 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","// REMOVE this runtime import:\n// import { jwtVerify, createRemoteJWKSet, type JWTPayload, type JWTVerifyOptions } from 'jose';\n// import { KeyLike } from 'crypto';\n\n// Keep types only (erased at compile)\nimport type { JWTPayload, JWTVerifyOptions } from 'jose';\nimport type { KeyLike } from 'crypto';\nimport { IncomingMessage } from 'http';\nimport { Server, Socket } from 'socket.io';\n\ntype Jose = typeof import('jose');\nlet _josePromise: Promise<Jose> | null = null;\nconst getJose = () => (_josePromise ??= import('jose'));\n\ntype JwtLocalSecret = { type: 'local'; secret: Uint8Array; algorithms?: JWTVerifyOptions['algorithms'] };\ntype JwtJwks = { type: 'jwks'; jwksUri: string; algorithms?: JWTVerifyOptions['algorithms'] };\nexport type BaseAuthConfig = JwtLocalSecret | JwtJwks;\n\nexport type AuthCustomization = {\n mapUser?: (payload: JWTPayload) => unknown;\n selectScopes?: (user: unknown) => string[] | undefined;\n getToken?: (req: IncomingMessage, socket: Socket) => string | undefined;\n};\n\nexport type AuthConfig = BaseAuthConfig & AuthCustomization;\n\nexport type AuthUser = JWTPayload & {\n sub: string;\n email?: string;\n roles?: string[];\n scopes?: string[];\n};\n\nexport function createAuthMiddleware(io: Server, cfg: AuthConfig) {\n const jwksUrl = cfg.type === 'jwks' ? new URL(cfg.jwksUri) : undefined;\n\n async function verify(token: string): Promise<JWTPayload> {\n const jose = await getJose();\n const opts: JWTVerifyOptions = { algorithms: cfg.algorithms ?? ['RS256', 'HS256'] };\n\n if (cfg.type === 'jwks') {\n const { payload } = await jose.jwtVerify(token, jose.createRemoteJWKSet(jwksUrl!), opts);\n return payload;\n } else {\n const key: KeyLike | Uint8Array = cfg.secret;\n const { payload } = await jose.jwtVerify(token, key, opts);\n return payload;\n }\n }\n\n const defaultGetToken = (req: IncomingMessage, socket: Socket): string | undefined => {\n const auth = req.headers['authorization'];\n const bearer = typeof auth === 'string' && auth.startsWith('Bearer ') ? auth.slice(7) : undefined;\n return bearer || (socket.handshake.auth as any)?.token;\n };\n\n return async (socket: Socket, next: (err?: any) => void) => {\n try {\n const token = (cfg.getToken ?? defaultGetToken)(socket.request, socket);\n if (!token) return next(new Error('unauthorized: missing token'));\n\n const raw = await verify(token);\n\n const user = cfg.mapUser ? cfg.mapUser(raw) : (raw as AuthUser);\n (socket.data as any).user = user;\n\n const scopes =\n (cfg.selectScopes ? cfg.selectScopes(user) : undefined) ??\n ((user as any)?.scopes ?? (user as any)?.roles ?? []);\n\n (socket.data as any).scopes = Array.isArray(scopes) ? scopes : [];\n next();\n } catch {\n next(new Error('unauthorized: invalid token'));\n }\n };\n}\n","// socket.server.index.ts\nimport { Server, Socket } from 'socket.io';\nimport { z } from 'zod';\nimport type { SocketEvent } from '@emeryld/rrroutes-contract';\nimport { createAuthMiddleware, type AuthConfig } from './socket.server.auth';\n\ntype EventMap = Record<string, SocketEvent>;\ntype Payload<T extends EventMap, K extends keyof T> = z.infer<T[K]['message']>;\n\nexport type HandlerCtx = {\n sentAt: Date;\n socket: Socket;\n socketId: string;\n nsp: string;\n rooms: string[];\n user?: unknown;\n scopes?: string[];\n ack?: (data?: unknown) => void; \n};\n\nexport interface SocketConnection<T extends EventMap> {\n on<K extends keyof T & string>(\n eventName: K,\n handler: (payload: Payload<T, K>, ctx: HandlerCtx) => void | Promise<void>\n ): () => void;\n\n off<K extends keyof T & string>(eventName: K): void;\n\n emit<K extends keyof T & string>(\n eventName: K,\n payload: Payload<T, K>,\n toRooms?: string[] | string,\n metadata?: Record<string, unknown>,\n onAck?: (ack: unknown) => void\n ): void;\n}\n\nexport type SocketServerDebugEvent =\n | { type: 'register'; event: string }\n | { type: 'unregister'; event: string }\n | { type: 'receive'; event: string; socketId: string; nsp: string; rooms: string[]; raw?: unknown }\n | { type: 'validation_error'; event: string; issues: any[] }\n | { type: 'handler_success'; event: string }\n | { type: 'handler_error'; event: string; error: unknown }\n | { type: 'emit'; event: string; rooms: string[]; envelope?: unknown }\n | { type: 'room_join'; rooms: string[]; socketId: string }\n | { type: 'room_leave'; rooms: string[]; socketId: string }\n | { type: 'auth_ok'; socketId: string; sub?: string }\n | { type: 'auth_error'; socketId?: string; err: string }\n | { type: 'ping'; socketId: string; payload?: Record<string, unknown> }\n | { type: 'pong'; socketId: string; sinceMs: number; payload?: Record<string, unknown> };\n\nexport type SocketDebugOptions = {\n verbose?: boolean;\n only?: string[];\n logger?: (e: SocketServerDebugEvent) => void;\n} & {\n [K in SocketServerDebugEvent['type']]?: boolean;\n};\n\nexport type BuiltInRoomHooks = {\n onRoomJoin?: (args: { room: string; socket: Socket }) => void | Promise<void>;\n onRoomLeave?: (args: { room: string; socket: Socket }) => void | Promise<void>;\n roomJoinEvent?: string;\n roomLeaveEvent?: string;\n};\n\n/** === NEW: enforced heartbeat config with schemas and callback */\nexport type HeartbeatServerOptions = {\n pingEvent?: string; // default 'sys:ping'\n pongEvent?: string; // default 'sys:pong'\n\n /** Validate incoming ping payload: event will be { payload: <ping> }. */\n pingSchema: z.ZodTypeAny;\n\n /** Build the pong payload you emit. It will receive the validated ping and socket. */\n makePongPayload: (args: { socket: Socket; ping: unknown }) => Record<string, unknown> | undefined;\n\n /** Optionally validate the outgoing pong object before emit. */\n pongSchema?: z.ZodTypeAny;\n};\n\nexport function createSocketConnections<T extends EventMap>(\n io: Server,\n events: T,\n opts?: {\n debug?: SocketDebugOptions;\n rooms?: BuiltInRoomHooks;\n auth?: AuthConfig;\n /** NEW: required heartbeat config */\n heartbeat: HeartbeatServerOptions;\n }\n): SocketConnection<T> {\n const debug = opts?.debug ?? {};\n const dbg = (name: string, e: SocketServerDebugEvent) => {\n if (!debug.logger || !debug[e.type]) return;\n if (debug.only && !debug.only.includes(name) && e.type === 'receive') return;\n debug.logger(e);\n };\n\n const getSchema = <K extends keyof T & string>(k: K) => events[k].message;\n const toArray = (rooms?: string[] | string): string[] =>\n rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms];\n\n // auth middleware\n if (opts?.auth) {\n const mw = createAuthMiddleware(io, opts.auth);\n io.use(async (socket, next) => {\n try {\n await mw(socket, next);\n } catch (e: any) {\n dbg('auth_error', { type: 'auth_error', socketId: socket?.id, err: String(e?.message ?? e) });\n next(e);\n }\n });\n }\n\n // registrations\n const registrations = new Map<\n string,\n Set<{\n connectionListener: (socket: Socket) => void;\n socketListeners: WeakMap<Socket, (raw: unknown) => void>;\n }>\n >();\n\n const addRegistration = (eventName: string, reg: {\n connectionListener: (socket: Socket) => void;\n socketListeners: WeakMap<Socket, (raw: unknown) => void>;\n }) => {\n let set = registrations.get(eventName);\n if (!set) {\n set = new Set();\n registrations.set(eventName, set);\n }\n set.add(reg);\n };\n\n const removeAllForEvent = (eventName: string) => {\n const set = registrations.get(eventName);\n if (!set) return;\n for (const reg of set) {\n io.off('connection', reg.connectionListener);\n for (const socket of io.sockets.sockets.values()) {\n const wrapped = reg.socketListeners.get(socket);\n if (wrapped) socket.off(String(eventName), wrapped);\n }\n }\n registrations.delete(eventName);\n dbg(eventName, { type: 'unregister', event: eventName });\n };\n\n const roomJoinEvent = opts?.rooms?.roomJoinEvent ?? 'room:join';\n const roomLeaveEvent = opts?.rooms?.roomLeaveEvent ?? 'room:leave';\n\n // === NEW: enforce heartbeat presence\n const hb = opts?.heartbeat;\n if (!hb) {\n throw new Error('createSocketConnections: heartbeat config is required');\n }\n const pingEvent = hb.pingEvent ?? 'sys:ping';\n const pongEvent = hb.pongEvent ?? 'sys:pong';\n\n io.on('connection', (socket) => {\n if ((socket.data as any)?.user) {\n dbg('auth_ok', { type: 'auth_ok', socketId: socket.id, sub: (socket.data as any).user?.sub });\n }\n\n // built-in room handlers\n const joinHandler = async ({ rooms }: { rooms?: string[] }) => {\n const list = toArray(rooms);\n for (const r of list) {\n await socket.join(r);\n dbg('room_join', { type: 'room_join', rooms: [r], socketId: socket.id });\n if (opts?.rooms?.onRoomJoin) await opts.rooms.onRoomJoin({ room: r, socket });\n }\n };\n const leaveHandler = async ({ rooms }: { rooms?: string[] }) => {\n const list = toArray(rooms);\n for (const r of list) {\n await socket.leave(r);\n dbg('room_leave', { type: 'room_leave', rooms: [r], socketId: socket.id });\n if (opts?.rooms?.onRoomLeave) await opts.rooms.onRoomLeave({ room: r, socket });\n }\n };\n socket.on(roomJoinEvent, joinHandler);\n socket.on(roomLeaveEvent, leaveHandler);\n socket.once('disconnect', () => {\n socket.off(roomJoinEvent, joinHandler);\n socket.off(roomLeaveEvent, leaveHandler);\n });\n\n // === NEW: app-level heartbeat with schema validation\n socket.on(pingEvent, (msg: { payload?: unknown } = {}, ack?: (x: { serverNow: string }) => void) => {\n const started = Date.now();\n const parsed = hb.pingSchema.safeParse(msg?.payload);\n if (!parsed.success) {\n socket.emit(`${pingEvent}:error`, { issues: parsed.error.issues });\n return;\n }\n\n dbg('ping', { type: 'ping', socketId: socket.id, payload: parsed.data as any });\n\n const pong = {\n serverNow: new Date().toISOString(),\n sinceMs: Date.now() - started,\n payload: hb.makePongPayload({ socket, ping: parsed.data }),\n clientEcho: parsed.data,\n };\n\n if (hb.pongSchema) {\n const check = hb.pongSchema.safeParse(pong);\n if (!check.success) {\n socket.emit(`${pongEvent}:error`, { issues: check.error.issues });\n return;\n }\n }\n\n socket.emit(pongEvent, pong);\n dbg('pong', { type: 'pong', socketId: socket.id, sinceMs: pong.sinceMs, payload: pong.payload });\n\n if (typeof ack === 'function') ack({ serverNow: pong.serverNow });\n });\n });\n\n const conn = {\n on<K extends keyof T & string>(\n eventName: K,\n handler: (payload: Payload<T, K>, ctx: HandlerCtx) => void | Promise<void>\n ): () => void {\n const socketListeners = new WeakMap<Socket, (raw: unknown) => void>();\n\nconst connectionListener = (socket: Socket) => {\n const wrapped = async (raw: unknown, maybeAck?: (data?: unknown) => void) => {\n const schema = getSchema(eventName);\n const parsed = schema.safeParse(raw);\n\n const ctx: HandlerCtx = {\n sentAt: new Date(),\n socket,\n socketId: socket.id,\n nsp: socket.nsp.name,\n rooms: Array.from(socket.rooms),\n user: (socket.data as any)?.user,\n scopes: (socket.data as any)?.scopes,\n ack: typeof maybeAck === 'function' ? (d?: unknown) => { try { maybeAck(d); } catch { /* noop */ } } : undefined, // NEW\n };\n\n dbg(String(eventName), {\n type: 'receive',\n event: String(eventName),\n socketId: ctx.socketId,\n nsp: ctx.nsp,\n rooms: ctx.rooms,\n raw: debug.verbose ? raw : undefined,\n });\n\n if (!parsed.success) {\n socket.emit(`${String(eventName)}:error`, { eventName, sentAt: ctx.sentAt, issues: parsed.error.issues });\n dbg(String(eventName), { type: 'validation_error', event: String(eventName), issues: parsed.error.issues });\n return;\n }\n\n try {\n await handler(parsed.data as Payload<T, K>, ctx);\n dbg(String(eventName), { type: 'handler_success', event: String(eventName) });\n } catch (error) {\n dbg(String(eventName), { type: 'handler_error', event: String(eventName), error });\n throw error;\n }\n };\n\n socketListeners.set(socket, wrapped);\n socket.on(String(eventName), wrapped);\n socket.once('disconnect', () => {\n const w = socketListeners.get(socket);\n if (w) socket.off(String(eventName), w);\n });\n};\n\n\n io.on('connection', connectionListener);\n addRegistration(String(eventName), { connectionListener, socketListeners });\n dbg(String(eventName), { type: 'register', event: String(eventName) });\n\n return () => {\n const set = registrations.get(String(eventName));\n if (!set) return;\n for (const reg of Array.from(set)) {\n if (reg.connectionListener === connectionListener) {\n io.off('connection', reg.connectionListener);\n for (const socket of io.sockets.sockets.values()) {\n const wrapped = reg.socketListeners.get(socket);\n if (wrapped) socket.off(String(eventName), wrapped);\n }\n set.delete(reg);\n break;\n }\n }\n if (set.size === 0) registrations.delete(String(eventName));\n dbg(String(eventName), { type: 'unregister', event: String(eventName) });\n };\n },\n\n off<K extends keyof T & string>(eventName: K): void {\n removeAllForEvent(String(eventName));\n },\n\nemit<K extends keyof T & string>(\n eventName: K,\n payload: Payload<T, K>,\n rooms?: string[] | string,\n metadata?: Record<string, unknown>,\n onAck?: (ack: unknown) => void, // NEW\n): void {\n const schema = getSchema(eventName);\n const check = schema.safeParse(payload);\n if (!check.success) throw new Error(`Invalid payload for \"${String(eventName)}\": ${check.error.message}`);\n\n const targets = toArray(rooms);\n const envelope = {\n eventName,\n sentAt: new Date(),\n sentTo: targets,\n data: check.data as Payload<T, K>,\n metadata: metadata ?? {},\n };\n\n // If exactly one target socket, use per-socket emit with ack\n if (targets.length === 1) {\n const sid = targets[0]!;\n const sock = io.sockets.sockets.get(sid as any);\n if (sock) {\n if (onAck) sock.emit(String(eventName), envelope, (ack: unknown) => { try { onAck(ack); } catch { /* noop */ } });\n else sock.emit(String(eventName), envelope);\n dbg(String(eventName), { type: 'emit', event: String(eventName), rooms: targets, envelope: debug.verbose ? envelope : undefined });\n return;\n }\n }\n\n // Fallback: broadcast, no ack possible\n if (targets.length === 0) io.emit(String(eventName), envelope);\n else io.to(targets).emit(String(eventName), envelope);\n\n dbg(String(eventName), { type: 'emit', event: String(eventName), rooms: targets, envelope: debug.verbose ? envelope : undefined });\n}\n,\n } as const satisfies SocketConnection<T>;\n\n return conn;\n}\n\nexport type { EventMap, Payload };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/routesV3.server.ts","../src/sockets/socket.server.index.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 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 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;\n const emit: ServerDebugEmitter<Names>['emit'] = (event, name) => {\n if (!whitelist.has(event.type) || !logger) 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 * - `upload` runs when cfg.bodyFiles has entries\n */\n fromCfg?: {\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// 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 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\n logger?.info?.(`${methodUpper}@${path} (${requestUrl})`);\n const ctx = (res.locals as any)[CTX_SYMBOL] as CtxWithRoutesLogger<Ctx>;\n const ctxRoutesLogger = ctx?.routesLogger;\n const emitWithCtx = (\n event: RouteServerDebugEvent,\n details?: Partial<RouteServerDebugEvent>,\n ) => {\n const decorated = decorateDebugEvent(isVerboseDebug, event, details);\n if (decorated.type === 'handler' && ctxRoutesLogger) {\n logHandlerDebugWithRoutesLogger(ctxRoutesLogger, decorated);\n }else{\n emit(decorated);\n }\n };\n try {\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 },\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 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 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 * 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","// socket.server.index.ts\nimport { Server, Socket } from 'socket.io';\nimport { z, ZodType } from 'zod';\nimport type { SocketEvent } from '@emeryld/rrroutes-contract';\n\ntype EventMap = Record<string, SocketEvent>;\ntype Payload<T extends EventMap, K extends keyof T> = z.infer<T[K]['message']>;\n\nexport type HandlerCtx = {\n sentAt: Date;\n socket: Socket;\n socketId: string;\n nsp: string;\n rooms: string[];\n user?: unknown;\n scopes?: string[];\n ack?: (data?: unknown) => void; \n};\n\nexport interface SocketConnection<T extends EventMap> {\n on<K extends keyof T & string>(\n eventName: K,\n handler: (payload: Payload<T, K>, ctx: HandlerCtx) => void | Promise<void>\n ): () => void;\n\n off<K extends keyof T & string>(eventName: K): void;\n\n emit<K extends keyof T & string>(\n eventName: K,\n payload: Payload<T, K>,\n toRooms?: string[] | string,\n metadata?: Record<string, unknown>,\n onAck?: (ack: unknown) => void\n ): void;\n}\n\nexport type SocketServerDebugEvent<Ping extends ZodType=ZodType, Pong extends ZodType=ZodType> =\n | { type: 'register'; event: string }\n | { type: 'unregister'; event: string }\n | { type: 'receive'; event: string; socketId: string; nsp: string; rooms: string[]; raw?: unknown }\n | { type: 'validation_error'; event: string; issues: any[] }\n | { type: 'handler_success'; event: string }\n | { type: 'handler_error'; event: string; error: unknown }\n | { type: 'emit'; event: string; rooms: string[]; envelope?: unknown }\n | { type: 'room_join'; rooms: string[]; socketId: string }\n | { type: 'room_leave'; rooms: string[]; socketId: string }\n | { type: 'auth_ok'; socketId: string; sub?: string }\n | { type: 'auth_error'; socketId?: string; err: string }\n | { type: 'ping'; socketId: string; payload?: NoInfer<z.infer<Ping>> }\n | { type: 'pong'; socketId: string; payload?: NoInfer<z.infer<Pong>>; issues?: any[], error:boolean };\n\nexport type SocketDebugOptions<Ping extends ZodType, Pong extends ZodType> = {\n verbose?: boolean;\n only?: string[];\n logger?: (e: SocketServerDebugEvent<Ping, Pong>) => void;\n} & {\n [K in SocketServerDebugEvent<Ping, Pong>['type']]?: boolean;\n};\n\nexport type BuiltInRoomHooks = {\n onRoomJoin?: (args: { room: string; socket: Socket }) => boolean | Promise<boolean>;\n onRoomLeave?: (args: { room: string; socket: Socket }) => boolean | Promise<boolean>;\n roomJoinEvent?: string;\n roomLeaveEvent?: string;\n};\n\n/** === NEW: enforced heartbeat config with schemas and callback */\nexport type HeartbeatServerOptions<Ping extends ZodType, Pong extends ZodType> = {\n pingEvent?: string; // default 'sys:ping'\n pongEvent?: string; // default 'sys:pong'\n\n /** Validate incoming ping payload: event will be { payload: <ping> }. */\n pingSchema: Ping;\n\n /** Build the pong payload you emit. It will receive the validated ping and socket. */\n makePongPayload: (args: { socket: Socket; ping: z.infer<Ping> }) => NoInfer<z.infer<Pong>>;\n\n /** Optionally validate the outgoing pong object before emit. */\n pongSchema?: Pong;\n};\n\nexport function createSocketConnections<Ping extends ZodType, Pong extends ZodType, T extends EventMap>(\n io: Server,\n events: T,\n opts?: {\n debug?: SocketDebugOptions<Ping, Pong>;\n rooms?: BuiltInRoomHooks;\n heartbeat: HeartbeatServerOptions<Ping, Pong>;\n }\n): SocketConnection<T> {\n const debug = opts?.debug ?? {};\n const dbg = (name: string, e: SocketServerDebugEvent<Ping, Pong>) => {\n if (!debug.logger || !debug[e.type]) return;\n if (debug.only && !debug.only.includes(name) && e.type === 'receive') return;\n debug.logger(e);\n };\n\n const getSchema = <K extends keyof T & string>(k: K) => events[k].message;\n const toArray = (rooms?: string[] | string): string[] =>\n rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms];\n\n // registrations\n const registrations = new Map<\n string,\n Set<{\n connectionListener: (socket: Socket) => void;\n socketListeners: WeakMap<Socket, (raw: unknown) => void>;\n }>\n >();\n\n const addRegistration = (eventName: string, reg: {\n connectionListener: (socket: Socket) => void;\n socketListeners: WeakMap<Socket, (raw: unknown) => void>;\n }) => {\n let set = registrations.get(eventName);\n if (!set) {\n set = new Set();\n registrations.set(eventName, set);\n }\n set.add(reg);\n };\n\n const removeAllForEvent = (eventName: string) => {\n const set = registrations.get(eventName);\n if (!set) return;\n for (const reg of set) {\n io.off('connection', reg.connectionListener);\n for (const socket of io.sockets.sockets.values()) {\n const wrapped = reg.socketListeners.get(socket);\n if (wrapped) socket.off(String(eventName), wrapped);\n }\n }\n registrations.delete(eventName);\n dbg(eventName, { type: 'unregister', event: eventName });\n };\n\n const roomJoinEvent = opts?.rooms?.roomJoinEvent ?? 'room:join';\n const roomLeaveEvent = opts?.rooms?.roomLeaveEvent ?? 'room:leave';\n\n const hb = opts?.heartbeat;\n if (!hb) {\n throw new Error('createSocketConnections: heartbeat config is required');\n }\n const pingEvent = hb.pingEvent ?? 'sys:ping';\n const pongEvent = hb.pongEvent ?? 'sys:pong';\n\n io.on('connection', (socket) => {\n if ((socket.data as any)?.user) {\n dbg('auth_ok', { type: 'auth_ok', socketId: socket.id, sub: (socket.data as any).user?.sub });\n }\n\n // built-in room handlers\n const joinHandler = async ({ rooms }: { rooms?: string[] }) => {\n const list = toArray(rooms);\n for (const r of list) {\n \n if (!opts?.rooms?.onRoomJoin ||( await opts.rooms.onRoomJoin({ room: r, socket }))==true){\n await socket.join(r);\n dbg('room_join', { type: 'room_join', rooms: [r], socketId: socket.id });\n } \n }\n };\n const leaveHandler = async ({ rooms }: { rooms?: string[] }) => {\n const list = toArray(rooms);\n for (const r of list) {\n \n \n if (!opts?.rooms?.onRoomLeave || ( await opts.rooms.onRoomLeave({ room: r, socket }))==true){\n dbg('room_leave', { type: 'room_leave', rooms: [r], socketId: socket.id });\nawait socket.leave(r);\n }\n }\n };\n socket.on(roomJoinEvent, joinHandler);\n socket.on(roomLeaveEvent, leaveHandler);\n socket.once('disconnect', () => {\n socket.off(roomJoinEvent, joinHandler);\n socket.off(roomLeaveEvent, leaveHandler);\n });\n\n socket.on(pingEvent, (msg: { payload?: unknown } = {}, ack?: (x: { serverNow: string }) => void) => {\n\n const parsed = hb.pingSchema.safeParse(msg?.payload);\n if (!parsed.success) {\n socket.emit(`${pingEvent}:error`, { issues: parsed.error.issues });\n return;\n }\n\n dbg('ping', { type: 'ping', socketId: socket.id, payload: parsed.data });\n\n const pong = hb.makePongPayload({ socket, ping: parsed.data })\n\n if (hb.pongSchema) {\n const check = hb.pongSchema.safeParse(pong);\n if (!check.success) {\n socket.emit(`${pongEvent}:error`, { issues: check.error.issues });\n dbg('pong', { type: 'pong', socketId: socket.id, issues: check.error.issues, error:true });\n return;\n }\n }\n\n socket.emit(pongEvent, pong);\n dbg('pong', { type: 'pong', socketId: socket.id, payload: pong , error:false});\n\n if (typeof ack === 'function') ack({ serverNow: new Date().toISOString() });\n });\n });\n\n const conn = {\n on<K extends keyof T & string>(\n eventName: K,\n handler: (payload: Payload<T, K>, ctx: HandlerCtx) => void | Promise<void>\n ): () => void {\n const socketListeners = new WeakMap<Socket, (raw: unknown) => void>();\n\nconst connectionListener = (socket: Socket) => {\n const wrapped = async (raw: unknown, maybeAck?: (data?: unknown) => void) => {\n const schema = getSchema(eventName);\n const parsed = schema.safeParse(raw);\n\n const ctx: HandlerCtx = {\n sentAt: new Date(),\n socket,\n socketId: socket.id,\n nsp: socket.nsp.name,\n rooms: Array.from(socket.rooms),\n user: (socket.data )?.user,\n scopes: (socket.data )?.scopes,\n ack: typeof maybeAck === 'function' ? (d?: unknown) => { try { maybeAck(d); } catch { /* noop */ } } : undefined, // NEW\n };\n\n dbg(String(eventName), {\n type: 'receive',\n event: String(eventName),\n socketId: ctx.socketId,\n nsp: ctx.nsp,\n rooms: ctx.rooms,\n raw: debug.verbose ? raw : undefined,\n });\n\n if (!parsed.success) {\n socket.emit(`${String(eventName)}:error`, { eventName, sentAt: ctx.sentAt, issues: parsed.error.issues });\n dbg(String(eventName), { type: 'validation_error', event: String(eventName), issues: parsed.error.issues });\n return;\n }\n\n try {\n await handler(parsed.data as Payload<T, K>, ctx);\n dbg(String(eventName), { type: 'handler_success', event: String(eventName) });\n } catch (error) {\n dbg(String(eventName), { type: 'handler_error', event: String(eventName), error });\n throw error;\n }\n };\n\n socketListeners.set(socket, wrapped);\n socket.on(String(eventName), wrapped);\n socket.once('disconnect', () => {\n const w = socketListeners.get(socket);\n if (w) socket.off(String(eventName), w);\n });\n};\n\n\n io.on('connection', connectionListener);\n addRegistration(String(eventName), { connectionListener, socketListeners });\n dbg(String(eventName), { type: 'register', event: String(eventName) });\n\n return () => {\n const set = registrations.get(String(eventName));\n if (!set) return;\n for (const reg of Array.from(set)) {\n if (reg.connectionListener === connectionListener) {\n io.off('connection', reg.connectionListener);\n for (const socket of io.sockets.sockets.values()) {\n const wrapped = reg.socketListeners.get(socket);\n if (wrapped) socket.off(String(eventName), wrapped);\n }\n set.delete(reg);\n break;\n }\n }\n if (set.size === 0) registrations.delete(String(eventName));\n dbg(String(eventName), { type: 'unregister', event: String(eventName) });\n };\n },\n\n off<K extends keyof T & string>(eventName: K): void {\n removeAllForEvent(String(eventName));\n },\n\nemit<K extends keyof T & string>(\n eventName: K,\n payload: Payload<T, K>,\n rooms?: string[] | string,\n metadata?: Record<string, unknown>,\n onAck?: (ack: unknown) => void, \n): void {\n const schema = getSchema(eventName);\n const check = schema.safeParse(payload);\n if (!check.success) throw new Error(`Invalid payload for \"${String(eventName)}\": ${check.error.message}`);\n\n const targets = toArray(rooms);\n const envelope = {\n eventName,\n sentAt: new Date(),\n sentTo: targets,\n data: check.data as Payload<T, K>,\n metadata: metadata ?? {},\n };\n\n // If exactly one target socket, use per-socket emit with ack\n if (targets.length === 1) {\n const sid = targets[0]!;\n const sock = io.sockets.sockets.get(sid as any);\n if (sock) {\n if (onAck) sock.emit(String(eventName), envelope, (ack: unknown) => { try { onAck(ack); } catch { /* noop */ } });\n else sock.emit(String(eventName), envelope);\n dbg(String(eventName), { type: 'emit', event: String(eventName), rooms: targets, envelope: debug.verbose ? envelope : undefined });\n return;\n }\n }\n\n // Fallback: broadcast, no ack possible\n if (targets.length === 0) io.emit(String(eventName), envelope);\n else io.to(targets).emit(String(eventName), envelope);\n\n dbg(String(eventName), { type: 'emit', event: String(eventName), rooms: targets, envelope: debug.verbose ? envelope : undefined });\n}\n,\n } as const satisfies SocketConnection<T>;\n\n return conn;\n}\n\nexport type { EventMap, Payload };\n"],"mappings":"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","names":[]}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Helper to warn about unimplemented routes
|
|
10
10
|
* - DX helpers to use `ctx` in any middleware with proper types
|
|
11
11
|
*/
|
|
12
|
-
import { AnyLeaf, FileField, HttpMethod, InferBody, InferOutput, InferParams, InferQuery
|
|
12
|
+
import { AnyLeaf, FileField, HttpMethod, InferBody, InferOutput, InferParams, InferQuery } from '@emeryld/rrroutes-contract';
|
|
13
13
|
import type * as express from 'express';
|
|
14
14
|
import type { RequestHandler, Router } from 'express';
|
|
15
15
|
/** Shape expected from optional logger implementations. */
|
|
@@ -203,14 +203,9 @@ export type RouteServerConfig<Ctx = unknown, Names extends string = string> = {
|
|
|
203
203
|
};
|
|
204
204
|
/**
|
|
205
205
|
* Derive middleware from MethodCfg.
|
|
206
|
-
* - `auth` runs when cfg.authenticated === true (or `when` overrides)
|
|
207
206
|
* - `upload` runs when cfg.bodyFiles has entries
|
|
208
207
|
*/
|
|
209
208
|
fromCfg?: {
|
|
210
|
-
auth?: RequestHandler | ((leaf: AnyLeaf) => RequestHandler);
|
|
211
|
-
when?: (cfg: MethodCfg, leaf: AnyLeaf) => {
|
|
212
|
-
auth?: boolean;
|
|
213
|
-
} | void;
|
|
214
209
|
upload?: (files: FileField[] | undefined, leaf: AnyLeaf) => RequestHandler[];
|
|
215
210
|
};
|
|
216
211
|
/** Validate handler return values with outputSchema (default: true) */
|
|
@@ -283,13 +278,6 @@ export declare const defineControllers: <R extends {
|
|
|
283
278
|
all: readonly AnyLeaf[];
|
|
284
279
|
byKey: Record<string, AnyLeaf>;
|
|
285
280
|
}, Ctx = unknown, Names extends string = string>() => <M extends PartialControllerMap<R, Ctx, Names>>(m: M) => M;
|
|
286
|
-
/**
|
|
287
|
-
* Wrap a plain RequestHandler as an auth factory compatible with `fromCfg.auth`.
|
|
288
|
-
* @param mw Middleware invoked for any leaf that requires authentication.
|
|
289
|
-
* @param _leaf Leaf metadata (ignored, but provided to match factory signature).
|
|
290
|
-
* @returns Factory that ignores the leaf and returns the same middleware.
|
|
291
|
-
*/
|
|
292
|
-
export declare const asLeafAuth: (mw: RequestHandler) => (_leaf: AnyLeaf) => RequestHandler;
|
|
293
281
|
/**
|
|
294
282
|
* Warn about leaves that don't have controllers.
|
|
295
283
|
* Call this during startup to surface missing routes.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Server, Socket } from 'socket.io';
|
|
2
|
-
import { z } from 'zod';
|
|
2
|
+
import { z, ZodType } from 'zod';
|
|
3
3
|
import type { SocketEvent } from '@emeryld/rrroutes-contract';
|
|
4
|
-
import { type AuthConfig } from './socket.server.auth';
|
|
5
4
|
type EventMap = Record<string, SocketEvent>;
|
|
6
5
|
type Payload<T extends EventMap, K extends keyof T> = z.infer<T[K]['message']>;
|
|
7
6
|
export type HandlerCtx = {
|
|
@@ -19,7 +18,7 @@ export interface SocketConnection<T extends EventMap> {
|
|
|
19
18
|
off<K extends keyof T & string>(eventName: K): void;
|
|
20
19
|
emit<K extends keyof T & string>(eventName: K, payload: Payload<T, K>, toRooms?: string[] | string, metadata?: Record<string, unknown>, onAck?: (ack: unknown) => void): void;
|
|
21
20
|
}
|
|
22
|
-
export type SocketServerDebugEvent = {
|
|
21
|
+
export type SocketServerDebugEvent<Ping extends ZodType = ZodType, Pong extends ZodType = ZodType> = {
|
|
23
22
|
type: 'register';
|
|
24
23
|
event: string;
|
|
25
24
|
} | {
|
|
@@ -67,51 +66,50 @@ export type SocketServerDebugEvent = {
|
|
|
67
66
|
} | {
|
|
68
67
|
type: 'ping';
|
|
69
68
|
socketId: string;
|
|
70
|
-
payload?:
|
|
69
|
+
payload?: NoInfer<z.infer<Ping>>;
|
|
71
70
|
} | {
|
|
72
71
|
type: 'pong';
|
|
73
72
|
socketId: string;
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
payload?: NoInfer<z.infer<Pong>>;
|
|
74
|
+
issues?: any[];
|
|
75
|
+
error: boolean;
|
|
76
76
|
};
|
|
77
|
-
export type SocketDebugOptions = {
|
|
77
|
+
export type SocketDebugOptions<Ping extends ZodType, Pong extends ZodType> = {
|
|
78
78
|
verbose?: boolean;
|
|
79
79
|
only?: string[];
|
|
80
|
-
logger?: (e: SocketServerDebugEvent) => void;
|
|
80
|
+
logger?: (e: SocketServerDebugEvent<Ping, Pong>) => void;
|
|
81
81
|
} & {
|
|
82
|
-
[K in SocketServerDebugEvent['type']]?: boolean;
|
|
82
|
+
[K in SocketServerDebugEvent<Ping, Pong>['type']]?: boolean;
|
|
83
83
|
};
|
|
84
84
|
export type BuiltInRoomHooks = {
|
|
85
85
|
onRoomJoin?: (args: {
|
|
86
86
|
room: string;
|
|
87
87
|
socket: Socket;
|
|
88
|
-
}) =>
|
|
88
|
+
}) => boolean | Promise<boolean>;
|
|
89
89
|
onRoomLeave?: (args: {
|
|
90
90
|
room: string;
|
|
91
91
|
socket: Socket;
|
|
92
|
-
}) =>
|
|
92
|
+
}) => boolean | Promise<boolean>;
|
|
93
93
|
roomJoinEvent?: string;
|
|
94
94
|
roomLeaveEvent?: string;
|
|
95
95
|
};
|
|
96
96
|
/** === NEW: enforced heartbeat config with schemas and callback */
|
|
97
|
-
export type HeartbeatServerOptions = {
|
|
97
|
+
export type HeartbeatServerOptions<Ping extends ZodType, Pong extends ZodType> = {
|
|
98
98
|
pingEvent?: string;
|
|
99
99
|
pongEvent?: string;
|
|
100
100
|
/** Validate incoming ping payload: event will be { payload: <ping> }. */
|
|
101
|
-
pingSchema:
|
|
101
|
+
pingSchema: Ping;
|
|
102
102
|
/** Build the pong payload you emit. It will receive the validated ping and socket. */
|
|
103
103
|
makePongPayload: (args: {
|
|
104
104
|
socket: Socket;
|
|
105
|
-
ping:
|
|
106
|
-
}) =>
|
|
105
|
+
ping: z.infer<Ping>;
|
|
106
|
+
}) => NoInfer<z.infer<Pong>>;
|
|
107
107
|
/** Optionally validate the outgoing pong object before emit. */
|
|
108
|
-
pongSchema?:
|
|
108
|
+
pongSchema?: Pong;
|
|
109
109
|
};
|
|
110
|
-
export declare function createSocketConnections<T extends EventMap>(io: Server, events: T, opts?: {
|
|
111
|
-
debug?: SocketDebugOptions
|
|
110
|
+
export declare function createSocketConnections<Ping extends ZodType, Pong extends ZodType, T extends EventMap>(io: Server, events: T, opts?: {
|
|
111
|
+
debug?: SocketDebugOptions<Ping, Pong>;
|
|
112
112
|
rooms?: BuiltInRoomHooks;
|
|
113
|
-
|
|
114
|
-
/** NEW: required heartbeat config */
|
|
115
|
-
heartbeat: HeartbeatServerOptions;
|
|
113
|
+
heartbeat: HeartbeatServerOptions<Ping, Pong>;
|
|
116
114
|
}): SocketConnection<T>;
|
|
117
115
|
export type { EventMap, Payload };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emeryld/rrroutes-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@emeryld/rrroutes-contract": "^1.6.
|
|
20
|
+
"@emeryld/rrroutes-contract": "^1.6.6",
|
|
21
21
|
"jose": "^6.1.1",
|
|
22
22
|
"socket.io": "^4.8.1",
|
|
23
23
|
"zod": "^4.1.12"
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { JWTPayload, JWTVerifyOptions } from 'jose';
|
|
2
|
-
import { IncomingMessage } from 'http';
|
|
3
|
-
import { Server, Socket } from 'socket.io';
|
|
4
|
-
type JwtLocalSecret = {
|
|
5
|
-
type: 'local';
|
|
6
|
-
secret: Uint8Array;
|
|
7
|
-
algorithms?: JWTVerifyOptions['algorithms'];
|
|
8
|
-
};
|
|
9
|
-
type JwtJwks = {
|
|
10
|
-
type: 'jwks';
|
|
11
|
-
jwksUri: string;
|
|
12
|
-
algorithms?: JWTVerifyOptions['algorithms'];
|
|
13
|
-
};
|
|
14
|
-
export type BaseAuthConfig = JwtLocalSecret | JwtJwks;
|
|
15
|
-
export type AuthCustomization = {
|
|
16
|
-
mapUser?: (payload: JWTPayload) => unknown;
|
|
17
|
-
selectScopes?: (user: unknown) => string[] | undefined;
|
|
18
|
-
getToken?: (req: IncomingMessage, socket: Socket) => string | undefined;
|
|
19
|
-
};
|
|
20
|
-
export type AuthConfig = BaseAuthConfig & AuthCustomization;
|
|
21
|
-
export type AuthUser = JWTPayload & {
|
|
22
|
-
sub: string;
|
|
23
|
-
email?: string;
|
|
24
|
-
roles?: string[];
|
|
25
|
-
scopes?: string[];
|
|
26
|
-
};
|
|
27
|
-
export declare function createAuthMiddleware(io: Server, cfg: AuthConfig): (socket: Socket, next: (err?: any) => void) => Promise<void>;
|
|
28
|
-
export {};
|