@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
@@ -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":"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","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
- constructor(options?: {
5
- host?: string;
6
- port?: number;
7
- password?: string;
8
- db?: number;
9
- keyPrefix?: string;
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.initPromise = this.initialize(options);
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
- try {
12
- const redisPath = resolveUserPackage('redis');
13
- const redis = await import(redisPath);
14
- this.client = redis.default.createClient({
15
- host: options.host || 'localhost',
16
- port: options.port || 6379,
17
- password: options.password,
18
- db: options.db || 0,
19
- key_prefix: options.keyPrefix || 'moro:cache:',
20
- });
21
- this.client.on('error', (err) => {
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
- catch {
27
- logger.error('Redis not available, falling back to memory cache', 'RedisCache');
28
- throw new Error('Redis package not installed. Run: npm install redis');
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 value = await this.client.get(key);
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
- await this.client.setex(key, ttl, serialized);
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 this.client.set(key, serialized);
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.client.del(key);
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.client.flushdb();
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 exists = await this.client.exists(key);
77
- return exists === 1;
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
- return await this.client.ttl(key);
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"]}
@@ -3,6 +3,7 @@ import { HttpRequest, HttpResponse } from '../../../../types/http.js';
3
3
  export interface SessionOptions {
4
4
  store?: 'memory' | 'redis' | 'file' | CacheAdapter;
5
5
  storeOptions?: {
6
+ url?: string;
6
7
  host?: string;
7
8
  port?: number;
8
9
  password?: string;
@@ -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).