@morojs/moro 1.8.6 → 1.8.8

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 (68) hide show
  1. package/dist/core/http/http-server.d.ts +5 -3
  2. package/dist/core/http/http-server.js +42 -11
  3. package/dist/core/http/http-server.js.map +1 -1
  4. package/dist/core/http/http2-server.d.ts +1 -1
  5. package/dist/core/http/http2-server.js +27 -9
  6. package/dist/core/http/http2-server.js.map +1 -1
  7. package/dist/core/http/moro-engine-server.d.ts +24 -4
  8. package/dist/core/http/moro-engine-server.js +198 -42
  9. package/dist/core/http/moro-engine-server.js.map +1 -1
  10. package/dist/core/http/uws-http-server.js +4 -0
  11. package/dist/core/http/uws-http-server.js.map +1 -1
  12. package/dist/core/middleware/built-in/cookie/core.d.ts +26 -1
  13. package/dist/core/middleware/built-in/cookie/core.js +70 -2
  14. package/dist/core/middleware/built-in/cookie/core.js.map +1 -1
  15. package/dist/core/middleware/built-in/cookie/hook.d.ts +3 -1
  16. package/dist/core/middleware/built-in/cookie/hook.js +17 -5
  17. package/dist/core/middleware/built-in/cookie/hook.js.map +1 -1
  18. package/dist/core/middleware/built-in/cookie/index.d.ts +1 -1
  19. package/dist/core/middleware/built-in/cookie/index.js +1 -1
  20. package/dist/core/middleware/built-in/cookie/index.js.map +1 -1
  21. package/dist/core/middleware/built-in/cookie/middleware.d.ts +1 -1
  22. package/dist/core/middleware/built-in/cookie/middleware.js +6 -4
  23. package/dist/core/middleware/built-in/cookie/middleware.js.map +1 -1
  24. package/dist/core/middleware/built-in/index.d.ts +4 -12
  25. package/dist/core/middleware/built-in/rate-limit/core.d.ts +24 -0
  26. package/dist/core/middleware/built-in/rate-limit/core.js +47 -0
  27. package/dist/core/middleware/built-in/rate-limit/core.js.map +1 -1
  28. package/dist/core/middleware/built-in/rate-limit/hook.d.ts +24 -9
  29. package/dist/core/middleware/built-in/rate-limit/hook.js +34 -12
  30. package/dist/core/middleware/built-in/rate-limit/hook.js.map +1 -1
  31. package/dist/core/middleware/built-in/rate-limit/middleware.js +9 -1
  32. package/dist/core/middleware/built-in/rate-limit/middleware.js.map +1 -1
  33. package/dist/core/middleware/built-in/session/core.d.ts +8 -0
  34. package/dist/core/middleware/built-in/session/core.js +29 -2
  35. package/dist/core/middleware/built-in/session/core.js.map +1 -1
  36. package/dist/core/middleware/built-in/session/hook.js +2 -3
  37. package/dist/core/middleware/built-in/session/hook.js.map +1 -1
  38. package/dist/core/middleware/built-in/session/middleware.js +2 -3
  39. package/dist/core/middleware/built-in/session/middleware.js.map +1 -1
  40. package/dist/core/middleware/built-in/static/core.d.ts +56 -0
  41. package/dist/core/middleware/built-in/static/core.js +214 -13
  42. package/dist/core/middleware/built-in/static/core.js.map +1 -1
  43. package/dist/core/middleware/built-in/static/middleware.d.ts +5 -0
  44. package/dist/core/middleware/built-in/static/middleware.js +5 -0
  45. package/dist/core/middleware/built-in/static/middleware.js.map +1 -1
  46. package/dist/core/middleware/built-in/upload/core.d.ts +17 -0
  47. package/dist/core/middleware/built-in/upload/core.js +30 -1
  48. package/dist/core/middleware/built-in/upload/core.js.map +1 -1
  49. package/dist/core/middleware/built-in/upload/middleware.js +12 -1
  50. package/dist/core/middleware/built-in/upload/middleware.js.map +1 -1
  51. package/dist/core/pooling/object-pool-manager.js +14 -20
  52. package/dist/core/pooling/object-pool-manager.js.map +1 -1
  53. package/dist/core/routing/index.d.ts +1 -0
  54. package/dist/core/routing/index.js.map +1 -1
  55. package/dist/core/routing/radix-tree.d.ts +1 -0
  56. package/dist/core/routing/radix-tree.js +6 -1
  57. package/dist/core/routing/radix-tree.js.map +1 -1
  58. package/dist/core/routing/unified-router.js +28 -25
  59. package/dist/core/routing/unified-router.js.map +1 -1
  60. package/dist/core/utilities/hooks.d.ts +2 -0
  61. package/dist/core/utilities/hooks.js +28 -2
  62. package/dist/core/utilities/hooks.js.map +1 -1
  63. package/dist/index.d.ts +2 -6
  64. package/dist/moro.js +12 -0
  65. package/dist/moro.js.map +1 -1
  66. package/dist/types/http.d.ts +3 -0
  67. package/dist/types/http.js.map +1 -1
  68. package/package.json +1 -1
@@ -1,17 +1,20 @@
1
1
  import { createFrameworkLogger } from '../../../logger/index.js';
2
- import { RateLimitCore } from './core.js';
2
+ import { RateLimitCore, onResponseFinished, shouldRefund } from './core.js';
3
3
  const logger = createFrameworkLogger('RateLimitMiddleware');
