@morojs/moro 1.8.12 → 1.8.14
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 +20 -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/config/schema.js +1 -0
- package/dist/core/config/schema.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 +19 -0
- package/dist/core/http/http-server.js +75 -1
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/http2-server.d.ts +15 -0
- package/dist/core/http/http2-server.js +36 -3
- 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 +138 -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/compression.d.ts +21 -0
- package/dist/core/http/utils/compression.js +53 -7
- package/dist/core/http/utils/compression.js.map +1 -1
- 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 +47 -2
- 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.d.ts +7 -0
- package/dist/core/middleware/built-in/compression/core.js +32 -1
- 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 -8
- 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 +46 -8
- package/dist/core/routing/unified-router.js +108 -32
- package/dist/core/routing/unified-router.js.map +1 -1
- package/dist/core/utilities/package-utils.d.ts +8 -1
- package/dist/core/utilities/package-utils.js +2 -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 +12 -1
- 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 +16 -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
|
@@ -19,7 +19,8 @@ import { randomUUID } from 'crypto';
|
|
|
19
19
|
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
|
-
import { resolveCompressionSettings, compressBuffer, isCompressible, negotiateEncoding, } from './utils/compression.js';
|
|
22
|
+
import { resolveCompressionSettings, compressBuffer, compressBufferSync, shouldInline, DEFAULT_MAX_INLINE_BYTES, 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;
|
|
@@ -743,6 +750,27 @@ export class EngineResponse extends LazyEventEmitter {
|
|
|
743
750
|
this._emitDone();
|
|
744
751
|
return;
|
|
745
752
|
}
|
|
753
|
+
// A small body is compressed on this thread: cheaper than the threadpool
|
|
754
|
+
// round trip, and the reply goes out in the same native call an
|
|
755
|
+
// uncompressed one would. A failure sends the body as it is.
|
|
756
|
+
if (shouldInline(bytes, s)) {
|
|
757
|
+
let compressed;
|
|
758
|
+
try {
|
|
759
|
+
compressed = compressBufferSync(body, encoding, s.level);
|
|
760
|
+
}
|
|
761
|
+
catch {
|
|
762
|
+
compressed = undefined;
|
|
763
|
+
}
|
|
764
|
+
if (compressed !== undefined) {
|
|
765
|
+
this.responseHeaders['content-encoding'] = encoding;
|
|
766
|
+
const vary = this.responseHeaders['vary'];
|
|
767
|
+
this.responseHeaders['vary'] = vary ? `${vary}, Accept-Encoding` : 'Accept-Encoding';
|
|
768
|
+
}
|
|
769
|
+
this._server._respond(this._reqId, this.statusCode, this._headersFlat(), compressed ?? body);
|
|
770
|
+
this.headersSent = true;
|
|
771
|
+
this._emitDone();
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
746
774
|
// Async path: compress, then respond (guarding against an abort that
|
|
747
775
|
// raced the await - the reqId would be invalid).
|
|
748
776
|
void compressBuffer(body, encoding, s.level)
|
|
@@ -1468,6 +1496,13 @@ export class MoroEngineServer {
|
|
|
1468
1496
|
/** @internal */ _setStaticRoute;
|
|
1469
1497
|
_staticOn = false;
|
|
1470
1498
|
staticRoutes = [];
|
|
1499
|
+
// Engine-answered parameter routes (engine >= 1.1.9, capabilities.paramRoutes):
|
|
1500
|
+
// one variable path segment between prefix and suffix, echoed as the body,
|
|
1501
|
+
// sent without calling into JS. Same guard and same re-apply story as the
|
|
1502
|
+
// static routes above.
|
|
1503
|
+
/** @internal */ _setParamRoute;
|
|
1504
|
+
_paramOn = false;
|
|
1505
|
+
paramRoutes = [];
|
|
1471
1506
|
// Batched pipelined dispatch (engine >= 1.1.6, capabilities.batchDispatch):
|
|
1472
1507
|
// the engine parses complete pipelined requests ahead and delivers them in
|
|
1473
1508
|
// one onRequestBatch(count) call; the descriptors/control/paths buffers are
|
|
@@ -1520,6 +1555,7 @@ export class MoroEngineServer {
|
|
|
1520
1555
|
enabled: false,
|
|
1521
1556
|
threshold: 1024,
|
|
1522
1557
|
level: 6,
|
|
1558
|
+
maxInlineBytes: DEFAULT_MAX_INLINE_BYTES,
|
|
1523
1559
|
encodings: ['br', 'gzip', 'deflate'],
|
|
1524
1560
|
};
|
|
1525
1561
|
// Set by close(); listen() then registers a fresh native server, because the
|
|
@@ -1593,6 +1629,10 @@ export class MoroEngineServer {
|
|
|
1593
1629
|
this._setStaticRoute = surface.setStaticRoute;
|
|
1594
1630
|
this._staticOn = true;
|
|
1595
1631
|
}
|
|
1632
|
+
if (this._capabilities?.paramRoutes === true && typeof surface.setParamRoute === 'function') {
|
|
1633
|
+
this._setParamRoute = surface.setParamRoute;
|
|
1634
|
+
this._paramOn = true;
|
|
1635
|
+
}
|
|
1596
1636
|
if (this._capabilities?.batchDispatch === true &&
|
|
1597
1637
|
typeof surface.getBatchBuffers === 'function' &&
|
|
1598
1638
|
typeof surface.getPath === 'function') {
|
|
@@ -1619,7 +1659,11 @@ export class MoroEngineServer {
|
|
|
1619
1659
|
this.handle = Object.defineProperties({
|
|
1620
1660
|
engine: '@morojs/engine',
|
|
1621
1661
|
address: () => this.isListening
|
|
1622
|
-
? {
|
|
1662
|
+
? {
|
|
1663
|
+
address: this.host || '::',
|
|
1664
|
+
family: (this.host || '::').includes(':') ? 'IPv6' : 'IPv4',
|
|
1665
|
+
port: this.port || 0,
|
|
1666
|
+
}
|
|
1623
1667
|
: null,
|
|
1624
1668
|
close: (cb) => {
|
|
1625
1669
|
this.close(cb);
|
|
@@ -1709,13 +1753,58 @@ export class MoroEngineServer {
|
|
|
1709
1753
|
}
|
|
1710
1754
|
}
|
|
1711
1755
|
// listen() after close() registers a fresh native server; give it every
|
|
1712
|
-
// static route the old one had.
|
|
1756
|
+
// static and parameter route the old one had.
|
|
1713
1757
|
_reapplyStaticRoutes() {
|
|
1714
1758
|
const fn = this._setStaticRoute;
|
|
1715
|
-
if (fn
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1759
|
+
if (fn !== undefined) {
|
|
1760
|
+
for (const r of this.staticRoutes) {
|
|
1761
|
+
fn(this.serverId, r.methodIdx, r.path, r.status, r.headersFlat, r.body);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
const pfn = this._setParamRoute;
|
|
1765
|
+
if (pfn !== undefined) {
|
|
1766
|
+
for (const r of this.paramRoutes) {
|
|
1767
|
+
pfn(this.serverId, r.methodIdx, r.prefix, r.suffix, r.status, r.headersFlat);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
/** Whether this engine build answers parameter routes itself. */
|
|
1772
|
+
get paramRoutesEnabled() {
|
|
1773
|
+
return this._paramOn;
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Register a route with one variable path segment, between `prefix` and
|
|
1777
|
+
* `suffix`, that the engine answers itself with that segment as the body:
|
|
1778
|
+
* "/user/" + "" for `/user/:id`. The segment goes out undecoded, as it is
|
|
1779
|
+
* on the wire. Returns false when this engine cannot take it (no
|
|
1780
|
+
* capability, compression on, or an unindexed method), so the caller keeps
|
|
1781
|
+
* its JS handler. Registering the same (method, prefix, suffix) again
|
|
1782
|
+
* replaces the earlier reply.
|
|
1783
|
+
*/
|
|
1784
|
+
setParamRoute(method, prefix, suffix, status = 200, headers) {
|
|
1785
|
+
const fn = this._setParamRoute;
|
|
1786
|
+
if (!this._paramOn || fn === undefined)
|
|
1787
|
+
return false;
|
|
1788
|
+
if (this._compression.enabled)
|
|
1789
|
+
return false;
|
|
1790
|
+
const methodIdx = METHODS.indexOf(method.toUpperCase());
|
|
1791
|
+
if (methodIdx < 0 || methodIdx >= STATIC_ROUTE_METHODS)
|
|
1792
|
+
return false;
|
|
1793
|
+
const headersFlat = flattenHeaders(headers);
|
|
1794
|
+
const entry = { methodIdx, prefix, suffix, status, headersFlat };
|
|
1795
|
+
const at = this.paramRoutes.findIndex(r => r.methodIdx === methodIdx && r.prefix === prefix && r.suffix === suffix);
|
|
1796
|
+
if (at === -1)
|
|
1797
|
+
this.paramRoutes.push(entry);
|
|
1798
|
+
else
|
|
1799
|
+
this.paramRoutes[at] = entry;
|
|
1800
|
+
fn(this.serverId, methodIdx, prefix, suffix, status, headersFlat);
|
|
1801
|
+
return true;
|
|
1802
|
+
}
|
|
1803
|
+
/** Drop every engine-answered parameter route; the JS routes are untouched. */
|
|
1804
|
+
clearParamRoutes() {
|
|
1805
|
+
this.paramRoutes.length = 0;
|
|
1806
|
+
if (this._paramOn && typeof this._engine.clearParamRoutes === 'function') {
|
|
1807
|
+
this._engine.clearParamRoutes(this.serverId);
|
|
1719
1808
|
}
|
|
1720
1809
|
}
|
|
1721
1810
|
// Register callbacks + limits with the native engine, returning the new
|
|
@@ -2279,6 +2368,9 @@ export class MoroEngineServer {
|
|
|
2279
2368
|
throw error;
|
|
2280
2369
|
}
|
|
2281
2370
|
const buffer = Buffer.from(raw);
|
|
2371
|
+
// getBody() handed us a stable copy, so the raw bytes cost nothing to keep
|
|
2372
|
+
// (webhook signature checks need them even for JSON bodies)
|
|
2373
|
+
httpReq._rawBody = buffer;
|
|
2282
2374
|
try {
|
|
2283
2375
|
if (contentType.includes('application/json')) {
|
|
2284
2376
|
httpReq.body = JSON.parse(buffer.toString('utf-8'));
|
|
@@ -2297,7 +2389,9 @@ export class MoroEngineServer {
|
|
|
2297
2389
|
httpReq.body = parseMultipartBuffer(buffer, contentType, this.multipartLimits);
|
|
2298
2390
|
}
|
|
2299
2391
|
else {
|
|
2300
|
-
|
|
2392
|
+
// Text decodes to a string; binary (octet-stream, images, protobuf,
|
|
2393
|
+
// ...) stays a Buffer - decoding it would corrupt it irreversibly.
|
|
2394
|
+
httpReq.body = bodyIsText(contentType) ? buffer.toString('utf-8') : buffer;
|
|
2301
2395
|
}
|
|
2302
2396
|
}
|
|
2303
2397
|
catch (parseError) {
|
|
@@ -2447,7 +2541,10 @@ export class MoroEngineServer {
|
|
|
2447
2541
|
this.logger.warn('Server is already listening', 'Listen');
|
|
2448
2542
|
return;
|
|
2449
2543
|
}
|
|
2450
|
-
|
|
2544
|
+
// No host = every interface, dual-stack, exactly like Node's
|
|
2545
|
+
// server.listen(port): a client that resolves `localhost` to ::1 first
|
|
2546
|
+
// must not be refused. Hosts without IPv6 fall back to IPv4-any below.
|
|
2547
|
+
const host = typeof hostOrCallback === 'string' ? hostOrCallback : '::';
|
|
2451
2548
|
const cb = typeof hostOrCallback === 'function' ? hostOrCallback : callback;
|
|
2452
2549
|
try {
|
|
2453
2550
|
// listen() after close(): the old native server's handles are torn
|
|
@@ -2461,11 +2558,23 @@ export class MoroEngineServer {
|
|
|
2461
2558
|
}
|
|
2462
2559
|
// Synchronous bind; throws on bind errors, returns the actual port
|
|
2463
2560
|
// (meaningful when port 0 asks for an ephemeral one)
|
|
2464
|
-
|
|
2561
|
+
let boundHost = host;
|
|
2562
|
+
let actualPort;
|
|
2563
|
+
try {
|
|
2564
|
+
actualPort = this._engine.listen(this.serverId, host, port);
|
|
2565
|
+
}
|
|
2566
|
+
catch (error) {
|
|
2567
|
+
const code = error?.code;
|
|
2568
|
+
if (host !== '::' || (code !== 'EAFNOSUPPORT' && code !== 'EADDRNOTAVAIL'))
|
|
2569
|
+
throw error;
|
|
2570
|
+
// IPv6 not configured on this host - IPv4-any is what Node does too
|
|
2571
|
+
boundHost = '0.0.0.0';
|
|
2572
|
+
actualPort = this._engine.listen(this.serverId, boundHost, port);
|
|
2573
|
+
}
|
|
2465
2574
|
this.port = typeof actualPort === 'number' ? actualPort : port;
|
|
2466
|
-
this.host =
|
|
2575
|
+
this.host = boundHost;
|
|
2467
2576
|
this.isListening = true;
|
|
2468
|
-
this.logger.info(`Moro engine HTTP server listening on ${
|
|
2577
|
+
this.logger.info(`Moro engine HTTP server listening on ${boundHost}:${this.port}`, 'Listen');
|
|
2469
2578
|
if (cb)
|
|
2470
2579
|
cb();
|
|
2471
2580
|
}
|
|
@@ -2539,6 +2648,23 @@ export class MoroEngineServer {
|
|
|
2539
2648
|
callback(error instanceof Error ? error : new Error(String(error)));
|
|
2540
2649
|
}
|
|
2541
2650
|
}
|
|
2651
|
+
/**
|
|
2652
|
+
* Replace the TLS certificate/key for every handshake from now on
|
|
2653
|
+
* (engine capabilities.tlsReload). Established connections keep the context
|
|
2654
|
+
* they handshaked with. Validated by the engine before the swap: a bad
|
|
2655
|
+
* config throws and the current certificate keeps serving.
|
|
2656
|
+
*/
|
|
2657
|
+
updateSsl(ssl) {
|
|
2658
|
+
if (!this._ssl || !this.isSsl) {
|
|
2659
|
+
throw new Error('updateSsl: this server was not started with TLS (server.ssl)');
|
|
2660
|
+
}
|
|
2661
|
+
if (!this._capabilities?.tlsReload || typeof this._engine.updateSsl !== 'function') {
|
|
2662
|
+
throw new Error('TLS reload needs @morojs/engine with capabilities.tlsReload (engine >= 1.1.8)');
|
|
2663
|
+
}
|
|
2664
|
+
this._engine.updateSsl(this.serverId, ssl);
|
|
2665
|
+
// A later close() -> listen() cycle re-registers with the new material
|
|
2666
|
+
this._ssl = ssl;
|
|
2667
|
+
}
|
|
2542
2668
|
getServer() {
|
|
2543
2669
|
// Stable observability handle (listening/address) - there is no app object
|
|
2544
2670
|
return this.handle;
|