@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 +9 -7
- package/dist/index.cjs +60 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +60 -49
- package/dist/index.js.map +1 -1
- package/dist/routesV3.server.batch.d.ts +27 -0
- package/dist/routesV3.server.d.ts +1 -21
- package/package.json +5 -5
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
|
|
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
|
-
|
|
188
|
+
getUser: {
|
|
189
|
+
encodedLeaf: encodeURIComponent('GET /v1/users/:userId'),
|
|
189
190
|
params: { userId: 'u_1' },
|
|
190
191
|
},
|
|
191
|
-
|
|
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
|
|
200
|
+
Response shape (same alias keys):
|
|
199
201
|
|
|
200
202
|
```ts
|
|
201
203
|
{
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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];
|