@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.
- 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 +115 -11
- 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 -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 +7 -0
- 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 +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
|
@@ -17,3 +17,21 @@ export declare function urlencoded(_options?: {
|
|
|
17
17
|
extended?: boolean;
|
|
18
18
|
limit?: number | string;
|
|
19
19
|
}): Middleware;
|
|
20
|
+
/**
|
|
21
|
+
* Raw body-parser middleware (Express `raw()` idiom). MoroJS already keeps
|
|
22
|
+
* binary bodies as Buffers, so this only converts a body that was decoded to
|
|
23
|
+
* a string back to its exact bytes for the matching content types (default
|
|
24
|
+
* `application/octet-stream`). `req.rawBody` is always available regardless.
|
|
25
|
+
*/
|
|
26
|
+
export declare function raw(options?: {
|
|
27
|
+
type?: string | string[];
|
|
28
|
+
limit?: number | string;
|
|
29
|
+
}): Middleware;
|
|
30
|
+
/**
|
|
31
|
+
* Text body-parser middleware (Express `text()` idiom): decodes a Buffer body
|
|
32
|
+
* to a UTF-8 string for the matching content types (default `text/plain`).
|
|
33
|
+
*/
|
|
34
|
+
export declare function text(options?: {
|
|
35
|
+
type?: string | string[];
|
|
36
|
+
limit?: number | string;
|
|
37
|
+
}): Middleware;
|
|
@@ -35,4 +35,45 @@ export function urlencoded(_options) {
|
|
|
35
35
|
next();
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
+
function typeMatches(contentType, wanted) {
|
|
39
|
+
const ct = contentType.toLowerCase().split(';')[0]?.trim() ?? '';
|
|
40
|
+
for (const t of Array.isArray(wanted) ? wanted : [wanted]) {
|
|
41
|
+
const w = t.toLowerCase();
|
|
42
|
+
if (w === '*/*' || w === ct)
|
|
43
|
+
return true;
|
|
44
|
+
if (w.endsWith('/*') && ct.startsWith(w.slice(0, -1)))
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Raw body-parser middleware (Express `raw()` idiom). MoroJS already keeps
|
|
51
|
+
* binary bodies as Buffers, so this only converts a body that was decoded to
|
|
52
|
+
* a string back to its exact bytes for the matching content types (default
|
|
53
|
+
* `application/octet-stream`). `req.rawBody` is always available regardless.
|
|
54
|
+
*/
|
|
55
|
+
export function raw(options) {
|
|
56
|
+
const wanted = options?.type ?? 'application/octet-stream';
|
|
57
|
+
return function moroRawBodyParser(req, _res, next) {
|
|
58
|
+
const ct = (req.headers['content-type'] || '');
|
|
59
|
+
if (typeof req.body === 'string' && typeMatches(ct, wanted)) {
|
|
60
|
+
req.body = req.rawBody ?? Buffer.from(req.body, 'utf8');
|
|
61
|
+
}
|
|
62
|
+
next();
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Text body-parser middleware (Express `text()` idiom): decodes a Buffer body
|
|
67
|
+
* to a UTF-8 string for the matching content types (default `text/plain`).
|
|
68
|
+
*/
|
|
69
|
+
export function text(options) {
|
|
70
|
+
const wanted = options?.type ?? 'text/plain';
|
|
71
|
+
return function moroTextBodyParser(req, _res, next) {
|
|
72
|
+
const ct = (req.headers['content-type'] || '');
|
|
73
|
+
if (Buffer.isBuffer(req.body) && typeMatches(ct, wanted)) {
|
|
74
|
+
req.body = req.body.toString('utf8');
|
|
75
|
+
}
|
|
76
|
+
next();
|
|
77
|
+
};
|
|
78
|
+
}
|
|
38
79
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/body-parsers/index.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,IAAI,CAAC,QAAwD;IAC3E,OAAO,SAAS,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI;QACjD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,QAA0D;IACnF,OAAO,SAAS,wBAAwB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI;QACtD,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAW,CAAC;QACzD,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,mCAAmC,CAAC,EAAE,CAAC;YACtE,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QACD,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACjC,MAAM,MAAM,GAA2B,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAClC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC,IAAI;oBAAE,SAAS;gBACpB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBAC7B,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACnD,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;gBAClD,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,kBAAkB,CACpE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CACtB,CAAC;YACJ,CAAC;YACD,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC;QACpB,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { Middleware } from '../../../../types/http.js';\n\n/**\n * JSON body-parser middleware. MoroJS auto-parses JSON bodies on POST/PUT/PATCH\n * requests upstream, so this middleware is effectively a pass-through. It exists\n * so code written in the `app.use(json())` idiom works verbatim without rewrites.\n */\nexport function json(_options?: { limit?: number | string; strict?: boolean }): Middleware {\n return function moroJsonBodyParser(_req, _res, next) {\n next();\n };\n}\n\n/**\n * URL-encoded body-parser middleware. Parses `application/x-www-form-urlencoded`\n * request bodies when the automatic parser left them as strings, populating\n * `req.body` with the decoded object.\n */\nexport function urlencoded(_options?: { extended?: boolean; limit?: number | string }): Middleware {\n return function moroUrlencodedBodyParser(req, _res, next) {\n const ct = (req.headers['content-type'] || '') as string;\n if (!ct.toLowerCase().startsWith('application/x-www-form-urlencoded')) {\n return next();\n }\n if (typeof req.body === 'string') {\n const parsed: Record<string, string> = {};\n const pairs = req.body.split('&');\n for (const pair of pairs) {\n if (!pair) continue;\n const eq = pair.indexOf('=');\n const k = eq === -1 ? pair : pair.substring(0, eq);\n const v = eq === -1 ? '' : pair.substring(eq + 1);\n parsed[decodeURIComponent(k.replace(/\\+/g, ' '))] = decodeURIComponent(\n v.replace(/\\+/g, ' ')\n );\n }\n req.body = parsed;\n }\n next();\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/body-parsers/index.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,IAAI,CAAC,QAAwD;IAC3E,OAAO,SAAS,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI;QACjD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,QAA0D;IACnF,OAAO,SAAS,wBAAwB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI;QACtD,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAW,CAAC;QACzD,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,mCAAmC,CAAC,EAAE,CAAC;YACtE,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QACD,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACjC,MAAM,MAAM,GAA2B,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAClC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC,IAAI;oBAAE,SAAS;gBACpB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBAC7B,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACnD,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;gBAClD,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,kBAAkB,CACpE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CACtB,CAAC;YACJ,CAAC;YACD,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC;QACpB,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,WAAmB,EAAE,MAAyB;IACjE,MAAM,EAAE,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACjE,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1D,MAAM,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC1B,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,EAAE;YAAE,OAAO,IAAI,CAAC;QACzC,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;IACrE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,GAAG,CAAC,OAA+D;IACjF,MAAM,MAAM,GAAG,OAAO,EAAE,IAAI,IAAI,0BAA0B,CAAC;IAC3D,OAAO,SAAS,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI;QAC/C,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAW,CAAC;QACzD,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,WAAW,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC;YAC5D,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1D,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,IAAI,CAAC,OAA+D;IAClF,MAAM,MAAM,GAAG,OAAO,EAAE,IAAI,IAAI,YAAY,CAAC;IAC7C,OAAO,SAAS,kBAAkB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI;QAChD,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAW,CAAC;QACzD,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC;YACzD,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACvC,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { Middleware } from '../../../../types/http.js';\n\n/**\n * JSON body-parser middleware. MoroJS auto-parses JSON bodies on POST/PUT/PATCH\n * requests upstream, so this middleware is effectively a pass-through. It exists\n * so code written in the `app.use(json())` idiom works verbatim without rewrites.\n */\nexport function json(_options?: { limit?: number | string; strict?: boolean }): Middleware {\n return function moroJsonBodyParser(_req, _res, next) {\n next();\n };\n}\n\n/**\n * URL-encoded body-parser middleware. Parses `application/x-www-form-urlencoded`\n * request bodies when the automatic parser left them as strings, populating\n * `req.body` with the decoded object.\n */\nexport function urlencoded(_options?: { extended?: boolean; limit?: number | string }): Middleware {\n return function moroUrlencodedBodyParser(req, _res, next) {\n const ct = (req.headers['content-type'] || '') as string;\n if (!ct.toLowerCase().startsWith('application/x-www-form-urlencoded')) {\n return next();\n }\n if (typeof req.body === 'string') {\n const parsed: Record<string, string> = {};\n const pairs = req.body.split('&');\n for (const pair of pairs) {\n if (!pair) continue;\n const eq = pair.indexOf('=');\n const k = eq === -1 ? pair : pair.substring(0, eq);\n const v = eq === -1 ? '' : pair.substring(eq + 1);\n parsed[decodeURIComponent(k.replace(/\\+/g, ' '))] = decodeURIComponent(\n v.replace(/\\+/g, ' ')\n );\n }\n req.body = parsed;\n }\n next();\n };\n}\n\nfunction typeMatches(contentType: string, wanted: string | string[]): boolean {\n const ct = contentType.toLowerCase().split(';')[0]?.trim() ?? '';\n for (const t of Array.isArray(wanted) ? wanted : [wanted]) {\n const w = t.toLowerCase();\n if (w === '*/*' || w === ct) return true;\n if (w.endsWith('/*') && ct.startsWith(w.slice(0, -1))) return true;\n }\n return false;\n}\n\n/**\n * Raw body-parser middleware (Express `raw()` idiom). MoroJS already keeps\n * binary bodies as Buffers, so this only converts a body that was decoded to\n * a string back to its exact bytes for the matching content types (default\n * `application/octet-stream`). `req.rawBody` is always available regardless.\n */\nexport function raw(options?: { type?: string | string[]; limit?: number | string }): Middleware {\n const wanted = options?.type ?? 'application/octet-stream';\n return function moroRawBodyParser(req, _res, next) {\n const ct = (req.headers['content-type'] || '') as string;\n if (typeof req.body === 'string' && typeMatches(ct, wanted)) {\n req.body = req.rawBody ?? Buffer.from(req.body, 'utf8');\n }\n next();\n };\n}\n\n/**\n * Text body-parser middleware (Express `text()` idiom): decodes a Buffer body\n * to a UTF-8 string for the matching content types (default `text/plain`).\n */\nexport function text(options?: { type?: string | string[]; limit?: number | string }): Middleware {\n const wanted = options?.type ?? 'text/plain';\n return function moroTextBodyParser(req, _res, next) {\n const ct = (req.headers['content-type'] || '') as string;\n if (Buffer.isBuffer(req.body) && typeMatches(ct, wanted)) {\n req.body = req.body.toString('utf8');\n }\n next();\n };\n}\n"]}
|
|
@@ -1,19 +1,43 @@
|
|
|
1
1
|
import { CacheAdapter } from '../../../../../../types/cache.js';
|
|
2
|
+
export interface RedisCacheOptions {
|
|
3
|
+
/** redis[s]://[[user][:password]@]host[:port][/db] - wins over host/port */
|
|
4
|
+
url?: string;
|
|
5
|
+
host?: string;
|
|
6
|
+
port?: number;
|
|
7
|
+
password?: string;
|
|
8
|
+
db?: number;
|
|
9
|
+
/** Prepended to every key (default 'moro:cache:'); clear() only touches prefixed keys */
|
|
10
|
+
keyPrefix?: string;
|
|
11
|
+
/** An existing node-redis client; the adapter connects it if it is not open */
|
|
12
|
+
client?: any;
|
|
13
|
+
}
|
|
2
14
|
export declare class RedisCacheAdapter implements CacheAdapter {
|
|
3
15
|
private client;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
});
|
|
11
|
-
private initPromise;
|
|
16
|
+
private readonly keyPrefix;
|
|
17
|
+
private readonly ready;
|
|
18
|
+
private initError;
|
|
19
|
+
/** True when the adapter created the client and therefore owns its lifetime */
|
|
20
|
+
private ownsClient;
|
|
21
|
+
constructor(options?: RedisCacheOptions);
|
|
12
22
|
private initialize;
|
|
23
|
+
/** Resolves to the connected client, or throws the initialization error. */
|
|
24
|
+
private connected;
|
|
25
|
+
private key;
|
|
13
26
|
get(key: string): Promise<any>;
|
|
14
27
|
set(key: string, value: any, ttl?: number): Promise<void>;
|
|
15
28
|
del(key: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Remove this adapter's keys. With a prefix that is a SCAN over
|
|
31
|
+
* `<prefix>*` (a shared Redis is not flushed wholesale); with an empty
|
|
32
|
+
* prefix every key is this adapter's and FLUSHDB is the cheap equivalent.
|
|
33
|
+
*/
|
|
16
34
|
clear(): Promise<void>;
|
|
17
35
|
exists(key: string): Promise<boolean>;
|
|
18
36
|
ttl(key: string): Promise<number>;
|
|
37
|
+
/**
|
|
38
|
+
* Close the connection. A client the adapter created is quit (it would
|
|
39
|
+
* otherwise keep the process alive); a caller-supplied client belongs to
|
|
40
|
+
* its owner and is left open. Later calls answer as misses.
|
|
41
|
+
*/
|
|
42
|
+
close(): Promise<void>;
|
|
19
43
|
}
|
|
@@ -3,34 +3,73 @@ import { resolveUserPackage } from '../../../../../utilities/package-utils.js';
|
|
|
3
3
|
const logger = createFrameworkLogger('RedisCacheAdapter');
|
|
4
4
|
export class RedisCacheAdapter {
|
|
5
5
|
client;
|
|
6
|
+
keyPrefix;
|
|
7
|
+
ready;
|
|
8
|
+
initError = null;
|
|
9
|
+
/** True when the adapter created the client and therefore owns its lifetime */
|
|
10
|
+
ownsClient = false;
|
|
6
11
|
constructor(options = {}) {
|
|
7
|
-
this.
|
|
12
|
+
this.keyPrefix = options.keyPrefix ?? 'moro:cache:';
|
|
13
|
+
// The failure is kept and rethrown per call (each method logs and returns
|
|
14
|
+
// its miss value); it must not surface as an unhandled rejection here.
|
|
15
|
+
this.ready = this.initialize(options).catch((err) => {
|
|
16
|
+
this.initError = err instanceof Error ? err : new Error(String(err));
|
|
17
|
+
logger.error('Redis cache adapter failed to initialize', 'RedisCache', {
|
|
18
|
+
error: this.initError.message,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
8
21
|
}
|
|
9
|
-
initPromise;
|
|
10
22
|
async initialize(options) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
23
|
+
let client = options.client;
|
|
24
|
+
if (!client) {
|
|
25
|
+
this.ownsClient = true;
|
|
26
|
+
let redis;
|
|
27
|
+
try {
|
|
28
|
+
redis = await import(resolveUserPackage('redis'));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new Error('Redis package not installed. Run: npm install redis');
|
|
32
|
+
}
|
|
33
|
+
const createClient = redis.createClient ?? redis.default?.createClient;
|
|
34
|
+
if (typeof createClient !== 'function') {
|
|
35
|
+
throw new Error('The installed "redis" package does not export createClient (node-redis 4+ required)');
|
|
36
|
+
}
|
|
37
|
+
client = options.url
|
|
38
|
+
? createClient({ url: options.url })
|
|
39
|
+
: createClient({
|
|
40
|
+
socket: { host: options.host || 'localhost', port: options.port || 6379 },
|
|
41
|
+
...(options.password !== undefined && { password: options.password }),
|
|
42
|
+
...(options.db !== undefined && { database: options.db }),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// Never let the client's own error events crash the process
|
|
46
|
+
if (typeof client.on === 'function') {
|
|
47
|
+
client.on('error', (err) => {
|
|
22
48
|
logger.error('Redis cache error', 'RedisCache', { error: err.message });
|
|
23
49
|
});
|
|
24
|
-
logger.info('Redis cache adapter initialized', 'RedisCache');
|
|
25
50
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
51
|
+
// node-redis 4+ needs an explicit connect(); a caller-supplied client may
|
|
52
|
+
// already be open (isOpen), and an ioredis-style client has no isOpen at all
|
|
53
|
+
if (typeof client.connect === 'function' && client.isOpen === false) {
|
|
54
|
+
await client.connect();
|
|
29
55
|
}
|
|
56
|
+
this.client = client;
|
|
57
|
+
logger.info('Redis cache adapter initialized', 'RedisCache');
|
|
58
|
+
}
|
|
59
|
+
/** Resolves to the connected client, or throws the initialization error. */
|
|
60
|
+
async connected() {
|
|
61
|
+
await this.ready;
|
|
62
|
+
if (this.initError)
|
|
63
|
+
throw this.initError;
|
|
64
|
+
return this.client;
|
|
65
|
+
}
|
|
66
|
+
key(key) {
|
|
67
|
+
return this.keyPrefix + key;
|
|
30
68
|
}
|
|
31
69
|
async get(key) {
|
|
32
70
|
try {
|
|
33
|
-
const
|
|
71
|
+
const client = await this.connected();
|
|
72
|
+
const value = await client.get(this.key(key));
|
|
34
73
|
return value ? JSON.parse(value) : null;
|
|
35
74
|
}
|
|
36
75
|
catch (error) {
|
|
@@ -40,12 +79,14 @@ export class RedisCacheAdapter {
|
|
|
40
79
|
}
|
|
41
80
|
async set(key, value, ttl = 3600) {
|
|
42
81
|
try {
|
|
82
|
+
const client = await this.connected();
|
|
43
83
|
const serialized = JSON.stringify(value);
|
|
44
84
|
if (ttl > 0) {
|
|
45
|
-
|
|
85
|
+
// PX keeps sub-second TTLs (EX rounds to whole seconds and rejects 0)
|
|
86
|
+
await client.set(this.key(key), serialized, { PX: Math.max(1, Math.round(ttl * 1000)) });
|
|
46
87
|
}
|
|
47
88
|
else {
|
|
48
|
-
await
|
|
89
|
+
await client.set(this.key(key), serialized);
|
|
49
90
|
}
|
|
50
91
|
logger.debug(`Cached item in Redis: ${key} (TTL: ${ttl}s)`, 'RedisCache');
|
|
51
92
|
}
|
|
@@ -55,16 +96,43 @@ export class RedisCacheAdapter {
|
|
|
55
96
|
}
|
|
56
97
|
async del(key) {
|
|
57
98
|
try {
|
|
58
|
-
await this.
|
|
99
|
+
const client = await this.connected();
|
|
100
|
+
await client.del(this.key(key));
|
|
59
101
|
logger.debug(`Deleted Redis cache item: ${key}`, 'RedisCache');
|
|
60
102
|
}
|
|
61
103
|
catch (error) {
|
|
62
104
|
logger.error('Redis del error', 'RedisCache', { key, error });
|
|
63
105
|
}
|
|
64
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Remove this adapter's keys. With a prefix that is a SCAN over
|
|
109
|
+
* `<prefix>*` (a shared Redis is not flushed wholesale); with an empty
|
|
110
|
+
* prefix every key is this adapter's and FLUSHDB is the cheap equivalent.
|
|
111
|
+
*/
|
|
65
112
|
async clear() {
|
|
66
113
|
try {
|
|
67
|
-
await this.
|
|
114
|
+
const client = await this.connected();
|
|
115
|
+
if (!this.keyPrefix) {
|
|
116
|
+
await (client.flushDb ?? client.flushdb).call(client);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const batch = [];
|
|
120
|
+
const flush = async () => {
|
|
121
|
+
if (batch.length === 0)
|
|
122
|
+
return;
|
|
123
|
+
await client.del(batch.splice(0, batch.length));
|
|
124
|
+
};
|
|
125
|
+
for await (const item of client.scanIterator({ MATCH: `${this.keyPrefix}*`, COUNT: 500 })) {
|
|
126
|
+
// node-redis 4 yields one key per iteration, 5 yields a page of keys
|
|
127
|
+
if (Array.isArray(item))
|
|
128
|
+
batch.push(...item);
|
|
129
|
+
else
|
|
130
|
+
batch.push(item);
|
|
131
|
+
if (batch.length >= 500)
|
|
132
|
+
await flush();
|
|
133
|
+
}
|
|
134
|
+
await flush();
|
|
135
|
+
}
|
|
68
136
|
logger.debug('Cleared all Redis cache items', 'RedisCache');
|
|
69
137
|
}
|
|
70
138
|
catch (error) {
|
|
@@ -73,8 +141,9 @@ export class RedisCacheAdapter {
|
|
|
73
141
|
}
|
|
74
142
|
async exists(key) {
|
|
75
143
|
try {
|
|
76
|
-
const
|
|
77
|
-
|
|
144
|
+
const client = await this.connected();
|
|
145
|
+
const count = await client.exists(this.key(key));
|
|
146
|
+
return Number(count) > 0;
|
|
78
147
|
}
|
|
79
148
|
catch (error) {
|
|
80
149
|
logger.error('Redis exists error', 'RedisCache', { key, error });
|
|
@@ -83,12 +152,35 @@ export class RedisCacheAdapter {
|
|
|
83
152
|
}
|
|
84
153
|
async ttl(key) {
|
|
85
154
|
try {
|
|
86
|
-
|
|
155
|
+
const client = await this.connected();
|
|
156
|
+
return await client.ttl(this.key(key));
|
|
87
157
|
}
|
|
88
158
|
catch (error) {
|
|
89
159
|
logger.error('Redis TTL error', 'RedisCache', { key, error });
|
|
90
160
|
return -1;
|
|
91
161
|
}
|
|
92
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Close the connection. A client the adapter created is quit (it would
|
|
165
|
+
* otherwise keep the process alive); a caller-supplied client belongs to
|
|
166
|
+
* its owner and is left open. Later calls answer as misses.
|
|
167
|
+
*/
|
|
168
|
+
async close() {
|
|
169
|
+
await this.ready;
|
|
170
|
+
const client = this.client;
|
|
171
|
+
this.client = null;
|
|
172
|
+
this.initError = new Error('Redis cache adapter is closed');
|
|
173
|
+
if (!client || !this.ownsClient)
|
|
174
|
+
return;
|
|
175
|
+
try {
|
|
176
|
+
if (typeof client.quit === 'function')
|
|
177
|
+
await client.quit();
|
|
178
|
+
else if (typeof client.disconnect === 'function')
|
|
179
|
+
await client.disconnect();
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
logger.error('Redis close error', 'RedisCache', { error });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
93
185
|
}
|
|
94
186
|
//# sourceMappingURL=redis.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../../../../../../src/core/middleware/built-in/cache/adapters/cache/redis.ts"],"names":[],"mappings":"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","sourcesContent":["// Redis Cache Adapter\nimport { CacheAdapter } from '../../../../../../types/cache.js';\nimport { createFrameworkLogger } from '../../../../../logger/index.js';\nimport { resolveUserPackage } from '../../../../../utilities/package-utils.js';\n\nconst logger = createFrameworkLogger('RedisCacheAdapter');\n\nexport class RedisCacheAdapter implements CacheAdapter {\n private client: any;\n\n constructor(\n options: {\n host?: string;\n port?: number;\n password?: string;\n db?: number;\n keyPrefix?: string;\n } = {}\n ) {\n this.initPromise = this.initialize(options);\n }\n\n private initPromise: Promise<void>;\n\n private async initialize(options: {\n host?: string;\n port?: number;\n password?: string;\n db?: number;\n keyPrefix?: string;\n }): Promise<void> {\n try {\n const redisPath = resolveUserPackage('redis');\n const redis = await import(redisPath);\n this.client = redis.default.createClient({\n host: options.host || 'localhost',\n port: options.port || 6379,\n password: options.password,\n db: options.db || 0,\n key_prefix: options.keyPrefix || 'moro:cache:',\n });\n\n this.client.on('error', (err: Error) => {\n logger.error('Redis cache error', 'RedisCache', { error: err.message });\n });\n\n logger.info('Redis cache adapter initialized', 'RedisCache');\n } catch {\n logger.error('Redis not available, falling back to memory cache', 'RedisCache');\n throw new Error('Redis package not installed. Run: npm install redis');\n }\n }\n\n async get(key: string): Promise<any> {\n try {\n const value = await this.client.get(key);\n return value ? JSON.parse(value) : null;\n } catch (error) {\n logger.error('Redis get error', 'RedisCache', { key, error });\n return null;\n }\n }\n\n async set(key: string, value: any, ttl: number = 3600): Promise<void> {\n try {\n const serialized = JSON.stringify(value);\n if (ttl > 0) {\n await this.client.setex(key, ttl, serialized);\n } else {\n await this.client.set(key, serialized);\n }\n logger.debug(`Cached item in Redis: ${key} (TTL: ${ttl}s)`, 'RedisCache');\n } catch (error) {\n logger.error('Redis set error', 'RedisCache', { key, error });\n }\n }\n\n async del(key: string): Promise<void> {\n try {\n await this.client.del(key);\n logger.debug(`Deleted Redis cache item: ${key}`, 'RedisCache');\n } catch (error) {\n logger.error('Redis del error', 'RedisCache', { key, error });\n }\n }\n\n async clear(): Promise<void> {\n try {\n await this.client.flushdb();\n logger.debug('Cleared all Redis cache items', 'RedisCache');\n } catch (error) {\n logger.error('Redis clear error', 'RedisCache', { error });\n }\n }\n\n async exists(key: string): Promise<boolean> {\n try {\n const exists = await this.client.exists(key);\n return exists === 1;\n } catch (error) {\n logger.error('Redis exists error', 'RedisCache', { key, error });\n return false;\n }\n }\n\n async ttl(key: string): Promise<number> {\n try {\n return await this.client.ttl(key);\n } catch (error) {\n logger.error('Redis TTL error', 'RedisCache', { key, error });\n return -1;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../../../../../../src/core/middleware/built-in/cache/adapters/cache/redis.ts"],"names":[],"mappings":"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","sourcesContent":["// Redis Cache Adapter (node-redis 4+)\n//\n// Targets the node-redis v4/v5 API: createClient({ url } | { socket }),\n// an explicit connect(), and the SET ... PX form for TTLs. Every command\n// awaits the connection first, so a call issued right after construction\n// waits for the import + connect instead of racing them.\nimport { CacheAdapter } from '../../../../../../types/cache.js';\nimport { createFrameworkLogger } from '../../../../../logger/index.js';\nimport { resolveUserPackage } from '../../../../../utilities/package-utils.js';\n\nconst logger = createFrameworkLogger('RedisCacheAdapter');\n\nexport interface RedisCacheOptions {\n /** redis[s]://[[user][:password]@]host[:port][/db] - wins over host/port */\n url?: string;\n host?: string;\n port?: number;\n password?: string;\n db?: number;\n /** Prepended to every key (default 'moro:cache:'); clear() only touches prefixed keys */\n keyPrefix?: string;\n /** An existing node-redis client; the adapter connects it if it is not open */\n client?: any;\n}\n\nexport class RedisCacheAdapter implements CacheAdapter {\n private client: any;\n private readonly keyPrefix: string;\n private readonly ready: Promise<void>;\n private initError: Error | null = null;\n /** True when the adapter created the client and therefore owns its lifetime */\n private ownsClient = false;\n\n constructor(options: RedisCacheOptions = {}) {\n this.keyPrefix = options.keyPrefix ?? 'moro:cache:';\n // The failure is kept and rethrown per call (each method logs and returns\n // its miss value); it must not surface as an unhandled rejection here.\n this.ready = this.initialize(options).catch((err: unknown) => {\n this.initError = err instanceof Error ? err : new Error(String(err));\n logger.error('Redis cache adapter failed to initialize', 'RedisCache', {\n error: this.initError.message,\n });\n });\n }\n\n private async initialize(options: RedisCacheOptions): Promise<void> {\n let client = options.client;\n if (!client) {\n this.ownsClient = true;\n let redis: any;\n try {\n redis = await import(resolveUserPackage('redis'));\n } catch {\n throw new Error('Redis package not installed. Run: npm install redis');\n }\n const createClient = redis.createClient ?? redis.default?.createClient;\n if (typeof createClient !== 'function') {\n throw new Error(\n 'The installed \"redis\" package does not export createClient (node-redis 4+ required)'\n );\n }\n client = options.url\n ? createClient({ url: options.url })\n : createClient({\n socket: { host: options.host || 'localhost', port: options.port || 6379 },\n ...(options.password !== undefined && { password: options.password }),\n ...(options.db !== undefined && { database: options.db }),\n });\n }\n\n // Never let the client's own error events crash the process\n if (typeof client.on === 'function') {\n client.on('error', (err: Error) => {\n logger.error('Redis cache error', 'RedisCache', { error: err.message });\n });\n }\n // node-redis 4+ needs an explicit connect(); a caller-supplied client may\n // already be open (isOpen), and an ioredis-style client has no isOpen at all\n if (typeof client.connect === 'function' && client.isOpen === false) {\n await client.connect();\n }\n\n this.client = client;\n logger.info('Redis cache adapter initialized', 'RedisCache');\n }\n\n /** Resolves to the connected client, or throws the initialization error. */\n private async connected(): Promise<any> {\n await this.ready;\n if (this.initError) throw this.initError;\n return this.client;\n }\n\n private key(key: string): string {\n return this.keyPrefix + key;\n }\n\n async get(key: string): Promise<any> {\n try {\n const client = await this.connected();\n const value = await client.get(this.key(key));\n return value ? JSON.parse(value) : null;\n } catch (error) {\n logger.error('Redis get error', 'RedisCache', { key, error });\n return null;\n }\n }\n\n async set(key: string, value: any, ttl: number = 3600): Promise<void> {\n try {\n const client = await this.connected();\n const serialized = JSON.stringify(value);\n if (ttl > 0) {\n // PX keeps sub-second TTLs (EX rounds to whole seconds and rejects 0)\n await client.set(this.key(key), serialized, { PX: Math.max(1, Math.round(ttl * 1000)) });\n } else {\n await client.set(this.key(key), serialized);\n }\n logger.debug(`Cached item in Redis: ${key} (TTL: ${ttl}s)`, 'RedisCache');\n } catch (error) {\n logger.error('Redis set error', 'RedisCache', { key, error });\n }\n }\n\n async del(key: string): Promise<void> {\n try {\n const client = await this.connected();\n await client.del(this.key(key));\n logger.debug(`Deleted Redis cache item: ${key}`, 'RedisCache');\n } catch (error) {\n logger.error('Redis del error', 'RedisCache', { key, error });\n }\n }\n\n /**\n * Remove this adapter's keys. With a prefix that is a SCAN over\n * `<prefix>*` (a shared Redis is not flushed wholesale); with an empty\n * prefix every key is this adapter's and FLUSHDB is the cheap equivalent.\n */\n async clear(): Promise<void> {\n try {\n const client = await this.connected();\n if (!this.keyPrefix) {\n await (client.flushDb ?? client.flushdb).call(client);\n } else {\n const batch: string[] = [];\n const flush = async () => {\n if (batch.length === 0) return;\n await client.del(batch.splice(0, batch.length));\n };\n for await (const item of client.scanIterator({ MATCH: `${this.keyPrefix}*`, COUNT: 500 })) {\n // node-redis 4 yields one key per iteration, 5 yields a page of keys\n if (Array.isArray(item)) batch.push(...item);\n else batch.push(item);\n if (batch.length >= 500) await flush();\n }\n await flush();\n }\n logger.debug('Cleared all Redis cache items', 'RedisCache');\n } catch (error) {\n logger.error('Redis clear error', 'RedisCache', { error });\n }\n }\n\n async exists(key: string): Promise<boolean> {\n try {\n const client = await this.connected();\n const count = await client.exists(this.key(key));\n return Number(count) > 0;\n } catch (error) {\n logger.error('Redis exists error', 'RedisCache', { key, error });\n return false;\n }\n }\n\n async ttl(key: string): Promise<number> {\n try {\n const client = await this.connected();\n return await client.ttl(this.key(key));\n } catch (error) {\n logger.error('Redis TTL error', 'RedisCache', { key, error });\n return -1;\n }\n }\n\n /**\n * Close the connection. A client the adapter created is quit (it would\n * otherwise keep the process alive); a caller-supplied client belongs to\n * its owner and is left open. Later calls answer as misses.\n */\n async close(): Promise<void> {\n await this.ready;\n const client = this.client;\n this.client = null;\n this.initError = new Error('Redis cache adapter is closed');\n if (!client || !this.ownsClient) return;\n try {\n if (typeof client.quit === 'function') await client.quit();\n else if (typeof client.disconnect === 'function') await client.disconnect();\n } catch (error) {\n logger.error('Redis close error', 'RedisCache', { error });\n }\n }\n}\n"]}
|
|
@@ -55,6 +55,11 @@ export class CompressionCore {
|
|
|
55
55
|
// the "no route matched" fallback can't 404 the response mid-compress.
|
|
56
56
|
// The compressed length isn't known yet, so the body streams (chunked).
|
|
57
57
|
if (!res.headersSent) {
|
|
58
|
+
// A Content-Length set for the uncompressed body would truncate (or
|
|
59
|
+
// hang) the compressed one; the compressed body streams chunked.
|
|
60
|
+
if (typeof res.removeHeader === 'function') {
|
|
61
|
+
res.removeHeader('Content-Length');
|
|
62
|
+
}
|
|
58
63
|
res.setHeader('Content-Encoding', encoding);
|
|
59
64
|
const vary = res.getHeader ? res.getHeader('vary') : undefined;
|
|
60
65
|
res.setHeader('Vary', vary ? `${vary}, Accept-Encoding` : 'Accept-Encoding');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/compression/core.ts"],"names":[],"mappings":"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","sourcesContent":["// Compression Core Logic\n//\n// The global compression middleware wraps res.json/res.send uniformly across\n// EVERY runtime (Node, uWS, HTTP/2, engine), so it is the single source of\n// response compression parity. It delegates encoding choice and content-type\n// filtering to the shared utility (utils/compression) so brotli, q-value\n// negotiation, and the compressible-type allowlist behave identically here and\n// in the per-server fallback paths.\nimport { HttpRequest, HttpResponse } from '../../../../types/http.js';\nimport {\n compressBuffer,\n negotiateEncoding,\n isCompressible,\n DEFAULT_ENCODINGS,\n type Encoding,\n} from '../../../http/utils/compression.js';\n\nexport interface CompressionOptions {\n threshold?: number;\n level?: number;\n encodings?: Encoding[];\n filter?: (req: HttpRequest, res: HttpResponse) => boolean;\n}\n\nexport class CompressionCore {\n private threshold: number;\n private level: number;\n private encodings: Encoding[];\n private filter?: ((req: HttpRequest, res: HttpResponse) => boolean) | undefined;\n\n constructor(options: CompressionOptions = {}) {\n this.threshold = options.threshold || 1024; // 1KB default\n this.level = options.level || 6; // Default compression level\n this.encodings = options.encodings || DEFAULT_ENCODINGS;\n this.filter = options.filter;\n }\n\n shouldCompress(req: HttpRequest, res: HttpResponse): boolean {\n // Check custom filter\n if (this.filter && !this.filter(req, res)) {\n return false;\n }\n\n // Check if client accepts any encoding we offer\n const acceptEncoding = (req.headers['accept-encoding'] as string) || '';\n return negotiateEncoding(acceptEncoding, this.encodings) !== null;\n }\n\n wrapResponse(req: HttpRequest, res: HttpResponse): void {\n const originalSend = res.send;\n const acceptEncoding = (req.headers['accept-encoding'] as string) || '';\n const level = this.level;\n const threshold = this.threshold;\n const encodings = this.encodings;\n const filterOk = this.filter ? this.filter(req, res) : true;\n\n const compressResponse = (data: any, isJson: boolean, contentType?: string) => {\n const content = isJson ? JSON.stringify(data) : data;\n const byteLength = Buffer.isBuffer(content)\n ? content.length\n : Buffer.byteLength(content ?? '');\n const ctHeader =\n contentType ??\n (res.getHeader ? (res.getHeader('content-type') as string | undefined) : undefined);\n const encoding = filterOk ? negotiateEncoding(acceptEncoding, encodings) : null;\n\n // Skip compression when: below threshold, client accepts nothing we\n // offer, or the content type isn't worth compressing (already-compressed\n // binary - images, video, octet-stream). For JSON we already have the\n // serialized string; ending with it here avoids re-entering res.json,\n // which would JSON.stringify the same data a second time.\n if (byteLength < threshold || !encoding || !isCompressible(ctHeader)) {\n if (isJson) {\n if (res.headersSent) return;\n res.setHeader('Content-Length', byteLength);\n res.end(content);\n return;\n }\n return originalSend.call(res, data);\n }\n\n const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content ?? '');\n\n // Commit the response headers synchronously, BEFORE the asynchronous\n // compression. This marks headersSent the moment send/json is called, so\n // the \"no route matched\" fallback can't 404 the response mid-compress.\n // The compressed length isn't known yet, so the body streams (chunked).\n if (!res.headersSent) {\n res.setHeader('Content-Encoding', encoding);\n const vary = res.getHeader ? (res.getHeader('vary') as string | undefined) : undefined;\n res.setHeader('Vary', vary ? `${vary}, Accept-Encoding` : 'Accept-Encoding');\n (res as any).writeHead((res as any).statusCode || 200);\n }\n\n compressBuffer(buffer, encoding, level)\n .then(compressed => res.end(compressed))\n .catch(() => res.end()); // headers already committed - can't fall back to raw\n\n return res;\n };\n\n res.json = function (data: any) {\n if (!this.getHeader('Content-Type')) {\n this.setHeader('Content-Type', 'application/json; charset=utf-8');\n }\n compressResponse(data, true, 'application/json');\n return this;\n };\n\n res.send = function (data: any) {\n compressResponse(data, false);\n return this;\n };\n }\n\n getThreshold(): number {\n return this.threshold;\n }\n\n getLevel(): number {\n return this.level;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/compression/core.ts"],"names":[],"mappings":"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","sourcesContent":["// Compression Core Logic\n//\n// The global compression middleware wraps res.json/res.send uniformly across\n// EVERY runtime (Node, uWS, HTTP/2, engine), so it is the single source of\n// response compression parity. It delegates encoding choice and content-type\n// filtering to the shared utility (utils/compression) so brotli, q-value\n// negotiation, and the compressible-type allowlist behave identically here and\n// in the per-server fallback paths.\nimport { HttpRequest, HttpResponse } from '../../../../types/http.js';\nimport {\n compressBuffer,\n negotiateEncoding,\n isCompressible,\n DEFAULT_ENCODINGS,\n type Encoding,\n} from '../../../http/utils/compression.js';\n\nexport interface CompressionOptions {\n threshold?: number;\n level?: number;\n encodings?: Encoding[];\n filter?: (req: HttpRequest, res: HttpResponse) => boolean;\n}\n\nexport class CompressionCore {\n private threshold: number;\n private level: number;\n private encodings: Encoding[];\n private filter?: ((req: HttpRequest, res: HttpResponse) => boolean) | undefined;\n\n constructor(options: CompressionOptions = {}) {\n this.threshold = options.threshold || 1024; // 1KB default\n this.level = options.level || 6; // Default compression level\n this.encodings = options.encodings || DEFAULT_ENCODINGS;\n this.filter = options.filter;\n }\n\n shouldCompress(req: HttpRequest, res: HttpResponse): boolean {\n // Check custom filter\n if (this.filter && !this.filter(req, res)) {\n return false;\n }\n\n // Check if client accepts any encoding we offer\n const acceptEncoding = (req.headers['accept-encoding'] as string) || '';\n return negotiateEncoding(acceptEncoding, this.encodings) !== null;\n }\n\n wrapResponse(req: HttpRequest, res: HttpResponse): void {\n const originalSend = res.send;\n const acceptEncoding = (req.headers['accept-encoding'] as string) || '';\n const level = this.level;\n const threshold = this.threshold;\n const encodings = this.encodings;\n const filterOk = this.filter ? this.filter(req, res) : true;\n\n const compressResponse = (data: any, isJson: boolean, contentType?: string) => {\n const content = isJson ? JSON.stringify(data) : data;\n const byteLength = Buffer.isBuffer(content)\n ? content.length\n : Buffer.byteLength(content ?? '');\n const ctHeader =\n contentType ??\n (res.getHeader ? (res.getHeader('content-type') as string | undefined) : undefined);\n const encoding = filterOk ? negotiateEncoding(acceptEncoding, encodings) : null;\n\n // Skip compression when: below threshold, client accepts nothing we\n // offer, or the content type isn't worth compressing (already-compressed\n // binary - images, video, octet-stream). For JSON we already have the\n // serialized string; ending with it here avoids re-entering res.json,\n // which would JSON.stringify the same data a second time.\n if (byteLength < threshold || !encoding || !isCompressible(ctHeader)) {\n if (isJson) {\n if (res.headersSent) return;\n res.setHeader('Content-Length', byteLength);\n res.end(content);\n return;\n }\n return originalSend.call(res, data);\n }\n\n const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content ?? '');\n\n // Commit the response headers synchronously, BEFORE the asynchronous\n // compression. This marks headersSent the moment send/json is called, so\n // the \"no route matched\" fallback can't 404 the response mid-compress.\n // The compressed length isn't known yet, so the body streams (chunked).\n if (!res.headersSent) {\n // A Content-Length set for the uncompressed body would truncate (or\n // hang) the compressed one; the compressed body streams chunked.\n if (typeof (res as any).removeHeader === 'function') {\n (res as any).removeHeader('Content-Length');\n }\n res.setHeader('Content-Encoding', encoding);\n const vary = res.getHeader ? (res.getHeader('vary') as string | undefined) : undefined;\n res.setHeader('Vary', vary ? `${vary}, Accept-Encoding` : 'Accept-Encoding');\n (res as any).writeHead((res as any).statusCode || 200);\n }\n\n compressBuffer(buffer, encoding, level)\n .then(compressed => res.end(compressed))\n .catch(() => res.end()); // headers already committed - can't fall back to raw\n\n return res;\n };\n\n res.json = function (data: any) {\n if (!this.getHeader('Content-Type')) {\n this.setHeader('Content-Type', 'application/json; charset=utf-8');\n }\n compressResponse(data, true, 'application/json');\n return this;\n };\n\n res.send = function (data: any) {\n compressResponse(data, false);\n return this;\n };\n }\n\n getThreshold(): number {\n return this.threshold;\n }\n\n getLevel(): number {\n return this.level;\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/session/core.ts"],"names":[],"mappings":"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","sourcesContent":["// Session Core - Reusable session management logic\nimport crypto from 'crypto';\nimport { createFrameworkLogger } from '../../../logger/index.js';\nimport { CacheAdapter } from '../../../../types/cache.js';\nimport { MemoryCacheAdapter } from '../cache/adapters/cache/memory.js';\nimport { RedisCacheAdapter } from '../cache/adapters/cache/redis.js';\nimport { FileCacheAdapter } from '../cache/adapters/cache/file.js';\nimport { HttpRequest, HttpResponse } from '../../../../types/http.js';\nimport { signCookieValue, unsignCookieValue, isSignedCookieValue } from '../cookie/core.js';\n\nconst logger = createFrameworkLogger('SessionCore');\n\n// ===== Types =====\n\nexport interface SessionOptions {\n // Session store configuration\n store?: 'memory' | 'redis' | 'file' | CacheAdapter;\n storeOptions?: {\n // Redis options\n host?: string;\n port?: number;\n password?: string;\n keyPrefix?: string;\n // File options\n path?: string;\n // Memory options\n max?: number;\n };\n\n // Session configuration\n secret?: string;\n name?: string; // Session cookie name\n genid?: () => string; // Session ID generator\n rolling?: boolean; // Reset expiry on each request\n resave?: boolean; // Save session even if not modified\n saveUninitialized?: boolean; // Save new but not modified sessions\n\n // Cookie configuration\n cookie?: {\n maxAge?: number; // Session timeout in ms\n expires?: Date; // Absolute expiry\n httpOnly?: boolean; // Prevent XSS access\n secure?: boolean; // HTTPS only\n sameSite?: 'strict' | 'lax' | 'none';\n domain?: string;\n path?: string;\n };\n\n // Security\n proxy?: boolean; // Trust proxy for secure cookies\n unset?: 'destroy' | 'keep'; // What to do when session is unset\n}\n\nexport interface SessionData {\n [key: string]: any;\n cookie?: {\n originalMaxAge?: number;\n expires?: Date;\n secure?: boolean;\n httpOnly?: boolean;\n sameSite?: string;\n };\n}\n\n// ===== Session Class =====\n\nexport class Session {\n private data: SessionData = {};\n private id: string;\n private store: CacheAdapter;\n private options: SessionOptions;\n private isNew: boolean = false;\n private isModified: boolean = false;\n\n constructor(id: string, store: CacheAdapter, options: SessionOptions, isNew: boolean = false) {\n this.id = id;\n this.store = store;\n this.options = options;\n this.isNew = isNew;\n }\n\n // Proxy to make session.prop = value work\n static create(\n id: string,\n store: CacheAdapter,\n options: SessionOptions,\n data: SessionData = {},\n isNew: boolean = false\n ): Session {\n const session = new Session(id, store, options, isNew);\n session.data = data;\n\n return new Proxy(session, {\n get(target, prop) {\n if (prop in target) {\n return target[prop as keyof Session];\n }\n return target.data[prop as string];\n },\n\n set(target, prop, value) {\n if (prop in target) {\n (target as any)[prop] = value;\n } else {\n target.data[prop as string] = value;\n target.isModified = true;\n }\n return true;\n },\n\n has(target, prop) {\n return prop in target || prop in target.data;\n },\n\n deleteProperty(target, prop) {\n if (prop in target.data) {\n delete target.data[prop as string];\n target.isModified = true;\n return true;\n }\n return false;\n },\n });\n }\n\n async save(): Promise<void> {\n if (this.isModified || this.isNew || this.options.resave) {\n const ttl = this.options.cookie?.maxAge\n ? Math.floor(this.options.cookie.maxAge / 1000)\n : 86400; // 24h default\n await this.store.set(this.id, this.data, ttl);\n this.isModified = false;\n this.isNew = false;\n logger.debug(`Session saved: ${this.id}`, 'SessionSave');\n }\n }\n\n async destroy(): Promise<void> {\n await this.store.del(this.id);\n this.data = {};\n this.isModified = false;\n logger.debug(`Session destroyed: ${this.id}`, 'SessionDestroy');\n }\n\n async regenerate(): Promise<string> {\n await this.destroy();\n this.id = this.generateId();\n this.isNew = true;\n this.isModified = true;\n logger.debug(`Session regenerated: ${this.id}`, 'SessionRegenerate');\n return this.id;\n }\n\n async touch(): Promise<void> {\n if (this.options.rolling) {\n this.isModified = true;\n await this.save();\n }\n }\n\n private generateId(): string {\n if (this.options.genid) {\n return this.options.genid();\n }\n\n return crypto.randomBytes(24).toString('hex');\n }\n\n get sessionID(): string {\n return this.id;\n }\n}\n\n// ===== SessionCore =====\n\n/**\n * SessionCore - Core session management logic\n * Used directly by the router for route-based session handling\n * Can be instantiated for use in middleware or hooks\n */\nexport class SessionCore {\n private store: CacheAdapter;\n private options: SessionOptions;\n\n constructor(options: SessionOptions = {}) {\n this.options = {\n store: 'memory',\n name: 'connect.sid',\n rolling: false,\n resave: false,\n saveUninitialized: false,\n cookie: {\n maxAge: 24 * 60 * 60 * 1000, // 24 hours\n httpOnly: true,\n secure: false,\n sameSite: 'lax',\n path: '/',\n },\n unset: 'keep',\n ...options,\n };\n\n // Initialize store\n if (typeof this.options.store === 'string') {\n switch (this.options.store) {\n case 'redis':\n this.store = new RedisCacheAdapter({\n keyPrefix: 'sess:',\n ...this.options.storeOptions,\n });\n break;\n case 'file':\n this.store = new FileCacheAdapter({\n cacheDir: this.options.storeOptions?.path || './sessions',\n });\n break;\n case 'memory':\n default:\n this.store = new MemoryCacheAdapter();\n break;\n }\n } else {\n this.store = this.options.store as CacheAdapter;\n }\n }\n\n generateSessionId(): string {\n if (this.options.genid) {\n return this.options.genid();\n }\n return crypto.randomBytes(24).toString('hex');\n }\n\n async loadSession(sessionId: string): Promise<SessionData | null> {\n if (!sessionId) {\n return null;\n }\n\n try {\n const data = await this.store.get(sessionId);\n logger.debug(`Session loaded: ${sessionId}`, 'SessionLoad');\n return data || null;\n } catch (error) {\n logger.warn(`Failed to load session: ${sessionId}`, 'SessionLoadError', { error });\n return null;\n }\n }\n\n /** Cookie value carrying an id — signed when a secret is configured. */\n private encodeSessionId(id: string): string {\n return this.options.secret ? signCookieValue(id, this.options.secret) : id;\n }\n\n /**\n * The session id the request carries, or undefined when there is none and\n * when a signed id fails verification — a tampered id must start a fresh\n * session rather than load someone else's.\n */\n readSessionId(req: HttpRequest): string | undefined {\n const name = this.options.name || 'connect.sid';\n // The cookie middleware verifies and strips signatures when it shares the\n // secret, in which case the id arrives already unwrapped.\n const verified = (req as any).signedCookies?.[name] as string | undefined;\n const raw = verified ?? req.cookies?.[name];\n if (!raw) return undefined;\n\n const secret = this.options.secret;\n if (!secret) return raw;\n\n if (isSignedCookieValue(raw)) {\n return unsignCookieValue(raw, secret) ?? undefined;\n }\n\n // A bare value is only trustworthy if it was verified upstream\n return verified;\n }\n\n async createSession(req: HttpRequest, res: HttpResponse, sessionId?: string): Promise<Session> {\n const id = sessionId || this.generateSessionId();\n const session = Session.create(id, this.store, this.options, {}, true);\n\n // Set session cookie\n res.cookie(this.options.name || 'connect.sid', this.encodeSessionId(id), {\n ...this.options.cookie,\n secure: !!(\n this.options.cookie?.secure ||\n (this.options.proxy && req.headers['x-forwarded-proto'] === 'https')\n ),\n });\n\n return session;\n }\n\n async attachSession(req: HttpRequest, res: HttpResponse, sessionId?: string): Promise<Session> {\n let id = sessionId;\n let sessionData: SessionData = {};\n let isNew = false;\n\n if (id) {\n sessionData = (await this.loadSession(id)) || {};\n // Fast empty check without Object.keys\n if (!sessionData) {\n id = this.generateSessionId();\n isNew = true;\n } else {\n let hasData = false;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n for (const _ in sessionData) {\n hasData = true;\n break;\n }\n if (!hasData) {\n id = this.generateSessionId();\n isNew = true;\n }\n }\n } else {\n id = this.generateSessionId();\n isNew = true;\n }\n\n const session = Session.create(id, this.store, this.options, sessionData, isNew);\n\n // Set session cookie if new or rolling\n if (isNew || this.options.rolling) {\n res.cookie(this.options.name || 'connect.sid', this.encodeSessionId(id), {\n ...this.options.cookie,\n secure: !!(\n this.options.cookie?.secure ||\n (this.options.proxy && req.headers['x-forwarded-proto'] === 'https')\n ),\n });\n }\n\n return session;\n }\n\n async saveSession(session: Session): Promise<void> {\n if (!session) {\n return;\n }\n\n try {\n if (\n this.options.saveUninitialized ||\n !(session as any).isNew ||\n (session as any).isModified\n ) {\n await session.save();\n }\n } catch (error) {\n logger.error('Failed to save session', 'SessionSaveError', { error });\n }\n }\n\n getStore(): CacheAdapter {\n return this.store;\n }\n\n getOptions(): SessionOptions {\n return this.options;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/session/core.ts"],"names":[],"mappings":"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","sourcesContent":["// Session Core - Reusable session management logic\nimport crypto from 'crypto';\nimport { createFrameworkLogger } from '../../../logger/index.js';\nimport { CacheAdapter } from '../../../../types/cache.js';\nimport { MemoryCacheAdapter } from '../cache/adapters/cache/memory.js';\nimport { RedisCacheAdapter } from '../cache/adapters/cache/redis.js';\nimport { FileCacheAdapter } from '../cache/adapters/cache/file.js';\nimport { HttpRequest, HttpResponse } from '../../../../types/http.js';\nimport { signCookieValue, unsignCookieValue, isSignedCookieValue } from '../cookie/core.js';\n\nconst logger = createFrameworkLogger('SessionCore');\n\n// ===== Types =====\n\nexport interface SessionOptions {\n // Session store configuration\n store?: 'memory' | 'redis' | 'file' | CacheAdapter;\n storeOptions?: {\n // Redis options (node-redis 4+; url wins over host/port)\n url?: string;\n host?: string;\n port?: number;\n password?: string;\n keyPrefix?: string;\n // File options\n path?: string;\n // Memory options\n max?: number;\n };\n\n // Session configuration\n secret?: string;\n name?: string; // Session cookie name\n genid?: () => string; // Session ID generator\n rolling?: boolean; // Reset expiry on each request\n resave?: boolean; // Save session even if not modified\n saveUninitialized?: boolean; // Save new but not modified sessions\n\n // Cookie configuration\n cookie?: {\n maxAge?: number; // Session timeout in ms\n expires?: Date; // Absolute expiry\n httpOnly?: boolean; // Prevent XSS access\n secure?: boolean; // HTTPS only\n sameSite?: 'strict' | 'lax' | 'none';\n domain?: string;\n path?: string;\n };\n\n // Security\n proxy?: boolean; // Trust proxy for secure cookies\n unset?: 'destroy' | 'keep'; // What to do when session is unset\n}\n\nexport interface SessionData {\n [key: string]: any;\n cookie?: {\n originalMaxAge?: number;\n expires?: Date;\n secure?: boolean;\n httpOnly?: boolean;\n sameSite?: string;\n };\n}\n\n// ===== Session Class =====\n\nexport class Session {\n private data: SessionData = {};\n private id: string;\n private store: CacheAdapter;\n private options: SessionOptions;\n private isNew: boolean = false;\n private isModified: boolean = false;\n\n constructor(id: string, store: CacheAdapter, options: SessionOptions, isNew: boolean = false) {\n this.id = id;\n this.store = store;\n this.options = options;\n this.isNew = isNew;\n }\n\n // Proxy to make session.prop = value work\n static create(\n id: string,\n store: CacheAdapter,\n options: SessionOptions,\n data: SessionData = {},\n isNew: boolean = false\n ): Session {\n const session = new Session(id, store, options, isNew);\n session.data = data;\n\n return new Proxy(session, {\n get(target, prop) {\n if (prop in target) {\n return target[prop as keyof Session];\n }\n return target.data[prop as string];\n },\n\n set(target, prop, value) {\n if (prop in target) {\n (target as any)[prop] = value;\n } else {\n target.data[prop as string] = value;\n target.isModified = true;\n }\n return true;\n },\n\n has(target, prop) {\n return prop in target || prop in target.data;\n },\n\n deleteProperty(target, prop) {\n if (prop in target.data) {\n delete target.data[prop as string];\n target.isModified = true;\n return true;\n }\n return false;\n },\n });\n }\n\n async save(): Promise<void> {\n if (this.isModified || this.isNew || this.options.resave) {\n const ttl = this.options.cookie?.maxAge\n ? Math.floor(this.options.cookie.maxAge / 1000)\n : 86400; // 24h default\n await this.store.set(this.id, this.data, ttl);\n this.isModified = false;\n this.isNew = false;\n logger.debug(`Session saved: ${this.id}`, 'SessionSave');\n }\n }\n\n async destroy(): Promise<void> {\n await this.store.del(this.id);\n this.data = {};\n this.isModified = false;\n logger.debug(`Session destroyed: ${this.id}`, 'SessionDestroy');\n }\n\n async regenerate(): Promise<string> {\n await this.destroy();\n this.id = this.generateId();\n this.isNew = true;\n this.isModified = true;\n logger.debug(`Session regenerated: ${this.id}`, 'SessionRegenerate');\n return this.id;\n }\n\n async touch(): Promise<void> {\n if (this.options.rolling) {\n this.isModified = true;\n await this.save();\n }\n }\n\n private generateId(): string {\n if (this.options.genid) {\n return this.options.genid();\n }\n\n return crypto.randomBytes(24).toString('hex');\n }\n\n get sessionID(): string {\n return this.id;\n }\n}\n\n// ===== SessionCore =====\n\n/**\n * SessionCore - Core session management logic\n * Used directly by the router for route-based session handling\n * Can be instantiated for use in middleware or hooks\n */\nexport class SessionCore {\n private store: CacheAdapter;\n private options: SessionOptions;\n\n constructor(options: SessionOptions = {}) {\n this.options = {\n store: 'memory',\n name: 'connect.sid',\n rolling: false,\n resave: false,\n saveUninitialized: false,\n cookie: {\n maxAge: 24 * 60 * 60 * 1000, // 24 hours\n httpOnly: true,\n secure: false,\n sameSite: 'lax',\n path: '/',\n },\n unset: 'keep',\n ...options,\n };\n\n // Initialize store\n if (typeof this.options.store === 'string') {\n switch (this.options.store) {\n case 'redis':\n this.store = new RedisCacheAdapter({\n keyPrefix: 'sess:',\n ...this.options.storeOptions,\n });\n break;\n case 'file':\n this.store = new FileCacheAdapter({\n cacheDir: this.options.storeOptions?.path || './sessions',\n });\n break;\n case 'memory':\n default:\n this.store = new MemoryCacheAdapter();\n break;\n }\n } else {\n this.store = this.options.store as CacheAdapter;\n }\n }\n\n generateSessionId(): string {\n if (this.options.genid) {\n return this.options.genid();\n }\n return crypto.randomBytes(24).toString('hex');\n }\n\n async loadSession(sessionId: string): Promise<SessionData | null> {\n if (!sessionId) {\n return null;\n }\n\n try {\n const data = await this.store.get(sessionId);\n logger.debug(`Session loaded: ${sessionId}`, 'SessionLoad');\n return data || null;\n } catch (error) {\n logger.warn(`Failed to load session: ${sessionId}`, 'SessionLoadError', { error });\n return null;\n }\n }\n\n /** Cookie value carrying an id — signed when a secret is configured. */\n private encodeSessionId(id: string): string {\n return this.options.secret ? signCookieValue(id, this.options.secret) : id;\n }\n\n /**\n * The session id the request carries, or undefined when there is none and\n * when a signed id fails verification — a tampered id must start a fresh\n * session rather than load someone else's.\n */\n readSessionId(req: HttpRequest): string | undefined {\n const name = this.options.name || 'connect.sid';\n // The cookie middleware verifies and strips signatures when it shares the\n // secret, in which case the id arrives already unwrapped.\n const verified = (req as any).signedCookies?.[name] as string | undefined;\n const raw = verified ?? req.cookies?.[name];\n if (!raw) return undefined;\n\n const secret = this.options.secret;\n if (!secret) return raw;\n\n if (isSignedCookieValue(raw)) {\n return unsignCookieValue(raw, secret) ?? undefined;\n }\n\n // A bare value is only trustworthy if it was verified upstream\n return verified;\n }\n\n async createSession(req: HttpRequest, res: HttpResponse, sessionId?: string): Promise<Session> {\n const id = sessionId || this.generateSessionId();\n const session = Session.create(id, this.store, this.options, {}, true);\n\n // Set session cookie\n res.cookie(this.options.name || 'connect.sid', this.encodeSessionId(id), {\n ...this.options.cookie,\n secure: !!(\n this.options.cookie?.secure ||\n (this.options.proxy && req.headers['x-forwarded-proto'] === 'https')\n ),\n });\n\n return session;\n }\n\n async attachSession(req: HttpRequest, res: HttpResponse, sessionId?: string): Promise<Session> {\n let id = sessionId;\n let sessionData: SessionData = {};\n let isNew = false;\n\n if (id) {\n sessionData = (await this.loadSession(id)) || {};\n // Fast empty check without Object.keys\n if (!sessionData) {\n id = this.generateSessionId();\n isNew = true;\n } else {\n let hasData = false;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n for (const _ in sessionData) {\n hasData = true;\n break;\n }\n if (!hasData) {\n id = this.generateSessionId();\n isNew = true;\n }\n }\n } else {\n id = this.generateSessionId();\n isNew = true;\n }\n\n const session = Session.create(id, this.store, this.options, sessionData, isNew);\n\n // Set session cookie if new or rolling\n if (isNew || this.options.rolling) {\n res.cookie(this.options.name || 'connect.sid', this.encodeSessionId(id), {\n ...this.options.cookie,\n secure: !!(\n this.options.cookie?.secure ||\n (this.options.proxy && req.headers['x-forwarded-proto'] === 'https')\n ),\n });\n }\n\n return session;\n }\n\n async saveSession(session: Session): Promise<void> {\n if (!session) {\n return;\n }\n\n try {\n if (\n this.options.saveUninitialized ||\n !(session as any).isNew ||\n (session as any).isModified\n ) {\n await session.save();\n }\n } catch (error) {\n logger.error('Failed to save session', 'SessionSaveError', { error });\n }\n }\n\n getStore(): CacheAdapter {\n return this.store;\n }\n\n getOptions(): SessionOptions {\n return this.options;\n }\n}\n"]}
|
|
@@ -23,6 +23,15 @@ export interface StaticOptions {
|
|
|
23
23
|
* multiple ranges is answered with the whole entity. Default true.
|
|
24
24
|
*/
|
|
25
25
|
acceptRanges?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Serve precompressed sidecars: when `app.js.br` or `app.js.gz` sits next
|
|
28
|
+
* to `app.js` and the client accepts that encoding, the sidecar is sent
|
|
29
|
+
* with the original's Content-Type, the matching Content-Encoding and
|
|
30
|
+
* `Vary: Accept-Encoding` (brotli preferred, then gzip). A sidecar older
|
|
31
|
+
* than its source is ignored, so a replaced file is never served stale.
|
|
32
|
+
* Range requests always get the identity file. Default false.
|
|
33
|
+
*/
|
|
34
|
+
precompressed?: boolean;
|
|
26
35
|
}
|
|
27
36
|
export declare class StaticCore {
|
|
28
37
|
private root;
|
|
@@ -33,11 +42,20 @@ export declare class StaticCore {
|
|
|
33
42
|
private etag;
|
|
34
43
|
private lastModified;
|
|
35
44
|
private acceptRanges;
|
|
45
|
+
private precompressed;
|
|
36
46
|
/** Files at or below this size are read in one go instead of streamed. */
|
|
37
47
|
private static readonly STREAM_THRESHOLD;
|
|
38
48
|
private mimeTypes;
|
|
39
49
|
constructor(options: StaticOptions);
|
|
40
50
|
handleRequest(req: HttpRequest, res: HttpResponse): Promise<boolean>;
|
|
51
|
+
/**
|
|
52
|
+
* The best precompressed sidecar for `filePath` that the client accepts, or
|
|
53
|
+
* null. A sidecar must be a regular file INSIDE the root (its real path is
|
|
54
|
+
* checked like the source's, so a symlinked `.br` cannot reach outside) and
|
|
55
|
+
* at least as new as the source, so a replaced asset never ships a stale
|
|
56
|
+
* compressed body.
|
|
57
|
+
*/
|
|
58
|
+
private findSidecar;
|
|
41
59
|
/**
|
|
42
60
|
* Whether the client's cached copy is still current. `If-None-Match` wins
|
|
43
61
|
* over `If-Modified-Since` when both are present (RFC 9110 13.1.3).
|