@morojs/moro 1.8.12 → 1.8.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) 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 +20 -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/config/schema.js +1 -0
  10. package/dist/core/config/schema.js.map +1 -1
  11. package/dist/core/framework.d.ts +26 -0
  12. package/dist/core/framework.js +195 -6
  13. package/dist/core/framework.js.map +1 -1
  14. package/dist/core/http/http-server.d.ts +19 -0
  15. package/dist/core/http/http-server.js +75 -1
  16. package/dist/core/http/http-server.js.map +1 -1
  17. package/dist/core/http/http2-server.d.ts +15 -0
  18. package/dist/core/http/http2-server.js +36 -3
  19. package/dist/core/http/http2-server.js.map +1 -1
  20. package/dist/core/http/moro-engine-server.d.ts +28 -0
  21. package/dist/core/http/moro-engine-server.js +138 -12
  22. package/dist/core/http/moro-engine-server.js.map +1 -1
  23. package/dist/core/http/utils/body-type.d.ts +12 -0
  24. package/dist/core/http/utils/body-type.js +70 -0
  25. package/dist/core/http/utils/body-type.js.map +1 -0
  26. package/dist/core/http/utils/compression.d.ts +21 -0
  27. package/dist/core/http/utils/compression.js +53 -7
  28. package/dist/core/http/utils/compression.js.map +1 -1
  29. package/dist/core/http/utils/ssl-config.d.ts +11 -0
  30. package/dist/core/http/utils/ssl-config.js +3 -0
  31. package/dist/core/http/utils/ssl-config.js.map +1 -1
  32. package/dist/core/http/utils/uws-worker-clustering.js +5 -4
  33. package/dist/core/http/utils/uws-worker-clustering.js.map +1 -1
  34. package/dist/core/http/uws-http-server.d.ts +3 -0
  35. package/dist/core/http/uws-http-server.js +47 -2
  36. package/dist/core/http/uws-http-server.js.map +1 -1
  37. package/dist/core/middleware/built-in/body-parsers/index.d.ts +18 -0
  38. package/dist/core/middleware/built-in/body-parsers/index.js +41 -0
  39. package/dist/core/middleware/built-in/body-parsers/index.js.map +1 -1
  40. package/dist/core/middleware/built-in/cache/adapters/cache/redis.d.ts +32 -8
  41. package/dist/core/middleware/built-in/cache/adapters/cache/redis.js +117 -25
  42. package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -1
  43. package/dist/core/middleware/built-in/compression/core.d.ts +7 -0
  44. package/dist/core/middleware/built-in/compression/core.js +32 -1
  45. package/dist/core/middleware/built-in/compression/core.js.map +1 -1
  46. package/dist/core/middleware/built-in/session/core.d.ts +1 -0
  47. package/dist/core/middleware/built-in/session/core.js.map +1 -1
  48. package/dist/core/middleware/built-in/static/core.d.ts +18 -0
  49. package/dist/core/middleware/built-in/static/core.js +128 -7
  50. package/dist/core/middleware/built-in/static/core.js.map +1 -1
  51. package/dist/core/networking/adapters/engine-adapter.d.ts +2 -2
  52. package/dist/core/networking/adapters/engine-adapter.js +40 -1
  53. package/dist/core/networking/adapters/engine-adapter.js.map +1 -1
  54. package/dist/core/networking/adapters/socketio-adapter.d.ts +2 -2
  55. package/dist/core/networking/adapters/socketio-adapter.js +10 -1
  56. package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
  57. package/dist/core/networking/adapters/uws-adapter.d.ts +2 -2
  58. package/dist/core/networking/adapters/uws-adapter.js +54 -3
  59. package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
  60. package/dist/core/networking/adapters/ws-adapter.d.ts +9 -2
  61. package/dist/core/networking/adapters/ws-adapter.js +97 -12
  62. package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
  63. package/dist/core/networking/websocket-adapter.d.ts +19 -1
  64. package/dist/core/networking/websocket-adapter.js.map +1 -1
  65. package/dist/core/routing/index.d.ts +12 -8
  66. package/dist/core/routing/index.js +7 -2
  67. package/dist/core/routing/index.js.map +1 -1
  68. package/dist/core/routing/unified-router.d.ts +46 -8
  69. package/dist/core/routing/unified-router.js +108 -32
  70. package/dist/core/routing/unified-router.js.map +1 -1
  71. package/dist/core/utilities/package-utils.d.ts +8 -1
  72. package/dist/core/utilities/package-utils.js +2 -0
  73. package/dist/core/utilities/package-utils.js.map +1 -1
  74. package/dist/core/workers/worker-manager.js +2 -2
  75. package/dist/core/workers/worker-manager.js.map +1 -1
  76. package/dist/index.d.ts +4 -3
  77. package/dist/index.js +2 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/moro.d.ts +12 -1
  80. package/dist/moro.js +63 -7
  81. package/dist/moro.js.map +1 -1
  82. package/dist/types/cache.d.ts +2 -0
  83. package/dist/types/cache.js.map +1 -1
  84. package/dist/types/config.d.ts +16 -0
  85. package/dist/types/config.js.map +1 -1
  86. package/dist/types/http.d.ts +6 -0
  87. package/dist/types/http.js.map +1 -1
  88. package/dist/types/session.d.ts +1 -0
  89. package/dist/types/session.js.map +1 -1
  90. 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"]}
