@emeryld/rrroutes-server 2.4.2 → 2.4.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 +38 -14
- package/dist/index.cjs +204 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +194 -39
- package/dist/index.js.map +1 -1
- package/dist/routesV3.server.d.ts +36 -9
- package/dist/sockets/socket.server.sys.d.ts +1 -1
- package/package.json +8 -5
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
keyOf,
|
|
7
7
|
lowProfileParse
|
|
8
8
|
} from "@emeryld/rrroutes-contract";
|
|
9
|
+
import multer from "multer";
|
|
9
10
|
var serverDebugEventTypes = [
|
|
10
11
|
"register",
|
|
11
12
|
"request",
|
|
@@ -64,6 +65,59 @@ var decodeJsonLikeQueryValue = (value) => {
|
|
|
64
65
|
return value;
|
|
65
66
|
};
|
|
66
67
|
var CTX_SYMBOL = /* @__PURE__ */ Symbol.for("typedLeaves.ctx");
|
|
68
|
+
var REQUEST_PAYLOAD_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
69
|
+
"typedLeaves.requestPayload"
|
|
70
|
+
);
|
|
71
|
+
function isMulterFile(value) {
|
|
72
|
+
if (!isPlainObject(value)) return false;
|
|
73
|
+
const candidate = value;
|
|
74
|
+
return typeof candidate.fieldname === "string";
|
|
75
|
+
}
|
|
76
|
+
function collectMulterFiles(req) {
|
|
77
|
+
const files = [];
|
|
78
|
+
const pushValue = (value) => {
|
|
79
|
+
if (value === void 0 || value === null) return;
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
value.forEach(pushValue);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (isMulterFile(value)) {
|
|
85
|
+
files.push(value);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (isPlainObject(value)) {
|
|
89
|
+
Object.values(value).forEach(pushValue);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
pushValue(req.files);
|
|
93
|
+
pushValue(req.file);
|
|
94
|
+
return files;
|
|
95
|
+
}
|
|
96
|
+
function resolveBodyFilesFromRequest(req, fields) {
|
|
97
|
+
if (!fields?.length) return void 0;
|
|
98
|
+
const allowedNames = new Set(fields.map((field) => field.name));
|
|
99
|
+
const collected = collectMulterFiles(req);
|
|
100
|
+
if (collected.length === 0) return void 0;
|
|
101
|
+
const result = {};
|
|
102
|
+
for (const file of collected) {
|
|
103
|
+
if (!allowedNames.has(file.fieldname)) continue;
|
|
104
|
+
const bucket = result[file.fieldname] ?? [];
|
|
105
|
+
bucket.push(file);
|
|
106
|
+
result[file.fieldname] = bucket;
|
|
107
|
+
}
|
|
108
|
+
return Object.keys(result).length ? result : void 0;
|
|
109
|
+
}
|
|
110
|
+
function getRouteRequestPayload(res) {
|
|
111
|
+
const payload = res.locals[REQUEST_PAYLOAD_SYMBOL];
|
|
112
|
+
if (payload) {
|
|
113
|
+
return payload;
|
|
114
|
+
}
|
|
115
|
+
throw new Error("Request payload was not initialized before middleware");
|
|
116
|
+
}
|
|
117
|
+
function setRouteRequestPayload(res, payload) {
|
|
118
|
+
;
|
|
119
|
+
res.locals[REQUEST_PAYLOAD_SYMBOL] = payload;
|
|
120
|
+
}
|
|
67
121
|
function getCtx(res) {
|
|
68
122
|
return res.locals[CTX_SYMBOL];
|
|
69
123
|
}
|
|
@@ -81,6 +135,26 @@ function adaptCtxMw(mw) {
|
|
|
81
135
|
}
|
|
82
136
|
};
|
|
83
137
|
}
|
|
138
|
+
function adaptRouteBeforeMw(mw) {
|
|
139
|
+
return (req, res, next) => {
|
|
140
|
+
try {
|
|
141
|
+
const result = mw({
|
|
142
|
+
req,
|
|
143
|
+
res,
|
|
144
|
+
next,
|
|
145
|
+
ctx: getCtx(res),
|
|
146
|
+
...getRouteRequestPayload(res)
|
|
147
|
+
});
|
|
148
|
+
if (result && typeof result.then === "function") {
|
|
149
|
+
return result.catch((err) => next(err));
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
} catch (err) {
|
|
153
|
+
next(err);
|
|
154
|
+
return void 0;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
84
158
|
function logHandlerDebugWithRoutesLogger(logger, event) {
|
|
85
159
|
if (!logger || event.type !== "handler") return;
|
|
86
160
|
const payload = [
|
|
@@ -95,6 +169,9 @@ function logHandlerDebugWithRoutesLogger(logger, event) {
|
|
|
95
169
|
;
|
|
96
170
|
(logger.debug ?? logger.verbose ?? logger.info ?? logger.log ?? logger.system)?.call(logger, ...payload);
|
|
97
171
|
}
|
|
172
|
+
var defaultMulterOptions = {
|
|
173
|
+
storage: multer.memoryStorage()
|
|
174
|
+
};
|
|
98
175
|
var defaultSend = (res, data) => {
|
|
99
176
|
res.json(data);
|
|
100
177
|
};
|
|
@@ -138,12 +215,22 @@ function createRRRoute(router, config) {
|
|
|
138
215
|
(mw) => adaptCtxMw(mw)
|
|
139
216
|
);
|
|
140
217
|
const registered = getRegisteredRouteStore(router);
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
218
|
+
const getMulterOptions = (fields) => {
|
|
219
|
+
if (!fields || fields.length === 0) return void 0;
|
|
220
|
+
const resolved = typeof config.multerOptions === "function" ? config.multerOptions(fields) : config.multerOptions;
|
|
221
|
+
if (resolved === false) return void 0;
|
|
222
|
+
return resolved ?? defaultMulterOptions;
|
|
223
|
+
};
|
|
224
|
+
const runMulterHandler = (handler, req, res) => {
|
|
225
|
+
return new Promise((resolve, reject) => {
|
|
226
|
+
handler(req, res, (err) => {
|
|
227
|
+
if (err) {
|
|
228
|
+
reject(err);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
resolve();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
147
234
|
};
|
|
148
235
|
function register(leaf, def) {
|
|
149
236
|
const method = leaf.method;
|
|
@@ -171,8 +258,84 @@ function createRRRoute(router, config) {
|
|
|
171
258
|
const emit = (event) => activeEmit(event, debugName);
|
|
172
259
|
const isVerboseDebug = activeDebugMode === "complete";
|
|
173
260
|
emit({ type: "register", method: methodUpper, path });
|
|
174
|
-
const routeSpecific = (def?.before ?? []).map(
|
|
175
|
-
|
|
261
|
+
const routeSpecific = (def?.before ?? []).map(
|
|
262
|
+
(mw) => adaptRouteBeforeMw(mw)
|
|
263
|
+
);
|
|
264
|
+
const resolvePayloadMw = async (req, res, next) => {
|
|
265
|
+
const requestUrl = req.originalUrl ?? path;
|
|
266
|
+
const startedAt = Date.now();
|
|
267
|
+
let params;
|
|
268
|
+
let query;
|
|
269
|
+
let body;
|
|
270
|
+
let bodyFiles;
|
|
271
|
+
try {
|
|
272
|
+
if (leaf.cfg.bodyFiles && leaf.cfg.bodyFiles.length > 0) {
|
|
273
|
+
const uploadOptions = getMulterOptions(leaf.cfg.bodyFiles);
|
|
274
|
+
if (uploadOptions) {
|
|
275
|
+
const fieldDefs = leaf.cfg.bodyFiles.map(({ name, maxCount }) => ({
|
|
276
|
+
name,
|
|
277
|
+
maxCount
|
|
278
|
+
}));
|
|
279
|
+
const uploader = multer(uploadOptions).fields(fieldDefs);
|
|
280
|
+
await runMulterHandler(uploader, req, res);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
bodyFiles = resolveBodyFilesFromRequest(req, leaf.cfg.bodyFiles);
|
|
284
|
+
params = leaf.cfg.paramsSchema ? lowProfileParse(leaf.cfg.paramsSchema, req.params) : Object.keys(req.params || {}).length ? req.params : void 0;
|
|
285
|
+
const hasQueryKeys = req.query && Object.keys(req.query || {}).length > 0;
|
|
286
|
+
const parsedQueryInput = leaf.cfg.querySchema && hasQueryKeys ? decodeJsonLikeQueryValue(req.query) : req.query;
|
|
287
|
+
if (leaf.cfg.querySchema) {
|
|
288
|
+
try {
|
|
289
|
+
query = lowProfileParse(
|
|
290
|
+
leaf.cfg.querySchema,
|
|
291
|
+
parsedQueryInput
|
|
292
|
+
);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
const parseError = new Error(
|
|
295
|
+
`Query parsing error: ${err.message ?? String(err)}`
|
|
296
|
+
);
|
|
297
|
+
parseError.raw = JSON.stringify(req.query);
|
|
298
|
+
parseError.cause = err;
|
|
299
|
+
throw parseError;
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
query = hasQueryKeys ? req.query : void 0;
|
|
303
|
+
}
|
|
304
|
+
body = leaf.cfg.bodySchema ? lowProfileParse(leaf.cfg.bodySchema, req.body) : req.body !== void 0 ? req.body : void 0;
|
|
305
|
+
} catch (err) {
|
|
306
|
+
const payloadError = {
|
|
307
|
+
params,
|
|
308
|
+
query,
|
|
309
|
+
body,
|
|
310
|
+
bodyFiles
|
|
311
|
+
};
|
|
312
|
+
emit(
|
|
313
|
+
decorateDebugEvent(
|
|
314
|
+
isVerboseDebug,
|
|
315
|
+
{
|
|
316
|
+
type: "request",
|
|
317
|
+
stage: "error",
|
|
318
|
+
method: methodUpper,
|
|
319
|
+
path,
|
|
320
|
+
url: requestUrl,
|
|
321
|
+
durationMs: Date.now() - startedAt,
|
|
322
|
+
error: err
|
|
323
|
+
},
|
|
324
|
+
isVerboseDebug ? payloadError : void 0
|
|
325
|
+
)
|
|
326
|
+
);
|
|
327
|
+
next(err);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const requestPayload = {
|
|
331
|
+
params,
|
|
332
|
+
query,
|
|
333
|
+
body,
|
|
334
|
+
bodyFiles
|
|
335
|
+
};
|
|
336
|
+
setRouteRequestPayload(res, requestPayload);
|
|
337
|
+
next();
|
|
338
|
+
};
|
|
176
339
|
const ctxMw = async (req, res, next) => {
|
|
177
340
|
const requestUrl = req.originalUrl ?? path;
|
|
178
341
|
const startedAt = Date.now();
|
|
@@ -184,7 +347,10 @@ function createRRRoute(router, config) {
|
|
|
184
347
|
url: requestUrl
|
|
185
348
|
});
|
|
186
349
|
try {
|
|
187
|
-
const ctx = await config.buildCtx(
|
|
350
|
+
const ctx = await config.buildCtx({
|
|
351
|
+
req,
|
|
352
|
+
res
|
|
353
|
+
});
|
|
188
354
|
res.locals[CTX_SYMBOL] = ctx;
|
|
189
355
|
emit({
|
|
190
356
|
type: "buildCtx",
|
|
@@ -209,9 +375,9 @@ function createRRRoute(router, config) {
|
|
|
209
375
|
}
|
|
210
376
|
};
|
|
211
377
|
const before = [
|
|
378
|
+
resolvePayloadMw,
|
|
212
379
|
ctxMw,
|
|
213
380
|
...globalBeforeMws,
|
|
214
|
-
...derived,
|
|
215
381
|
...routeSpecific
|
|
216
382
|
];
|
|
217
383
|
const wrapped = async (req, res, next) => {
|
|
@@ -224,9 +390,11 @@ function createRRRoute(router, config) {
|
|
|
224
390
|
path,
|
|
225
391
|
url: requestUrl
|
|
226
392
|
});
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
393
|
+
const requestPayload = getRouteRequestPayload(res);
|
|
394
|
+
const params = requestPayload.params;
|
|
395
|
+
const query = requestPayload.query;
|
|
396
|
+
const body = requestPayload.body;
|
|
397
|
+
const bodyFiles = requestPayload.bodyFiles;
|
|
230
398
|
let responsePayload;
|
|
231
399
|
let hasResponsePayload = false;
|
|
232
400
|
const downstreamNext = next;
|
|
@@ -244,26 +412,6 @@ function createRRRoute(router, config) {
|
|
|
244
412
|
}
|
|
245
413
|
};
|
|
246
414
|
try {
|
|
247
|
-
params = leaf.cfg.paramsSchema ? lowProfileParse(leaf.cfg.paramsSchema, req.params) : Object.keys(req.params || {}).length ? req.params : void 0;
|
|
248
|
-
try {
|
|
249
|
-
const parsedQueryInput = leaf.cfg.querySchema && req.query ? decodeJsonLikeQueryValue(req.query) : req.query;
|
|
250
|
-
query = leaf.cfg.querySchema ? lowProfileParse(leaf.cfg.querySchema, parsedQueryInput) : Object.keys(req.query || {}).length ? req.query : void 0;
|
|
251
|
-
} catch (e) {
|
|
252
|
-
emitWithCtx({
|
|
253
|
-
type: "request",
|
|
254
|
-
stage: "error",
|
|
255
|
-
method: methodUpper,
|
|
256
|
-
path,
|
|
257
|
-
url: requestUrl,
|
|
258
|
-
error: {
|
|
259
|
-
...e,
|
|
260
|
-
raw: JSON.stringify(req.query),
|
|
261
|
-
message: `Query parsing error: ${e.message}`
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
throw e;
|
|
265
|
-
}
|
|
266
|
-
body = leaf.cfg.bodySchema ? lowProfileParse(leaf.cfg.bodySchema, req.body) : req.body !== void 0 ? req.body : void 0;
|
|
267
415
|
const handlerStartedAt = Date.now();
|
|
268
416
|
emitWithCtx(
|
|
269
417
|
{
|
|
@@ -272,7 +420,7 @@ function createRRRoute(router, config) {
|
|
|
272
420
|
method: methodUpper,
|
|
273
421
|
path
|
|
274
422
|
},
|
|
275
|
-
isVerboseDebug ? { params, query, body } : void 0
|
|
423
|
+
isVerboseDebug ? { params, query, body, bodyFiles } : void 0
|
|
276
424
|
);
|
|
277
425
|
let result;
|
|
278
426
|
try {
|
|
@@ -283,7 +431,8 @@ function createRRRoute(router, config) {
|
|
|
283
431
|
ctx,
|
|
284
432
|
params,
|
|
285
433
|
query,
|
|
286
|
-
body
|
|
434
|
+
body,
|
|
435
|
+
bodyFiles
|
|
287
436
|
});
|
|
288
437
|
emitWithCtx(
|
|
289
438
|
{
|
|
@@ -297,6 +446,7 @@ function createRRRoute(router, config) {
|
|
|
297
446
|
params,
|
|
298
447
|
query,
|
|
299
448
|
body,
|
|
449
|
+
bodyFiles,
|
|
300
450
|
...result !== void 0 ? { output: result } : {}
|
|
301
451
|
} : void 0
|
|
302
452
|
);
|
|
@@ -310,7 +460,7 @@ function createRRRoute(router, config) {
|
|
|
310
460
|
durationMs: Date.now() - handlerStartedAt,
|
|
311
461
|
error: e
|
|
312
462
|
},
|
|
313
|
-
isVerboseDebug ? { params, query, body } : void 0
|
|
463
|
+
isVerboseDebug ? { params, query, body, bodyFiles } : void 0
|
|
314
464
|
);
|
|
315
465
|
throw e;
|
|
316
466
|
}
|
|
@@ -331,6 +481,7 @@ function createRRRoute(router, config) {
|
|
|
331
481
|
params,
|
|
332
482
|
query,
|
|
333
483
|
body,
|
|
484
|
+
bodyFiles,
|
|
334
485
|
...hasResponsePayload ? { output: responsePayload } : {}
|
|
335
486
|
} : void 0
|
|
336
487
|
);
|
|
@@ -345,7 +496,7 @@ function createRRRoute(router, config) {
|
|
|
345
496
|
durationMs: Date.now() - startedAt,
|
|
346
497
|
error: err
|
|
347
498
|
},
|
|
348
|
-
isVerboseDebug ? { params, query, body } : void 0
|
|
499
|
+
isVerboseDebug ? { params, query, body, bodyFiles } : void 0
|
|
349
500
|
);
|
|
350
501
|
next(err);
|
|
351
502
|
}
|
|
@@ -472,8 +623,12 @@ function createBuiltInConnectionHandlers(opts) {
|
|
|
472
623
|
const pingEvent = "sys:ping";
|
|
473
624
|
const pongEvent = "sys:pong";
|
|
474
625
|
const heartbeatEnabled = heartbeat?.enabled !== false;
|
|
475
|
-
const joinPayloadSchema = buildRoomPayloadSchema(
|
|
476
|
-
|
|
626
|
+
const joinPayloadSchema = buildRoomPayloadSchema(
|
|
627
|
+
config.joinMetaMessage
|
|
628
|
+
);
|
|
629
|
+
const leavePayloadSchema = buildRoomPayloadSchema(
|
|
630
|
+
config.leaveMetaMessage
|
|
631
|
+
);
|
|
477
632
|
const pingPayloadSchema = config.pingPayload;
|
|
478
633
|
const pongPayloadSchema = config.pongPayload;
|
|
479
634
|
const sysEvents = sys;
|