@emeryld/rrroutes-server 2.6.2 → 2.6.4

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
@@ -169,7 +169,7 @@ const server = createRRRoute(app, {
169
169
 
170
170
  ### Batch endpoint helper (`batchLeaf`)
171
171
 
172
- Use `batchLeaf` to register one endpoint that dispatches multiple already-registered controllers by encoded route keys.
172
+ Use `batchLeaf` to register one endpoint that dispatches multiple already-registered controllers by alias, while each entry carries its encoded route key.
173
173
 
174
174
  ```ts
175
175
  import { batchLeaf } from '@emeryld/rrroutes-server'
@@ -185,29 +185,31 @@ Client request body shape:
185
185
 
186
186
  ```ts
187
187
  {
188
- [encodeURIComponent('GET /v1/users/:userId')]: {
188
+ getUser: {
189
+ encodedLeaf: encodeURIComponent('GET /v1/users/:userId'),
189
190
  params: { userId: 'u_1' },
190
191
  },
191
- [encodeURIComponent('PATCH /v1/users/:userId')]: {
192
+ updateUser: {
193
+ encodedLeaf: encodeURIComponent('PATCH /v1/users/:userId'),
192
194
  params: { userId: 'u_1' },
193
195
  body: { name: 'Emery' },
194
196
  },
195
197
  }
196
198
  ```
197
199
 
198
- Response shape (same encoded keys):
200
+ Response shape (same alias keys):
199
201
 
200
202
  ```ts
201
203
  {
202
- [encodeURIComponent('GET /v1/users/:userId')]: { out: { ... }, meta: ... },
203
- [encodeURIComponent('PATCH /v1/users/:userId')]: { out: { ... }, meta: ... },
204
+ getUser: { out: { ... }, meta: ... },
205
+ updateUser: { out: { ... }, meta: ... },
204
206
  }
205
207
  ```
206
208
 
207
209
  Notes:
208
210
 
209
211
  - Register controllers before calling `batchLeaf(...)`; unknown keys fail at runtime.
210
- - Dispatch uses `server.invoke(...)` for each entry, so each sub-call runs per-leaf parsing, `buildCtx`, `route.before`, handler execution, and output validation.
212
+ - Dispatch uses `server.invoke(...)` in parallel for each entry, so each sub-call runs per-leaf parsing, `buildCtx`, `route.before`, handler execution, and output validation.
211
213
  - Batch dispatch does not replay the full global middleware chain of the original route registration (`sanitizer`, `preCtx`, `postCtx`, Multer).
212
214
 
213
215
  ### Middleware order and ctx usage
package/dist/index.cjs CHANGED
@@ -235,6 +235,62 @@ var createRequestSanitizationMiddleware = (options = {}) => {
235
235
  };
236
236
  var requestSanitizationMiddleware = createRequestSanitizationMiddleware();
237
237
 
238
+ // src/routesV3.server.batch.ts
239
+ var isPlainObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
240
+ function batchLeaf(server, path, registry, options) {
241
+ const method = String(options?.method ?? "post").toLowerCase();
242
+ const allowedMethods = ["get", "post", "put", "patch", "delete"];
243
+ if (!allowedMethods.includes(method)) {
244
+ throw new Error(
245
+ `Invalid batch method "${String(options?.method)}". Expected one of: ${allowedMethods.join(", ")}.`
246
+ );
247
+ }
248
+ ;
249
+ server.router[method](
250
+ path,
251
+ async (req, res, next) => {
252
+ try {
253
+ const body = req.body;
254
+ if (!isPlainObject2(body)) {
255
+ throw new Error(
256
+ "Batch request body must be a plain object keyed by branch aliases."
257
+ );
258
+ }
259
+ const entries = Object.entries(body);
260
+ const outputEntries = await Promise.all(
261
+ entries.map(async ([alias, value]) => {
262
+ const payload = isPlainObject2(value) ? value : {};
263
+ if (typeof payload.encodedLeaf !== "string" || payload.encodedLeaf.length === 0) {
264
+ throw new Error(
265
+ `Batch entry "${alias}" must include a non-empty "encodedLeaf" string.`
266
+ );
267
+ }
268
+ const decodedKey = decodeURIComponent(payload.encodedLeaf);
269
+ const leaf = registry.byKey[decodedKey];
270
+ if (!leaf) {
271
+ throw new Error(`Unknown batch route key: ${decodedKey}`);
272
+ }
273
+ const result = await server.invoke(decodedKey, {
274
+ req,
275
+ res,
276
+ next,
277
+ params: payload.params,
278
+ query: payload.query,
279
+ body: payload.body,
280
+ bodyFiles: payload.bodyFiles
281
+ });
282
+ return [alias, result];
283
+ })
284
+ );
285
+ res.json(Object.fromEntries(outputEntries));
286
+ } catch (err) {
287
+ next(err);
288
+ }
289
+ }
290
+ );
291
+ return server.router;
292
+ }
293
+
238
294
  // src/routesV3.server.ts
239
295
  var serverDebugEventTypes = [
240
296
  "register",
@@ -269,12 +325,12 @@ function createServerDebugEmitter(option) {
269
325
  }
270
326
  return disabled;
271
327
  }
272
- var isPlainObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
328
+ var isPlainObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
273
329
  var decodeJsonLikeQueryValue = (value) => {
274
330
  if (Array.isArray(value)) {
275
331
  return value.map((entry) => decodeJsonLikeQueryValue(entry));
276
332
  }
277
- if (isPlainObject2(value)) {
333
+ if (isPlainObject3(value)) {
278
334
  const next = {};
279
335
  for (const [key, child] of Object.entries(value)) {
280
336
  next[key] = decodeJsonLikeQueryValue(child);
@@ -298,7 +354,7 @@ var REQUEST_PAYLOAD_SYMBOL = /* @__PURE__ */ Symbol.for(
298
354
  "typedLeaves.requestPayload"
299
355
  );
300
356
  function isMulterFile(value) {
301
- if (!isPlainObject2(value)) return false;
357
+ if (!isPlainObject3(value)) return false;
302
358
  const candidate = value;
303
359
  return typeof candidate.fieldname === "string";
304
360
  }
@@ -314,7 +370,7 @@ function collectMulterFiles(req) {
314
370
  files.push(value);
315
371
  return;
316
372
  }
317
- if (isPlainObject2(value)) {
373
+ if (isPlainObject3(value)) {
318
374
  Object.values(value).forEach(pushValue);
319
375
  }
320
376
  };
@@ -899,51 +955,6 @@ function bindAll(router, registry, controllers, config) {
899
955
  server.registerControllers(registry, controllers);
900
956
  return router;
901
957
  }
902
- function batchLeaf(server, path, registry, options) {
903
- const method = String(options?.method ?? "post").toLowerCase();
904
- const allowedMethods = ["get", "post", "put", "patch", "delete"];
905
- if (!allowedMethods.includes(method)) {
906
- throw new Error(
907
- `Invalid batch method "${String(options?.method)}". Expected one of: ${allowedMethods.join(", ")}.`
908
- );
909
- }
910
- ;
911
- server.router[method](
912
- path,
913
- async (req, res, next) => {
914
- try {
915
- const body = req.body;
916
- if (!isPlainObject2(body)) {
917
- throw new Error(
918
- "Batch request body must be a plain object keyed by encoded route identifiers."
919
- );
920
- }
921
- const output = {};
922
- for (const [encodedKey, value] of Object.entries(body)) {
923
- const decodedKey = decodeURIComponent(encodedKey);
924
- const leaf = registry.byKey[decodedKey];
925
- if (!leaf) {
926
- throw new Error(`Unknown batch route key: ${decodedKey}`);
927
- }
928
- const payload = isPlainObject2(value) ? value : {};
929
- output[encodedKey] = await server.invoke(decodedKey, {
930
- req,
931
- res,
932
- next,
933
- params: payload.params,
934
- query: payload.query,
935
- body: payload.body,
936
- bodyFiles: payload.bodyFiles
937
- });
938
- }
939
- res.json(output);
940
- } catch (err) {
941
- next(err);
942
- }
943
- }
944
- );
945
- return server.router;
946
- }
947
958
  var defineControllers = () => (m) => m;
948
959
  function warnMissingControllers(router, registry, logger) {
949
960
  const registeredStore = router[REGISTERED_ROUTES_SYMBOL];