@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.
Files changed (84) hide show
  1. package/README.md +4 -2
  2. package/dist/core/cluster/thread-cluster.d.ts +25 -0
  3. package/dist/core/cluster/thread-cluster.js +95 -3
  4. package/dist/core/cluster/thread-cluster.js.map +1 -1
  5. package/dist/core/config/config-validator.js +15 -0
  6. package/dist/core/config/config-validator.js.map +1 -1
  7. package/dist/core/config/index.js +19 -2
  8. package/dist/core/config/index.js.map +1 -1
  9. package/dist/core/framework.d.ts +26 -0
  10. package/dist/core/framework.js +195 -6
  11. package/dist/core/framework.js.map +1 -1
  12. package/dist/core/http/http-server.d.ts +17 -0
  13. package/dist/core/http/http-server.js +21 -1
  14. package/dist/core/http/http-server.js.map +1 -1
  15. package/dist/core/http/http2-server.d.ts +13 -0
  16. package/dist/core/http/http2-server.js +11 -0
  17. package/dist/core/http/http2-server.js.map +1 -1
  18. package/dist/core/http/moro-engine-server.d.ts +28 -0
  19. package/dist/core/http/moro-engine-server.js +118 -12
  20. package/dist/core/http/moro-engine-server.js.map +1 -1
  21. package/dist/core/http/utils/body-type.d.ts +12 -0
  22. package/dist/core/http/utils/body-type.js +70 -0
  23. package/dist/core/http/utils/body-type.js.map +1 -0
  24. package/dist/core/http/utils/ssl-config.d.ts +11 -0
  25. package/dist/core/http/utils/ssl-config.js +3 -0
  26. package/dist/core/http/utils/ssl-config.js.map +1 -1
  27. package/dist/core/http/utils/uws-worker-clustering.js +5 -4
  28. package/dist/core/http/utils/uws-worker-clustering.js.map +1 -1
  29. package/dist/core/http/uws-http-server.d.ts +3 -0
  30. package/dist/core/http/uws-http-server.js +22 -1
  31. package/dist/core/http/uws-http-server.js.map +1 -1
  32. package/dist/core/middleware/built-in/body-parsers/index.d.ts +18 -0
  33. package/dist/core/middleware/built-in/body-parsers/index.js +41 -0
  34. package/dist/core/middleware/built-in/body-parsers/index.js.map +1 -1
  35. package/dist/core/middleware/built-in/cache/adapters/cache/redis.d.ts +32 -8
  36. package/dist/core/middleware/built-in/cache/adapters/cache/redis.js +117 -25
  37. package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -1
  38. package/dist/core/middleware/built-in/compression/core.js +5 -0
  39. package/dist/core/middleware/built-in/compression/core.js.map +1 -1
  40. package/dist/core/middleware/built-in/session/core.d.ts +1 -0
  41. package/dist/core/middleware/built-in/session/core.js.map +1 -1
  42. package/dist/core/middleware/built-in/static/core.d.ts +18 -0
  43. package/dist/core/middleware/built-in/static/core.js +128 -7
  44. package/dist/core/middleware/built-in/static/core.js.map +1 -1
  45. package/dist/core/networking/adapters/engine-adapter.d.ts +2 -2
  46. package/dist/core/networking/adapters/engine-adapter.js +40 -1
  47. package/dist/core/networking/adapters/engine-adapter.js.map +1 -1
  48. package/dist/core/networking/adapters/socketio-adapter.d.ts +2 -2
  49. package/dist/core/networking/adapters/socketio-adapter.js +10 -1
  50. package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
  51. package/dist/core/networking/adapters/uws-adapter.d.ts +2 -2
  52. package/dist/core/networking/adapters/uws-adapter.js +54 -3
  53. package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
  54. package/dist/core/networking/adapters/ws-adapter.d.ts +9 -2
  55. package/dist/core/networking/adapters/ws-adapter.js +97 -12
  56. package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
  57. package/dist/core/networking/websocket-adapter.d.ts +19 -1
  58. package/dist/core/networking/websocket-adapter.js.map +1 -1
  59. package/dist/core/routing/index.d.ts +12 -7
  60. package/dist/core/routing/index.js +7 -2
  61. package/dist/core/routing/index.js.map +1 -1
  62. package/dist/core/routing/unified-router.d.ts +48 -9
  63. package/dist/core/routing/unified-router.js +112 -34
  64. package/dist/core/routing/unified-router.js.map +1 -1
  65. package/dist/core/utilities/package-utils.d.ts +11 -0
  66. package/dist/core/utilities/package-utils.js +3 -0
  67. package/dist/core/utilities/package-utils.js.map +1 -1
  68. package/dist/core/workers/worker-manager.js +2 -2
  69. package/dist/core/workers/worker-manager.js.map +1 -1
  70. package/dist/index.d.ts +4 -3
  71. package/dist/index.js +2 -1
  72. package/dist/index.js.map +1 -1
  73. package/dist/moro.d.ts +29 -18
  74. package/dist/moro.js +63 -7
  75. package/dist/moro.js.map +1 -1
  76. package/dist/types/cache.d.ts +2 -0
  77. package/dist/types/cache.js.map +1 -1
  78. package/dist/types/config.d.ts +9 -0
  79. package/dist/types/config.js.map +1 -1
  80. package/dist/types/http.d.ts +6 -0
  81. package/dist/types/http.js.map +1 -1
  82. package/dist/types/session.d.ts +1 -0
  83. package/dist/types/session.js.map +1 -1
  84. 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
- handler: RouteHandler;
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, as res.send(body) would (status 200, the implied content-type). When
49
- * nothing else is configured on the route - no auth, validation, rate limit,
50
- * cache or middleware, and a literal path - the schema also carries `static`,
51
- * so a server that can answer it natively does so without calling into JS.
52
- * Anything configured keeps the route on the normal pipeline, where the
53
- * handler sends the same bytes after that configuration has run.
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, as res.send(body) would (status 200, the implied content-type). When
30
- * nothing else is configured on the route - no auth, validation, rate limit,
31
- * cache or middleware, and a literal path - the schema also carries `static`,
32
- * so a server that can answer it natively does so without calling into JS.
33
- * Anything configured keeps the route on the normal pipeline, where the
34
- * handler sends the same bytes after that configuration has run.
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
- schema.handler = (_req, res) => {
38
- res.send(body);
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 mw = schema.middleware;
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 = { status: 200, headers: ['content-type', impliedContentType(body)], body };
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, or the response body itself (see
192
- // compileStaticRoute for what a literal body means).
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
- throw new Error('Handler is required: a function, or the response body as a string or Buffer');
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 body = schema.handler;
338
- if (typeof body !== 'string' && !Buffer.isBuffer(body)) {
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
- 'or the response body as a string or Buffer');
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: schema.handler,
442
+ handler: registered.handler,
365
443
  isFastPath,
366
444
  executionOrder,
367
445
  rateLimitConfig: schema.rateLimit,