@morojs/moro 1.8.12 → 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 +115 -11
  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 -8
  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 +46 -8
  63. package/dist/core/routing/unified-router.js +108 -32
  64. package/dist/core/routing/unified-router.js.map +1 -1
  65. package/dist/core/utilities/package-utils.d.ts +7 -0
  66. package/dist/core/utilities/package-utils.js +2 -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 +12 -1
  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,6 +20,7 @@ import { createFrameworkLogger } from '../logger/index.js';
20
20
  import { loadNativeEngine, getNativeEngineLoadErrors, } from '../utilities/package-utils.js';
21
21
  import { parseMultipart as parseMultipartBuffer, } from './utils/multipart-parser.js';
22
22
  import { resolveCompressionSettings, compressBuffer, isCompressible, negotiateEncoding, } from './utils/compression.js';
23
+ import { bodyIsText } from './utils/body-type.js';
23
24
  import { LazyEventEmitter } from './utils/lazy-event-emitter.js';
24
25
  import { parseRawQueryString as parseUwsQueryString } from './utils/query-parser.js';
25
26
  import { PathMatcher } from '../routing/path-matcher.js';
@@ -148,6 +149,12 @@ export class EngineRequest extends LazyEventEmitter {
148
149
  method;
149
150
  path;
150
151
  body = null;
152
+ /** Undecoded request body bytes (parseBody keeps the same Buffer it parsed) */
153
+ _rawBody = undefined;
154
+ /** The request body exactly as received, before any parsing; null when empty. */
155
+ get rawBody() {
156
+ return this._rawBody ?? null;
157
+ }
151
158
  _server;
152
159
  _reqId;
153
160
  _params = undefined;
@@ -1468,6 +1475,13 @@ export class MoroEngineServer {
1468
1475
  /** @internal */ _setStaticRoute;
1469
1476
  _staticOn = false;
1470
1477
  staticRoutes = [];
1478
+ // Engine-answered parameter routes (engine >= 1.1.9, capabilities.paramRoutes):
1479
+ // one variable path segment between prefix and suffix, echoed as the body,
1480
+ // sent without calling into JS. Same guard and same re-apply story as the
1481
+ // static routes above.
1482
+ /** @internal */ _setParamRoute;
1483
+ _paramOn = false;
1484
+ paramRoutes = [];
1471
1485
  // Batched pipelined dispatch (engine >= 1.1.6, capabilities.batchDispatch):
1472
1486
  // the engine parses complete pipelined requests ahead and delivers them in
1473
1487
  // one onRequestBatch(count) call; the descriptors/control/paths buffers are
@@ -1593,6 +1607,10 @@ export class MoroEngineServer {
1593
1607
  this._setStaticRoute = surface.setStaticRoute;
1594
1608
  this._staticOn = true;
1595
1609
  }
1610
+ if (this._capabilities?.paramRoutes === true && typeof surface.setParamRoute === 'function') {
1611
+ this._setParamRoute = surface.setParamRoute;
1612
+ this._paramOn = true;
1613
+ }
1596
1614
  if (this._capabilities?.batchDispatch === true &&
1597
1615
  typeof surface.getBatchBuffers === 'function' &&
1598
1616
  typeof surface.getPath === 'function') {
@@ -1619,7 +1637,11 @@ export class MoroEngineServer {
1619
1637
  this.handle = Object.defineProperties({
1620
1638
  engine: '@morojs/engine',
1621
1639
  address: () => this.isListening
1622
- ? { address: this.host || '0.0.0.0', family: 'IPv4', port: this.port || 0 }
1640
+ ? {
1641
+ address: this.host || '::',
1642
+ family: (this.host || '::').includes(':') ? 'IPv6' : 'IPv4',
1643
+ port: this.port || 0,
1644
+ }
1623
1645
  : null,
1624
1646
  close: (cb) => {
1625
1647
  this.close(cb);
@@ -1709,13 +1731,58 @@ export class MoroEngineServer {
1709
1731
  }
1710
1732
  }
1711
1733
  // listen() after close() registers a fresh native server; give it every
1712
- // static route the old one had.
1734
+ // static and parameter route the old one had.
1713
1735
  _reapplyStaticRoutes() {
1714
1736
  const fn = this._setStaticRoute;
1715
- if (fn === undefined)
1716
- return;
1717
- for (const r of this.staticRoutes) {
1718
- fn(this.serverId, r.methodIdx, r.path, r.status, r.headersFlat, r.body);
1737
+ if (fn !== undefined) {
1738
+ for (const r of this.staticRoutes) {
1739
+ fn(this.serverId, r.methodIdx, r.path, r.status, r.headersFlat, r.body);
1740
+ }
1741
+ }
1742
+ const pfn = this._setParamRoute;
1743
+ if (pfn !== undefined) {
1744
+ for (const r of this.paramRoutes) {
1745
+ pfn(this.serverId, r.methodIdx, r.prefix, r.suffix, r.status, r.headersFlat);
1746
+ }
1747
+ }
1748
+ }
1749
+ /** Whether this engine build answers parameter routes itself. */
1750
+ get paramRoutesEnabled() {
1751
+ return this._paramOn;
1752
+ }
1753
+ /**
1754
+ * Register a route with one variable path segment, between `prefix` and
1755
+ * `suffix`, that the engine answers itself with that segment as the body:
1756
+ * "/user/" + "" for `/user/:id`. The segment goes out undecoded, as it is
1757
+ * on the wire. Returns false when this engine cannot take it (no
1758
+ * capability, compression on, or an unindexed method), so the caller keeps
1759
+ * its JS handler. Registering the same (method, prefix, suffix) again
1760
+ * replaces the earlier reply.
1761
+ */
1762
+ setParamRoute(method, prefix, suffix, status = 200, headers) {
1763
+ const fn = this._setParamRoute;
1764
+ if (!this._paramOn || fn === undefined)
1765
+ return false;
1766
+ if (this._compression.enabled)
1767
+ return false;
1768
+ const methodIdx = METHODS.indexOf(method.toUpperCase());
1769
+ if (methodIdx < 0 || methodIdx >= STATIC_ROUTE_METHODS)
1770
+ return false;
1771
+ const headersFlat = flattenHeaders(headers);
1772
+ const entry = { methodIdx, prefix, suffix, status, headersFlat };
1773
+ const at = this.paramRoutes.findIndex(r => r.methodIdx === methodIdx && r.prefix === prefix && r.suffix === suffix);
1774
+ if (at === -1)
1775
+ this.paramRoutes.push(entry);
1776
+ else
1777
+ this.paramRoutes[at] = entry;
1778
+ fn(this.serverId, methodIdx, prefix, suffix, status, headersFlat);
1779
+ return true;
1780
+ }
1781
+ /** Drop every engine-answered parameter route; the JS routes are untouched. */
1782
+ clearParamRoutes() {
1783
+ this.paramRoutes.length = 0;
1784
+ if (this._paramOn && typeof this._engine.clearParamRoutes === 'function') {
1785
+ this._engine.clearParamRoutes(this.serverId);
1719
1786
  }
1720
1787
  }
1721
1788
  // Register callbacks + limits with the native engine, returning the new
@@ -2279,6 +2346,9 @@ export class MoroEngineServer {
2279
2346
  throw error;
2280
2347
  }
2281
2348
  const buffer = Buffer.from(raw);
2349
+ // getBody() handed us a stable copy, so the raw bytes cost nothing to keep
2350
+ // (webhook signature checks need them even for JSON bodies)
2351
+ httpReq._rawBody = buffer;
2282
2352
  try {
2283
2353
  if (contentType.includes('application/json')) {
2284
2354
  httpReq.body = JSON.parse(buffer.toString('utf-8'));
@@ -2297,7 +2367,9 @@ export class MoroEngineServer {
2297
2367
  httpReq.body = parseMultipartBuffer(buffer, contentType, this.multipartLimits);
2298
2368
  }
2299
2369
  else {
2300
- httpReq.body = buffer.toString('utf-8');
2370
+ // Text decodes to a string; binary (octet-stream, images, protobuf,
2371
+ // ...) stays a Buffer - decoding it would corrupt it irreversibly.
2372
+ httpReq.body = bodyIsText(contentType) ? buffer.toString('utf-8') : buffer;
2301
2373
  }
2302
2374
  }
2303
2375
  catch (parseError) {
@@ -2447,7 +2519,10 @@ export class MoroEngineServer {
2447
2519
  this.logger.warn('Server is already listening', 'Listen');
2448
2520
  return;
2449
2521
  }
2450
- const host = typeof hostOrCallback === 'string' ? hostOrCallback : '0.0.0.0';
2522
+ // No host = every interface, dual-stack, exactly like Node's
2523
+ // server.listen(port): a client that resolves `localhost` to ::1 first
2524
+ // must not be refused. Hosts without IPv6 fall back to IPv4-any below.
2525
+ const host = typeof hostOrCallback === 'string' ? hostOrCallback : '::';
2451
2526
  const cb = typeof hostOrCallback === 'function' ? hostOrCallback : callback;
2452
2527
  try {
2453
2528
  // listen() after close(): the old native server's handles are torn
@@ -2461,11 +2536,23 @@ export class MoroEngineServer {
2461
2536
  }
2462
2537
  // Synchronous bind; throws on bind errors, returns the actual port
2463
2538
  // (meaningful when port 0 asks for an ephemeral one)
2464
- const actualPort = this._engine.listen(this.serverId, host, port);
2539
+ let boundHost = host;
2540
+ let actualPort;
2541
+ try {
2542
+ actualPort = this._engine.listen(this.serverId, host, port);
2543
+ }
2544
+ catch (error) {
2545
+ const code = error?.code;
2546
+ if (host !== '::' || (code !== 'EAFNOSUPPORT' && code !== 'EADDRNOTAVAIL'))
2547
+ throw error;
2548
+ // IPv6 not configured on this host - IPv4-any is what Node does too
2549
+ boundHost = '0.0.0.0';
2550
+ actualPort = this._engine.listen(this.serverId, boundHost, port);
2551
+ }
2465
2552
  this.port = typeof actualPort === 'number' ? actualPort : port;
2466
- this.host = host;
2553
+ this.host = boundHost;
2467
2554
  this.isListening = true;
2468
- this.logger.info(`Moro engine HTTP server listening on ${host}:${this.port}`, 'Listen');
2555
+ this.logger.info(`Moro engine HTTP server listening on ${boundHost}:${this.port}`, 'Listen');
2469
2556
  if (cb)
2470
2557
  cb();
2471
2558
  }
@@ -2539,6 +2626,23 @@ export class MoroEngineServer {
2539
2626
  callback(error instanceof Error ? error : new Error(String(error)));
2540
2627
  }
2541
2628
  }
2629
+ /**
2630
+ * Replace the TLS certificate/key for every handshake from now on
2631
+ * (engine capabilities.tlsReload). Established connections keep the context
2632
+ * they handshaked with. Validated by the engine before the swap: a bad
2633
+ * config throws and the current certificate keeps serving.
2634
+ */
2635
+ updateSsl(ssl) {
2636
+ if (!this._ssl || !this.isSsl) {
2637
+ throw new Error('updateSsl: this server was not started with TLS (server.ssl)');
2638
+ }
2639
+ if (!this._capabilities?.tlsReload || typeof this._engine.updateSsl !== 'function') {
2640
+ throw new Error('TLS reload needs @morojs/engine with capabilities.tlsReload (engine >= 1.1.8)');
2641
+ }
2642
+ this._engine.updateSsl(this.serverId, ssl);
2643
+ // A later close() -> listen() cycle re-registers with the new material
2644
+ this._ssl = ssl;
2645
+ }
2542
2646
  getServer() {
2543
2647
  // Stable observability handle (listening/address) - there is no app object
2544
2648
  return this.handle;