4
4
  /**
5
5
  * Rate limit hook for global usage
6
6
  * Registers with the hooks system for application-wide rate limiting
7
7
  *
8
+ * Rejected requests are answered directly with the configured status (429 by
9
+ * default) and a `Retry-After` header — the request never reaches your routes.
10
+ *
8
11
  * @example
9
12
  * ```ts
10
- * import { rateLimit } from '@/middleware/built-in/rate-limit';
13
+ * import { middleware } from '@morojs/moro';
11
14
  *
12
- * app.use(rateLimit({
13
- * windowMs: 60000, // 1 minute
14
- * max: 100 // 100 requests per window
15
+ * app.use(middleware.rateLimit({
16
+ * requests: 100, // or max: 100
17
+ * window: 60000, // or windowMs: 60000
15
18
  * }));
16
19
  * ```
17
20
  */
@@ -28,20 +31,39 @@ export const rateLimit = (options = {}) => ({
28
31
  logger.debug('Installing rate limit middleware', 'Installation', {
29
32
  options,
30
33
  });
31
- const windowMs = options.windowMs || 60000; // 1 minute default
32
- const max = options.max || 100; // 100 requests per window
34
+ // Both spellings are accepted; `requests`/`window` is the Moro form used by
35
+ // route-level .rateLimit(), `max`/`windowMs` the express-rate-limit one.
36
+ const windowMs = options.window ?? options.windowMs ?? 60000; // 1 minute default
37
+ const max = options.requests ?? options.max ?? 100; // 100 requests per window
38
+ const statusCode = options.statusCode ?? 429;
39
+ const message = options.message || 'Too many requests';
33
40
  const core = new RateLimitCore();
34
41
  hooks.before('request', async (context) => {
35
42
  const req = context.request;
36
- const clientId = req.connection?.remoteAddress || 'unknown';
43
+ const res = context.response;
44
+ const clientId = req.ip || req.connection?.remoteAddress || 'unknown';
37
45
  const routeKey = `${req.method}:${req.path}`;
38
46
  const allowed = core.check(clientId, routeKey, max, windowMs);
39
47
  if (!allowed) {
40
- logger.warn(`Rate limit exceeded for ${clientId}`, 'RateLimit', {
41
- clientId,
42
- max,
48
+ // Answer here rather than throwing: a thrown error reaches the error
49
+ // boundary and becomes a 500, which is both the wrong status and an
50
+ // ERROR log line for every request the limiter correctly rejects.
51
+ if (res && !res.headersSent) {
52
+ const retryAfter = core.getRetryAfter(clientId, routeKey);
53
+ res.setHeader('Retry-After', String(retryAfter));
54
+ res.status(statusCode).json({
55
+ success: false,
56
+ error: message,
57
+ retryAfter,
58
+ });
59
+ }
60
+ return;
61
+ }
62
+ if ((options.skipSuccessfulRequests || options.skipFailedRequests) && res) {
63
+ onResponseFinished(res, () => {
64
+ if (shouldRefund(res.statusCode || 200, options))
65
+ core.refund(clientId, routeKey);
43
66
  });
44
- throw new Error(options.message || 'Too many requests');
45
67
  }
46
68
  });
47
69
  },
@@ -1 +1 @@
1
- {"version":3,"file":"hook.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/rate-limit/hook.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,MAAM,GAAG,qBAAqB,CAAC,qBAAqB,CAAC,CAAC;AAE5D;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,UAII,EAAE,EACe,EAAE,CAAC,CAAC;IACzB,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE;QACR,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,OAAO;QAChB,WAAW,EAAE,oDAAoD;QACjE,MAAM,EAAE,aAAa;KACtB;IAED,OAAO,EAAE,KAAK,EAAE,KAAU,EAAE,qBAA0B,EAAE,EAAE,EAAE;QAC1D,MAAM,CAAC,KAAK,CAAC,kCAAkC,EAAE,cAAc,EAAE;YAC/D,OAAO;SACR,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC,mBAAmB;QAC/D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,0BAA0B;QAC1D,MAAM,IAAI,GAAG,IAAI,aAAa,EAAE,CAAC;QAEjC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,OAAoB,EAAE,EAAE;YACrD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAc,CAAC;YACnC,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,EAAE,aAAa,IAAI,SAAS,CAAC;YAC5D,MAAM,QAAQ,GAAG,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YAE7C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;YAE9D,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,2BAA2B,QAAQ,EAAE,EAAE,WAAW,EAAE;oBAC9D,QAAQ;oBACR,GAAG;iBACJ,CAAC,CAAC;gBACH,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,IAAI,mBAAmB,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC","sourcesContent":["// Rate Limit Hook - MiddlewareInterface for global registration\nimport { MiddlewareInterface, HookContext } from '../../../../types/hooks.js';\nimport { createFrameworkLogger } from '../../../logger/index.js';\nimport { RateLimitCore } from './core.js';\n\nconst logger = createFrameworkLogger('RateLimitMiddleware');\n\n/**\n * Rate limit hook for global usage\n * Registers with the hooks system for application-wide rate limiting\n *\n * @example\n * ```ts\n * import { rateLimit } from '@/middleware/built-in/rate-limit';\n *\n * app.use(rateLimit({\n * windowMs: 60000, // 1 minute\n * max: 100 // 100 requests per window\n * }));\n * ```\n */\nexport const rateLimit = (\n options: {\n windowMs?: number;\n max?: number;\n message?: string;\n } = {}\n): MiddlewareInterface => ({\n name: 'rate-limit',\n version: '1.0.0',\n metadata: {\n name: 'rate-limit',\n version: '1.0.0',\n description: 'Rate limiting middleware with configurable windows',\n author: 'MoroJS Team',\n },\n\n install: async (hooks: any, _middlewareOptions: any = {}) => {\n logger.debug('Installing rate limit middleware', 'Installation', {\n options,\n });\n\n const windowMs = options.windowMs || 60000; // 1 minute default\n const max = options.max || 100; // 100 requests per window\n const core = new RateLimitCore();\n\n hooks.before('request', async (context: HookContext) => {\n const req = context.request as any;\n const clientId = req.connection?.remoteAddress || 'unknown';\n const routeKey = `${req.method}:${req.path}`;\n\n const allowed = core.check(clientId, routeKey, max, windowMs);\n\n if (!allowed) {\n logger.warn(`Rate limit exceeded for ${clientId}`, 'RateLimit', {\n clientId,\n max,\n });\n throw new Error(options.message || 'Too many requests');\n }\n });\n },\n});\n"]}
1
+ {"version":3,"file":"hook.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/rate-limit/hook.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE5E,MAAM,MAAM,GAAG,qBAAqB,CAAC,qBAAqB,CAAC,CAAC;AAmB5D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,UAAgC,EAAE,EAAuB,EAAE,CAAC,CAAC;IACrF,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE;QACR,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,OAAO;QAChB,WAAW,EAAE,oDAAoD;QACjE,MAAM,EAAE,aAAa;KACtB;IAED,OAAO,EAAE,KAAK,EAAE,KAAU,EAAE,qBAA0B,EAAE,EAAE,EAAE;QAC1D,MAAM,CAAC,KAAK,CAAC,kCAAkC,EAAE,cAAc,EAAE;YAC/D,OAAO;SACR,CAAC,CAAC;QAEH,4EAA4E;QAC5E,yEAAyE;QACzE,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC,mBAAmB;QACjF,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,0BAA0B;QAC9E,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC;QAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,mBAAmB,CAAC;QACvD,MAAM,IAAI,GAAG,IAAI,aAAa,EAAE,CAAC;QAEjC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,OAAoB,EAAE,EAAE;YACrD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAc,CAAC;YACnC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAe,CAAC;YACpC,MAAM,QAAQ,GAAG,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,UAAU,EAAE,aAAa,IAAI,SAAS,CAAC;YACtE,MAAM,QAAQ,GAAG,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YAE7C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;YAE9D,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,qEAAqE;gBACrE,oEAAoE;gBACpE,kEAAkE;gBAClE,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;oBAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;oBAC1D,GAAG,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;oBACjD,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;wBAC1B,OAAO,EAAE,KAAK;wBACd,KAAK,EAAE,OAAO;wBACd,UAAU;qBACX,CAAC,CAAC;gBACL,CAAC;gBACD,OAAO;YACT,CAAC;YAED,IAAI,CAAC,OAAO,CAAC,sBAAsB,IAAI,OAAO,CAAC,kBAAkB,CAAC,IAAI,GAAG,EAAE,CAAC;gBAC1E,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE;oBAC3B,IAAI,YAAY,CAAC,GAAG,CAAC,UAAU,IAAI,GAAG,EAAE,OAAO,CAAC;wBAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;gBACpF,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC","sourcesContent":["// Rate Limit Hook - MiddlewareInterface for global registration\nimport { MiddlewareInterface, HookContext } from '../../../../types/hooks.js';\nimport { createFrameworkLogger } from '../../../logger/index.js';\nimport { RateLimitCore, onResponseFinished, shouldRefund } from './core.js';\n\nconst logger = createFrameworkLogger('RateLimitMiddleware');\n\nexport interface RateLimitHookOptions {\n /** Requests allowed per window. `max` is the express-rate-limit spelling. */\n requests?: number;\n max?: number;\n /** Window length in milliseconds. `windowMs` is the express-rate-limit spelling. */\n window?: number;\n windowMs?: number;\n /** Body message for a rejected request. Default: 'Too many requests'. */\n message?: string;\n /** Status for a rejected request. Default: 429. */\n statusCode?: number;\n /** Only count requests that did not complete successfully. */\n skipSuccessfulRequests?: boolean;\n /** Only count requests that did complete successfully. */\n skipFailedRequests?: boolean;\n}\n\n/**\n * Rate limit hook for global usage\n * Registers with the hooks system for application-wide rate limiting\n *\n * Rejected requests are answered directly with the configured status (429 by\n * default) and a `Retry-After` header — the request never reaches your routes.\n *\n * @example\n * ```ts\n * import { middleware } from '@morojs/moro';\n *\n * app.use(middleware.rateLimit({\n * requests: 100, // or max: 100\n * window: 60000, // or windowMs: 60000\n * }));\n * ```\n */\nexport const rateLimit = (options: RateLimitHookOptions = {}): MiddlewareInterface => ({\n name: 'rate-limit',\n version: '1.0.0',\n metadata: {\n name: 'rate-limit',\n version: '1.0.0',\n description: 'Rate limiting middleware with configurable windows',\n author: 'MoroJS Team',\n },\n\n install: async (hooks: any, _middlewareOptions: any = {}) => {\n logger.debug('Installing rate limit middleware', 'Installation', {\n options,\n });\n\n // Both spellings are accepted; `requests`/`window` is the Moro form used by\n // route-level .rateLimit(), `max`/`windowMs` the express-rate-limit one.\n const windowMs = options.window ?? options.windowMs ?? 60000; // 1 minute default\n const max = options.requests ?? options.max ?? 100; // 100 requests per window\n const statusCode = options.statusCode ?? 429;\n const message = options.message || 'Too many requests';\n const core = new RateLimitCore();\n\n hooks.before('request', async (context: HookContext) => {\n const req = context.request as any;\n const res = context.response as any;\n const clientId = req.ip || req.connection?.remoteAddress || 'unknown';\n const routeKey = `${req.method}:${req.path}`;\n\n const allowed = core.check(clientId, routeKey, max, windowMs);\n\n if (!allowed) {\n // Answer here rather than throwing: a thrown error reaches the error\n // boundary and becomes a 500, which is both the wrong status and an\n // ERROR log line for every request the limiter correctly rejects.\n if (res && !res.headersSent) {\n const retryAfter = core.getRetryAfter(clientId, routeKey);\n res.setHeader('Retry-After', String(retryAfter));\n res.status(statusCode).json({\n success: false,\n error: message,\n retryAfter,\n });\n }\n return;\n }\n\n if ((options.skipSuccessfulRequests || options.skipFailedRequests) && res) {\n onResponseFinished(res, () => {\n if (shouldRefund(res.statusCode || 200, options)) core.refund(clientId, routeKey);\n });\n }\n });\n },\n});\n"]}
@@ -1,4 +1,4 @@
1
- import { sharedRateLimitCore } from './core.js';
1
+ import { sharedRateLimitCore, onResponseFinished, shouldRefund, } from './core.js';
2
2
  /**
3
3
  * Create rate limit middleware for use in middleware chains
4
4
  *
@@ -22,6 +22,7 @@ export function createRateLimitMiddleware(config) {
22
22
  const allowed = sharedRateLimitCore.check(clientId, routeKey, config.requests, config.window);
23
23
  if (!allowed) {
24
24
  const retryAfter = sharedRateLimitCore.getRetryAfter(clientId, routeKey);
25
+ res.setHeader('Retry-After', String(retryAfter));
25
26
  res.status(429).json({
26
27
  success: false,
27
28
  error: 'Rate limit exceeded',
@@ -29,6 +30,13 @@ export function createRateLimitMiddleware(config) {
29
30
  });
30
31
  return;
31
32
  }
33
+ if (config.skipSuccessfulRequests || config.skipFailedRequests) {
34
+ onResponseFinished(res, () => {
35
+ if (shouldRefund(res.statusCode || 200, config)) {
36
+ sharedRateLimitCore.refund(clientId, routeKey);
37
+ }
38
+ });
39
+ }
32
40
  next();
33
41
  };
34
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/rate-limit/middleware.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAwB,MAAM,WAAW,CAAC;AAEtE;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAAuB;IAC/D,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAClD,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;IACtC,CAAC;IAED,OAAO,KAAK,EAAE,GAAQ,EAAE,GAAQ,EAAE,IAAgB,EAAE,EAAE;QACpD,MAAM,QAAQ,GAAG,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,UAAU,EAAE,aAAa,IAAI,SAAS,CAAC;QACtE,MAAM,QAAQ,GAAG,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QAE7C,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAE9F,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,UAAU,GAAG,mBAAmB,CAAC,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACzE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,qBAAqB;gBAC5B,UAAU;aACX,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC","sourcesContent":["// Rate Limit Middleware - Standard (req, res, next) middleware function\nimport { StandardMiddleware } from '../../../../types/hooks.js';\nimport { sharedRateLimitCore, type RateLimitConfig } from './core.js';\n\n/**\n * Create rate limit middleware for use in middleware chains\n *\n * @example\n * ```ts\n * const rateLimitMw = createRateLimitMiddleware({\n * requests: 100,\n * window: 60000\n * });\n *\n * app.use(rateLimitMw);\n * ```\n */\nexport function createRateLimitMiddleware(config: RateLimitConfig): StandardMiddleware {\n if (!config || !config.requests || !config.window) {\n return (_req, _res, next) => next();\n }\n\n return async (req: any, res: any, next: () => void) => {\n const clientId = req.ip || req.connection?.remoteAddress || 'unknown';\n const routeKey = `${req.method}:${req.path}`;\n\n const allowed = sharedRateLimitCore.check(clientId, routeKey, config.requests, config.window);\n\n if (!allowed) {\n const retryAfter = sharedRateLimitCore.getRetryAfter(clientId, routeKey);\n res.status(429).json({\n success: false,\n error: 'Rate limit exceeded',\n retryAfter,\n });\n return;\n }\n\n next();\n };\n}\n"]}
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/rate-limit/middleware.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,GAEb,MAAM,WAAW,CAAC;AAEnB;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAAuB;IAC/D,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAClD,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;IACtC,CAAC;IAED,OAAO,KAAK,EAAE,GAAQ,EAAE,GAAQ,EAAE,IAAgB,EAAE,EAAE;QACpD,MAAM,QAAQ,GAAG,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,UAAU,EAAE,aAAa,IAAI,SAAS,CAAC;QACtE,MAAM,QAAQ,GAAG,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QAE7C,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAE9F,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,UAAU,GAAG,mBAAmB,CAAC,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACzE,GAAG,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;YACjD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,qBAAqB;gBAC5B,UAAU;aACX,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,MAAM,CAAC,sBAAsB,IAAI,MAAM,CAAC,kBAAkB,EAAE,CAAC;YAC/D,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE;gBAC3B,IAAI,YAAY,CAAC,GAAG,CAAC,UAAU,IAAI,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC;oBAChD,mBAAmB,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;gBACjD,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC","sourcesContent":["// Rate Limit Middleware - Standard (req, res, next) middleware function\nimport { StandardMiddleware } from '../../../../types/hooks.js';\nimport {\n sharedRateLimitCore,\n onResponseFinished,\n shouldRefund,\n type RateLimitConfig,\n} from './core.js';\n\n/**\n * Create rate limit middleware for use in middleware chains\n *\n * @example\n * ```ts\n * const rateLimitMw = createRateLimitMiddleware({\n * requests: 100,\n * window: 60000\n * });\n *\n * app.use(rateLimitMw);\n * ```\n */\nexport function createRateLimitMiddleware(config: RateLimitConfig): StandardMiddleware {\n if (!config || !config.requests || !config.window) {\n return (_req, _res, next) => next();\n }\n\n return async (req: any, res: any, next: () => void) => {\n const clientId = req.ip || req.connection?.remoteAddress || 'unknown';\n const routeKey = `${req.method}:${req.path}`;\n\n const allowed = sharedRateLimitCore.check(clientId, routeKey, config.requests, config.window);\n\n if (!allowed) {\n const retryAfter = sharedRateLimitCore.getRetryAfter(clientId, routeKey);\n res.setHeader('Retry-After', String(retryAfter));\n res.status(429).json({\n success: false,\n error: 'Rate limit exceeded',\n retryAfter,\n });\n return;\n }\n\n if (config.skipSuccessfulRequests || config.skipFailedRequests) {\n onResponseFinished(res, () => {\n if (shouldRefund(res.statusCode || 200, config)) {\n sharedRateLimitCore.refund(clientId, routeKey);\n }\n });\n }\n\n next();\n };\n}\n"]}
@@ -65,6 +65,14 @@ export declare class SessionCore {
65
65
  constructor(options?: SessionOptions);
66
66
  generateSessionId(): string;
67
67
  loadSession(sessionId: string): Promise<SessionData | null>;
68
+ /** Cookie value carrying an id — signed when a secret is configured. */
69
+ private encodeSessionId;
70
+ /**
71
+ * The session id the request carries, or undefined when there is none and
72
+ * when a signed id fails verification — a tampered id must start a fresh
73
+ * session rather than load someone else's.
74
+ */
75
+ readSessionId(req: HttpRequest): string | undefined;
68
76
  createSession(req: HttpRequest, res: HttpResponse, sessionId?: string): Promise<Session>;
69
77
  attachSession(req: HttpRequest, res: HttpResponse, sessionId?: string): Promise<Session>;
70
78
  saveSession(session: Session): Promise<void>;
@@ -4,6 +4,7 @@ import { createFrameworkLogger } from '../../../logger/index.js';
4
4
  import { MemoryCacheAdapter } from '../cache/adapters/cache/memory.js';
5
5
  import { RedisCacheAdapter } from '../cache/adapters/cache/redis.js';
6
6
  import { FileCacheAdapter } from '../cache/adapters/cache/file.js';
7
+ import { signCookieValue, unsignCookieValue, isSignedCookieValue } from '../cookie/core.js';
7
8
  const logger = createFrameworkLogger('SessionCore');
8
9
  // ===== Session Class =====
9
10
  export class Session {
@@ -164,11 +165,37 @@ export class SessionCore {
164
165
  return null;
165
166
  }
166
167
  }
168
+ /** Cookie value carrying an id — signed when a secret is configured. */
169
+ encodeSessionId(id) {
170
+ return this.options.secret ? signCookieValue(id, this.options.secret) : id;
171
+ }
172
+ /**
173
+ * The session id the request carries, or undefined when there is none and
174
+ * when a signed id fails verification — a tampered id must start a fresh
175
+ * session rather than load someone else's.
176
+ */
177
+ readSessionId(req) {
178
+ const name = this.options.name || 'connect.sid';
179
+ // The cookie middleware verifies and strips signatures when it shares the
180
+ // secret, in which case the id arrives already unwrapped.
181
+ const verified = req.signedCookies?.[name];
182
+ const raw = verified ?? req.cookies?.[name];
183
+ if (!raw)
184
+ return undefined;
185
+ const secret = this.options.secret;
186
+ if (!secret)
187
+ return raw;
188
+ if (isSignedCookieValue(raw)) {
189
+ return unsignCookieValue(raw, secret) ?? undefined;
190
+ }
191
+ // A bare value is only trustworthy if it was verified upstream
192
+ return verified;
193
+ }
167
194
  async createSession(req, res, sessionId) {
168
195
  const id = sessionId || this.generateSessionId();
169
196
  const session = Session.create(id, this.store, this.options, {}, true);
170
197
  // Set session cookie
171
- res.cookie(this.options.name || 'connect.sid', id, {
198
+ res.cookie(this.options.name || 'connect.sid', this.encodeSessionId(id), {
172
199
  ...this.options.cookie,
173
200
  secure: !!(this.options.cookie?.secure ||
174
201
  (this.options.proxy && req.headers['x-forwarded-proto'] === 'https')),
@@ -206,7 +233,7 @@ export class SessionCore {
206
233
  const session = Session.create(id, this.store, this.options, sessionData, isNew);
207
234
  // Set session cookie if new or rolling
208
235
  if (isNew || this.options.rolling) {
209
- res.cookie(this.options.name || 'connect.sid', id, {
236
+ res.cookie(this.options.name || 'connect.sid', this.encodeSessionId(id), {
210
237
  ...this.options.cookie,
211
238
  secure: !!(this.options.cookie?.secure ||
212
239
  (this.options.proxy && req.headers['x-forwarded-proto'] === 'https')),
@@ -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';\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 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', 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', 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\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"]}
@@ -32,12 +32,11 @@ export const session = (options = {}) => ({
32
32
  ...middlewareOptions,
33
33
  };
34
34
  const sessionCore = new SessionCore(config);
35
- const cookieName = config.name || 'connect.sid';
36
35
  hooks.before('request', async (context) => {
37
36
  const req = context.request;
38
37
  const res = context.response;
39
- // Get session ID from cookie
40
- const sessionId = req.cookies?.[cookieName];
38
+ // Get session ID from cookie, verifying its signature when one is used
39
+ const sessionId = sessionCore.readSessionId(req);
41
40
  // Attach session to request
42
41
  req.session = await sessionCore.attachSession(req, res, sessionId);
43
42
  });
@@ -1 +1 @@
1
- {"version":3,"file":"hook.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/session/hook.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,WAAW,EAAuB,MAAM,WAAW,CAAC;AAE7D,MAAM,MAAM,GAAG,qBAAqB,CAAC,mBAAmB,CAAC,CAAC;AAE1D;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,UAA0B,EAAE,EAAuB,EAAE,CAAC,CAAC;IAC7E,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE;QACR,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,OAAO;QAChB,WAAW,EAAE,4DAA4D;QACzE,MAAM,EAAE,aAAa;KACtB;IAED,OAAO,EAAE,KAAK,EAAE,KAAU,EAAE,oBAAyB,EAAE,EAAE,EAAE;QACzD,MAAM,CAAC,KAAK,CAAC,+BAA+B,EAAE,cAAc,CAAC,CAAC;QAE9D,gBAAgB;QAChB,MAAM,MAAM,GAAmB;YAC7B,GAAG,OAAO;YACV,GAAG,iBAAiB;SACrB,CAAC;QAEF,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;QAC5C,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,IAAI,aAAa,CAAC;QAEhD,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,OAAoB,EAAE,EAAE;YACrD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAc,CAAC;YACnC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAe,CAAC;YAEpC,6BAA6B;YAC7B,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,CAAC;YAE5C,4BAA4B;YAC5B,GAAG,CAAC,OAAO,GAAG,MAAM,WAAW,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;QACrE,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,EAAE,OAAoB,EAAE,EAAE;YACrD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAc,CAAC;YAEnC,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBAChB,MAAM,WAAW,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,qCAAqC,MAAM,CAAC,KAAK,QAAQ,EAAE,cAAc,CAAC,CAAC;IACzF,CAAC;CACF,CAAC,CAAC","sourcesContent":["// Session Hook - MiddlewareInterface for global registration\nimport { MiddlewareInterface, HookContext } from '../../../../types/hooks.js';\nimport { createFrameworkLogger } from '../../../logger/index.js';\nimport { SessionCore, type SessionOptions } from './core.js';\n\nconst logger = createFrameworkLogger('SessionMiddleware');\n\n/**\n * Session hook for global usage\n * Registers with the hooks system for application-wide session management\n *\n * @example\n * ```ts\n * import { session } from '@/middleware/built-in/session';\n *\n * app.use(session({\n * store: 'redis',\n * cookie: { maxAge: 86400000 }\n * }));\n * ```\n */\nexport const session = (options: SessionOptions = {}): MiddlewareInterface => ({\n name: 'session',\n version: '1.0.0',\n metadata: {\n name: 'session',\n version: '1.0.0',\n description: 'Session management middleware with multiple store adapters',\n author: 'MoroJS Team',\n },\n\n install: async (hooks: any, middlewareOptions: any = {}) => {\n logger.debug('Installing session middleware', 'Installation');\n\n // Merge options\n const config: SessionOptions = {\n ...options,\n ...middlewareOptions,\n };\n\n const sessionCore = new SessionCore(config);\n const cookieName = config.name || 'connect.sid';\n\n hooks.before('request', async (context: HookContext) => {\n const req = context.request as any;\n const res = context.response as any;\n\n // Get session ID from cookie\n const sessionId = req.cookies?.[cookieName];\n\n // Attach session to request\n req.session = await sessionCore.attachSession(req, res, sessionId);\n });\n\n hooks.after('response', async (context: HookContext) => {\n const req = context.request as any;\n\n if (req.session) {\n await sessionCore.saveSession(req.session);\n }\n });\n\n logger.info(`Session middleware installed with ${config.store} store`, 'Installation');\n },\n});\n"]}
1
+ {"version":3,"file":"hook.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/session/hook.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,WAAW,EAAuB,MAAM,WAAW,CAAC;AAE7D,MAAM,MAAM,GAAG,qBAAqB,CAAC,mBAAmB,CAAC,CAAC;AAE1D;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,UAA0B,EAAE,EAAuB,EAAE,CAAC,CAAC;IAC7E,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE;QACR,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,OAAO;QAChB,WAAW,EAAE,4DAA4D;QACzE,MAAM,EAAE,aAAa;KACtB;IAED,OAAO,EAAE,KAAK,EAAE,KAAU,EAAE,oBAAyB,EAAE,EAAE,EAAE;QACzD,MAAM,CAAC,KAAK,CAAC,+BAA+B,EAAE,cAAc,CAAC,CAAC;QAE9D,gBAAgB;QAChB,MAAM,MAAM,GAAmB;YAC7B,GAAG,OAAO;YACV,GAAG,iBAAiB;SACrB,CAAC;QAEF,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;QAE5C,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,OAAoB,EAAE,EAAE;YACrD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAc,CAAC;YACnC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAe,CAAC;YAEpC,uEAAuE;YACvE,MAAM,SAAS,GAAG,WAAW,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YAEjD,4BAA4B;YAC5B,GAAG,CAAC,OAAO,GAAG,MAAM,WAAW,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;QACrE,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,EAAE,OAAoB,EAAE,EAAE;YACrD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAc,CAAC;YAEnC,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBAChB,MAAM,WAAW,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,qCAAqC,MAAM,CAAC,KAAK,QAAQ,EAAE,cAAc,CAAC,CAAC;IACzF,CAAC;CACF,CAAC,CAAC","sourcesContent":["// Session Hook - MiddlewareInterface for global registration\nimport { MiddlewareInterface, HookContext } from '../../../../types/hooks.js';\nimport { createFrameworkLogger } from '../../../logger/index.js';\nimport { SessionCore, type SessionOptions } from './core.js';\n\nconst logger = createFrameworkLogger('SessionMiddleware');\n\n/**\n * Session hook for global usage\n * Registers with the hooks system for application-wide session management\n *\n * @example\n * ```ts\n * import { session } from '@/middleware/built-in/session';\n *\n * app.use(session({\n * store: 'redis',\n * cookie: { maxAge: 86400000 }\n * }));\n * ```\n */\nexport const session = (options: SessionOptions = {}): MiddlewareInterface => ({\n name: 'session',\n version: '1.0.0',\n metadata: {\n name: 'session',\n version: '1.0.0',\n description: 'Session management middleware with multiple store adapters',\n author: 'MoroJS Team',\n },\n\n install: async (hooks: any, middlewareOptions: any = {}) => {\n logger.debug('Installing session middleware', 'Installation');\n\n // Merge options\n const config: SessionOptions = {\n ...options,\n ...middlewareOptions,\n };\n\n const sessionCore = new SessionCore(config);\n\n hooks.before('request', async (context: HookContext) => {\n const req = context.request as any;\n const res = context.response as any;\n\n // Get session ID from cookie, verifying its signature when one is used\n const sessionId = sessionCore.readSessionId(req);\n\n // Attach session to request\n req.session = await sessionCore.attachSession(req, res, sessionId);\n });\n\n hooks.after('response', async (context: HookContext) => {\n const req = context.request as any;\n\n if (req.session) {\n await sessionCore.saveSession(req.session);\n }\n });\n\n logger.info(`Session middleware installed with ${config.store} store`, 'Installation');\n },\n});\n"]}
@@ -17,11 +17,10 @@ const logger = createFrameworkLogger('SessionMiddleware');
17
17
  */
18
18
  export function createSessionMiddleware(options = {}) {
19
19
  const sessionCore = new SessionCore(options);
20
- const cookieName = options.name || 'connect.sid';
21
20
  return async (req, res, next) => {
22
21
  try {
23
- // Get session ID from cookie
24
- const sessionId = req.cookies?.[cookieName];
22
+ // Get session ID from cookie, verifying its signature when one is used
23
+ const sessionId = sessionCore.readSessionId(req);
25
24
  // Attach session to request
26
25
  req.session = await sessionCore.attachSession(req, res, sessionId);
27
26
  // Execute next middleware
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/session/middleware.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,OAAO,EAAE,WAAW,EAAuB,MAAM,WAAW,CAAC;AAE7D,MAAM,MAAM,GAAG,qBAAqB,CAAC,mBAAmB,CAAC,CAAC;AAE1D;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CAAC,UAA0B,EAAE;IAClE,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,IAAI,aAAa,CAAC;IAEjD,OAAO,KAAK,EAAE,GAAgB,EAAE,GAAiB,EAAE,IAAyB,EAAE,EAAE;QAC9E,IAAI,CAAC;YACH,6BAA6B;YAC7B,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,CAAC;YAE5C,4BAA4B;YAC3B,GAAW,CAAC,OAAO,GAAG,MAAM,WAAW,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;YAE5E,0BAA0B;YAC1B,MAAM,IAAI,EAAE,CAAC;YAEb,8BAA8B;YAC9B,MAAM,WAAW,CAAC,WAAW,CAAE,GAAW,CAAC,OAAO,CAAC,CAAC;QACtD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,0BAA0B,EAAE,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YACpE,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC","sourcesContent":["// Session Middleware - Standard (req, res, next) middleware function\nimport { StandardMiddleware } from '../../../../types/hooks.js';\nimport { createFrameworkLogger } from '../../../logger/index.js';\nimport { HttpRequest, HttpResponse } from '../../../../types/http.js';\nimport { SessionCore, type SessionOptions } from './core.js';\n\nconst logger = createFrameworkLogger('SessionMiddleware');\n\n/**\n * Create session middleware for use in middleware chains\n *\n * @example\n * ```ts\n * const sessionMw = createSessionMiddleware({\n * store: 'redis',\n * name: 'my-session',\n * cookie: { maxAge: 3600000 }\n * });\n *\n * app.use(sessionMw);\n * ```\n */\nexport function createSessionMiddleware(options: SessionOptions = {}): StandardMiddleware {\n const sessionCore = new SessionCore(options);\n const cookieName = options.name || 'connect.sid';\n\n return async (req: HttpRequest, res: HttpResponse, next: () => Promise<void>) => {\n try {\n // Get session ID from cookie\n const sessionId = req.cookies?.[cookieName];\n\n // Attach session to request\n (req as any).session = await sessionCore.attachSession(req, res, sessionId);\n\n // Execute next middleware\n await next();\n\n // Save session after response\n await sessionCore.saveSession((req as any).session);\n } catch (error) {\n logger.error('Session middleware error', 'SessionError', { error });\n throw error;\n }\n };\n}\n"]}
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/session/middleware.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,OAAO,EAAE,WAAW,EAAuB,MAAM,WAAW,CAAC;AAE7D,MAAM,MAAM,GAAG,qBAAqB,CAAC,mBAAmB,CAAC,CAAC;AAE1D;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CAAC,UAA0B,EAAE;IAClE,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;IAE7C,OAAO,KAAK,EAAE,GAAgB,EAAE,GAAiB,EAAE,IAAyB,EAAE,EAAE;QAC9E,IAAI,CAAC;YACH,uEAAuE;YACvE,MAAM,SAAS,GAAG,WAAW,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YAEjD,4BAA4B;YAC3B,GAAW,CAAC,OAAO,GAAG,MAAM,WAAW,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;YAE5E,0BAA0B;YAC1B,MAAM,IAAI,EAAE,CAAC;YAEb,8BAA8B;YAC9B,MAAM,WAAW,CAAC,WAAW,CAAE,GAAW,CAAC,OAAO,CAAC,CAAC;QACtD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,0BAA0B,EAAE,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YACpE,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC","sourcesContent":["// Session Middleware - Standard (req, res, next) middleware function\nimport { StandardMiddleware } from '../../../../types/hooks.js';\nimport { createFrameworkLogger } from '../../../logger/index.js';\nimport { HttpRequest, HttpResponse } from '../../../../types/http.js';\nimport { SessionCore, type SessionOptions } from './core.js';\n\nconst logger = createFrameworkLogger('SessionMiddleware');\n\n/**\n * Create session middleware for use in middleware chains\n *\n * @example\n * ```ts\n * const sessionMw = createSessionMiddleware({\n * store: 'redis',\n * name: 'my-session',\n * cookie: { maxAge: 3600000 }\n * });\n *\n * app.use(sessionMw);\n * ```\n */\nexport function createSessionMiddleware(options: SessionOptions = {}): StandardMiddleware {\n const sessionCore = new SessionCore(options);\n\n return async (req: HttpRequest, res: HttpResponse, next: () => Promise<void>) => {\n try {\n // Get session ID from cookie, verifying its signature when one is used\n const sessionId = sessionCore.readSessionId(req);\n\n // Attach session to request\n (req as any).session = await sessionCore.attachSession(req, res, sessionId);\n\n // Execute next middleware\n await next();\n\n // Save session after response\n await sessionCore.saveSession((req as any).session);\n } catch (error) {\n logger.error('Session middleware error', 'SessionError', { error });\n throw error;\n }\n };\n}\n"]}
@@ -1,20 +1,76 @@
1
1
  import { HttpRequest, HttpResponse } from '../../../../types/http.js';
2
2
  export interface StaticOptions {
3
3
  root: string;
4
+ /**
5
+ * URL path the files are served under, e.g. `/assets`. Requests outside the
6
+ * prefix fall through untouched; inside it, the prefix is stripped before the
7
+ * path is resolved against `root` (`/assets/app.css` -> `<root>/app.css`).
8
+ * Matching is exact and case-sensitive. Defaults to the URL root.
9
+ */
10
+ prefix?: string;
4
11
  maxAge?: number;
5
12
  index?: string[];
6
13
  dotfiles?: 'allow' | 'deny' | 'ignore';
7
14
  etag?: boolean;
15
+ /**
16
+ * Send `Last-Modified` and answer `If-Modified-Since` with a 304.
17
+ * Default true.
18
+ */
19
+ lastModified?: boolean;
20
+ /**
21
+ * Advertise `Accept-Ranges: bytes` and serve `Range` requests as 206
22
+ * responses — what media seeking and resumable downloads need. A request for
23
+ * multiple ranges is answered with the whole entity. Default true.
24
+ */
25
+ acceptRanges?: boolean;
8
26
  }
9
27
  export declare class StaticCore {
10
28
  private root;
29
+ private prefix;
11
30
  private maxAge;
12
31
  private index;
13
32
  private dotfiles;
14
33
  private etag;
34
+ private lastModified;
35
+ private acceptRanges;
36
+ /** Files at or below this size are read in one go instead of streamed. */
37
+ private static readonly STREAM_THRESHOLD;
15
38
  private mimeTypes;
16
39
  constructor(options: StaticOptions);
17
40
  handleRequest(req: HttpRequest, res: HttpResponse): Promise<boolean>;
41
+ /**
42
+ * Whether the client's cached copy is still current. `If-None-Match` wins
43
+ * over `If-Modified-Since` when both are present (RFC 9110 13.1.3).
44
+ */
45
+ private isNotModified;
46
+ /**
47
+ * The byte range to serve, `'unsatisfiable'` for a 416, or null to send the
48
+ * whole entity — which is also the answer for a multi-range request and for
49
+ * an `If-Range` that no longer matches.
50
+ */
51
+ private resolveRange;
52
+ /**
53
+ * Pipe a file (or a slice of one) to the response, closing it on abort.
54
+ * Resolves once the response is done — the caller must not return before
55
+ * then, or the router would see an untouched response and 404 the request.
56
+ */
57
+ private streamFile;
58
+ /** Read just the requested slice — the fallback where piping isn't available. */
59
+ private static readSlice;
60
+ /** HTTP/2 responses are plain objects with no stream plumbing to pipe into. */
61
+ private static canStream;
62
+ /**
63
+ * `/assets/` -> `/assets`, `/` and `''` -> `''` (serve from the URL root).
64
+ * A prefix given without a leading slash still mounts where you'd expect.
65
+ */
66
+ private static normalizePrefix;
67
+ /**
68
+ * Request path relative to the mount point, or `null` when the request falls
69
+ * outside it. `/assets` itself maps to `/` so the directory index still
70
+ * resolves; `/assetsfoo` is a different route and is left alone.
71
+ */
72
+ private stripPrefix;
18
73
  private addCharsetIfNeeded;
19
74
  getRoot(): string;
75
+ getPrefix(): string;
20
76
  }