@morojs/moro 1.8.11 → 1.8.13
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 +4 -2
- package/dist/core/cluster/thread-cluster.d.ts +25 -0
- package/dist/core/cluster/thread-cluster.js +95 -3
- package/dist/core/cluster/thread-cluster.js.map +1 -1
- package/dist/core/config/config-validator.js +15 -0
- package/dist/core/config/config-validator.js.map +1 -1
- package/dist/core/config/index.js +19 -2
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/framework.d.ts +26 -0
- package/dist/core/framework.js +195 -6
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/http-server.d.ts +17 -0
- package/dist/core/http/http-server.js +21 -1
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/http2-server.d.ts +13 -0
- package/dist/core/http/http2-server.js +11 -0
- package/dist/core/http/http2-server.js.map +1 -1
- package/dist/core/http/moro-engine-server.d.ts +28 -0
- package/dist/core/http/moro-engine-server.js +118 -12
- package/dist/core/http/moro-engine-server.js.map +1 -1
- package/dist/core/http/utils/body-type.d.ts +12 -0
- package/dist/core/http/utils/body-type.js +70 -0
- package/dist/core/http/utils/body-type.js.map +1 -0
- package/dist/core/http/utils/ssl-config.d.ts +11 -0
- package/dist/core/http/utils/ssl-config.js +3 -0
- package/dist/core/http/utils/ssl-config.js.map +1 -1
- package/dist/core/http/utils/uws-worker-clustering.js +5 -4
- package/dist/core/http/utils/uws-worker-clustering.js.map +1 -1
- package/dist/core/http/uws-http-server.d.ts +3 -0
- package/dist/core/http/uws-http-server.js +22 -1
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/middleware/built-in/body-parsers/index.d.ts +18 -0
- package/dist/core/middleware/built-in/body-parsers/index.js +41 -0
- package/dist/core/middleware/built-in/body-parsers/index.js.map +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.d.ts +32 -8
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js +117 -25
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -1
- package/dist/core/middleware/built-in/compression/core.js +5 -0
- package/dist/core/middleware/built-in/compression/core.js.map +1 -1
- package/dist/core/middleware/built-in/session/core.d.ts +1 -0
- package/dist/core/middleware/built-in/session/core.js.map +1 -1
- package/dist/core/middleware/built-in/static/core.d.ts +18 -0
- package/dist/core/middleware/built-in/static/core.js +128 -7
- package/dist/core/middleware/built-in/static/core.js.map +1 -1
- package/dist/core/networking/adapters/engine-adapter.d.ts +2 -2
- package/dist/core/networking/adapters/engine-adapter.js +40 -1
- package/dist/core/networking/adapters/engine-adapter.js.map +1 -1
- package/dist/core/networking/adapters/socketio-adapter.d.ts +2 -2
- package/dist/core/networking/adapters/socketio-adapter.js +10 -1
- package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
- package/dist/core/networking/adapters/uws-adapter.d.ts +2 -2
- package/dist/core/networking/adapters/uws-adapter.js +54 -3
- package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
- package/dist/core/networking/adapters/ws-adapter.d.ts +9 -2
- package/dist/core/networking/adapters/ws-adapter.js +97 -12
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
- package/dist/core/networking/websocket-adapter.d.ts +19 -1
- package/dist/core/networking/websocket-adapter.js.map +1 -1
- package/dist/core/routing/index.d.ts +12 -7
- package/dist/core/routing/index.js +7 -2
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/routing/unified-router.d.ts +48 -9
- package/dist/core/routing/unified-router.js +112 -34
- package/dist/core/routing/unified-router.js.map +1 -1
- package/dist/core/utilities/package-utils.d.ts +11 -0
- package/dist/core/utilities/package-utils.js +3 -0
- package/dist/core/utilities/package-utils.js.map +1 -1
- package/dist/core/workers/worker-manager.js +2 -2
- package/dist/core/workers/worker-manager.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/moro.d.ts +29 -18
- package/dist/moro.js +63 -7
- package/dist/moro.js.map +1 -1
- package/dist/types/cache.d.ts +2 -0
- package/dist/types/cache.js.map +1 -1
- package/dist/types/config.d.ts +9 -0
- package/dist/types/config.js.map +1 -1
- package/dist/types/http.d.ts +6 -0
- package/dist/types/http.js.map +1 -1
- package/dist/types/session.d.ts +1 -0
- package/dist/types/session.js.map +1 -1
- package/package.json +1 -1
|
@@ -20,7 +20,8 @@ export interface MiddlewarePhases {
|
|
|
20
20
|
export interface RouteSchema {
|
|
21
21
|
method: HttpMethod;
|
|
22
22
|
path: string;
|
|
23
|
-
|
|
23
|
+
/** A function, or the response body itself (see StaticBody) */
|
|
24
|
+
handler: RouteHandler | StaticBody | ParamEcho;
|
|
24
25
|
validation?: ValidationConfig;
|
|
25
26
|
auth?: AuthConfig;
|
|
26
27
|
rateLimit?: RateLimitConfig;
|
|
@@ -33,6 +34,18 @@ export interface RouteSchema {
|
|
|
33
34
|
* calling into JS; every other server runs `handler`, which sends the
|
|
34
35
|
* same bytes. */
|
|
35
36
|
static?: StaticResponse;
|
|
37
|
+
/** The engine shape of a param(name) handler on a bare route with exactly
|
|
38
|
+
* that one parameter: the path segment between prefix and suffix is the
|
|
39
|
+
* body. MoroEngineServer on @morojs/engine >= 1.1.9 (paramRoutes) answers
|
|
40
|
+
* it without calling into JS; every other server runs `handler`. */
|
|
41
|
+
paramEcho?: ParamRouteReply;
|
|
42
|
+
}
|
|
43
|
+
export interface ParamRouteReply {
|
|
44
|
+
name: string;
|
|
45
|
+
prefix: string;
|
|
46
|
+
suffix: string;
|
|
47
|
+
status: number;
|
|
48
|
+
headers: string[] | null;
|
|
36
49
|
}
|
|
37
50
|
/** The reply a literal handler sends, in the engine's shape. */
|
|
38
51
|
export interface StaticResponse {
|
|
@@ -43,14 +56,40 @@ export interface StaticResponse {
|
|
|
43
56
|
}
|
|
44
57
|
/** What .handler() accepts in place of a function: the response body itself. */
|
|
45
58
|
export type StaticBody = string | Buffer;
|
|
59
|
+
export declare const PARAM_ECHO: unique symbol;
|
|
60
|
+
/** The marker param(name) produces: "answer with this path parameter as the
|
|
61
|
+
* body". `.handler(param('id'))` on `/user/:id` answers `GET /user/42` with
|
|
62
|
+
* `42`, as `res.end(req.params.id)` would - and on Moro's native engine, on
|
|
63
|
+
* a route with nothing else configured, without entering JS at all. */
|
|
64
|
+
export interface ParamEcho {
|
|
65
|
+
readonly [PARAM_ECHO]: true;
|
|
66
|
+
readonly name: string;
|
|
67
|
+
}
|
|
68
|
+
export declare function param(name: string): ParamEcho;
|
|
69
|
+
export declare function isParamEcho(value: unknown): value is ParamEcho;
|
|
70
|
+
/**
|
|
71
|
+
* param(name) in place of a handler: the route answers with that path
|
|
72
|
+
* parameter as the body, as res.end(req.params[name]) would. When `:name` is
|
|
73
|
+
* a whole segment of the path, the only parameter in it, and nothing else is
|
|
74
|
+
* configured, the schema also carries `paramEcho` - prefix and suffix around
|
|
75
|
+
* the segment - so a server that can answer it natively does so without
|
|
76
|
+
* calling into JS. The engine echoes the segment as it is on the wire,
|
|
77
|
+
* undecoded (as uWS does); a parameter that needs decoding stays a function
|
|
78
|
+
* handler.
|
|
79
|
+
*/
|
|
80
|
+
export declare function compileParamRoute(schema: Partial<RouteSchema>, echo: ParamEcho): RouteSchema;
|
|
46
81
|
/**
|
|
47
82
|
* A literal body in place of a handler: the route answers with exactly that
|
|
48
|
-
* body
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
83
|
+
* body. A non-empty body goes out as res.send(body) would (status 200, the
|
|
84
|
+
* implied content-type). An empty body has nothing a content-type could
|
|
85
|
+
* describe, so it goes out as res.end() would: status and Content-Length: 0,
|
|
86
|
+
* no header block - 38 bytes less per response, and byte-identical to the
|
|
87
|
+
* engine's own static reply with no headers. When nothing else is configured
|
|
88
|
+
* on the route - no auth, validation, rate limit, cache or middleware, and a
|
|
89
|
+
* literal path - the schema also carries `static`, so a server that can
|
|
90
|
+
* answer it natively does so without calling into JS. Anything configured
|
|
91
|
+
* keeps the route on the normal pipeline, where the handler sends the same
|
|
92
|
+
* bytes after that configuration has run.
|
|
54
93
|
*/
|
|
55
94
|
export declare function compileStaticRoute(schema: Partial<RouteSchema>, body: StaticBody): RouteSchema;
|
|
56
95
|
export declare class RouteBuilder {
|
|
@@ -71,7 +110,7 @@ export declare class RouteBuilder {
|
|
|
71
110
|
use(...middleware: Middleware[]): this;
|
|
72
111
|
describe(description: string): this;
|
|
73
112
|
tag(...tags: string[]): this;
|
|
74
|
-
handler<T>(handler: RouteHandler<T> | StaticBody): void;
|
|
113
|
+
handler<T>(handler: RouteHandler<T> | StaticBody | ParamEcho): void;
|
|
75
114
|
}
|
|
76
115
|
export declare class UnifiedRouter {
|
|
77
116
|
private static instance;
|
|
@@ -129,7 +168,7 @@ export declare class UnifiedRouter {
|
|
|
129
168
|
/**
|
|
130
169
|
* Direct API (for backward compatibility)
|
|
131
170
|
*/
|
|
132
|
-
addRoute(method: HttpMethod, path: string, handler: RouteHandler, middleware?: Middleware[]): void;
|
|
171
|
+
addRoute(method: HttpMethod, path: string, handler: RouteHandler | StaticBody, middleware?: Middleware[]): void;
|
|
133
172
|
/**
|
|
134
173
|
* Find a matching route for the request - HYBRID OPTIMIZED
|
|
135
174
|
* Returns boolean (sync) for fast-path routes, Promise<boolean> for others
|
|
@@ -13,6 +13,74 @@ const logger = createFrameworkLogger('UnifiedRouter');
|
|
|
13
13
|
const rateLimitCore = new RateLimitCore();
|
|
14
14
|
const cacheCore = new CacheCore();
|
|
15
15
|
const validationCore = new ValidationCore();
|
|
16
|
+
export const PARAM_ECHO = Symbol.for('moro.paramEcho');
|
|
17
|
+
export function param(name) {
|
|
18
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
19
|
+
throw new Error('param(name) needs the parameter name, e.g. param("id") for "/user/:id"');
|
|
20
|
+
}
|
|
21
|
+
return Object.freeze({ [PARAM_ECHO]: true, name });
|
|
22
|
+
}
|
|
23
|
+
export function isParamEcho(value) {
|
|
24
|
+
return (typeof value === 'object' &&
|
|
25
|
+
value !== null &&
|
|
26
|
+
value[PARAM_ECHO] === true);
|
|
27
|
+
}
|
|
28
|
+
// Nothing configured that a reply answered inside the engine would skip:
|
|
29
|
+
// no auth, validation, rate limit, cache or middleware. (Static replies also
|
|
30
|
+
// need a literal path; parameter echoes need exactly one parameter.)
|
|
31
|
+
function isBareRoute(schema) {
|
|
32
|
+
const mw = schema.middleware;
|
|
33
|
+
const hasMiddleware = Array.isArray(mw)
|
|
34
|
+
? mw.length > 0
|
|
35
|
+
: mw !== undefined &&
|
|
36
|
+
Object.keys(mw).some(phase => {
|
|
37
|
+
const list = mw[phase];
|
|
38
|
+
return Array.isArray(list) && list.length > 0;
|
|
39
|
+
});
|
|
40
|
+
return !schema.auth && !schema.validation && !schema.rateLimit && !schema.cache && !hasMiddleware;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* param(name) in place of a handler: the route answers with that path
|
|
44
|
+
* parameter as the body, as res.end(req.params[name]) would. When `:name` is
|
|
45
|
+
* a whole segment of the path, the only parameter in it, and nothing else is
|
|
46
|
+
* configured, the schema also carries `paramEcho` - prefix and suffix around
|
|
47
|
+
* the segment - so a server that can answer it natively does so without
|
|
48
|
+
* calling into JS. The engine echoes the segment as it is on the wire,
|
|
49
|
+
* undecoded (as uWS does); a parameter that needs decoding stays a function
|
|
50
|
+
* handler.
|
|
51
|
+
*/
|
|
52
|
+
export function compileParamRoute(schema, echo) {
|
|
53
|
+
const name = echo.name;
|
|
54
|
+
schema.handler = (req, res) => {
|
|
55
|
+
const value = req.params?.[name];
|
|
56
|
+
res.end(value === undefined ? undefined : String(value));
|
|
57
|
+
};
|
|
58
|
+
const path = schema.path ?? '';
|
|
59
|
+
const segments = path.split('/');
|
|
60
|
+
const marker = `:${name}`;
|
|
61
|
+
const at = segments.indexOf(marker);
|
|
62
|
+
if (at === -1) {
|
|
63
|
+
// The router names a parameter after its whole segment, so ":name.json"
|
|
64
|
+
// is a parameter called "name.json" and param('name') would never see it.
|
|
65
|
+
// (":names" is a different parameter, not a misuse; it gets no echo.)
|
|
66
|
+
const partial = segments.some(seg => seg.startsWith(marker) && /^[^A-Za-z0-9_$]/.test(seg.slice(marker.length)));
|
|
67
|
+
if (partial) {
|
|
68
|
+
throw new Error(`param('${name}') needs '${marker}' as a whole segment of the route path; ` +
|
|
69
|
+
`'${path}' has it inside a segment`);
|
|
70
|
+
}
|
|
71
|
+
return schema;
|
|
72
|
+
}
|
|
73
|
+
const rest = segments.slice(at + 1);
|
|
74
|
+
const plainShape = !segments.some((seg, i) => i !== at && seg.startsWith(':')) &&
|
|
75
|
+
!path.includes('*') &&
|
|
76
|
+
!rest.includes(''); // no trailing or doubled slash after the parameter
|
|
77
|
+
if (plainShape && isBareRoute(schema)) {
|
|
78
|
+
const prefix = segments.slice(0, at).join('/') + '/';
|
|
79
|
+
const suffix = rest.length > 0 ? '/' + rest.join('/') : '';
|
|
80
|
+
schema.paramEcho = { name, prefix, suffix, status: 200, headers: null };
|
|
81
|
+
}
|
|
82
|
+
return schema;
|
|
83
|
+
}
|
|
16
84
|
// The content-type res.send() implies for a body it was given no header for:
|
|
17
85
|
// the same three cases MoroEngineServer's send() picks a template by, spelled
|
|
18
86
|
// out here so an engine-answered reply carries the bytes send() would have.
|
|
@@ -26,35 +94,34 @@ function impliedContentType(body) {
|
|
|
26
94
|
}
|
|
27
95
|
/**
|
|
28
96
|
* A literal body in place of a handler: the route answers with exactly that
|
|
29
|
-
* body
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
97
|
+
* body. A non-empty body goes out as res.send(body) would (status 200, the
|
|
98
|
+
* implied content-type). An empty body has nothing a content-type could
|
|
99
|
+
* describe, so it goes out as res.end() would: status and Content-Length: 0,
|
|
100
|
+
* no header block - 38 bytes less per response, and byte-identical to the
|
|
101
|
+
* engine's own static reply with no headers. When nothing else is configured
|
|
102
|
+
* on the route - no auth, validation, rate limit, cache or middleware, and a
|
|
103
|
+
* literal path - the schema also carries `static`, so a server that can
|
|
104
|
+
* answer it natively does so without calling into JS. Anything configured
|
|
105
|
+
* keeps the route on the normal pipeline, where the handler sends the same
|
|
106
|
+
* bytes after that configuration has run.
|
|
35
107
|
*/
|
|
36
108
|
export function compileStaticRoute(schema, body) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
109
|
+
const empty = body.length === 0;
|
|
110
|
+
schema.handler = empty
|
|
111
|
+
? (_req, res) => {
|
|
112
|
+
res.end();
|
|
113
|
+
}
|
|
114
|
+
: (_req, res) => {
|
|
115
|
+
res.send(body);
|
|
116
|
+
};
|
|
40
117
|
const path = schema.path ?? '';
|
|
41
|
-
const
|
|
42
|
-
const hasMiddleware = Array.isArray(mw)
|
|
43
|
-
? mw.length > 0
|
|
44
|
-
: mw !== undefined &&
|
|
45
|
-
Object.keys(mw).some(phase => {
|
|
46
|
-
const list = mw[phase];
|
|
47
|
-
return Array.isArray(list) && list.length > 0;
|
|
48
|
-
});
|
|
49
|
-
const bare = !path.includes(':') &&
|
|
50
|
-
!path.includes('*') &&
|
|
51
|
-
!schema.auth &&
|
|
52
|
-
!schema.validation &&
|
|
53
|
-
!schema.rateLimit &&
|
|
54
|
-
!schema.cache &&
|
|
55
|
-
!hasMiddleware;
|
|
118
|
+
const bare = !path.includes(':') && !path.includes('*') && isBareRoute(schema);
|
|
56
119
|
if (bare) {
|
|
57
|
-
schema.static = {
|
|
120
|
+
schema.static = {
|
|
121
|
+
status: 200,
|
|
122
|
+
headers: empty ? null : ['content-type', impliedContentType(body)],
|
|
123
|
+
body,
|
|
124
|
+
};
|
|
58
125
|
}
|
|
59
126
|
return schema;
|
|
60
127
|
}
|
|
@@ -188,8 +255,8 @@ export class RouteBuilder {
|
|
|
188
255
|
}
|
|
189
256
|
return this;
|
|
190
257
|
}
|
|
191
|
-
// Terminal method: a function,
|
|
192
|
-
// compileStaticRoute
|
|
258
|
+
// Terminal method: a function, the response body itself (see
|
|
259
|
+
// compileStaticRoute), or param(name) (see compileParamRoute).
|
|
193
260
|
handler(handler) {
|
|
194
261
|
if (typeof handler === 'function') {
|
|
195
262
|
// Avoid spread operator - add handler directly
|
|
@@ -201,7 +268,11 @@ export class RouteBuilder {
|
|
|
201
268
|
this.router.registerRoute(compileStaticRoute(this.schema, handler));
|
|
202
269
|
return;
|
|
203
270
|
}
|
|
204
|
-
|
|
271
|
+
if (isParamEcho(handler)) {
|
|
272
|
+
this.router.registerRoute(compileParamRoute(this.schema, handler));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
throw new Error('Handler is required: a function, the response body as a string or Buffer, or param(name)');
|
|
205
276
|
}
|
|
206
277
|
}
|
|
207
278
|
// ===== Unified Router =====
|
|
@@ -334,13 +405,20 @@ export class UnifiedRouter {
|
|
|
334
405
|
// - so this is where the body becomes a handler and, on a bare route, a
|
|
335
406
|
// reply the native engine can answer itself.
|
|
336
407
|
if (typeof schema.handler !== 'function') {
|
|
337
|
-
const
|
|
338
|
-
if (
|
|
408
|
+
const given = schema.handler;
|
|
409
|
+
if (isParamEcho(given)) {
|
|
410
|
+
compileParamRoute(schema, given);
|
|
411
|
+
}
|
|
412
|
+
else if (typeof given === 'string' || Buffer.isBuffer(given)) {
|
|
413
|
+
compileStaticRoute(schema, given);
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
339
416
|
throw new Error(`Handler for ${schema.method} ${schema.path} must be a function, ` +
|
|
340
|
-
'
|
|
417
|
+
'the response body as a string or Buffer, or param(name)');
|
|
341
418
|
}
|
|
342
|
-
compileStaticRoute(schema, body);
|
|
343
419
|
}
|
|
420
|
+
// From here on the handler is a function, whichever form was registered.
|
|
421
|
+
const registered = schema;
|
|
344
422
|
// OPTIMIZATION: Skip PathMatcher.compile for better performance
|
|
345
423
|
// Determine if static route (optimized check)
|
|
346
424
|
const pathLen = schema.path.length;
|
|
@@ -359,9 +437,9 @@ export class UnifiedRouter {
|
|
|
359
437
|
// OPTIMIZATION: Lazy auth middleware creation (only create when needed, not during registration)
|
|
360
438
|
// This speeds up route registration significantly
|
|
361
439
|
const route = {
|
|
362
|
-
schema,
|
|
440
|
+
schema: registered,
|
|
363
441
|
compiledPath: null, // Will be set lazily if needed
|
|
364
|
-
handler:
|
|
442
|
+
handler: registered.handler,
|
|
365
443
|
isFastPath,
|
|
366
444
|
executionOrder,
|
|
367
445
|
rateLimitConfig: schema.rateLimit,
|