@emeryld/rrroutes-server 2.4.2 → 2.4.3
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 +19 -0
- package/dist/index.cjs +106 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +106 -27
- package/dist/index.js.map +1 -1
- package/dist/routesV3.server.d.ts +19 -2
- package/dist/sockets/socket.server.sys.d.ts +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -153,6 +153,19 @@ bindExpressRoutes(
|
|
|
153
153
|
)
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
+
If you need access to the parsed params/query/body inside `buildCtx`, destructure them from the single argument:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const server = createRRRoute(app, {
|
|
160
|
+
buildCtx: ({ params, query, body }) => ({
|
|
161
|
+
user: lookupUser(params.id),
|
|
162
|
+
verbose: query?.verbose === 'yes',
|
|
163
|
+
}),
|
|
164
|
+
})
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
> `buildCtx` now receives the `{ req, res, params, query, body }` object; the legacy `(req, res)` signature is no longer supported.
|
|
168
|
+
|
|
156
169
|
- `defineControllers<Registry, Ctx>()(map)` keeps literal `"METHOD /path"` keys accurate and infers params/query/body/output types per leaf.
|
|
157
170
|
- `registerControllers` accepts partial maps (missing routes are skipped); `bindAll` enforces completeness at compile time.
|
|
158
171
|
- `warnMissingControllers(router, registry, logger)` inspects the Express stack and warns for any leaf without a handler.
|
|
@@ -174,6 +187,11 @@ const server = createRRRoute(app, {
|
|
|
174
187
|
globalMiddleware: { before: [audit] },
|
|
175
188
|
})
|
|
176
189
|
|
|
190
|
+
const routeBefore = ({ params, query, body, ctx, next }) => {
|
|
191
|
+
ctx.routesLogger?.debug?.('route.before payload', { params, query, body })
|
|
192
|
+
next()
|
|
193
|
+
}
|
|
194
|
+
|
|
177
195
|
// Inside any Express middleware (even outside route.before), use getCtx to retrieve typed ctx:
|
|
178
196
|
app.use((req, res, next) => {
|
|
179
197
|
const ctx = getCtx<Ctx>(res)
|
|
@@ -183,6 +201,7 @@ app.use((req, res, next) => {
|
|
|
183
201
|
```
|
|
184
202
|
|
|
185
203
|
- `CtxRequestHandler` receives `{ req, res, next, ctx }` with your typed ctx.
|
|
204
|
+
- `route.before` handlers now receive the same parsed `params`, `query`, and `body` payload as the handler, alongside `req`, `res`, and `ctx`.
|
|
186
205
|
- Need post-response hooks? Register a middleware that wires `res.on('finish', handler)` inside `route.before`/`global.before` instead of relying on a dedicated "after" stage.
|
|
187
206
|
|
|
188
207
|
### Derived middleware from route cfg (uploads)
|
package/dist/index.cjs
CHANGED
|
@@ -94,6 +94,20 @@ var decodeJsonLikeQueryValue = (value) => {
|
|
|
94
94
|
return value;
|
|
95
95
|
};
|
|
96
96
|
var CTX_SYMBOL = /* @__PURE__ */ Symbol.for("typedLeaves.ctx");
|
|
97
|
+
var REQUEST_PAYLOAD_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
98
|
+
"typedLeaves.requestPayload"
|
|
99
|
+
);
|
|
100
|
+
function getRouteRequestPayload(res) {
|
|
101
|
+
const payload = res.locals[REQUEST_PAYLOAD_SYMBOL];
|
|
102
|
+
if (payload) {
|
|
103
|
+
return payload;
|
|
104
|
+
}
|
|
105
|
+
throw new Error("Request payload was not initialized before middleware");
|
|
106
|
+
}
|
|
107
|
+
function setRouteRequestPayload(res, payload) {
|
|
108
|
+
;
|
|
109
|
+
res.locals[REQUEST_PAYLOAD_SYMBOL] = payload;
|
|
110
|
+
}
|
|
97
111
|
function getCtx(res) {
|
|
98
112
|
return res.locals[CTX_SYMBOL];
|
|
99
113
|
}
|
|
@@ -111,6 +125,26 @@ function adaptCtxMw(mw) {
|
|
|
111
125
|
}
|
|
112
126
|
};
|
|
113
127
|
}
|
|
128
|
+
function adaptRouteBeforeMw(mw) {
|
|
129
|
+
return (req, res, next) => {
|
|
130
|
+
try {
|
|
131
|
+
const result = mw({
|
|
132
|
+
req,
|
|
133
|
+
res,
|
|
134
|
+
next,
|
|
135
|
+
ctx: getCtx(res),
|
|
136
|
+
...getRouteRequestPayload(res)
|
|
137
|
+
});
|
|
138
|
+
if (result && typeof result.then === "function") {
|
|
139
|
+
return result.catch((err) => next(err));
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
} catch (err) {
|
|
143
|
+
next(err);
|
|
144
|
+
return void 0;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
114
148
|
function logHandlerDebugWithRoutesLogger(logger, event) {
|
|
115
149
|
if (!logger || event.type !== "handler") return;
|
|
116
150
|
const payload = [
|
|
@@ -201,7 +235,9 @@ function createRRRoute(router, config) {
|
|
|
201
235
|
const emit = (event) => activeEmit(event, debugName);
|
|
202
236
|
const isVerboseDebug = activeDebugMode === "complete";
|
|
203
237
|
emit({ type: "register", method: methodUpper, path });
|
|
204
|
-
const routeSpecific = (def?.before ?? []).map(
|
|
238
|
+
const routeSpecific = (def?.before ?? []).map(
|
|
239
|
+
(mw) => adaptRouteBeforeMw(mw)
|
|
240
|
+
);
|
|
205
241
|
const derived = buildDerived(leaf);
|
|
206
242
|
const ctxMw = async (req, res, next) => {
|
|
207
243
|
const requestUrl = req.originalUrl ?? path;
|
|
@@ -213,8 +249,66 @@ function createRRRoute(router, config) {
|
|
|
213
249
|
path,
|
|
214
250
|
url: requestUrl
|
|
215
251
|
});
|
|
252
|
+
let params;
|
|
253
|
+
let query;
|
|
254
|
+
let body;
|
|
216
255
|
try {
|
|
217
|
-
|
|
256
|
+
params = leaf.cfg.paramsSchema ? (0, import_rrroutes_contract.lowProfileParse)(leaf.cfg.paramsSchema, req.params) : Object.keys(req.params || {}).length ? req.params : void 0;
|
|
257
|
+
const hasQueryKeys = req.query && Object.keys(req.query || {}).length > 0;
|
|
258
|
+
const parsedQueryInput = leaf.cfg.querySchema && hasQueryKeys ? decodeJsonLikeQueryValue(req.query) : req.query;
|
|
259
|
+
if (leaf.cfg.querySchema) {
|
|
260
|
+
try {
|
|
261
|
+
query = (0, import_rrroutes_contract.lowProfileParse)(
|
|
262
|
+
leaf.cfg.querySchema,
|
|
263
|
+
parsedQueryInput
|
|
264
|
+
);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
const parseError = new Error(
|
|
267
|
+
`Query parsing error: ${err.message ?? String(err)}`
|
|
268
|
+
);
|
|
269
|
+
parseError.raw = JSON.stringify(req.query);
|
|
270
|
+
parseError.cause = err;
|
|
271
|
+
throw parseError;
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
query = hasQueryKeys ? req.query : void 0;
|
|
275
|
+
}
|
|
276
|
+
body = leaf.cfg.bodySchema ? (0, import_rrroutes_contract.lowProfileParse)(leaf.cfg.bodySchema, req.body) : req.body !== void 0 ? req.body : void 0;
|
|
277
|
+
} catch (err) {
|
|
278
|
+
const payloadError = {
|
|
279
|
+
params,
|
|
280
|
+
query,
|
|
281
|
+
body
|
|
282
|
+
};
|
|
283
|
+
emit(
|
|
284
|
+
decorateDebugEvent(
|
|
285
|
+
isVerboseDebug,
|
|
286
|
+
{
|
|
287
|
+
type: "request",
|
|
288
|
+
stage: "error",
|
|
289
|
+
method: methodUpper,
|
|
290
|
+
path,
|
|
291
|
+
url: requestUrl,
|
|
292
|
+
durationMs: Date.now() - startedAt,
|
|
293
|
+
error: err
|
|
294
|
+
},
|
|
295
|
+
isVerboseDebug ? payloadError : void 0
|
|
296
|
+
)
|
|
297
|
+
);
|
|
298
|
+
next(err);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const requestPayload = {
|
|
302
|
+
params,
|
|
303
|
+
query,
|
|
304
|
+
body
|
|
305
|
+
};
|
|
306
|
+
setRouteRequestPayload(res, requestPayload);
|
|
307
|
+
try {
|
|
308
|
+
const ctx = await config.buildCtx({
|
|
309
|
+
req,
|
|
310
|
+
res
|
|
311
|
+
});
|
|
218
312
|
res.locals[CTX_SYMBOL] = ctx;
|
|
219
313
|
emit({
|
|
220
314
|
type: "buildCtx",
|
|
@@ -254,9 +348,10 @@ function createRRRoute(router, config) {
|
|
|
254
348
|
path,
|
|
255
349
|
url: requestUrl
|
|
256
350
|
});
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
351
|
+
const requestPayload = getRouteRequestPayload(res);
|
|
352
|
+
const params = requestPayload.params;
|
|
353
|
+
const query = requestPayload.query;
|
|
354
|
+
const body = requestPayload.body;
|
|
260
355
|
let responsePayload;
|
|
261
356
|
let hasResponsePayload = false;
|
|
262
357
|
const downstreamNext = next;
|
|
@@ -274,26 +369,6 @@ function createRRRoute(router, config) {
|
|
|
274
369
|
}
|
|
275
370
|
};
|
|
276
371
|
try {
|
|
277
|
-
params = leaf.cfg.paramsSchema ? (0, import_rrroutes_contract.lowProfileParse)(leaf.cfg.paramsSchema, req.params) : Object.keys(req.params || {}).length ? req.params : void 0;
|
|
278
|
-
try {
|
|
279
|
-
const parsedQueryInput = leaf.cfg.querySchema && req.query ? decodeJsonLikeQueryValue(req.query) : req.query;
|
|
280
|
-
query = leaf.cfg.querySchema ? (0, import_rrroutes_contract.lowProfileParse)(leaf.cfg.querySchema, parsedQueryInput) : Object.keys(req.query || {}).length ? req.query : void 0;
|
|
281
|
-
} catch (e) {
|
|
282
|
-
emitWithCtx({
|
|
283
|
-
type: "request",
|
|
284
|
-
stage: "error",
|
|
285
|
-
method: methodUpper,
|
|
286
|
-
path,
|
|
287
|
-
url: requestUrl,
|
|
288
|
-
error: {
|
|
289
|
-
...e,
|
|
290
|
-
raw: JSON.stringify(req.query),
|
|
291
|
-
message: `Query parsing error: ${e.message}`
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
throw e;
|
|
295
|
-
}
|
|
296
|
-
body = leaf.cfg.bodySchema ? (0, import_rrroutes_contract.lowProfileParse)(leaf.cfg.bodySchema, req.body) : req.body !== void 0 ? req.body : void 0;
|
|
297
372
|
const handlerStartedAt = Date.now();
|
|
298
373
|
emitWithCtx(
|
|
299
374
|
{
|
|
@@ -502,8 +577,12 @@ function createBuiltInConnectionHandlers(opts) {
|
|
|
502
577
|
const pingEvent = "sys:ping";
|
|
503
578
|
const pongEvent = "sys:pong";
|
|
504
579
|
const heartbeatEnabled = heartbeat?.enabled !== false;
|
|
505
|
-
const joinPayloadSchema = buildRoomPayloadSchema(
|
|
506
|
-
|
|
580
|
+
const joinPayloadSchema = buildRoomPayloadSchema(
|
|
581
|
+
config.joinMetaMessage
|
|
582
|
+
);
|
|
583
|
+
const leavePayloadSchema = buildRoomPayloadSchema(
|
|
584
|
+
config.leaveMetaMessage
|
|
585
|
+
);
|
|
507
586
|
const pingPayloadSchema = config.pingPayload;
|
|
508
587
|
const pongPayloadSchema = config.pongPayload;
|
|
509
588
|
const sysEvents = sys;
|