@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 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((mw) => adaptCtxMw(mw));
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
- const ctx = await config.buildCtx(req, res);
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
- let params;
258
- let query;
259
- let body;
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(config.joinMetaMessage);
506
- const leavePayloadSchema = buildRoomPayloadSchema(config.leaveMetaMessage);
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;