@@ -4,12 +4,19 @@ export interface CompressionOptions {
4
4
  threshold?: number;
5
5
  level?: number;
6
6
  encodings?: Encoding[];
7
+ /**
8
+ * Bodies at or below this size are compressed on the calling thread and
9
+ * sent with a Content-Length; larger ones go to the threadpool and stream
10
+ * chunked. 0 sends every body through the pool. Default 16384.
11
+ */
12
+ maxInlineBytes?: number;
7
13
  filter?: (req: HttpRequest, res: HttpResponse) => boolean;
8
14
  }
9
15
  export declare class CompressionCore {
10
16
  private threshold;
11
17
  private level;
12
18
  private encodings;
19
+ private maxInlineBytes;
13
20
  private filter?;
14
21
  constructor(options?: CompressionOptions);
15
22
  shouldCompress(req: HttpRequest, res: HttpResponse): boolean;
@@ -1,13 +1,15 @@
1
- import { compressBuffer, negotiateEncoding, isCompressible, DEFAULT_ENCODINGS, } from '../../../http/utils/compression.js';
1
+ import { compressBuffer, compressBufferSync, negotiateEncoding, isCompressible, DEFAULT_ENCODINGS, DEFAULT_MAX_INLINE_BYTES, } from '../../../http/utils/compression.js';
2
2
  export class CompressionCore {
3
3
  threshold;
4
4
  level;
5
5
  encodings;
6
+ maxInlineBytes;
6
7
  filter;
7
8
  constructor(options = {}) {
8
9
  this.threshold = options.threshold || 1024; // 1KB default
9
10
  this.level = options.level || 6; // Default compression level
10
11
  this.encodings = options.encodings || DEFAULT_ENCODINGS;
12
+ this.maxInlineBytes = options.maxInlineBytes ?? DEFAULT_MAX_INLINE_BYTES;
11
13
  this.filter = options.filter;
12
14
  }
13
15
  shouldCompress(req, res) {
@@ -25,6 +27,7 @@ export class CompressionCore {
25
27
  const level = this.level;
26
28
  const threshold = this.threshold;
27
29
  const encodings = this.encodings;
30
+ const maxInline = this.maxInlineBytes;
28
31
  const filterOk = this.filter ? this.filter(req, res) : true;
29
32
  const compressResponse = (data, isJson, contentType) => {
30
33
  const content = isJson ? JSON.stringify(data) : data;
@@ -50,11 +53,39 @@ export class CompressionCore {
50
53
  return originalSend.call(res, data);
51
54
  }
52
55
  const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content ?? '');
56
+ // A small body is compressed here and now: cheaper than the round trip
57
+ // through the threadpool, and the response carries a Content-Length
58
+ // instead of streaming chunked. A failure falls back to the plain body.
59
+ if (maxInline > 0 && byteLength <= maxInline && !res.headersSent) {
60
+ let compressed;
61
+ try {
62
+ compressed = compressBufferSync(buffer, encoding, level);
63
+ }
64
+ catch {
65
+ if (isJson) {
66
+ res.setHeader('Content-Length', byteLength);
67
+ res.end(content);
68
+ return;
69
+ }
70
+ return originalSend.call(res, data);
71
+ }
72
+ res.setHeader('Content-Encoding', encoding);
73
+ const vary = res.getHeader ? res.getHeader('vary') : undefined;
74
+ res.setHeader('Vary', vary ? `${vary}, Accept-Encoding` : 'Accept-Encoding');
75
+ res.setHeader('Content-Length', compressed.length);
76
+ res.end(compressed);
77
+ return res;
78
+ }
53
79
  // Commit the response headers synchronously, BEFORE the asynchronous
54
80
  // compression. This marks headersSent the moment send/json is called, so
55
81
  // the "no route matched" fallback can't 404 the response mid-compress.
56
82
  // The compressed length isn't known yet, so the body streams (chunked).
57
83
  if (!res.headersSent) {
84
+ // A Content-Length set for the uncompressed body would truncate (or
85
+ // hang) the compressed one; the compressed body streams chunked.
86
+ if (typeof res.removeHeader === 'function') {
87
+ res.removeHeader('Content-Length');
88
+ }
58
89
  res.setHeader('Content-Encoding', encoding);
59
90
  const vary = res.getHeader ? res.getHeader('vary') : undefined;
60
91
  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":"AASA,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,wBAAwB,GAEzB,MAAM,oCAAoC,CAAC;AAe5C,MAAM,OAAO,eAAe;IAClB,SAAS,CAAS;IAClB,KAAK,CAAS;IACd,SAAS,CAAa;IACtB,cAAc,CAAS;IACvB,MAAM,CAAkE;IAEhF,YAAY,UAA8B,EAAE;QAC1C,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC,cAAc;QAC1D,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,4BAA4B;QAC7D,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,iBAAiB,CAAC;QACxD,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,wBAAwB,CAAC;QACzE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC/B,CAAC;IAED,cAAc,CAAC,GAAgB,EAAE,GAAiB;QAChD,sBAAsB;QACtB,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;YAC1C,OAAO,KAAK,CAAC;QACf,CAAC;QAED,gDAAgD;QAChD,MAAM,cAAc,GAAI,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAY,IAAI,EAAE,CAAC;QACxE,OAAO,iBAAiB,CAAC,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;IACpE,CAAC;IAED,YAAY,CAAC,GAAgB,EAAE,GAAiB;QAC9C,MAAM,YAAY,GAAG,GAAG,CAAC,IAAI,CAAC;QAC9B,MAAM,cAAc,GAAI,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAY,IAAI,EAAE,CAAC;QACxE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QACjC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QACjC,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAE5D,MAAM,gBAAgB,GAAG,CAAC,IAAS,EAAE,MAAe,EAAE,WAAoB,EAAE,EAAE;YAC5E,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YACrD,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;gBACzC,CAAC,CAAC,OAAO,CAAC,MAAM;gBAChB,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;YACrC,MAAM,QAAQ,GACZ,WAAW;gBACX,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAE,GAAG,CAAC,SAAS,CAAC,cAAc,CAAwB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACtF,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAEhF,oEAAoE;YACpE,yEAAyE;YACzE,sEAAsE;YACtE,sEAAsE;YACtE,0DAA0D;YAC1D,IAAI,UAAU,GAAG,SAAS,IAAI,CAAC,QAAQ,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACrE,IAAI,MAAM,EAAE,CAAC;oBACX,IAAI,GAAG,CAAC,WAAW;wBAAE,OAAO;oBAC5B,GAAG,CAAC,SAAS,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;oBAC5C,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBACjB,OAAO;gBACT,CAAC;gBACD,OAAO,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACtC,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;YAE/E,uEAAuE;YACvE,oEAAoE;YACpE,wEAAwE;YACxE,IAAI,SAAS,GAAG,CAAC,IAAI,UAAU,IAAI,SAAS,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;gBACjE,IAAI,UAAkB,CAAC;gBACvB,IAAI,CAAC;oBACH,UAAU,GAAG,kBAAkB,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;gBAC3D,CAAC;gBAAC,MAAM,CAAC;oBACP,IAAI,MAAM,EAAE,CAAC;wBACX,GAAG,CAAC,SAAS,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;wBAC5C,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;wBACjB,OAAO;oBACT,CAAC;oBACD,OAAO,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBACtC,CAAC;gBACD,GAAG,CAAC,SAAS,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;gBAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAE,GAAG,CAAC,SAAS,CAAC,MAAM,CAAwB,CAAC,CAAC,CAAC,SAAS,CAAC;gBACvF,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,mBAAmB,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;gBAC7E,GAAG,CAAC,SAAS,CAAC,gBAAgB,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;gBACnD,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBACpB,OAAO,GAAG,CAAC;YACb,CAAC;YAED,qEAAqE;YACrE,yEAAyE;YACzE,uEAAuE;YACvE,wEAAwE;YACxE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;gBACrB,oEAAoE;gBACpE,iEAAiE;gBACjE,IAAI,OAAQ,GAAW,CAAC,YAAY,KAAK,UAAU,EAAE,CAAC;oBACnD,GAAW,CAAC,YAAY,CAAC,gBAAgB,CAAC,CAAC;gBAC9C,CAAC;gBACD,GAAG,CAAC,SAAS,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;gBAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAE,GAAG,CAAC,SAAS,CAAC,MAAM,CAAwB,CAAC,CAAC,CAAC,SAAS,CAAC;gBACvF,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,mBAAmB,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;gBAC5E,GAAW,CAAC,SAAS,CAAE,GAAW,CAAC,UAAU,IAAI,GAAG,CAAC,CAAC;YACzD,CAAC;YAED,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC;iBACpC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;iBACvC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,qDAAqD;YAEhF,OAAO,GAAG,CAAC;QACb,CAAC,CAAC;QAEF,GAAG,CAAC,IAAI,GAAG,UAAU,IAAS;YAC5B,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,EAAE,CAAC;gBACpC,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,iCAAiC,CAAC,CAAC;YACpE,CAAC;YACD,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC;YACjD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;QAEF,GAAG,CAAC,IAAI,GAAG,UAAU,IAAS;YAC5B,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC9B,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;IACJ,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;CACF","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 compressBufferSync,\n negotiateEncoding,\n isCompressible,\n DEFAULT_ENCODINGS,\n DEFAULT_MAX_INLINE_BYTES,\n type Encoding,\n} from '../../../http/utils/compression.js';\n\nexport interface CompressionOptions {\n threshold?: number;\n level?: number;\n encodings?: Encoding[];\n /**\n * Bodies at or below this size are compressed on the calling thread and\n * sent with a Content-Length; larger ones go to the threadpool and stream\n * chunked. 0 sends every body through the pool. Default 16384.\n */\n maxInlineBytes?: number;\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 maxInlineBytes: number;\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.maxInlineBytes = options.maxInlineBytes ?? DEFAULT_MAX_INLINE_BYTES;\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 maxInline = this.maxInlineBytes;\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 // A small body is compressed here and now: cheaper than the round trip\n // through the threadpool, and the response carries a Content-Length\n // instead of streaming chunked. A failure falls back to the plain body.\n if (maxInline > 0 && byteLength <= maxInline && !res.headersSent) {\n let compressed: Buffer;\n try {\n compressed = compressBufferSync(buffer, encoding, level);\n } catch {\n if (isJson) {\n res.setHeader('Content-Length', byteLength);\n res.end(content);\n return;\n }\n return originalSend.call(res, data);\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.setHeader('Content-Length', compressed.length);\n res.end(compressed);\n return res;\n }\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;