stratal 0.0.24 → 0.0.26

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 (137) hide show
  1. package/dist/bin/quarry.mjs +60 -4
  2. package/dist/bin/quarry.mjs.map +1 -1
  3. package/dist/cache/index.d.mts +1 -1
  4. package/dist/cache/index.mjs +2 -2
  5. package/dist/{command-CPhFHjG3.d.mts → command-DoBD2Cwl.d.mts} +2 -2
  6. package/dist/{command-CPhFHjG3.d.mts.map → command-DoBD2Cwl.d.mts.map} +1 -1
  7. package/dist/config/index.d.mts +1 -1
  8. package/dist/config/index.mjs +2 -2
  9. package/dist/{controller.decorator-C5UVeJS3.mjs → controller.decorator-YSTPQntu.mjs} +3 -3
  10. package/dist/{controller.decorator-C5UVeJS3.mjs.map → controller.decorator-YSTPQntu.mjs.map} +1 -1
  11. package/dist/cron/index.d.mts +1 -1
  12. package/dist/cron/index.mjs +1 -1
  13. package/dist/{cron.module-Bgzq5hiT.mjs → cron.module-C81HTzR7.mjs} +3 -3
  14. package/dist/{cron.module-Bgzq5hiT.mjs.map → cron.module-C81HTzR7.mjs.map} +1 -1
  15. package/dist/di/index.d.mts +2 -2
  16. package/dist/di/index.mjs +2 -2
  17. package/dist/{di-DseMn-z9.mjs → di-D7qmrAir.mjs} +60 -3
  18. package/dist/di-D7qmrAir.mjs.map +1 -0
  19. package/dist/email/index.d.mts +2 -2
  20. package/dist/email/index.mjs +3 -3
  21. package/dist/errors/index.d.mts +2 -2
  22. package/dist/errors/index.mjs +3 -3
  23. package/dist/{errors-mXYxG0XB.mjs → errors-C01O2T-n.mjs} +19 -4
  24. package/dist/errors-C01O2T-n.mjs.map +1 -0
  25. package/dist/events/index.d.mts +8 -0
  26. package/dist/events/index.d.mts.map +1 -1
  27. package/dist/events/index.mjs +1 -1
  28. package/dist/{events-BXJGZjpG.mjs → events-BhEQuT1X.mjs} +5 -2
  29. package/dist/events-BhEQuT1X.mjs.map +1 -0
  30. package/dist/{exception-context-kEoMFwze.mjs → exception-context-D-kvney-.mjs} +2 -2
  31. package/dist/{exception-context-kEoMFwze.mjs.map → exception-context-D-kvney-.mjs.map} +1 -1
  32. package/dist/{gateway-context-TMu_AlJt.mjs → gateway-context-m7kEzRa2.mjs} +4 -4
  33. package/dist/{gateway-context-TMu_AlJt.mjs.map → gateway-context-m7kEzRa2.mjs.map} +1 -1
  34. package/dist/guards/index.d.mts +1 -1
  35. package/dist/{hono-app-CvV3hOfT.mjs → hono-app-COAgmutc.mjs} +18 -11
  36. package/dist/hono-app-COAgmutc.mjs.map +1 -0
  37. package/dist/{http-method.decorator-ByWZb9DO.mjs → http-method.decorator-BljM8BDj.mjs} +2 -2
  38. package/dist/{http-method.decorator-ByWZb9DO.mjs.map → http-method.decorator-BljM8BDj.mjs.map} +1 -1
  39. package/dist/i18n/index.d.mts +10 -3
  40. package/dist/i18n/index.d.mts.map +1 -1
  41. package/dist/i18n/index.mjs +3 -3
  42. package/dist/{i18n.module-DRQAZoSZ.mjs → i18n.module-B2DvWUPa.mjs} +24 -9
  43. package/dist/i18n.module-B2DvWUPa.mjs.map +1 -0
  44. package/dist/{index-B5JBRcWD.d.mts → index-CNuFQSNj.d.mts} +6 -4
  45. package/dist/{index-B5JBRcWD.d.mts.map → index-CNuFQSNj.d.mts.map} +1 -1
  46. package/dist/{index-B_JoEl3V.d.mts → index-uybm0bhQ.d.mts} +111 -6
  47. package/dist/index-uybm0bhQ.d.mts.map +1 -0
  48. package/dist/index.d.mts +3 -3
  49. package/dist/index.mjs +2 -2
  50. package/dist/{lazy-module-loader-Ib383jH_.d.mts → lazy-module-loader-M6YKudNL.d.mts} +2 -2
  51. package/dist/{lazy-module-loader-Ib383jH_.d.mts.map → lazy-module-loader-M6YKudNL.d.mts.map} +1 -1
  52. package/dist/{locale-path.service-D-dHiIPc.mjs → locale-path.service-CH0CaxwH.mjs} +3 -3
  53. package/dist/{locale-path.service-D-dHiIPc.mjs.map → locale-path.service-CH0CaxwH.mjs.map} +1 -1
  54. package/dist/{locale-url.service-C2EWmGdq.mjs → locale-url.service-6bgia24_.mjs} +2 -2
  55. package/dist/{locale-url.service-C2EWmGdq.mjs.map → locale-url.service-6bgia24_.mjs.map} +1 -1
  56. package/dist/logger/index.mjs +1 -1
  57. package/dist/module/index.d.mts +2 -2
  58. package/dist/module/index.mjs +2 -2
  59. package/dist/{module-registry-Dm-pqHd3.mjs → module-registry-NxX5O0Qk.mjs} +4 -4
  60. package/dist/{module-registry-Dm-pqHd3.mjs.map → module-registry-NxX5O0Qk.mjs.map} +1 -1
  61. package/dist/openapi/index.d.mts +2 -2
  62. package/dist/openapi/index.mjs +1 -1
  63. package/dist/{openapi-CstuTM8S.mjs → openapi-CMwuCp31.mjs} +3 -3
  64. package/dist/{openapi-CstuTM8S.mjs.map → openapi-CMwuCp31.mjs.map} +1 -1
  65. package/dist/{openapi.service-YhTiJ1bO.d.mts → openapi.service-2rvJBCEg.d.mts} +2 -2
  66. package/dist/{openapi.service-YhTiJ1bO.d.mts.map → openapi.service-2rvJBCEg.d.mts.map} +1 -1
  67. package/dist/quarry/index.d.mts +3 -3
  68. package/dist/quarry/index.mjs +1 -1
  69. package/dist/quarry/runner.d.mts +6 -6
  70. package/dist/quarry/runner.mjs +5 -5
  71. package/dist/{quarry-registry-CXg0RFXq.d.mts → quarry-registry-DRnV-DDa.d.mts} +3 -3
  72. package/dist/{quarry-registry-CXg0RFXq.d.mts.map → quarry-registry-DRnV-DDa.d.mts.map} +1 -1
  73. package/dist/{quarry.module-BuRPGMDm.mjs → quarry.module-CcGxU2dJ.mjs} +3 -3
  74. package/dist/{quarry.module-BuRPGMDm.mjs.map → quarry.module-CcGxU2dJ.mjs.map} +1 -1
  75. package/dist/queue/index.d.mts +1 -1
  76. package/dist/queue/index.mjs +2 -2
  77. package/dist/{queue.module-nddvxzCB.mjs → queue.module-CEs4_kEM.mjs} +15 -8
  78. package/dist/{queue.module-nddvxzCB.mjs.map → queue.module-CEs4_kEM.mjs.map} +1 -1
  79. package/dist/{r2-storage.provider-DCxQt9dD.mjs → r2-storage.provider-BoZmR6Ut.mjs} +2 -2
  80. package/dist/{r2-storage.provider-DCxQt9dD.mjs.map → r2-storage.provider-BoZmR6Ut.mjs.map} +1 -1
  81. package/dist/{rate-limit.decorator-BPAie_p3.mjs → rate-limit.decorator-Djs4oYDB.mjs} +2 -2
  82. package/dist/{rate-limit.decorator-BPAie_p3.mjs.map → rate-limit.decorator-Djs4oYDB.mjs.map} +1 -1
  83. package/dist/rate-limiter/index.d.mts +54 -47
  84. package/dist/rate-limiter/index.d.mts.map +1 -1
  85. package/dist/rate-limiter/index.mjs +16 -7
  86. package/dist/rate-limiter/index.mjs.map +1 -1
  87. package/dist/{route-registration.service-D6vSwiKP.mjs → route-registration.service-CDPQKpm4.mjs} +9 -9
  88. package/dist/{route-registration.service-D6vSwiKP.mjs.map → route-registration.service-CDPQKpm4.mjs.map} +1 -1
  89. package/dist/{route-registry-CYqLp2Nj.mjs → route-registry-BvLJisvK.mjs} +3 -3
  90. package/dist/{route-registry-CYqLp2Nj.mjs.map → route-registry-BvLJisvK.mjs.map} +1 -1
  91. package/dist/router/index.d.mts +2 -2
  92. package/dist/router/index.mjs +16 -16
  93. package/dist/{router-CWGBD-Bg.mjs → router-DwyqEXgf.mjs} +13 -13
  94. package/dist/{router-CWGBD-Bg.mjs.map → router-DwyqEXgf.mjs.map} +1 -1
  95. package/dist/{router-resolver-D4YlPNlm.mjs → router-resolver-sUV_jTrU.mjs} +2 -2
  96. package/dist/{router-resolver-D4YlPNlm.mjs.map → router-resolver-sUV_jTrU.mjs.map} +1 -1
  97. package/dist/seeder/index.d.mts +2 -2
  98. package/dist/seeder/index.mjs +3 -3
  99. package/dist/{seeder-7ubkms-Y.mjs → seeder-BPGY5rUb.mjs} +4 -4
  100. package/dist/{seeder-7ubkms-Y.mjs.map → seeder-BPGY5rUb.mjs.map} +1 -1
  101. package/dist/{seeder-registry-CyUmKsJq.mjs → seeder-registry-DEvCycsT.mjs} +3 -3
  102. package/dist/{seeder-registry-CyUmKsJq.mjs.map → seeder-registry-DEvCycsT.mjs.map} +1 -1
  103. package/dist/{seeder.module-CYYwk3Qk.mjs → seeder.module-CIwQbdN4.mjs} +2 -2
  104. package/dist/{seeder.module-CYYwk3Qk.mjs.map → seeder.module-CIwQbdN4.mjs.map} +1 -1
  105. package/dist/storage/index.d.mts +1 -1
  106. package/dist/storage/index.mjs +2 -2
  107. package/dist/storage/providers/index.mjs +1 -1
  108. package/dist/{storage-MDZypIE9.mjs → storage-C30X81CS.mjs} +7 -7
  109. package/dist/{storage-MDZypIE9.mjs.map → storage-C30X81CS.mjs.map} +1 -1
  110. package/dist/{storage.error-Dnib4VHc.mjs → storage.error-BStXPmO4.mjs} +2 -2
  111. package/dist/{storage.error-Dnib4VHc.mjs.map → storage.error-BStXPmO4.mjs.map} +1 -1
  112. package/dist/{stratal-DL9M38_s.mjs → stratal-BL6FKUM_.mjs} +85 -35
  113. package/dist/stratal-BL6FKUM_.mjs.map +1 -0
  114. package/dist/{stratal-DwDJPY9N.d.mts → stratal-D5j_I14G.d.mts} +13 -3
  115. package/dist/stratal-D5j_I14G.d.mts.map +1 -0
  116. package/dist/trailing-slash-2SctvePW.mjs +80 -0
  117. package/dist/trailing-slash-2SctvePW.mjs.map +1 -0
  118. package/dist/{uri-h7Q8Jug9.mjs → uri-iwofWJ_T.mjs} +12 -10
  119. package/dist/uri-iwofWJ_T.mjs.map +1 -0
  120. package/dist/{versioning.service-C6aHky8-.mjs → versioning.service-CCa2oYMJ.mjs} +3 -3
  121. package/dist/{versioning.service-C6aHky8-.mjs.map → versioning.service-CCa2oYMJ.mjs.map} +1 -1
  122. package/dist/websocket/index.d.mts +1 -1
  123. package/dist/websocket/index.mjs +1 -1
  124. package/dist/workers/index.d.mts +1 -1
  125. package/dist/workers/index.mjs +2 -2
  126. package/package.json +1 -1
  127. package/dist/di-DseMn-z9.mjs.map +0 -1
  128. package/dist/errors-mXYxG0XB.mjs.map +0 -1
  129. package/dist/events-BXJGZjpG.mjs.map +0 -1
  130. package/dist/hono-app-CvV3hOfT.mjs.map +0 -1
  131. package/dist/i18n.module-DRQAZoSZ.mjs.map +0 -1
  132. package/dist/index-B_JoEl3V.d.mts.map +0 -1
  133. package/dist/stratal-DL9M38_s.mjs.map +0 -1
  134. package/dist/stratal-DwDJPY9N.d.mts.map +0 -1
  135. package/dist/trailing-slash-CFyw8nYu.mjs +0 -34
  136. package/dist/trailing-slash-CFyw8nYu.mjs.map +0 -1
  137. package/dist/uri-h7Q8Jug9.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
- import { Dn as AsyncModuleOptions, Dr as ApplicationError, kn as DynamicModule } from "../index-B_JoEl3V.mjs";
1
+ import { Fn as DynamicModule, Ir as ApplicationError, Nn as AsyncModuleOptions } from "../index-uybm0bhQ.mjs";
2
2
  import { a as z } from "../zod-wecrEVAs.mjs";
3
3
  import { a as QueueBinding } from "../consumer-registry-D3iMTSdy.mjs";
4
- import { m as IQueueSender } from "../index-B5JBRcWD.mjs";
4
+ import { m as IQueueSender } from "../index-CNuFQSNj.mjs";
5
5
  import { ReactElement } from "react";
6
6
 
7
7
  //#region src/email/email.error.d.ts
@@ -1,13 +1,13 @@
1
- import { c as Transient, d as inject } from "../di-DseMn-z9.mjs";
1
+ import { p as inject, u as Transient } from "../di-D7qmrAir.mjs";
2
2
  import { a as ApplicationError } from "../container-storage-BmOJ4_Na.mjs";
3
3
  import { n as __decorateParam, t as __decorate } from "../decorate-CuAoSZvs.mjs";
4
4
  import { LOGGER_TOKENS } from "../logger/index.mjs";
5
- import "../errors-mXYxG0XB.mjs";
5
+ import "../errors-C01O2T-n.mjs";
6
6
  import { n as Module } from "../module.decorator-CYHY6pG5.mjs";
7
7
  import "../module/index.mjs";
8
8
  import { t as QUEUE_TOKENS } from "../queue.tokens-DjHnFmre.mjs";
9
9
  import "../queue/index.mjs";
10
- import { p as STORAGE_TOKENS } from "../storage-MDZypIE9.mjs";
10
+ import { p as STORAGE_TOKENS } from "../storage-C30X81CS.mjs";
11
11
  import { r as z } from "../zod-eKqqhZ5_.mjs";
12
12
  import { t as withZodI18n } from "../validation-CpOjviyT.mjs";
13
13
  //#region src/email/email.error.ts
@@ -1,2 +1,2 @@
1
- import { A as ErrorResponse, C as HttpExceptionContext, D as createHttpExceptionContext, Dr as ApplicationError, E as createCronExceptionContext, O as createQueueExceptionContext, S as ExceptionContext, T as createCliExceptionContext, _ as Reportable, a as isApplicationError, b as CliExceptionContext, c as HttpException, d as ExceptionHandler, f as ApplicationErrorConstructor, g as RenderableCallback, h as LogSeverity, i as ContainerNotInitializedError, j as isErrorResponse, k as Environment, l as abort, m as ErrorPageCallback, n as DatabaseError, o as InternalError, p as ContextCallback, r as StratalNotInitializedError, s as HTTP_STATUS_MESSAGES, t as AuthError, u as DefaultExceptionHandler, v as ReportableCallback, w as QueueExceptionContext, x as CronExceptionContext, y as RespondCallback } from "../index-B_JoEl3V.mjs";
2
- export { ApplicationError, ApplicationErrorConstructor, AuthError, CliExceptionContext, ContainerNotInitializedError, ContextCallback, CronExceptionContext, DatabaseError, DefaultExceptionHandler, Environment, ErrorPageCallback, ErrorResponse, ExceptionContext, ExceptionHandler, HTTP_STATUS_MESSAGES, HttpException, HttpExceptionContext, InternalError, LogSeverity, QueueExceptionContext, RenderableCallback, Reportable, ReportableCallback, RespondCallback, StratalNotInitializedError, abort, createCliExceptionContext, createCronExceptionContext, createHttpExceptionContext, createQueueExceptionContext, isApplicationError, isErrorResponse };
1
+ import { A as Environment, C as ExceptionContext, D as createCronExceptionContext, E as createCliExceptionContext, Ir as ApplicationError, M as isErrorResponse, O as createHttpExceptionContext, S as CronExceptionContext, T as QueueExceptionContext, _ as RenderableCallback, a as ContainerNotInitializedError, b as RespondCallback, c as HTTP_STATUS_MESSAGES, d as DefaultExceptionHandler, f as ExceptionHandler, g as LogSeverity, h as ErrorPageCallback, i as StratalNotInitializedError, j as ErrorResponse, k as createQueueExceptionContext, l as HttpException, m as ContextCallback, n as DatabaseError, o as isApplicationError, p as ApplicationErrorConstructor, r as StratalSupersededError, s as InternalError, t as AuthError, u as abort, v as Reportable, w as HttpExceptionContext, x as CliExceptionContext, y as ReportableCallback } from "../index-uybm0bhQ.mjs";
2
+ export { ApplicationError, ApplicationErrorConstructor, AuthError, CliExceptionContext, ContainerNotInitializedError, ContextCallback, CronExceptionContext, DatabaseError, DefaultExceptionHandler, Environment, ErrorPageCallback, ErrorResponse, ExceptionContext, ExceptionHandler, HTTP_STATUS_MESSAGES, HttpException, HttpExceptionContext, InternalError, LogSeverity, QueueExceptionContext, RenderableCallback, Reportable, ReportableCallback, RespondCallback, StratalNotInitializedError, StratalSupersededError, abort, createCliExceptionContext, createCronExceptionContext, createHttpExceptionContext, createQueueExceptionContext, isApplicationError, isErrorResponse };
@@ -1,4 +1,4 @@
1
1
  import { a as ApplicationError, i as ContainerNotInitializedError } from "../container-storage-BmOJ4_Na.mjs";
2
- import { a as DefaultExceptionHandler, c as InternalError, d as abort, i as isErrorResponse, l as HTTP_STATUS_MESSAGES, n as DatabaseError, o as ExceptionHandler, r as StratalNotInitializedError, s as isApplicationError, t as AuthError, u as HttpException } from "../errors-mXYxG0XB.mjs";
3
- import { i as createQueueExceptionContext, n as createCronExceptionContext, r as createHttpExceptionContext, t as createCliExceptionContext } from "../exception-context-kEoMFwze.mjs";
4
- export { ApplicationError, AuthError, ContainerNotInitializedError, DatabaseError, DefaultExceptionHandler, ExceptionHandler, HTTP_STATUS_MESSAGES, HttpException, InternalError, StratalNotInitializedError, abort, createCliExceptionContext, createCronExceptionContext, createHttpExceptionContext, createQueueExceptionContext, isApplicationError, isErrorResponse };
2
+ import { a as isErrorResponse, c as isApplicationError, d as HttpException, f as abort, i as StratalNotInitializedError, l as InternalError, n as DatabaseError, o as DefaultExceptionHandler, r as StratalSupersededError, s as ExceptionHandler, t as AuthError, u as HTTP_STATUS_MESSAGES } from "../errors-C01O2T-n.mjs";
3
+ import { i as createQueueExceptionContext, n as createCronExceptionContext, r as createHttpExceptionContext, t as createCliExceptionContext } from "../exception-context-D-kvney-.mjs";
4
+ export { ApplicationError, AuthError, ContainerNotInitializedError, DatabaseError, DefaultExceptionHandler, ExceptionHandler, HTTP_STATUS_MESSAGES, HttpException, InternalError, StratalNotInitializedError, StratalSupersededError, abort, createCliExceptionContext, createCronExceptionContext, createHttpExceptionContext, createQueueExceptionContext, isApplicationError, isErrorResponse };
@@ -1,8 +1,8 @@
1
- import { c as Transient, d as inject, n as CONTAINER_TOKEN, r as DI_TOKENS } from "./di-DseMn-z9.mjs";
1
+ import { n as CONTAINER_TOKEN, p as inject, r as DI_TOKENS, u as Transient } from "./di-D7qmrAir.mjs";
2
2
  import { a as ApplicationError } from "./container-storage-BmOJ4_Na.mjs";
3
3
  import { n as __decorateParam, t as __decorate } from "./decorate-CuAoSZvs.mjs";
4
4
  import { LOGGER_TOKENS } from "./logger/index.mjs";
5
- import "./exception-context-kEoMFwze.mjs";
5
+ import "./exception-context-D-kvney-.mjs";
6
6
  //#region src/errors/http-exception.ts
7
7
  const HTTP_STATUS_MESSAGES = {
8
8
  400: "Bad Request",
@@ -314,6 +314,21 @@ var StratalNotInitializedError = class extends ApplicationError {
314
314
  }
315
315
  };
316
316
  //#endregion
317
+ //#region src/errors/stratal-superseded.error.ts
318
+ /**
319
+ * Thrown (as a promise rejection) when a Stratal generation is superseded by a
320
+ * newer instance before its initialization completed — e.g. a Vite HMR reload
321
+ * re-evaluated the worker entry mid-boot. Awaiters should retry against the
322
+ * current generation via `Stratal.resolveApplication()`.
323
+ */
324
+ var StratalSupersededError = class extends ApplicationError {
325
+ generation;
326
+ constructor(generation) {
327
+ super(`Stratal generation ${generation} was superseded by a newer instance (hot reload).`);
328
+ this.generation = generation;
329
+ }
330
+ };
331
+ //#endregion
317
332
  //#region src/errors/database.error.ts
318
333
  var DatabaseError = class extends ApplicationError {
319
334
  constructor(message, cause) {
@@ -328,6 +343,6 @@ var AuthError = class extends ApplicationError {
328
343
  }
329
344
  };
330
345
  //#endregion
331
- export { DefaultExceptionHandler as a, InternalError as c, abort as d, isErrorResponse as i, HTTP_STATUS_MESSAGES as l, DatabaseError as n, ExceptionHandler as o, StratalNotInitializedError as r, isApplicationError as s, AuthError as t, HttpException as u };
346
+ export { isErrorResponse as a, isApplicationError as c, HttpException as d, abort as f, StratalNotInitializedError as i, InternalError as l, DatabaseError as n, DefaultExceptionHandler as o, StratalSupersededError as r, ExceptionHandler as s, AuthError as t, HTTP_STATUS_MESSAGES as u };
332
347
 
333
- //# sourceMappingURL=errors-mXYxG0XB.mjs.map
348
+ //# sourceMappingURL=errors-C01O2T-n.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-C01O2T-n.mjs","names":[],"sources":["../src/errors/http-exception.ts","../src/errors/internal-error.ts","../src/errors/is-application-error.ts","../src/errors/exception-handler.ts","../src/errors/default-exception-handler.ts","../src/errors/error-response.ts","../src/errors/stratal-not-initialized.error.ts","../src/errors/stratal-superseded.error.ts","../src/errors/database.error.ts","../src/errors/auth.error.ts"],"sourcesContent":["import type { ContentfulStatusCode } from 'hono/utils/http-status'\nimport { ApplicationError } from './application-error'\n\nexport const HTTP_STATUS_MESSAGES: Partial<Record<number, string>> = {\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 402: 'Payment Required',\n 403: 'Forbidden',\n 404: 'Not Found',\n 409: 'Conflict',\n 413: 'Content Too Large',\n 422: 'Unprocessable Entity',\n 423: 'Locked',\n 429: 'Too Many Requests',\n 500: 'Internal Server Error',\n 502: 'Bad Gateway',\n 503: 'Service Unavailable',\n}\n\nexport class HttpException extends ApplicationError {\n public readonly httpStatus: ContentfulStatusCode\n\n constructor(httpStatus: ContentfulStatusCode, message?: string, cause?: unknown) {\n super(message ?? HTTP_STATUS_MESSAGES[httpStatus] ?? 'Internal Server Error', cause)\n this.httpStatus = httpStatus\n }\n}\n\nexport function abort(\n status: ContentfulStatusCode,\n message?: string,\n): never {\n throw new HttpException(status, message)\n}\n","import { ApplicationError } from './application-error'\n\nexport class InternalError extends ApplicationError {\n constructor(cause?: unknown) {\n super('Internal Server Error', cause)\n }\n}\n","import { ApplicationError } from './application-error'\n\nexport function isApplicationError(error: unknown): error is ApplicationError {\n if (error instanceof ApplicationError) return true\n return error instanceof Error\n && typeof (error as ApplicationError).timestamp === 'string'\n}\n","import type { ContentfulStatusCode } from 'hono/utils/http-status';\nimport { inject } from '../di';\nimport { CONTAINER_TOKEN, type Container } from '../di';\nimport { Transient } from '../di/decorators';\nimport { DI_TOKENS } from '../di/tokens';\nimport { type StratalEnv } from '../env';\nimport type { StratalExecutionContext } from '../execution-context';\nimport { LOGGER_TOKENS, type LoggerService } from '../logger';\nimport type { ApplicationError } from './application-error';\nimport type { Environment, ErrorResponse } from './error-response';\nimport type { ExceptionContext, HttpExceptionContext } from './exception-context';\nimport type {\n ApplicationErrorConstructor,\n ContextCallback,\n ErrorPageCallback,\n LogSeverity,\n RenderableCallback,\n Reportable,\n ReportableCallback,\n RespondCallback,\n} from './exception-handler.types';\nimport { HTTP_STATUS_MESSAGES, HttpException } from './http-exception';\nimport { InternalError } from './internal-error';\nimport { isApplicationError } from './is-application-error';\n\ninterface ReportableEntry {\n errorClass: ApplicationErrorConstructor\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n callback: ReportableCallback<any>\n shouldStop: boolean\n}\n\ninterface RenderableEntry {\n errorClass: ApplicationErrorConstructor\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n callback: RenderableCallback<any>\n}\n\n@Transient()\nexport abstract class ExceptionHandler {\n private readonly reportables: ReportableEntry[] = []\n private readonly renderables: RenderableEntry[] = []\n private readonly dontReportSet = new Set<ApplicationErrorConstructor>()\n private readonly levelOverrides = new Map<ApplicationErrorConstructor, LogSeverity>()\n private readonly contextCallbacks: ContextCallback[] = []\n private readonly respondCallbacks: RespondCallback[] = []\n private readonly errorPages: ErrorPageCallback[] = []\n private readonly environment: Environment\n\n constructor(\n @inject(LOGGER_TOKENS.LoggerService) protected readonly logger: LoggerService,\n @inject(DI_TOKENS.CloudflareEnv) protected readonly env: StratalEnv,\n @inject(CONTAINER_TOKEN) private readonly container: Container,\n @inject(DI_TOKENS.ExecutionContext) private readonly executionContext: StratalExecutionContext,\n ) {\n this.environment = this.env.ENVIRONMENT as Environment\n }\n\n abstract register(): void\n\n // ── Public Configuration API ──────────────────────────────────────\n\n reportable<T extends ApplicationError>(\n errorClass: ApplicationErrorConstructor<T>,\n callback: ReportableCallback<T>,\n ): Reportable {\n const entry: ReportableEntry = { errorClass, callback, shouldStop: false }\n this.reportables.push(entry)\n return {\n stop: () => { entry.shouldStop = true },\n }\n }\n\n renderable<T extends ApplicationError>(\n errorClass: ApplicationErrorConstructor<T>,\n callback: RenderableCallback<T>,\n ): void {\n this.renderables.push({ errorClass, callback })\n }\n\n dontReport(errorClasses: ApplicationErrorConstructor[]): void {\n for (const cls of errorClasses) {\n this.dontReportSet.add(cls)\n }\n }\n\n level(errorClass: ApplicationErrorConstructor, severity: LogSeverity): void {\n this.levelOverrides.set(errorClass, severity)\n }\n\n context(callback: ContextCallback): void {\n this.contextCallbacks.push(callback)\n }\n\n respond(callback: RespondCallback): void {\n this.respondCallbacks.push(callback)\n }\n\n errorPage(callback: ErrorPageCallback): void {\n this.errorPages.push(callback)\n }\n\n resolve<T>(token: symbol | (new (...args: unknown[]) => T)): T {\n return this.container.resolve<T>(token)\n }\n\n // ── Pipeline Entry Point ──────────────────────────────────────────\n\n async handle(error: unknown, context: ExceptionContext): Promise<Response> {\n const appError = this.normalizeError(error)\n\n this.executionContext.waitUntil(this.performReport(appError, context))\n\n const response = await this.performRender(appError, context)\n\n return this.applyRespondCallbacks(response, appError, context)\n }\n\n // ── Internals ─────────────────────────────────────────────────────\n\n private normalizeError(error: unknown): ApplicationError {\n if (isApplicationError(error)) {\n return error\n }\n\n return new InternalError(error)\n }\n\n private async performReport(error: ApplicationError, context: ExceptionContext): Promise<void> {\n if (this.shouldNotReport(error)) return\n\n const entry = this.findReportable(error)\n if (entry) {\n await entry.callback(error, context)\n if (entry.shouldStop) return\n }\n\n this.defaultReport(error)\n }\n\n private async performRender(error: ApplicationError, context: ExceptionContext): Promise<Response> {\n const entry = this.findRenderable(error)\n if (entry) {\n const result = entry.callback(error, context)\n if (result !== undefined) {\n return this.toResponse(await result, error)\n }\n }\n\n return await this.defaultRender(error, context)\n }\n\n private applyRespondCallbacks(\n response: Response,\n error: ApplicationError,\n context: ExceptionContext,\n ): Response {\n let result = response\n for (const callback of this.respondCallbacks) {\n result = callback(result, error, context)\n }\n return result\n }\n\n private shouldNotReport(error: ApplicationError): boolean {\n for (const cls of this.dontReportSet) {\n if (error instanceof cls) return true\n }\n return false\n }\n\n private findReportable(error: ApplicationError): ReportableEntry | undefined {\n let best: ReportableEntry | undefined\n for (const entry of this.reportables) {\n if (this.matchesErrorClass(error, entry.errorClass)) {\n if (!best || !this.matchesErrorClass(error, best.errorClass) || entry.errorClass.prototype instanceof best.errorClass) {\n best = entry\n }\n }\n }\n return best\n }\n\n private findRenderable(error: ApplicationError): RenderableEntry | undefined {\n let best: RenderableEntry | undefined\n for (const entry of this.renderables) {\n if (this.matchesErrorClass(error, entry.errorClass)) {\n if (!best || !this.matchesErrorClass(error, best.errorClass) || entry.errorClass.prototype instanceof best.errorClass) {\n best = entry\n }\n }\n }\n return best\n }\n\n private matchesErrorClass(error: ApplicationError, cls: ApplicationErrorConstructor): boolean {\n if (error instanceof cls) return true\n return (error as Error).constructor.name === cls.name\n }\n\n private defaultReport(error: ApplicationError): void {\n const severity = this.resolveSeverity(error)\n const globalContext = this.gatherContext()\n\n const logData: Record<string, unknown> = {\n message: error.message,\n timestamp: error.timestamp,\n name: error.name,\n stack: error.stack,\n ...globalContext,\n }\n\n const chain = serializeErrorChain(error)\n if (chain) {\n logData.cause = chain\n }\n\n switch (severity) {\n case 'debug':\n this.logger.debug('[ApplicationError]', logData)\n break\n case 'info':\n this.logger.info('[ApplicationError]', logData)\n break\n case 'warn':\n this.logger.warn('[ApplicationError]', logData)\n break\n case 'error':\n this.logger.error('[ApplicationError]', logData)\n break\n }\n }\n\n private async defaultRender(error: ApplicationError, context: ExceptionContext): Promise<Response> {\n const status = this.resolveStatus(error)\n const errorResponse = this.buildErrorResponse(error, status)\n\n if (context.type === 'http' && this.wantsHtml(context)) {\n for (const callback of this.errorPages) {\n try {\n const result = await callback(errorResponse, status, context, error)\n if (result !== undefined) return result\n } catch (callbackError) {\n this.logger.warn('errorPage callback failed, falling back to next handler', {\n error: callbackError instanceof Error ? callbackError.message : String(callbackError),\n })\n }\n }\n return this.renderDefaultHtml(errorResponse, status)\n }\n\n return Response.json(errorResponse, { status })\n }\n\n // ── Content Negotiation ──────────────────────────────────────────\n\n protected wantsHtml(context: HttpExceptionContext): boolean {\n const accept = context.ctx.c.req.header('accept') ?? ''\n return accept.includes('text/html')\n }\n\n protected renderDefaultHtml(\n errorResponse: ErrorResponse,\n status: ContentfulStatusCode,\n ): Response {\n const title = this.escapeHtml(errorResponse.message)\n const html = `<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>${status} - ${title}</title>\n<link rel=\"icon\" type=\"image/x-icon\" href=\"/favicon.svg\">\n<style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui,-apple-system,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f8fafc;color:#334155}.container{text-align:center;padding:2rem}.status{font-size:6rem;font-weight:800;color:#13c397;line-height:1}.message{font-size:1.25rem;color:#64748b;margin-top:1rem}</style>\n</head><body><div class=\"container\"><div class=\"status\">${status}</div><div class=\"message\">${title}</div></div></body></html>`\n return new Response(html, {\n status,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n })\n }\n\n private resolveStatus(error: ApplicationError): ContentfulStatusCode {\n if (error instanceof HttpException) return error.httpStatus\n const httpStatus = (error as { httpStatus?: number }).httpStatus\n if (typeof httpStatus === 'number') return httpStatus as ContentfulStatusCode\n return 500\n }\n\n private buildErrorResponse(error: ApplicationError, status: ContentfulStatusCode): ErrorResponse {\n const isServerError = status >= 500\n const isDev = this.environment === 'development'\n const message = isServerError && !isDev\n ? (HTTP_STATUS_MESSAGES[status] ?? 'Internal Server Error')\n : error.message\n\n return {\n message,\n timestamp: error.timestamp,\n stack: isDev ? error.stack : undefined,\n }\n }\n\n private escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n }\n\n private toResponse(result: Response | ErrorResponse, error: ApplicationError): Response {\n if (result instanceof Response) return result\n const status = this.resolveStatus(error)\n return Response.json(result, { status })\n }\n\n private resolveSeverity(error: ApplicationError): LogSeverity {\n let bestClass: ApplicationErrorConstructor | undefined\n let bestSeverity: LogSeverity | undefined\n\n for (const [cls, severity] of this.levelOverrides) {\n if (error instanceof cls) {\n if (!bestClass || cls.prototype instanceof bestClass) {\n bestClass = cls\n bestSeverity = severity\n }\n }\n }\n\n if (bestSeverity) return bestSeverity\n\n const status = this.resolveStatus(error)\n return this.getDefaultSeverity(status)\n }\n\n private getDefaultSeverity(status: number): LogSeverity {\n if (status >= 500) return 'error'\n if (status >= 400) return 'warn'\n return 'error'\n }\n\n private gatherContext(): Record<string, unknown> {\n if (this.contextCallbacks.length === 0) return {}\n const merged: Record<string, unknown> = {}\n for (const callback of this.contextCallbacks) {\n Object.assign(merged, callback())\n }\n return merged\n }\n}\n\ninterface SerializedErrorNode {\n name: string\n message: string\n stack?: string\n cause?: SerializedErrorNode | { value: unknown }\n errors?: SerializedErrorNode[]\n}\n\nconst MAX_CAUSE_DEPTH = 5\n\nfunction serializeErrorChain(error: Error): SerializedErrorNode | undefined {\n const cause = (error as { cause?: unknown }).cause\n const aggregated = error instanceof AggregateError && Array.isArray(error.errors) && error.errors.length > 0\n if (cause === undefined && !aggregated) {\n return undefined\n }\n if (cause !== undefined) {\n return cause instanceof Error\n ? serializeErrorNode(cause, 0)\n : ({ value: cause } as unknown as SerializedErrorNode)\n }\n return {\n name: error.name,\n message: error.message,\n errors: (error as AggregateError).errors.map((inner) =>\n inner instanceof Error\n ? serializeErrorNode(inner, 0)\n : { name: 'Unknown', message: String(inner) },\n ),\n }\n}\n\nfunction serializeErrorNode(error: Error, depth: number): SerializedErrorNode {\n const out: SerializedErrorNode = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n\n if (depth >= MAX_CAUSE_DEPTH) return out\n\n if (error instanceof AggregateError && Array.isArray(error.errors) && error.errors.length > 0) {\n out.errors = error.errors.map((inner) =>\n inner instanceof Error\n ? serializeErrorNode(inner, depth + 1)\n : { name: 'Unknown', message: String(inner) },\n )\n }\n\n const cause = (error as { cause?: unknown }).cause\n if (cause !== undefined) {\n out.cause = cause instanceof Error\n ? serializeErrorNode(cause, depth + 1)\n : { value: cause }\n }\n\n return out\n}\n","import { Transient } from '../di/decorators';\nimport { ExceptionHandler } from './exception-handler';\n\n/**\n * DefaultExceptionHandler — the built-in exception handler used when no\n * custom handler is provided via `ApplicationConfig.exceptionHandler`.\n *\n * Has an empty `register()` method, so all exceptions flow through the\n * default pipeline: severity-based logging, i18n translation, and JSON\n * error response serialization.\n *\n * To customize exception handling, extend {@link ExceptionHandler} and\n * override `register()`, then pass your class to the Stratal config:\n *\n * @example\n * ```typescript\n * new Stratal({\n * module: AppModule,\n * exceptionHandler: AppExceptionHandler,\n * })\n * ```\n */\n@Transient()\nexport class DefaultExceptionHandler extends ExceptionHandler {\n register(): void {\n // No custom configuration — uses all defaults\n }\n}\n","export type Environment = 'development' | 'staging' | 'production'\n\nexport interface ErrorResponse {\n message: string\n timestamp: string\n stack?: string\n}\n\nexport function isErrorResponse(obj: unknown): obj is ErrorResponse {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'message' in obj &&\n typeof (obj as ErrorResponse).message === 'string' &&\n 'timestamp' in obj &&\n typeof (obj as ErrorResponse).timestamp === 'string'\n )\n}\n","import { ApplicationError } from './application-error'\n\nexport class StratalNotInitializedError extends ApplicationError {\n constructor() {\n super('Stratal has not been initialized. Ensure you export a Stratal instance as the default export.')\n }\n}\n","import { ApplicationError } from './application-error'\n\n/**\n * Thrown (as a promise rejection) when a Stratal generation is superseded by a\n * newer instance before its initialization completed — e.g. a Vite HMR reload\n * re-evaluated the worker entry mid-boot. Awaiters should retry against the\n * current generation via `Stratal.resolveApplication()`.\n */\nexport class StratalSupersededError extends ApplicationError {\n constructor(readonly generation: number) {\n super(`Stratal generation ${generation} was superseded by a newer instance (hot reload).`)\n }\n}\n","import { ApplicationError } from './application-error'\n\nexport class DatabaseError extends ApplicationError {\n constructor(message?: string, cause?: unknown) {\n super(message ?? 'Database error', cause)\n }\n}\n","import { ApplicationError } from './application-error'\n\nexport class AuthError extends ApplicationError {\n constructor(message?: string, cause?: unknown) {\n super(message ?? 'Authentication error', cause)\n }\n}\n"],"mappings":"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"}
@@ -143,6 +143,13 @@ interface IEventRegistry {
143
143
  emit<E extends EventName>(event: E, context?: Partial<EventContext<E>>): Promise<void>;
144
144
  off<E extends EventName>(event: E, handler: EventHandler<E>): void;
145
145
  once<E extends EventName>(event: E, handler: EventHandler<E>, options?: EventOptions): void;
146
+ /**
147
+ * Whether any registered handler matches the event, using the same
148
+ * pattern matching as `emit()` (exact, model wildcard, operation
149
+ * wildcard, phase wildcard). Lets emitters skip expensive payload
150
+ * construction when nobody is listening.
151
+ */
152
+ hasListeners(event: EventName): boolean;
146
153
  }
147
154
  /**
148
155
  * Metadata stored by `@On()` decorator for each handler method
@@ -190,6 +197,7 @@ declare class EventRegistry implements IEventRegistry {
190
197
  on<E extends EventName>(event: E, handler: EventHandler<E>, options?: EventOptions): void;
191
198
  emit<E extends EventName>(event: E, context?: Partial<EventContext<E>>): Promise<void>;
192
199
  off<E extends EventName>(event: E, handler: EventHandler<E>): void;
200
+ hasListeners(event: EventName): boolean;
193
201
  once<E extends EventName>(event: E, handler: EventHandler<E>, options?: EventOptions): void;
194
202
  /**
195
203
  * Find all handlers matching the event using pattern matching.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/events/constants.ts","../../src/events/decorators/listener.decorator.ts","../../src/events/types.ts","../../src/events/decorators/on.decorator.ts","../../src/events/event-registry.ts","../../src/events/events.module.ts"],"mappings":";;;;;;;;AAMA;;cAAa,sBAAA;EAAA,SAGH,WAAA;EAAA,SAAA,cAAA;AAAA;;;;;;AAHV;;;;;;;;ACgBA;;;;;;iBAAgB,QAAA,eACa,WAAA,EAAa,MAAA,EAAQ,CAAA,KAAC,CAAA;;;;iBAUnC,UAAA,CAAW,MAAmB,EAAX,WAAW;;;;;;;AD3B9C;;;;;;;;ACgBA;;;;;;;;;;;;;;AACmD;AAUnD;;;;AAA8C;;;;ACS9C;;UAAiB,mBAAA;AAAmB;AAMpC;;;AANoC,KAMxB,SAAA,SAAkB,mBAAA,0BAE1B,OAAA,OAAc,mBAAA;;;;;;;;;AAAmB;AAkBrC;;;UAAiB,kBAAA;EACf,IAAA,EAAM,KAAA;EACN,QAAA,GAAW,MAAM;AAAA;;;;AAAA;AAQnB;KAAY,YAAA,WAAuB,SAAA,GAAY,SAAA,IAC7C,CAAA,eAAgB,mBAAA,GACd,mBAAA,CAAoB,CAAA;EAClB,IAAA;EAAgB,MAAA;AAAA;;;;UASL,YAAA;EAVQ;EAYvB,QAAA;EAduB;;;;;;;;EAuBvB,QAAQ;AAAA;AApBkB;AAS5B;;AAT4B,KA0BhB,YAAA,WAAuB,SAAA,GAAY,SAAA,KAC7C,OAAA,EAAS,YAAA,CAAa,CAAA,MACnB,OAAA;;AARK;AAMV;;UAQiB,iBAAA;EACf,OAAA,EAAS,YAAY;EACrB,QAAA;EACA,QAAA;AAAA;;;;UAMe,cAAA;EACf,EAAA,WAAa,SAAA,EACX,KAAA,EAAO,CAAA,EACP,OAAA,EAAS,YAAA,CAAa,CAAA,GACtB,OAAA,GAAU,YAAA;EAGZ,IAAA,WAAe,SAAA,EACb,KAAA,EAAO,CAAA,EACP,OAAA,GAAU,OAAA,CAAQ,YAAA,CAAa,CAAA,KAC9B,OAAA;EAEH,GAAA,WAAc,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,YAAA,CAAa,CAAA;EAEzD,IAAA,WAAe,SAAA,EACb,KAAA,EAAO,CAAA,EACP,OAAA,EAAS,YAAA,CAAa,CAAA,GACtB,OAAA,GAAU,YAAA;AAAA;;;AAhCF;UA2CK,uBAAA;EACf,UAAA;EACA,KAAA;EACA,OAAA,GAAU,YAAY;AAAA;;;;;;AFrJxB;;;;;;;;ACgBA;;;;;;;;;;iBEGgB,EAAA,WAAa,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,GAAU,YAAA,IAExD,MAAA,UACA,WAAA,UACA,WAAA,EAAa,kBAAA;;;;iBAsBD,mBAAA,CAAoB,MAAA,WAAiB,uBAAuB;;;cCrC/D,aAAA,YAAyB,cAAA;EAAA,iBAImB,GAAA;EAAA,iBACC,MAAA;EAAA,QAJhD,QAAA;cAG+C,GAAA,EAAK,gBAAA,EACJ,MAAA,EAAQ,aAAA;EAGhE,EAAA,WAAa,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,YAAA,CAAa,CAAA,GAAI,OAAA,GAAU,YAAA;EAkBhE,IAAA,WAAe,SAAA,EACnB,KAAA,EAAO,CAAA,EACP,OAAA,GAAU,OAAA,CAAQ,YAAA,CAAa,CAAA,KAC9B,OAAA;EAmCH,GAAA,WAAc,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,YAAA,CAAa,CAAA;EAczD,IAAA,WAAe,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,YAAA,CAAa,CAAA,GAAI,OAAA,GAAU,YAAA;;AHtE1E;;;UGmFU,oBAAA;EHlFwC;;;EAAA,QGuHxC,kBAAA;EHvHS;;;EAAA,QG0IH,cAAA;AAAA;;;;;;;AJ3JhB;;;;cKWa,YAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/events/constants.ts","../../src/events/decorators/listener.decorator.ts","../../src/events/types.ts","../../src/events/decorators/on.decorator.ts","../../src/events/event-registry.ts","../../src/events/events.module.ts"],"mappings":";;;;;;;;AAMA;;cAAa,sBAAA;EAAA,SAGH,WAAA;EAAA,SAAA,cAAA;AAAA;;;;;;AAHV;;;;;;;;ACgBA;;;;;;iBAAgB,QAAA,eACa,WAAA,EAAa,MAAA,EAAQ,CAAA,KAAC,CAAA;;;;iBAUnC,UAAA,CAAW,MAAmB,EAAX,WAAW;;;;;;;AD3B9C;;;;;;;;ACgBA;;;;;;;;;;;;;;AACmD;AAUnD;;;;AAA8C;;;;ACS9C;;UAAiB,mBAAA;AAAmB;AAMpC;;;AANoC,KAMxB,SAAA,SAAkB,mBAAA,0BAE1B,OAAA,OAAc,mBAAA;;;;;;;;;AAAmB;AAkBrC;;;UAAiB,kBAAA;EACf,IAAA,EAAM,KAAA;EACN,QAAA,GAAW,MAAM;AAAA;;;;AAAA;AAQnB;KAAY,YAAA,WAAuB,SAAA,GAAY,SAAA,IAC7C,CAAA,eAAgB,mBAAA,GACd,mBAAA,CAAoB,CAAA;EAClB,IAAA;EAAgB,MAAA;AAAA;;;;UASL,YAAA;EAVQ;EAYvB,QAAA;EAduB;;;;;;;;EAuBvB,QAAQ;AAAA;AApBkB;AAS5B;;AAT4B,KA0BhB,YAAA,WAAuB,SAAA,GAAY,SAAA,KAC7C,OAAA,EAAS,YAAA,CAAa,CAAA,MACnB,OAAA;;AARK;AAMV;;UAQiB,iBAAA;EACf,OAAA,EAAS,YAAY;EACrB,QAAA;EACA,QAAA;AAAA;;;;UAMe,cAAA;EACf,EAAA,WAAa,SAAA,EACX,KAAA,EAAO,CAAA,EACP,OAAA,EAAS,YAAA,CAAa,CAAA,GACtB,OAAA,GAAU,YAAA;EAGZ,IAAA,WAAe,SAAA,EACb,KAAA,EAAO,CAAA,EACP,OAAA,GAAU,OAAA,CAAQ,YAAA,CAAa,CAAA,KAC9B,OAAA;EAEH,GAAA,WAAc,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,YAAA,CAAa,CAAA;EAEzD,IAAA,WAAe,SAAA,EACb,KAAA,EAAO,CAAA,EACP,OAAA,EAAS,YAAA,CAAa,CAAA,GACtB,OAAA,GAAU,YAAA;EAjCZ;;;AACU;AAMZ;;EAmCE,YAAA,CAAa,KAAA,EAAO,SAAA;AAAA;;;;UAUL,uBAAA;EACf,UAAA;EACA,KAAA;EACA,OAAA,GAAU,YAAY;AAAA;;;;;;AF7JxB;;;;;;;;ACgBA;;;;;;;;;;iBEGgB,EAAA,WAAa,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,GAAU,YAAA,IAExD,MAAA,UACA,WAAA,UACA,WAAA,EAAa,kBAAA;;;;iBAsBD,mBAAA,CAAoB,MAAA,WAAiB,uBAAuB;;;cCrC/D,aAAA,YAAyB,cAAA;EAAA,iBAImB,GAAA;EAAA,iBACC,MAAA;EAAA,QAJhD,QAAA;cAG+C,GAAA,EAAK,gBAAA,EACJ,MAAA,EAAQ,aAAA;EAGhE,EAAA,WAAa,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,YAAA,CAAa,CAAA,GAAI,OAAA,GAAU,YAAA;EAkBhE,IAAA,WAAe,SAAA,EACnB,KAAA,EAAO,CAAA,EACP,OAAA,GAAU,OAAA,CAAQ,YAAA,CAAa,CAAA,KAC9B,OAAA;EAmCH,GAAA,WAAc,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,YAAA,CAAa,CAAA;EAczD,YAAA,CAAa,KAAA,EAAO,SAAA;EAIpB,IAAA,WAAe,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,YAAA,CAAa,CAAA,GAAI,OAAA,GAAU,YAAA;EH1E1D;;;;EAAA,QGuFN,oBAAA;EHtFyC;;;EAAA,QG2HzC,kBAAA;EH3HmB;;;EAAA,QG8Ib,cAAA;AAAA;;;;;;;AJ/JhB;;;;cKWa,YAAA"}
@@ -1,2 +1,2 @@
1
- import { a as getListenerHandlers, c as LISTENER_METADATA_KEYS, i as On, o as Listener, r as EventRegistry, s as isListener, t as EventsModule } from "../events-BXJGZjpG.mjs";
1
+ import { a as getListenerHandlers, c as LISTENER_METADATA_KEYS, i as On, o as Listener, r as EventRegistry, s as isListener, t as EventsModule } from "../events-BhEQuT1X.mjs";
2
2
  export { EventRegistry, EventsModule, LISTENER_METADATA_KEYS, Listener, On, getListenerHandlers, isListener };
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-BBjsoOtd.mjs";
2
- import { c as Transient, d as inject, r as DI_TOKENS, s as Singleton } from "./di-DseMn-z9.mjs";
2
+ import { l as Singleton, p as inject, r as DI_TOKENS, u as Transient } from "./di-D7qmrAir.mjs";
3
3
  import { n as getMetadata, t as defineMetadata } from "./metadata-DzzprcID.mjs";
4
4
  import { n as __decorateParam, t as __decorate } from "./decorate-CuAoSZvs.mjs";
5
5
  import { LOGGER_TOKENS } from "./logger/index.mjs";
@@ -131,6 +131,9 @@ let EventRegistry = class EventRegistry {
131
131
  else this.handlers.delete(event);
132
132
  this.logger.debug("Event handler unregistered", { event });
133
133
  }
134
+ hasListeners(event) {
135
+ return this.findMatchingHandlers(event).length > 0;
136
+ }
134
137
  once(event, handler, options) {
135
138
  const wrappedHandler = (async (context) => {
136
139
  await handler(context);
@@ -199,4 +202,4 @@ EventsModule = __decorate([Module({ providers: [{
199
202
  //#endregion
200
203
  export { getListenerHandlers as a, LISTENER_METADATA_KEYS as c, On as i, events_module_exports as n, Listener as o, EventRegistry as r, isListener as s, EventsModule as t };
201
204
 
202
- //# sourceMappingURL=events-BXJGZjpG.mjs.map
205
+ //# sourceMappingURL=events-BhEQuT1X.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events-BhEQuT1X.mjs","names":[],"sources":["../src/events/constants.ts","../src/events/decorators/listener.decorator.ts","../src/events/decorators/on.decorator.ts","../src/events/event-registry.ts","../src/events/events.module.ts"],"sourcesContent":["/**\n * Metadata keys for event listener decorators.\n *\n * Uses `Symbol.for()` (global symbol registry) so that both core and\n * framework packages can reference the same symbols without cross-imports.\n */\nexport const LISTENER_METADATA_KEYS = {\n IS_LISTENER: Symbol.for('stratal:listener'),\n EVENT_HANDLERS: Symbol.for('stratal:listener:handlers'),\n} as const\n","import { Transient } from '../../di/decorators'\nimport { defineMetadata, getMetadata } from '../../di/metadata'\nimport type { Constructor } from '../../types'\nimport { LISTENER_METADATA_KEYS } from '../constants'\n\n/**\n * Mark a class as an event listener.\n *\n * Applies `@Transient()` for DI and sets metadata so the module system\n * can auto-discover and wire listener handlers at bootstrap time.\n *\n * @example\n * ```typescript\n * @Listener()\n * export class UserCreatedListener {\n * @On('after.User.create')\n * async sendWelcomeEmail(context: EventContext<'after.User.create'>) {\n * // ...\n * }\n * }\n * ```\n */\nexport function Listener() {\n return function <T extends Constructor>(target: T) {\n Transient()(target)\n defineMetadata(LISTENER_METADATA_KEYS.IS_LISTENER, true, target)\n return target\n }\n}\n\n/**\n * Check if a class is decorated with `@Listener()`\n */\nexport function isListener(target: Constructor): boolean {\n return getMetadata(LISTENER_METADATA_KEYS.IS_LISTENER, target) === true\n}\n","import { defineMetadata, getMetadata } from '../../di/metadata'\nimport { LISTENER_METADATA_KEYS } from '../constants'\nimport type { EventName, EventOptions, ListenerHandlerMetadata } from '../types'\n\n/**\n * Register a method as an event handler within a `@Listener()` class.\n *\n * Accumulates handler metadata on the class so the framework can\n * auto-wire handlers with the EventRegistry at bootstrap time.\n *\n * @param event - Event name to listen for (fully typed with autocomplete)\n * @param options - Optional handler options (priority, blocking)\n *\n * @example\n * ```typescript\n * @Listener()\n * export class AuditListener {\n * @On('after.User.create')\n * async logCreate(context: EventContext<'after.User.create'>) { ... }\n *\n * @On('after.User.delete', { priority: 10 })\n * async logDelete(context: EventContext<'after.User.delete'>) { ... }\n * }\n * ```\n */\nexport function On<E extends EventName>(event: E, options?: EventOptions) {\n return function (\n target: object,\n propertyKey: string,\n _descriptor: PropertyDescriptor\n ) {\n const existingHandlers: ListenerHandlerMetadata[] =\n getMetadata<ListenerHandlerMetadata[]>(LISTENER_METADATA_KEYS.EVENT_HANDLERS, target.constructor) ?? []\n\n existingHandlers.push({\n methodName: propertyKey,\n event: event as string,\n options,\n })\n\n defineMetadata(\n LISTENER_METADATA_KEYS.EVENT_HANDLERS,\n existingHandlers,\n target.constructor\n )\n }\n}\n\n/**\n * Get all `@On()` handler metadata from a listener class\n */\nexport function getListenerHandlers(target: object): ListenerHandlerMetadata[] {\n const metadataTarget = typeof target === 'function' ? target : target.constructor\n return getMetadata<ListenerHandlerMetadata[]>(LISTENER_METADATA_KEYS.EVENT_HANDLERS, metadataTarget) ?? []\n}\n","import { inject } from '../di'\nimport { Singleton } from '../di/decorators'\nimport { DI_TOKENS } from '../di/tokens'\nimport { LOGGER_TOKENS, type LoggerService } from '../logger'\nimport type {\n EventContext,\n EventHandler,\n EventName,\n EventOptions,\n IEventRegistry,\n RegisteredHandler\n} from './types'\n\n@Singleton(DI_TOKENS.EventRegistry)\nexport class EventRegistry implements IEventRegistry {\n private handlers = new Map<string, RegisteredHandler[]>()\n\n constructor(\n @inject(DI_TOKENS.ExecutionContext) private readonly ctx: ExecutionContext,\n @inject(LOGGER_TOKENS.LoggerService) private readonly logger: LoggerService\n ) { }\n\n on<E extends EventName>(event: E, handler: EventHandler<E>, options?: EventOptions): void {\n const registered: RegisteredHandler = {\n handler: handler as EventHandler,\n priority: options?.priority ?? 0,\n blocking: options?.blocking\n }\n\n const existingHandlers = this.handlers.get(event) ?? []\n existingHandlers.push(registered)\n this.handlers.set(event, existingHandlers)\n\n this.logger.debug('Event handler registered', {\n event,\n priority: registered.priority,\n blocking: registered.blocking\n })\n }\n\n async emit<E extends EventName>(\n event: E,\n context?: Partial<EventContext<E>>\n ): Promise<void> {\n // Build full context with caller-provided fields\n const fullContext = {\n ...context\n } as EventContext<E>\n\n // Find matching handlers using pattern matching\n const matchingHandlers = this.findMatchingHandlers(event)\n\n if (matchingHandlers.length === 0) {\n return\n }\n\n // Sort by priority (higher first)\n const sortedHandlers = [...matchingHandlers].sort(\n (a, b) => b.priority - a.priority\n )\n\n // Determine if we should use waitUntil\n const shouldUseWaitUntil = this.shouldUseWaitUntil(event, sortedHandlers)\n\n // Execute handlers\n const promises = sortedHandlers.map((registered) =>\n this.executeHandler(registered.handler, fullContext, event)\n )\n\n if (shouldUseWaitUntil) {\n // Non-blocking: use ctx.waitUntil\n this.ctx.waitUntil(Promise.all(promises))\n } else {\n // Blocking: await all handlers\n await Promise.all(promises)\n }\n }\n\n off<E extends EventName>(event: E, handler: EventHandler<E>): void {\n const existingHandlers = this.handlers.get(event)\n if (!existingHandlers) return\n\n const filtered = existingHandlers.filter((h) => h.handler !== handler)\n if (filtered.length > 0) {\n this.handlers.set(event, filtered)\n } else {\n this.handlers.delete(event)\n }\n\n this.logger.debug('Event handler unregistered', { event })\n }\n\n hasListeners(event: EventName): boolean {\n return this.findMatchingHandlers(event).length > 0\n }\n\n once<E extends EventName>(event: E, handler: EventHandler<E>, options?: EventOptions): void {\n const wrappedHandler = (async (context: EventContext<E>) => {\n await handler(context)\n this.off(event, wrappedHandler)\n }) as EventHandler<E>\n\n this.on(event, wrappedHandler, options)\n }\n\n /**\n * Find all handlers matching the event using pattern matching.\n * Order: exact match -> model wildcard -> operation wildcard -> global wildcard\n */\n private findMatchingHandlers(event: string): RegisteredHandler[] {\n const handlers: RegisteredHandler[] = []\n\n const parts = event.split('.')\n\n if (parts.length === 3) {\n // Database event: \"phase.model.operation\"\n const [phase, model, operation] = parts\n\n // 1. Exact match: \"after.user.create\"\n handlers.push(...(this.handlers.get(event) ?? []))\n\n // 2. Model wildcard: \"after.user\"\n handlers.push(...(this.handlers.get(`${phase}.${model}`) ?? []))\n\n // 3. Operation wildcard: \"after.create\"\n handlers.push(...(this.handlers.get(`${phase}.${operation}`) ?? []))\n\n // 4. Global wildcard: \"after\"\n handlers.push(...(this.handlers.get(phase) ?? []))\n } else if (parts.length === 2) {\n // Could be wildcard like \"after.user\" or custom event like \"auth.verified\"\n handlers.push(...(this.handlers.get(event) ?? []))\n\n if (parts[0] === 'before' || parts[0] === 'after') {\n handlers.push(...(this.handlers.get(parts[0]) ?? []))\n }\n } else {\n handlers.push(...(this.handlers.get(event) ?? []))\n }\n\n return handlers\n }\n\n /**\n * Determine if we should use ctx.waitUntil (non-blocking) or await (blocking)\n */\n private shouldUseWaitUntil(\n event: string,\n handlers: RegisteredHandler[]\n ): boolean {\n const hasBlockingHandler = handlers.some((h) => h.blocking === true)\n if (hasBlockingHandler) return false\n\n const hasNonBlockingHandler = handlers.some((h) => h.blocking === false)\n if (hasNonBlockingHandler) return true\n\n const phase = event.split('.')[0]\n if (phase === 'before') return false\n if (phase === 'after') return true\n return false // Custom events block by default\n }\n\n /**\n * Execute a single handler with error isolation\n */\n private async executeHandler<E extends EventName>(\n handler: EventHandler,\n context: EventContext<E>,\n event: string\n ): Promise<void> {\n try {\n await handler(context as EventContext)\n } catch (error) {\n this.logger.error('Event handler error', {\n event,\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n })\n }\n }\n}\n","import { DI_TOKENS } from '../di/tokens'\nimport { Module } from '../module/module.decorator'\nimport { EventRegistry } from './event-registry'\n\n/**\n * Registers the event registry (`DI_TOKENS.EventRegistry`).\n *\n * Lazy: loaded on demand via `LazyModuleLoader` — by the framework's\n * `DatabaseModule` (which needs it to wire DB event emission) and by the\n * application's event-listener trigger paths. Kept out of cold start for apps\n * that neither emit events nor use the database.\n */\n@Module({\n providers: [\n { provide: DI_TOKENS.EventRegistry, useClass: EventRegistry },\n ],\n})\nexport class EventsModule { }\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { _ as ContainerError, v as ROUTER_TOKENS } from "./di-DseMn-z9.mjs";
1
+ import { b as ROUTER_TOKENS, y as ContainerError } from "./di-D7qmrAir.mjs";
2
2
  import { t as Macroable } from "./macroable-cvDTFZ_A.mjs";
3
3
  import { deleteCookie, getCookie, setCookie } from "hono/cookie";
4
4
  import { stream, streamSSE, streamText } from "hono/streaming";
@@ -426,4 +426,4 @@ function createCliExceptionContext(commandName) {
426
426
  //#endregion
427
427
  export { RouterContext as a, METHOD_STATUS_CODES as c, SECURITY_SCHEMES as d, VERSION_NEUTRAL as f, createQueueExceptionContext as i, ROUTER_CONTEXT_KEYS as l, createCronExceptionContext as n, DEFAULT_CONTENT_TYPE as o, createHttpExceptionContext as r, HTTP_METHODS as s, createCliExceptionContext as t, ROUTE_METADATA_KEYS as u };
428
428
 
429
- //# sourceMappingURL=exception-context-kEoMFwze.mjs.map
429
+ //# sourceMappingURL=exception-context-D-kvney-.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"exception-context-kEoMFwze.mjs","names":["honoGetCookie","honoDeleteCookie","honoStream","honoStreamText","honoStreamSSE"],"sources":["../src/router/constants.ts","../src/router/router-context.ts","../src/errors/exception-context.ts"],"sourcesContent":["/**\n * Type-safe context keys for Hono router variables\n * Using symbols to avoid string collisions\n */\nexport const ROUTER_CONTEXT_KEYS = {\n REQUEST_CONTAINER: 'requestContainer',\n LOCALE: 'locale'\n} as const satisfies Record<string, string>\n\n/**\n * Metadata keys for storing route and controller configuration\n * Using symbols to avoid collisions with other decorators\n */\nexport const ROUTE_METADATA_KEYS = {\n CONTROLLER_ROUTE: Symbol.for('stratal:controller:route'),\n CONTROLLER_OPTIONS: Symbol.for('stratal:controller:options'),\n CONTROLLER_MIDDLEWARES: Symbol.for('stratal:controller:middlewares'),\n ROUTE_CONFIG: Symbol.for('stratal:route:config'),\n DECORATED_METHODS: Symbol.for('stratal:decorated:methods'),\n AUTH_GUARD: Symbol.for('stratal:auth:guard'),\n GATEWAY_MARKER: Symbol.for('stratal:gateway:marker'),\n WS_ON_MESSAGE: Symbol.for('stratal:ws:on-message'),\n WS_ON_CLOSE: Symbol.for('stratal:ws:on-close'),\n WS_ON_ERROR: Symbol.for('stratal:ws:on-error'),\n RATE_LIMIT: Symbol.for('stratal:route:rate-limit'),\n} as const\n\n/**\n * Security scheme identifiers for OpenAPI\n * These reference the security scheme definitions in security.schemas.ts\n */\nexport const SECURITY_SCHEMES = {\n BEARER_AUTH: 'bearerAuth',\n API_KEY: 'apiKey',\n SESSION_COOKIE: 'sessionCookie'\n} as const\n\n/**\n * HTTP method mapping for RESTful controller methods\n * Maps controller method names to HTTP verbs and path patterns\n */\nexport const HTTP_METHODS = {\n index: { method: 'get', path: '' } as const,\n show: { method: 'get', path: '/:id' } as const,\n create: { method: 'post', path: '' } as const,\n update: { method: 'put', path: '/:id' } as const,\n patch: { method: 'patch', path: '/:id' } as const,\n destroy: { method: 'delete', path: '/:id' } as const\n} as const\n\n/**\n * Default success status codes for RESTful controller methods\n * Used by @Route() decorator to auto-derive response status\n */\nexport const METHOD_STATUS_CODES = {\n index: 200,\n show: 200,\n create: 201,\n update: 200,\n patch: 200,\n destroy: 200\n} as const\n\n/**\n * Sentinel symbol to opt a controller out of versioning.\n * When used as the version, no prefix is applied even when defaultVersion is set.\n */\nexport const VERSION_NEUTRAL = Symbol.for('stratal:version:neutral')\n\n/**\n * Default content type for request bodies and responses\n */\nexport const DEFAULT_CONTENT_TYPE = 'application/json'\n","import type { Context } from 'hono'\nimport {\n deleteCookie as honoDeleteCookie,\n getCookie as honoGetCookie,\n setCookie as honoSetCookie,\n} from 'hono/cookie'\nimport type { SSEStreamingApi } from 'hono/streaming'\nimport { stream as honoStream, streamSSE as honoStreamSSE, streamText as honoStreamText } from 'hono/streaming'\nimport type { CookieOptions } from 'hono/utils/cookie'\nimport type { ContentfulStatusCode, RedirectStatusCode } from 'hono/utils/http-status'\nimport type { StreamingApi } from 'hono/utils/stream'\nimport type { Container } from '../di/container'\nimport { ContainerError } from '../di/container.error'\nimport { Macroable } from '../macroable'\nimport { ROUTER_CONTEXT_KEYS } from './constants'\nimport type { RouteName, RouteParams } from './route-map'\nimport { ROUTER_TOKENS } from './router.tokens'\nimport type { RouterEnv } from './types'\nimport type { SignedUriOptions, Uri, UriOptions } from './uri'\n\nexport type ContextQueryResult<R extends Record<string, unknown> | undefined, K extends string | undefined> = K extends string ? string : R extends undefined ? Record<string, unknown> : R\n\n/**\n * Router context wrapper with helper methods\n *\n * Provides convenient access to Hono's context and common request/response operations.\n * The native Hono context is available via the `c` property for advanced use cases.\n *\n * @example\n * ```typescript\n * async index(ctx: RouterContext): Promise<Response> {\n * // Use helper methods\n * const users = await this.service.findAll()\n * return ctx.json(users)\n * }\n *\n * async show(ctx: RouterContext): Promise<Response> {\n * // Access route params\n * const id = ctx.param('id')\n * const user = await this.service.findById(id)\n * return ctx.json(user)\n * }\n *\n * async create(ctx: RouterContext): Promise<Response> {\n * // Parse request body\n * const body = await ctx.body<CreateUserInput>()\n * const user = await this.service.create(body)\n * return ctx.json(user, 201)\n * }\n * ```\n */\nexport class RouterContext<T extends RouterEnv = RouterEnv> extends Macroable {\n /**\n * Native Hono context\n * Access for advanced use cases not covered by helper methods\n */\n constructor(\n public readonly c: Context<T>\n ) {\n super()\n }\n\n /**\n * Cloudflare-provided request properties (geo, TLS, bot management, etc.).\n * Always available on Cloudflare Workers requests via `c.req.raw.cf`.\n */\n get cf(): CfProperties {\n return this.c.req.raw.cf!\n }\n\n /**\n * Get request-scoped DI container\n * Contains request-specific services and context (AuthContext)\n *\n * @throws Error if container not initialized\n */\n getContainer(): Container {\n const container = this.c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER)\n if (!container) {\n throw new ContainerError('Request container has not been initialized')\n }\n return container as Container\n }\n\n /**\n * Set locale for the current request\n *\n * @param locale - Locale code (e.g., 'en', 'fr')\n */\n setLocale(locale: string): void {\n this.c.set(ROUTER_CONTEXT_KEYS.LOCALE, locale)\n }\n\n /**\n * Get locale for the current request\n *\n * @returns Current locale code\n */\n getLocale(): string {\n const locale = this.c.get(ROUTER_CONTEXT_KEYS.LOCALE)\n return (locale as string) || 'en'\n }\n\n /**\n * Return JSON response\n *\n * When data is null, automatically returns 204 No Content (configurable via status param).\n *\n * @param data - Data to serialize as JSON, or null for 204\n * @param status - HTTP status code (default: 200, or 204 when data is null)\n */\n json(data: object | null, status?: ContentfulStatusCode): Response {\n if (data === null) {\n return this.c.body(null, status ?? 204)\n }\n return this.c.json(data, status)\n }\n\n /**\n * Get route parameter value(s).\n *\n * Reads the validated, Zod-coerced param record from `c.req.valid('param')`,\n * which is what `@hono/zod-openapi` populates for every route registered\n * via `app.openapi(...)`. Bare `c.req.param()` returns `undefined` for those\n * routes — always go through the validated record.\n *\n * - With a key → returns the single string value.\n * - With no args → returns the full `Record<string, string>` (or `{}` when\n * the matched route has no path params).\n *\n * @param key - Parameter name (e.g., 'id' for /users/:id)\n */\n param(): Record<string, string>\n param(key: string): string\n param(key?: string): string | Record<string, string> {\n const all = (this.c.req as unknown as { valid(target: 'param'): Record<string, string> | undefined }).valid('param') ?? {}\n return key === undefined ? all : all[key]\n }\n\n /**\n * Get query parameter value\n *\n * @param key - Query parameter name\n */\n query<R extends Record<string, unknown> | undefined = undefined, K extends string | undefined = undefined>(key?: K): ContextQueryResult<R, K> {\n const validated = (this.c.req as unknown as { valid(target: 'query'): Record<string, unknown> }).valid('query')\n return key ? validated[key] as ContextQueryResult<R, K> : validated as ContextQueryResult<R, K>\n }\n\n /**\n * Get request header value\n *\n * @param name - Header name (case-insensitive)\n */\n header(name: string): string | undefined {\n return this.c.req.header(name)\n }\n\n /**\n * Read a cookie value from the current request.\n *\n * @param name - Cookie name\n * @returns The cookie value, or `undefined` if the cookie is not present\n *\n * @example\n * ```typescript\n * const redirectTo = ctx.getCookie('redirectTo')\n * ```\n */\n getCookie(name: string): string | undefined {\n return honoGetCookie(this.c, name)\n }\n\n /**\n * Set a cookie on the response.\n *\n * Cookie operations must run while the response is mutable — call this\n * before returning the final `Response` from the handler.\n *\n * @param name - Cookie name\n * @param value - Cookie value\n * @param options - Cookie attributes (httpOnly, secure, sameSite, path, etc.)\n *\n * @example\n * ```typescript\n * ctx.setCookie('redirectTo', '/app/', {\n * httpOnly: true,\n * secure: true,\n * sameSite: 'lax',\n * path: '/',\n * })\n * ```\n */\n setCookie(name: string, value: string, options?: CookieOptions): void {\n honoSetCookie(this.c, name, value, options)\n }\n\n /**\n * Delete a cookie from the response.\n *\n * Pass the same `path` and `domain` options that were used when the cookie\n * was set, otherwise the browser will not clear the matching cookie.\n *\n * @param name - Cookie name\n * @param options - Cookie attributes used at set time (path, domain, etc.)\n * @returns The deleted cookie's previous value, or `undefined`\n *\n * @example\n * ```typescript\n * ctx.deleteCookie('redirectTo', { path: '/' })\n * ```\n */\n deleteCookie(name: string, options?: CookieOptions): string | undefined {\n return honoDeleteCookie(this.c, name, options)\n }\n\n /**\n * Get validated request body from OpenAPI route\n * Returns pre-validated data that has passed schema validation\n *\n * @returns Validated JSON body\n */\n body<T>(): Promise<T> {\n // Type assertion needed because req.valid() is type-safe per route\n // but this is a generic helper method that works across all routes\n return (this.c.req as unknown as { valid(target: 'json'): Promise<T> }).valid('json')\n }\n\n /**\n * Return text response\n *\n * @param text - Text content\n * @param status - HTTP status code (default: 200)\n */\n text(text: string, status?: ContentfulStatusCode): Response {\n return this.c.text(text, status)\n }\n\n /**\n * Return HTML response\n *\n * @param html - HTML content\n * @param status - HTTP status code (default: 200)\n */\n html(html: string, status?: ContentfulStatusCode): Response {\n return this.c.html(html, status)\n }\n\n /**\n * Generate a URL from a named route.\n *\n * Keys matching `:param` placeholders fill the path.\n * Domain params are consumed from the same object.\n * Extra keys become query string parameters.\n *\n * @param name - Named route identifier\n * @param params - Route params + domain params + extra query params\n * @param options - URL generation options (e.g., `{ absolute: true }`)\n *\n * @example\n * ```typescript\n * ctx.route('users.show', { id: '1' }) // '/v1/users/1'\n * ctx.route('users.show', { id: '1', q: 'test' }) // '/v1/users/1?q=test'\n * ```\n */\n route<N extends RouteName>(name: N, params?: RouteParams<N>, options?: UriOptions): string {\n return this.resolveUri().route(name, params, options)\n }\n\n /**\n * Get a domain parameter value from the current request.\n * Domain params are set by the domain matching middleware.\n *\n * @param key - Domain parameter name (e.g., 'tenant' from '{tenant}.myapp.com')\n *\n * @example\n * ```typescript\n * const tenant = ctx.domain('tenant')\n * ```\n */\n domain(key: string): string {\n return this.c.get(`domain:${key}`) as string\n }\n\n /**\n * Generate a signed URL from a named route.\n *\n * @param name - Named route identifier\n * @param params - Route params (same as route())\n * @param options - Signing options (e.g., expiresIn) and URL options\n * @returns Signed URL string with signature query param\n */\n async signedUrl<N extends RouteName>(name: N, params?: RouteParams<N>, options?: SignedUriOptions): Promise<string> {\n return this.resolveUri().signedRoute(name, params, options)\n }\n\n /**\n * Check if the current request has a valid signature.\n *\n * @returns true if the URL signature is valid and not expired\n */\n async hasValidSignature(): Promise<boolean> {\n return this.resolveUri().hasValidSignature()\n }\n\n /**\n * Redirect to another URL\n *\n * @param url - Target URL\n * @param status - HTTP status code (default: 302)\n */\n redirect(url: string, status?: RedirectStatusCode): Response {\n return this.c.redirect(url, status)\n }\n\n /**\n * Return a streaming response (binary/generic)\n *\n * @param callback - Async function that writes to the stream\n * @param onError - Optional error handler called if an error occurs during streaming\n */\n stream(callback: (stream: StreamingApi) => Promise<void>, onError?: (err: Error, stream: StreamingApi) => Promise<void>): Response {\n return honoStream(this.c, callback, onError)\n }\n\n /**\n * Return a streaming text response\n *\n * Automatically sets `Content-Encoding: Identity` for Cloudflare Workers compatibility.\n *\n * @param callback - Async function that writes text to the stream\n * @param onError - Optional error handler called if an error occurs during streaming\n */\n streamText(callback: (stream: StreamingApi) => Promise<void>, onError?: (err: Error, stream: StreamingApi) => Promise<void>): Response {\n this.c.header('Content-Encoding', 'Identity')\n return honoStreamText(this.c, callback, onError)\n }\n\n /**\n * Return a Server-Sent Events (SSE) streaming response\n *\n * Automatically sets `Content-Encoding: Identity` for Cloudflare Workers compatibility.\n *\n * @param callback - Async function that writes SSE events to the stream\n * @param onError - Optional error handler called if an error occurs during streaming\n */\n streamSSE(callback: (stream: SSEStreamingApi) => Promise<void>, onError?: (err: Error, stream: SSEStreamingApi) => Promise<void>): Response {\n this.c.header('Content-Encoding', 'Identity')\n return honoStreamSSE(this.c, callback, onError)\n }\n\n private resolveUri(): Uri {\n return this.getContainer().resolve<Uri>(ROUTER_TOKENS.Uri)\n }\n}\n","import type { Context } from 'hono'\nimport { RouterContext } from '../router/router-context'\nimport type { RouterEnv } from '../router/types'\n\n/**\n * Exception context for errors occurring during HTTP request handling.\n *\n * Provides access to the full {@link RouterContext} for building responses\n * with `ctx.json()`, `ctx.text()`, `ctx.html()`, etc.\n */\nexport interface HttpExceptionContext {\n readonly type: 'http'\n /** Stratal RouterContext — use for building HTTP responses */\n readonly ctx: RouterContext\n}\n\n/**\n * Exception context for errors occurring during queue message processing.\n */\nexport interface QueueExceptionContext {\n readonly type: 'queue'\n /** Name of the queue being processed */\n readonly queueName: string\n}\n\n/**\n * Exception context for errors occurring during scheduled cron execution.\n */\nexport interface CronExceptionContext {\n readonly type: 'cron'\n}\n\n/**\n * Exception context for errors occurring during CLI command execution.\n */\nexport interface CliExceptionContext {\n readonly type: 'cli'\n /** Name of the command that threw */\n readonly commandName: string\n}\n\n/**\n * Discriminated union of all exception context types.\n *\n * Narrow via `ctx.type` to access context-specific properties:\n *\n * @example\n * ```typescript\n * handler.renderable(MyError, (error, ctx) => {\n * if (ctx.type === 'http') {\n * return ctx.ctx.json({ message: 'Something went wrong' }, 500)\n * }\n * // Non-HTTP contexts: return undefined to use default rendering\n * })\n * ```\n */\nexport type ExceptionContext =\n | HttpExceptionContext\n | QueueExceptionContext\n | CronExceptionContext\n | CliExceptionContext\n\n/**\n * Create an HTTP exception context from a Hono context.\n *\n * @param c - The raw Hono context from the request\n * @returns An {@link HttpExceptionContext} wrapping a RouterContext\n */\nexport function createHttpExceptionContext(c: Context<RouterEnv>): HttpExceptionContext {\n return { type: 'http', ctx: new RouterContext(c) }\n}\n\n/**\n * Create a queue exception context.\n *\n * @param queueName - The name of the queue being processed\n * @returns A {@link QueueExceptionContext}\n */\nexport function createQueueExceptionContext(queueName: string): QueueExceptionContext {\n return { type: 'queue', queueName }\n}\n\n/**\n * Create a cron exception context.\n *\n * @returns A {@link CronExceptionContext}\n */\nexport function createCronExceptionContext(): CronExceptionContext {\n return { type: 'cron' }\n}\n\n/**\n * Create a CLI command exception context.\n *\n * @param commandName - The name of the command that threw\n * @returns A {@link CliExceptionContext}\n */\nexport function createCliExceptionContext(commandName: string): CliExceptionContext {\n return { type: 'cli', commandName }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"exception-context-D-kvney-.mjs","names":["honoGetCookie","honoDeleteCookie","honoStream","honoStreamText","honoStreamSSE"],"sources":["../src/router/constants.ts","../src/router/router-context.ts","../src/errors/exception-context.ts"],"sourcesContent":["/**\n * Type-safe context keys for Hono router variables\n * Using symbols to avoid string collisions\n */\nexport const ROUTER_CONTEXT_KEYS = {\n REQUEST_CONTAINER: 'requestContainer',\n LOCALE: 'locale'\n} as const satisfies Record<string, string>\n\n/**\n * Metadata keys for storing route and controller configuration\n * Using symbols to avoid collisions with other decorators\n */\nexport const ROUTE_METADATA_KEYS = {\n CONTROLLER_ROUTE: Symbol.for('stratal:controller:route'),\n CONTROLLER_OPTIONS: Symbol.for('stratal:controller:options'),\n CONTROLLER_MIDDLEWARES: Symbol.for('stratal:controller:middlewares'),\n ROUTE_CONFIG: Symbol.for('stratal:route:config'),\n DECORATED_METHODS: Symbol.for('stratal:decorated:methods'),\n AUTH_GUARD: Symbol.for('stratal:auth:guard'),\n GATEWAY_MARKER: Symbol.for('stratal:gateway:marker'),\n WS_ON_MESSAGE: Symbol.for('stratal:ws:on-message'),\n WS_ON_CLOSE: Symbol.for('stratal:ws:on-close'),\n WS_ON_ERROR: Symbol.for('stratal:ws:on-error'),\n RATE_LIMIT: Symbol.for('stratal:route:rate-limit'),\n} as const\n\n/**\n * Security scheme identifiers for OpenAPI\n * These reference the security scheme definitions in security.schemas.ts\n */\nexport const SECURITY_SCHEMES = {\n BEARER_AUTH: 'bearerAuth',\n API_KEY: 'apiKey',\n SESSION_COOKIE: 'sessionCookie'\n} as const\n\n/**\n * HTTP method mapping for RESTful controller methods\n * Maps controller method names to HTTP verbs and path patterns\n */\nexport const HTTP_METHODS = {\n index: { method: 'get', path: '' } as const,\n show: { method: 'get', path: '/:id' } as const,\n create: { method: 'post', path: '' } as const,\n update: { method: 'put', path: '/:id' } as const,\n patch: { method: 'patch', path: '/:id' } as const,\n destroy: { method: 'delete', path: '/:id' } as const\n} as const\n\n/**\n * Default success status codes for RESTful controller methods\n * Used by @Route() decorator to auto-derive response status\n */\nexport const METHOD_STATUS_CODES = {\n index: 200,\n show: 200,\n create: 201,\n update: 200,\n patch: 200,\n destroy: 200\n} as const\n\n/**\n * Sentinel symbol to opt a controller out of versioning.\n * When used as the version, no prefix is applied even when defaultVersion is set.\n */\nexport const VERSION_NEUTRAL = Symbol.for('stratal:version:neutral')\n\n/**\n * Default content type for request bodies and responses\n */\nexport const DEFAULT_CONTENT_TYPE = 'application/json'\n","import type { Context } from 'hono'\nimport {\n deleteCookie as honoDeleteCookie,\n getCookie as honoGetCookie,\n setCookie as honoSetCookie,\n} from 'hono/cookie'\nimport type { SSEStreamingApi } from 'hono/streaming'\nimport { stream as honoStream, streamSSE as honoStreamSSE, streamText as honoStreamText } from 'hono/streaming'\nimport type { CookieOptions } from 'hono/utils/cookie'\nimport type { ContentfulStatusCode, RedirectStatusCode } from 'hono/utils/http-status'\nimport type { StreamingApi } from 'hono/utils/stream'\nimport type { Container } from '../di/container'\nimport { ContainerError } from '../di/container.error'\nimport { Macroable } from '../macroable'\nimport { ROUTER_CONTEXT_KEYS } from './constants'\nimport type { RouteName, RouteParams } from './route-map'\nimport { ROUTER_TOKENS } from './router.tokens'\nimport type { RouterEnv } from './types'\nimport type { SignedUriOptions, Uri, UriOptions } from './uri'\n\nexport type ContextQueryResult<R extends Record<string, unknown> | undefined, K extends string | undefined> = K extends string ? string : R extends undefined ? Record<string, unknown> : R\n\n/**\n * Router context wrapper with helper methods\n *\n * Provides convenient access to Hono's context and common request/response operations.\n * The native Hono context is available via the `c` property for advanced use cases.\n *\n * @example\n * ```typescript\n * async index(ctx: RouterContext): Promise<Response> {\n * // Use helper methods\n * const users = await this.service.findAll()\n * return ctx.json(users)\n * }\n *\n * async show(ctx: RouterContext): Promise<Response> {\n * // Access route params\n * const id = ctx.param('id')\n * const user = await this.service.findById(id)\n * return ctx.json(user)\n * }\n *\n * async create(ctx: RouterContext): Promise<Response> {\n * // Parse request body\n * const body = await ctx.body<CreateUserInput>()\n * const user = await this.service.create(body)\n * return ctx.json(user, 201)\n * }\n * ```\n */\nexport class RouterContext<T extends RouterEnv = RouterEnv> extends Macroable {\n /**\n * Native Hono context\n * Access for advanced use cases not covered by helper methods\n */\n constructor(\n public readonly c: Context<T>\n ) {\n super()\n }\n\n /**\n * Cloudflare-provided request properties (geo, TLS, bot management, etc.).\n * Always available on Cloudflare Workers requests via `c.req.raw.cf`.\n */\n get cf(): CfProperties {\n return this.c.req.raw.cf!\n }\n\n /**\n * Get request-scoped DI container\n * Contains request-specific services and context (AuthContext)\n *\n * @throws Error if container not initialized\n */\n getContainer(): Container {\n const container = this.c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER)\n if (!container) {\n throw new ContainerError('Request container has not been initialized')\n }\n return container as Container\n }\n\n /**\n * Set locale for the current request\n *\n * @param locale - Locale code (e.g., 'en', 'fr')\n */\n setLocale(locale: string): void {\n this.c.set(ROUTER_CONTEXT_KEYS.LOCALE, locale)\n }\n\n /**\n * Get locale for the current request\n *\n * @returns Current locale code\n */\n getLocale(): string {\n const locale = this.c.get(ROUTER_CONTEXT_KEYS.LOCALE)\n return (locale as string) || 'en'\n }\n\n /**\n * Return JSON response\n *\n * When data is null, automatically returns 204 No Content (configurable via status param).\n *\n * @param data - Data to serialize as JSON, or null for 204\n * @param status - HTTP status code (default: 200, or 204 when data is null)\n */\n json(data: object | null, status?: ContentfulStatusCode): Response {\n if (data === null) {\n return this.c.body(null, status ?? 204)\n }\n return this.c.json(data, status)\n }\n\n /**\n * Get route parameter value(s).\n *\n * Reads the validated, Zod-coerced param record from `c.req.valid('param')`,\n * which is what `@hono/zod-openapi` populates for every route registered\n * via `app.openapi(...)`. Bare `c.req.param()` returns `undefined` for those\n * routes — always go through the validated record.\n *\n * - With a key → returns the single string value.\n * - With no args → returns the full `Record<string, string>` (or `{}` when\n * the matched route has no path params).\n *\n * @param key - Parameter name (e.g., 'id' for /users/:id)\n */\n param(): Record<string, string>\n param(key: string): string\n param(key?: string): string | Record<string, string> {\n const all = (this.c.req as unknown as { valid(target: 'param'): Record<string, string> | undefined }).valid('param') ?? {}\n return key === undefined ? all : all[key]\n }\n\n /**\n * Get query parameter value\n *\n * @param key - Query parameter name\n */\n query<R extends Record<string, unknown> | undefined = undefined, K extends string | undefined = undefined>(key?: K): ContextQueryResult<R, K> {\n const validated = (this.c.req as unknown as { valid(target: 'query'): Record<string, unknown> }).valid('query')\n return key ? validated[key] as ContextQueryResult<R, K> : validated as ContextQueryResult<R, K>\n }\n\n /**\n * Get request header value\n *\n * @param name - Header name (case-insensitive)\n */\n header(name: string): string | undefined {\n return this.c.req.header(name)\n }\n\n /**\n * Read a cookie value from the current request.\n *\n * @param name - Cookie name\n * @returns The cookie value, or `undefined` if the cookie is not present\n *\n * @example\n * ```typescript\n * const redirectTo = ctx.getCookie('redirectTo')\n * ```\n */\n getCookie(name: string): string | undefined {\n return honoGetCookie(this.c, name)\n }\n\n /**\n * Set a cookie on the response.\n *\n * Cookie operations must run while the response is mutable — call this\n * before returning the final `Response` from the handler.\n *\n * @param name - Cookie name\n * @param value - Cookie value\n * @param options - Cookie attributes (httpOnly, secure, sameSite, path, etc.)\n *\n * @example\n * ```typescript\n * ctx.setCookie('redirectTo', '/app/', {\n * httpOnly: true,\n * secure: true,\n * sameSite: 'lax',\n * path: '/',\n * })\n * ```\n */\n setCookie(name: string, value: string, options?: CookieOptions): void {\n honoSetCookie(this.c, name, value, options)\n }\n\n /**\n * Delete a cookie from the response.\n *\n * Pass the same `path` and `domain` options that were used when the cookie\n * was set, otherwise the browser will not clear the matching cookie.\n *\n * @param name - Cookie name\n * @param options - Cookie attributes used at set time (path, domain, etc.)\n * @returns The deleted cookie's previous value, or `undefined`\n *\n * @example\n * ```typescript\n * ctx.deleteCookie('redirectTo', { path: '/' })\n * ```\n */\n deleteCookie(name: string, options?: CookieOptions): string | undefined {\n return honoDeleteCookie(this.c, name, options)\n }\n\n /**\n * Get validated request body from OpenAPI route\n * Returns pre-validated data that has passed schema validation\n *\n * @returns Validated JSON body\n */\n body<T>(): Promise<T> {\n // Type assertion needed because req.valid() is type-safe per route\n // but this is a generic helper method that works across all routes\n return (this.c.req as unknown as { valid(target: 'json'): Promise<T> }).valid('json')\n }\n\n /**\n * Return text response\n *\n * @param text - Text content\n * @param status - HTTP status code (default: 200)\n */\n text(text: string, status?: ContentfulStatusCode): Response {\n return this.c.text(text, status)\n }\n\n /**\n * Return HTML response\n *\n * @param html - HTML content\n * @param status - HTTP status code (default: 200)\n */\n html(html: string, status?: ContentfulStatusCode): Response {\n return this.c.html(html, status)\n }\n\n /**\n * Generate a URL from a named route.\n *\n * Keys matching `:param` placeholders fill the path.\n * Domain params are consumed from the same object.\n * Extra keys become query string parameters.\n *\n * @param name - Named route identifier\n * @param params - Route params + domain params + extra query params\n * @param options - URL generation options (e.g., `{ absolute: true }`)\n *\n * @example\n * ```typescript\n * ctx.route('users.show', { id: '1' }) // '/v1/users/1'\n * ctx.route('users.show', { id: '1', q: 'test' }) // '/v1/users/1?q=test'\n * ```\n */\n route<N extends RouteName>(name: N, params?: RouteParams<N>, options?: UriOptions): string {\n return this.resolveUri().route(name, params, options)\n }\n\n /**\n * Get a domain parameter value from the current request.\n * Domain params are set by the domain matching middleware.\n *\n * @param key - Domain parameter name (e.g., 'tenant' from '{tenant}.myapp.com')\n *\n * @example\n * ```typescript\n * const tenant = ctx.domain('tenant')\n * ```\n */\n domain(key: string): string {\n return this.c.get(`domain:${key}`) as string\n }\n\n /**\n * Generate a signed URL from a named route.\n *\n * @param name - Named route identifier\n * @param params - Route params (same as route())\n * @param options - Signing options (e.g., expiresIn) and URL options\n * @returns Signed URL string with signature query param\n */\n async signedUrl<N extends RouteName>(name: N, params?: RouteParams<N>, options?: SignedUriOptions): Promise<string> {\n return this.resolveUri().signedRoute(name, params, options)\n }\n\n /**\n * Check if the current request has a valid signature.\n *\n * @returns true if the URL signature is valid and not expired\n */\n async hasValidSignature(): Promise<boolean> {\n return this.resolveUri().hasValidSignature()\n }\n\n /**\n * Redirect to another URL\n *\n * @param url - Target URL\n * @param status - HTTP status code (default: 302)\n */\n redirect(url: string, status?: RedirectStatusCode): Response {\n return this.c.redirect(url, status)\n }\n\n /**\n * Return a streaming response (binary/generic)\n *\n * @param callback - Async function that writes to the stream\n * @param onError - Optional error handler called if an error occurs during streaming\n */\n stream(callback: (stream: StreamingApi) => Promise<void>, onError?: (err: Error, stream: StreamingApi) => Promise<void>): Response {\n return honoStream(this.c, callback, onError)\n }\n\n /**\n * Return a streaming text response\n *\n * Automatically sets `Content-Encoding: Identity` for Cloudflare Workers compatibility.\n *\n * @param callback - Async function that writes text to the stream\n * @param onError - Optional error handler called if an error occurs during streaming\n */\n streamText(callback: (stream: StreamingApi) => Promise<void>, onError?: (err: Error, stream: StreamingApi) => Promise<void>): Response {\n this.c.header('Content-Encoding', 'Identity')\n return honoStreamText(this.c, callback, onError)\n }\n\n /**\n * Return a Server-Sent Events (SSE) streaming response\n *\n * Automatically sets `Content-Encoding: Identity` for Cloudflare Workers compatibility.\n *\n * @param callback - Async function that writes SSE events to the stream\n * @param onError - Optional error handler called if an error occurs during streaming\n */\n streamSSE(callback: (stream: SSEStreamingApi) => Promise<void>, onError?: (err: Error, stream: SSEStreamingApi) => Promise<void>): Response {\n this.c.header('Content-Encoding', 'Identity')\n return honoStreamSSE(this.c, callback, onError)\n }\n\n private resolveUri(): Uri {\n return this.getContainer().resolve<Uri>(ROUTER_TOKENS.Uri)\n }\n}\n","import type { Context } from 'hono'\nimport { RouterContext } from '../router/router-context'\nimport type { RouterEnv } from '../router/types'\n\n/**\n * Exception context for errors occurring during HTTP request handling.\n *\n * Provides access to the full {@link RouterContext} for building responses\n * with `ctx.json()`, `ctx.text()`, `ctx.html()`, etc.\n */\nexport interface HttpExceptionContext {\n readonly type: 'http'\n /** Stratal RouterContext — use for building HTTP responses */\n readonly ctx: RouterContext\n}\n\n/**\n * Exception context for errors occurring during queue message processing.\n */\nexport interface QueueExceptionContext {\n readonly type: 'queue'\n /** Name of the queue being processed */\n readonly queueName: string\n}\n\n/**\n * Exception context for errors occurring during scheduled cron execution.\n */\nexport interface CronExceptionContext {\n readonly type: 'cron'\n}\n\n/**\n * Exception context for errors occurring during CLI command execution.\n */\nexport interface CliExceptionContext {\n readonly type: 'cli'\n /** Name of the command that threw */\n readonly commandName: string\n}\n\n/**\n * Discriminated union of all exception context types.\n *\n * Narrow via `ctx.type` to access context-specific properties:\n *\n * @example\n * ```typescript\n * handler.renderable(MyError, (error, ctx) => {\n * if (ctx.type === 'http') {\n * return ctx.ctx.json({ message: 'Something went wrong' }, 500)\n * }\n * // Non-HTTP contexts: return undefined to use default rendering\n * })\n * ```\n */\nexport type ExceptionContext =\n | HttpExceptionContext\n | QueueExceptionContext\n | CronExceptionContext\n | CliExceptionContext\n\n/**\n * Create an HTTP exception context from a Hono context.\n *\n * @param c - The raw Hono context from the request\n * @returns An {@link HttpExceptionContext} wrapping a RouterContext\n */\nexport function createHttpExceptionContext(c: Context<RouterEnv>): HttpExceptionContext {\n return { type: 'http', ctx: new RouterContext(c) }\n}\n\n/**\n * Create a queue exception context.\n *\n * @param queueName - The name of the queue being processed\n * @returns A {@link QueueExceptionContext}\n */\nexport function createQueueExceptionContext(queueName: string): QueueExceptionContext {\n return { type: 'queue', queueName }\n}\n\n/**\n * Create a cron exception context.\n *\n * @returns A {@link CronExceptionContext}\n */\nexport function createCronExceptionContext(): CronExceptionContext {\n return { type: 'cron' }\n}\n\n/**\n * Create a CLI command exception context.\n *\n * @param commandName - The name of the command that threw\n * @returns A {@link CliExceptionContext}\n */\nexport function createCliExceptionContext(commandName: string): CliExceptionContext {\n return { type: 'cli', commandName }\n}\n"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  import { a as ApplicationError } from "./container-storage-BmOJ4_Na.mjs";
2
2
  import { n as getMetadata, t as defineMetadata } from "./metadata-DzzprcID.mjs";
3
3
  import { LOGGER_TOKENS } from "./logger/index.mjs";
4
- import "./errors-mXYxG0XB.mjs";
5
- import { a as RouterContext, u as ROUTE_METADATA_KEYS } from "./exception-context-kEoMFwze.mjs";
6
- import { t as Controller } from "./controller.decorator-C5UVeJS3.mjs";
4
+ import "./errors-C01O2T-n.mjs";
5
+ import { a as RouterContext, u as ROUTE_METADATA_KEYS } from "./exception-context-D-kvney-.mjs";
6
+ import { t as Controller } from "./controller.decorator-YSTPQntu.mjs";
7
7
  //#region src/websocket/decorators/gateway.decorator.ts
8
8
  const GATEWAY_MARKER_KEY = ROUTE_METADATA_KEYS.GATEWAY_MARKER;
9
9
  /**
@@ -222,4 +222,4 @@ var GatewayContext = class extends RouterContext {
222
222
  //#endregion
223
223
  export { getWsOnCloseMethod as a, WebSocketError as c, OnMessage as i, Gateway as l, OnClose as n, getWsOnErrorMethod as o, OnError as r, getWsOnMessageMethod as s, GatewayContext as t, isGateway as u };
224
224
 
225
- //# sourceMappingURL=gateway-context-TMu_AlJt.mjs.map
225
+ //# sourceMappingURL=gateway-context-m7kEzRa2.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"gateway-context-TMu_AlJt.mjs","names":[],"sources":["../src/websocket/decorators/gateway.decorator.ts","../src/websocket/websocket.error.ts","../src/websocket/decorators/ws-event.decorator.ts","../src/websocket/gateway-context.ts"],"sourcesContent":["import { defineMetadata, getMetadata } from '../../di/metadata'\nimport { ROUTE_METADATA_KEYS } from '../../router/constants'\nimport { Controller } from '../../router/decorators/controller.decorator'\nimport { type Constructor } from '../../types'\nimport type { GatewayOptions } from '../../websocket/types'\n\nconst GATEWAY_MARKER_KEY = ROUTE_METADATA_KEYS.GATEWAY_MARKER\n\n/**\n * Gateway decorator for WebSocket route registration\n *\n * Marks a class as a WebSocket gateway and stores route metadata.\n * Reuses the same metadata key as @Controller for middleware compatibility —\n * `getControllerRoute()`, `forRoutes()`, and the entire middleware system work\n * with zero changes.\n *\n * @param route - WebSocket route path (e.g., '/ws/chat')\n *\n * @example\n * ```typescript\n * import { type GatewayContext, Gateway, OnMessage, OnClose } from 'stratal/websocket'\n *\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnMessage()\n * handleMessage(evt: MessageEvent, ctx: GatewayContext) {\n * ctx.send('ack')\n * }\n *\n * @OnClose()\n * handleClose(evt: CloseEvent, ctx: GatewayContext) {\n * console.log('closed')\n * }\n * }\n * ```\n */\nexport function Gateway(route: string, options?: GatewayOptions) {\n return function <T extends Constructor>(target: T) {\n Controller(route, options)(target)\n defineMetadata(GATEWAY_MARKER_KEY, true, target)\n return target\n }\n}\n\n/**\n * Check if a class is a WebSocket gateway\n *\n * @param target - Class constructor or instance\n * @returns true if the class is decorated with @Gateway\n */\nexport function isGateway(target: object): boolean {\n const metadataTarget = typeof target === 'function' ? target : (target as { constructor: object }).constructor\n return getMetadata(GATEWAY_MARKER_KEY, metadataTarget) === true\n}\n","import { ApplicationError } from '../errors'\n\nexport class WebSocketError extends ApplicationError {}\n","import { defineMetadata, getMetadata } from '../../di/metadata'\nimport { ROUTE_METADATA_KEYS } from '../../router/constants'\nimport type { Constructor } from '../../types'\nimport { WebSocketError } from '../websocket.error'\n\nconst WS_ON_MESSAGE_KEY = ROUTE_METADATA_KEYS.WS_ON_MESSAGE\nconst WS_ON_CLOSE_KEY = ROUTE_METADATA_KEYS.WS_ON_CLOSE\nconst WS_ON_ERROR_KEY = ROUTE_METADATA_KEYS.WS_ON_ERROR\n\n/**\n * Define a single-handler metadata key on the prototype.\n * Throws if a different method already owns this key (prevents silent override).\n */\nfunction defineSingleHandlerMetadata(key: symbol, propertyKey: string | symbol, target: object, decoratorName: string): void {\n const existing = getMetadata<string | symbol>(key, target)\n if (existing !== undefined && existing !== propertyKey) {\n throw new WebSocketError(`Duplicate @${decoratorName} handler: method \"${String(existing)}\" is already registered`)\n }\n defineMetadata(key, propertyKey, target)\n}\n\n/**\n * Marks a method as the WebSocket message handler\n *\n * @example\n * ```typescript\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnMessage()\n * handleMessage(evt: MessageEvent, ctx: GatewayContext) {\n * ctx.send(evt.data)\n * }\n * }\n * ```\n */\nexport function OnMessage(): MethodDecorator {\n // `_target` is the class prototype (method decorator convention).\n // The getter functions below read from `target.prototype` symmetrically.\n return (_target: object, propertyKey: string | symbol) => {\n defineSingleHandlerMetadata(WS_ON_MESSAGE_KEY, propertyKey, _target, 'OnMessage')\n }\n}\n\n/**\n * Marks a method as the WebSocket close handler\n *\n * @example\n * ```typescript\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnClose()\n * handleClose(evt: CloseEvent, ctx: GatewayContext) {\n * console.log('closed')\n * }\n * }\n * ```\n */\nexport function OnClose(): MethodDecorator {\n return (_target: object, propertyKey: string | symbol) => {\n defineSingleHandlerMetadata(WS_ON_CLOSE_KEY, propertyKey, _target, 'OnClose')\n }\n}\n\n/**\n * Marks a method as the WebSocket error handler\n *\n * @example\n * ```typescript\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnError()\n * handleError(evt: Event, ctx: GatewayContext) {\n * console.error('WebSocket error', evt)\n * }\n * }\n * ```\n */\nexport function OnError(): MethodDecorator {\n return (_target: object, propertyKey: string | symbol) => {\n defineSingleHandlerMetadata(WS_ON_ERROR_KEY, propertyKey, _target, 'OnError')\n }\n}\n\n/**\n * Get the method name decorated with @OnMessage\n */\nexport function getWsOnMessageMethod(target: Constructor): string | undefined {\n return getMetadata<string>(WS_ON_MESSAGE_KEY, target.prototype as object)\n}\n\n/**\n * Get the method name decorated with @OnClose\n */\nexport function getWsOnCloseMethod(target: Constructor): string | undefined {\n return getMetadata<string>(WS_ON_CLOSE_KEY, target.prototype as object)\n}\n\n/**\n * Get the method name decorated with @OnError\n */\nexport function getWsOnErrorMethod(target: Constructor): string | undefined {\n return getMetadata<string>(WS_ON_ERROR_KEY, target.prototype as object)\n}\n","import type { Context } from 'hono'\nimport type { WSContext, WSReadyState } from 'hono/ws'\nimport { LOGGER_TOKENS, type LoggerService } from '../logger'\nimport type { ContextQueryResult } from '../router/router-context'\nimport { RouterContext } from '../router/router-context'\nimport type { RouterEnv } from '../router/types'\nimport { WebSocketError } from './websocket.error'\n\n/** WebSocket OPEN ready state (`WSReadyState` is a type-only union in hono/ws). */\nconst WS_OPEN = 1\n\n/**\n * WebSocket gateway context\n *\n * Extends RouterContext with WebSocket-specific methods.\n * Inherits `getContainer()`, `param()`, `query()`, `header()`, `getLocale()`\n * from RouterContext. HTTP response methods (`json()`, `redirect()`, etc.) are\n * inherited but harmless post-upgrade.\n *\n * @example\n * ```typescript\n * @OnMessage()\n * handleMessage(evt: MessageEvent, ctx: GatewayContext) {\n * ctx.send('ack') // convenience method\n * ctx.header('Authorization') // upgrade request headers\n * }\n * ```\n */\nexport class GatewayContext extends RouterContext {\n constructor(c: Context<RouterEnv>, public readonly ws: WSContext) {\n super(c)\n }\n\n /** Send data through the WebSocket connection */\n send(data: string | ArrayBuffer | Uint8Array<ArrayBuffer>): void {\n this.ws.send(data)\n }\n\n /**\n * Send only if the socket is still open. Returns `false` (and logs a warning)\n * when the socket is closing/closed — e.g. inside `@OnError`, which fires on a\n * transport error after the socket is already dead, or after an `await` in\n * `@OnMessage` when the client disconnected mid-handler. Use this for\n * fire-and-forget acks/errors instead of `send()`, which throws on a closed\n * socket (\"Can't call WebSocket send() after close()\").\n */\n trySend(data: string | ArrayBuffer | Uint8Array<ArrayBuffer>): boolean {\n if (this.ws.readyState !== WS_OPEN) {\n this.getContainer()\n .resolve<LoggerService>(LOGGER_TOKENS.LoggerService)\n .warn('Skipped WebSocket send on non-open socket', { readyState: this.ws.readyState })\n return false\n }\n this.ws.send(data)\n return true\n }\n\n /** Close the WebSocket connection */\n close(code?: number, reason?: string): void {\n this.ws.close(code, reason)\n }\n\n /** Current WebSocket ready state */\n get readyState(): WSReadyState {\n return this.ws.readyState\n }\n\n /**\n * Get route parameter value(s) from the raw request — WebSocket gateways are\n * not OpenAPI-registered, so reads come straight from Hono's matcher.\n *\n * - With a key → single string value.\n * - With no args → full `Record<string, string>` (or `{}` when none).\n *\n * @param key - Parameter name (e.g., 'id' for /ws/chat/:id)\n */\n override param(): Record<string, string>\n override param(key: string): string\n override param(key?: string): string | Record<string, string> {\n if (key === undefined) return this.c.req.param() ?? {}\n return this.c.req.param(key)!\n }\n\n /**\n * Get query parameter value from the raw request (no OpenAPI validation)\n *\n * @param key - Query parameter name\n */\n override query<R extends Record<string, unknown> | undefined = undefined, K extends string | undefined = undefined>(key?: K): ContextQueryResult<R, K> {\n if (key) {\n return this.c.req.query(key) as ContextQueryResult<R, K>\n }\n return this.c.req.query() as ContextQueryResult<R, K>\n }\n\n /**\n * Request body is not available in WebSocket gateways\n *\n * @throws WebSocketError always — WebSocket upgrade requests do not have a body\n */\n override body<T>(): Promise<T> {\n throw new WebSocketError('Request body is not available in WebSocket gateways')\n }\n}\n"],"mappings":";;;;;;;AAMA,MAAM,qBAAqB,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8B/C,SAAgB,QAAQ,OAAe,SAA0B;CAC/D,OAAO,SAAiC,QAAW;EACjD,WAAW,OAAO,OAAO,EAAE,MAAM;EACjC,eAAe,oBAAoB,MAAM,MAAM;EAC/C,OAAO;CACT;AACF;;;;;;;AAQA,SAAgB,UAAU,QAAyB;CAEjD,OAAO,YAAY,oBADI,OAAO,WAAW,aAAa,SAAU,OAAmC,WAC9C,MAAM;AAC7D;;;ACnDA,IAAa,iBAAb,cAAoC,iBAAiB,CAAC;;;ACGtD,MAAM,oBAAoB,oBAAoB;AAC9C,MAAM,kBAAkB,oBAAoB;AAC5C,MAAM,kBAAkB,oBAAoB;;;;;AAM5C,SAAS,4BAA4B,KAAa,aAA8B,QAAgB,eAA6B;CAC3H,MAAM,WAAW,YAA6B,KAAK,MAAM;CACzD,IAAI,aAAa,KAAA,KAAa,aAAa,aACzC,MAAM,IAAI,eAAe,cAAc,cAAc,oBAAoB,OAAO,QAAQ,EAAE,wBAAwB;CAEpH,eAAe,KAAK,aAAa,MAAM;AACzC;;;;;;;;;;;;;;;AAgBA,SAAgB,YAA6B;CAG3C,QAAQ,SAAiB,gBAAiC;EACxD,4BAA4B,mBAAmB,aAAa,SAAS,WAAW;CAClF;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,UAA2B;CACzC,QAAQ,SAAiB,gBAAiC;EACxD,4BAA4B,iBAAiB,aAAa,SAAS,SAAS;CAC9E;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,UAA2B;CACzC,QAAQ,SAAiB,gBAAiC;EACxD,4BAA4B,iBAAiB,aAAa,SAAS,SAAS;CAC9E;AACF;;;;AAKA,SAAgB,qBAAqB,QAAyC;CAC5E,OAAO,YAAoB,mBAAmB,OAAO,SAAmB;AAC1E;;;;AAKA,SAAgB,mBAAmB,QAAyC;CAC1E,OAAO,YAAoB,iBAAiB,OAAO,SAAmB;AACxE;;;;AAKA,SAAgB,mBAAmB,QAAyC;CAC1E,OAAO,YAAoB,iBAAiB,OAAO,SAAmB;AACxE;;;;AC7FA,MAAM,UAAU;;;;;;;;;;;;;;;;;;AAmBhB,IAAa,iBAAb,cAAoC,cAAc;CACG;CAAnD,YAAY,GAAuB,IAA+B;EAChE,MAAM,CAAC;EAD0C,KAAA,KAAA;CAEnD;;CAGA,KAAK,MAA4D;EAC/D,KAAK,GAAG,KAAK,IAAI;CACnB;;;;;;;;;CAUA,QAAQ,MAA+D;EACrE,IAAI,KAAK,GAAG,eAAe,SAAS;GAClC,KAAK,aAAa,EACf,QAAuB,cAAc,aAAa,EAClD,KAAK,6CAA6C,EAAE,YAAY,KAAK,GAAG,WAAW,CAAC;GACvF,OAAO;EACT;EACA,KAAK,GAAG,KAAK,IAAI;EACjB,OAAO;CACT;;CAGA,MAAM,MAAe,QAAuB;EAC1C,KAAK,GAAG,MAAM,MAAM,MAAM;CAC5B;;CAGA,IAAI,aAA2B;EAC7B,OAAO,KAAK,GAAG;CACjB;CAaA,MAAe,KAA+C;EAC5D,IAAI,QAAQ,KAAA,GAAW,OAAO,KAAK,EAAE,IAAI,MAAM,KAAK,CAAC;EACrD,OAAO,KAAK,EAAE,IAAI,MAAM,GAAG;CAC7B;;;;;;CAOA,MAAoH,KAAmC;EACrJ,IAAI,KACF,OAAO,KAAK,EAAE,IAAI,MAAM,GAAG;EAE7B,OAAO,KAAK,EAAE,IAAI,MAAM;CAC1B;;;;;;CAOA,OAA+B;EAC7B,MAAM,IAAI,eAAe,qDAAqD;CAChF;AACF"}
1
+ {"version":3,"file":"gateway-context-m7kEzRa2.mjs","names":[],"sources":["../src/websocket/decorators/gateway.decorator.ts","../src/websocket/websocket.error.ts","../src/websocket/decorators/ws-event.decorator.ts","../src/websocket/gateway-context.ts"],"sourcesContent":["import { defineMetadata, getMetadata } from '../../di/metadata'\nimport { ROUTE_METADATA_KEYS } from '../../router/constants'\nimport { Controller } from '../../router/decorators/controller.decorator'\nimport { type Constructor } from '../../types'\nimport type { GatewayOptions } from '../../websocket/types'\n\nconst GATEWAY_MARKER_KEY = ROUTE_METADATA_KEYS.GATEWAY_MARKER\n\n/**\n * Gateway decorator for WebSocket route registration\n *\n * Marks a class as a WebSocket gateway and stores route metadata.\n * Reuses the same metadata key as @Controller for middleware compatibility —\n * `getControllerRoute()`, `forRoutes()`, and the entire middleware system work\n * with zero changes.\n *\n * @param route - WebSocket route path (e.g., '/ws/chat')\n *\n * @example\n * ```typescript\n * import { type GatewayContext, Gateway, OnMessage, OnClose } from 'stratal/websocket'\n *\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnMessage()\n * handleMessage(evt: MessageEvent, ctx: GatewayContext) {\n * ctx.send('ack')\n * }\n *\n * @OnClose()\n * handleClose(evt: CloseEvent, ctx: GatewayContext) {\n * console.log('closed')\n * }\n * }\n * ```\n */\nexport function Gateway(route: string, options?: GatewayOptions) {\n return function <T extends Constructor>(target: T) {\n Controller(route, options)(target)\n defineMetadata(GATEWAY_MARKER_KEY, true, target)\n return target\n }\n}\n\n/**\n * Check if a class is a WebSocket gateway\n *\n * @param target - Class constructor or instance\n * @returns true if the class is decorated with @Gateway\n */\nexport function isGateway(target: object): boolean {\n const metadataTarget = typeof target === 'function' ? target : (target as { constructor: object }).constructor\n return getMetadata(GATEWAY_MARKER_KEY, metadataTarget) === true\n}\n","import { ApplicationError } from '../errors'\n\nexport class WebSocketError extends ApplicationError {}\n","import { defineMetadata, getMetadata } from '../../di/metadata'\nimport { ROUTE_METADATA_KEYS } from '../../router/constants'\nimport type { Constructor } from '../../types'\nimport { WebSocketError } from '../websocket.error'\n\nconst WS_ON_MESSAGE_KEY = ROUTE_METADATA_KEYS.WS_ON_MESSAGE\nconst WS_ON_CLOSE_KEY = ROUTE_METADATA_KEYS.WS_ON_CLOSE\nconst WS_ON_ERROR_KEY = ROUTE_METADATA_KEYS.WS_ON_ERROR\n\n/**\n * Define a single-handler metadata key on the prototype.\n * Throws if a different method already owns this key (prevents silent override).\n */\nfunction defineSingleHandlerMetadata(key: symbol, propertyKey: string | symbol, target: object, decoratorName: string): void {\n const existing = getMetadata<string | symbol>(key, target)\n if (existing !== undefined && existing !== propertyKey) {\n throw new WebSocketError(`Duplicate @${decoratorName} handler: method \"${String(existing)}\" is already registered`)\n }\n defineMetadata(key, propertyKey, target)\n}\n\n/**\n * Marks a method as the WebSocket message handler\n *\n * @example\n * ```typescript\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnMessage()\n * handleMessage(evt: MessageEvent, ctx: GatewayContext) {\n * ctx.send(evt.data)\n * }\n * }\n * ```\n */\nexport function OnMessage(): MethodDecorator {\n // `_target` is the class prototype (method decorator convention).\n // The getter functions below read from `target.prototype` symmetrically.\n return (_target: object, propertyKey: string | symbol) => {\n defineSingleHandlerMetadata(WS_ON_MESSAGE_KEY, propertyKey, _target, 'OnMessage')\n }\n}\n\n/**\n * Marks a method as the WebSocket close handler\n *\n * @example\n * ```typescript\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnClose()\n * handleClose(evt: CloseEvent, ctx: GatewayContext) {\n * console.log('closed')\n * }\n * }\n * ```\n */\nexport function OnClose(): MethodDecorator {\n return (_target: object, propertyKey: string | symbol) => {\n defineSingleHandlerMetadata(WS_ON_CLOSE_KEY, propertyKey, _target, 'OnClose')\n }\n}\n\n/**\n * Marks a method as the WebSocket error handler\n *\n * @example\n * ```typescript\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnError()\n * handleError(evt: Event, ctx: GatewayContext) {\n * console.error('WebSocket error', evt)\n * }\n * }\n * ```\n */\nexport function OnError(): MethodDecorator {\n return (_target: object, propertyKey: string | symbol) => {\n defineSingleHandlerMetadata(WS_ON_ERROR_KEY, propertyKey, _target, 'OnError')\n }\n}\n\n/**\n * Get the method name decorated with @OnMessage\n */\nexport function getWsOnMessageMethod(target: Constructor): string | undefined {\n return getMetadata<string>(WS_ON_MESSAGE_KEY, target.prototype as object)\n}\n\n/**\n * Get the method name decorated with @OnClose\n */\nexport function getWsOnCloseMethod(target: Constructor): string | undefined {\n return getMetadata<string>(WS_ON_CLOSE_KEY, target.prototype as object)\n}\n\n/**\n * Get the method name decorated with @OnError\n */\nexport function getWsOnErrorMethod(target: Constructor): string | undefined {\n return getMetadata<string>(WS_ON_ERROR_KEY, target.prototype as object)\n}\n","import type { Context } from 'hono'\nimport type { WSContext, WSReadyState } from 'hono/ws'\nimport { LOGGER_TOKENS, type LoggerService } from '../logger'\nimport type { ContextQueryResult } from '../router/router-context'\nimport { RouterContext } from '../router/router-context'\nimport type { RouterEnv } from '../router/types'\nimport { WebSocketError } from './websocket.error'\n\n/** WebSocket OPEN ready state (`WSReadyState` is a type-only union in hono/ws). */\nconst WS_OPEN = 1\n\n/**\n * WebSocket gateway context\n *\n * Extends RouterContext with WebSocket-specific methods.\n * Inherits `getContainer()`, `param()`, `query()`, `header()`, `getLocale()`\n * from RouterContext. HTTP response methods (`json()`, `redirect()`, etc.) are\n * inherited but harmless post-upgrade.\n *\n * @example\n * ```typescript\n * @OnMessage()\n * handleMessage(evt: MessageEvent, ctx: GatewayContext) {\n * ctx.send('ack') // convenience method\n * ctx.header('Authorization') // upgrade request headers\n * }\n * ```\n */\nexport class GatewayContext extends RouterContext {\n constructor(c: Context<RouterEnv>, public readonly ws: WSContext) {\n super(c)\n }\n\n /** Send data through the WebSocket connection */\n send(data: string | ArrayBuffer | Uint8Array<ArrayBuffer>): void {\n this.ws.send(data)\n }\n\n /**\n * Send only if the socket is still open. Returns `false` (and logs a warning)\n * when the socket is closing/closed — e.g. inside `@OnError`, which fires on a\n * transport error after the socket is already dead, or after an `await` in\n * `@OnMessage` when the client disconnected mid-handler. Use this for\n * fire-and-forget acks/errors instead of `send()`, which throws on a closed\n * socket (\"Can't call WebSocket send() after close()\").\n */\n trySend(data: string | ArrayBuffer | Uint8Array<ArrayBuffer>): boolean {\n if (this.ws.readyState !== WS_OPEN) {\n this.getContainer()\n .resolve<LoggerService>(LOGGER_TOKENS.LoggerService)\n .warn('Skipped WebSocket send on non-open socket', { readyState: this.ws.readyState })\n return false\n }\n this.ws.send(data)\n return true\n }\n\n /** Close the WebSocket connection */\n close(code?: number, reason?: string): void {\n this.ws.close(code, reason)\n }\n\n /** Current WebSocket ready state */\n get readyState(): WSReadyState {\n return this.ws.readyState\n }\n\n /**\n * Get route parameter value(s) from the raw request — WebSocket gateways are\n * not OpenAPI-registered, so reads come straight from Hono's matcher.\n *\n * - With a key → single string value.\n * - With no args → full `Record<string, string>` (or `{}` when none).\n *\n * @param key - Parameter name (e.g., 'id' for /ws/chat/:id)\n */\n override param(): Record<string, string>\n override param(key: string): string\n override param(key?: string): string | Record<string, string> {\n if (key === undefined) return this.c.req.param() ?? {}\n return this.c.req.param(key)!\n }\n\n /**\n * Get query parameter value from the raw request (no OpenAPI validation)\n *\n * @param key - Query parameter name\n */\n override query<R extends Record<string, unknown> | undefined = undefined, K extends string | undefined = undefined>(key?: K): ContextQueryResult<R, K> {\n if (key) {\n return this.c.req.query(key) as ContextQueryResult<R, K>\n }\n return this.c.req.query() as ContextQueryResult<R, K>\n }\n\n /**\n * Request body is not available in WebSocket gateways\n *\n * @throws WebSocketError always — WebSocket upgrade requests do not have a body\n */\n override body<T>(): Promise<T> {\n throw new WebSocketError('Request body is not available in WebSocket gateways')\n }\n}\n"],"mappings":";;;;;;;AAMA,MAAM,qBAAqB,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8B/C,SAAgB,QAAQ,OAAe,SAA0B;CAC/D,OAAO,SAAiC,QAAW;EACjD,WAAW,OAAO,OAAO,EAAE,MAAM;EACjC,eAAe,oBAAoB,MAAM,MAAM;EAC/C,OAAO;CACT;AACF;;;;;;;AAQA,SAAgB,UAAU,QAAyB;CAEjD,OAAO,YAAY,oBADI,OAAO,WAAW,aAAa,SAAU,OAAmC,WAC9C,MAAM;AAC7D;;;ACnDA,IAAa,iBAAb,cAAoC,iBAAiB,CAAC;;;ACGtD,MAAM,oBAAoB,oBAAoB;AAC9C,MAAM,kBAAkB,oBAAoB;AAC5C,MAAM,kBAAkB,oBAAoB;;;;;AAM5C,SAAS,4BAA4B,KAAa,aAA8B,QAAgB,eAA6B;CAC3H,MAAM,WAAW,YAA6B,KAAK,MAAM;CACzD,IAAI,aAAa,KAAA,KAAa,aAAa,aACzC,MAAM,IAAI,eAAe,cAAc,cAAc,oBAAoB,OAAO,QAAQ,EAAE,wBAAwB;CAEpH,eAAe,KAAK,aAAa,MAAM;AACzC;;;;;;;;;;;;;;;AAgBA,SAAgB,YAA6B;CAG3C,QAAQ,SAAiB,gBAAiC;EACxD,4BAA4B,mBAAmB,aAAa,SAAS,WAAW;CAClF;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,UAA2B;CACzC,QAAQ,SAAiB,gBAAiC;EACxD,4BAA4B,iBAAiB,aAAa,SAAS,SAAS;CAC9E;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,UAA2B;CACzC,QAAQ,SAAiB,gBAAiC;EACxD,4BAA4B,iBAAiB,aAAa,SAAS,SAAS;CAC9E;AACF;;;;AAKA,SAAgB,qBAAqB,QAAyC;CAC5E,OAAO,YAAoB,mBAAmB,OAAO,SAAmB;AAC1E;;;;AAKA,SAAgB,mBAAmB,QAAyC;CAC1E,OAAO,YAAoB,iBAAiB,OAAO,SAAmB;AACxE;;;;AAKA,SAAgB,mBAAmB,QAAyC;CAC1E,OAAO,YAAoB,iBAAiB,OAAO,SAAmB;AACxE;;;;AC7FA,MAAM,UAAU;;;;;;;;;;;;;;;;;;AAmBhB,IAAa,iBAAb,cAAoC,cAAc;CACG;CAAnD,YAAY,GAAuB,IAA+B;EAChE,MAAM,CAAC;EAD0C,KAAA,KAAA;CAEnD;;CAGA,KAAK,MAA4D;EAC/D,KAAK,GAAG,KAAK,IAAI;CACnB;;;;;;;;;CAUA,QAAQ,MAA+D;EACrE,IAAI,KAAK,GAAG,eAAe,SAAS;GAClC,KAAK,aAAa,EACf,QAAuB,cAAc,aAAa,EAClD,KAAK,6CAA6C,EAAE,YAAY,KAAK,GAAG,WAAW,CAAC;GACvF,OAAO;EACT;EACA,KAAK,GAAG,KAAK,IAAI;EACjB,OAAO;CACT;;CAGA,MAAM,MAAe,QAAuB;EAC1C,KAAK,GAAG,MAAM,MAAM,MAAM;CAC5B;;CAGA,IAAI,aAA2B;EAC7B,OAAO,KAAK,GAAG;CACjB;CAaA,MAAe,KAA+C;EAC5D,IAAI,QAAQ,KAAA,GAAW,OAAO,KAAK,EAAE,IAAI,MAAM,KAAK,CAAC;EACrD,OAAO,KAAK,EAAE,IAAI,MAAM,GAAG;CAC7B;;;;;;CAOA,MAAoH,KAAmC;EACrJ,IAAI,KACF,OAAO,KAAK,EAAE,IAAI,MAAM,GAAG;EAE7B,OAAO,KAAK,EAAE,IAAI,MAAM;CAC1B;;;;;;CAOA,OAA+B;EAC7B,MAAM,IAAI,eAAe,qDAAqD;CAChF;AACF"}
@@ -1,4 +1,4 @@
1
- import { Y as Container, rt as RouterContext } from "../index-B_JoEl3V.mjs";
1
+ import { $ as Container, st as RouterContext } from "../index-uybm0bhQ.mjs";
2
2
  import { r as LoggerService } from "../index-BUt92sAE.mjs";
3
3
  import { t as Constructor } from "../types-CmV_9xBD.mjs";
4
4
 
@@ -1,15 +1,15 @@
1
1
  import { t as __exportAll } from "./chunk-BBjsoOtd.mjs";
2
- import { d as inject, n as CONTAINER_TOKEN, r as DI_TOKENS, s as Singleton } from "./di-DseMn-z9.mjs";
2
+ import { b as ROUTER_TOKENS, l as Singleton, n as CONTAINER_TOKEN, p as inject, r as DI_TOKENS } from "./di-D7qmrAir.mjs";
3
3
  import { r as runWithContainer } from "./container-storage-BmOJ4_Na.mjs";
4
4
  import { n as __decorateParam, t as __decorate } from "./decorate-CuAoSZvs.mjs";
5
5
  import { LOGGER_TOKENS } from "./logger/index.mjs";
6
- import { a as RouterContext, l as ROUTER_CONTEXT_KEYS, r as createHttpExceptionContext } from "./exception-context-kEoMFwze.mjs";
7
- import { o as RouterError } from "./module-registry-Dm-pqHd3.mjs";
6
+ import { a as RouterContext, l as ROUTER_CONTEXT_KEYS, r as createHttpExceptionContext } from "./exception-context-D-kvney-.mjs";
7
+ import { o as RouterError } from "./module-registry-NxX5O0Qk.mjs";
8
8
  import { t as OpenAPIHono } from "./zod-eKqqhZ5_.mjs";
9
9
  import { n as OPENAPI_TOKENS } from "./openapi-tools.service-BC5EC3R3.mjs";
10
- import "./openapi-CstuTM8S.mjs";
11
- import { p as createMiddlewareChain, t as RouteRegistrationService, v as SchemaValidationError, y as RouteNotFoundError } from "./route-registration.service-D6vSwiKP.mjs";
12
- import { t as applyTrailingSlash } from "./trailing-slash-CFyw8nYu.mjs";
10
+ import "./openapi-CMwuCp31.mjs";
11
+ import { p as createMiddlewareChain, t as RouteRegistrationService, v as SchemaValidationError, y as RouteNotFoundError } from "./route-registration.service-CDPQKpm4.mjs";
12
+ import { r as resolveTrailingSlash, t as applyTrailingSlash } from "./trailing-slash-2SctvePW.mjs";
13
13
  //#region src/router/middleware/logger.middleware.ts
14
14
  /**
15
15
  * Create a Hono middleware that logs HTTP requests using our Logger service
@@ -54,6 +54,12 @@ const REDIRECT_STATUS = 308;
54
54
  * Paths whose last segment contains `.` (e.g. `/api/openapi.json`) are skipped.
55
55
  * - `'never'` — trailing requests redirect to the non-trailing form.
56
56
  *
57
+ * Paths in the config's `exclude` list are never redirected — both forms
58
+ * are served as requested (routes match either, `strict: false`). When
59
+ * `getLocales` is provided, exclusions also match locale-prefixed request
60
+ * paths (`/fr/callback` for exclude `'/callback'`). It is a thunk because
61
+ * the middleware is created before the i18n module resolves its locales.
62
+ *
57
63
  * Root (`/`) is always passed through unchanged.
58
64
  *
59
65
  * 308 is used so that POST/PUT/PATCH bodies survive the redirect.
@@ -63,11 +69,12 @@ const REDIRECT_STATUS = 308;
63
69
  * mismatches behind HTTPS-terminating proxies that proxy HTTPS pages to an
64
70
  * HTTP-speaking backend (which would otherwise produce a mixed-content block).
65
71
  */
66
- function createTrailingSlashRedirect(mode) {
67
- if (mode === "ignore") return null;
72
+ function createTrailingSlashRedirect(config, getLocales) {
73
+ const options = resolveTrailingSlash(config);
74
+ if (options.mode === "ignore") return null;
68
75
  return async (c, next) => {
69
76
  const url = new URL(c.req.url);
70
- const canonicalPath = applyTrailingSlash(url.pathname, mode);
77
+ const canonicalPath = applyTrailingSlash(url.pathname, options, getLocales?.());
71
78
  if (canonicalPath === url.pathname) return next();
72
79
  return c.redirect(`${canonicalPath}${url.search}`, REDIRECT_STATUS);
73
80
  };
@@ -108,7 +115,7 @@ let HonoApp = class HonoApp extends OpenAPIHono {
108
115
  }
109
116
  return this.nativeUse(...args);
110
117
  });
111
- const trailingSlashRedirect = createTrailingSlashRedirect(trailingSlash);
118
+ const trailingSlashRedirect = createTrailingSlashRedirect(trailingSlash, () => this._container.tryResolve(ROUTER_TOKENS.LocalePathService)?.localePathConfig?.allLocales);
112
119
  if (trailingSlashRedirect) this.nativeUse("*", trailingSlashRedirect);
113
120
  this.setupRequestScope();
114
121
  this.applyGlobalMiddleware();
@@ -158,4 +165,4 @@ HonoApp = __decorate([
158
165
  //#endregion
159
166
  export { hono_app_exports as n, HonoApp as t };
160
167
 
161
- //# sourceMappingURL=hono-app-CvV3hOfT.mjs.map
168
+ //# sourceMappingURL=hono-app-COAgmutc.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hono-app-COAgmutc.mjs","names":[],"sources":["../src/router/middleware/logger.middleware.ts","../src/router/middleware/trailing-slash-redirect.ts","../src/router/hono-app.ts"],"sourcesContent":["import type { MiddlewareHandler } from 'hono'\nimport type { LoggerService } from '../../logger'\n\n/**\n * Create a Hono middleware that logs HTTP requests using our Logger service\n *\n * Logs request method, path, status code, and duration in milliseconds.\n * Format: [HTTP] METHOD /path -> STATUS (duration ms)\n *\n * @param logger - Logger service instance\n * @returns Hono middleware handler\n *\n * @example\n * ```typescript\n * const logger = container.resolve<LoggerService>(LOGGER_TOKENS.LoggerService)\n * app.use('*', createLoggerMiddleware(logger))\n * ```\n */\nexport function createLoggerMiddleware(logger: LoggerService): MiddlewareHandler {\n return async (c, next) => {\n const start = Date.now()\n const method = c.req.method\n const path = c.req.path\n\n await next()\n\n const duration = Date.now() - start\n const status = c.res.status\n\n logger.info(`[HTTP] ${method} ${path} -> ${status}`, {\n method,\n path,\n status,\n duration,\n })\n }\n}\n","import type { MiddlewareHandler } from 'hono'\nimport { applyTrailingSlash, resolveTrailingSlash } from '../trailing-slash'\nimport type { RouterEnv, TrailingSlashConfig } from '../types'\n\nconst REDIRECT_STATUS = 308\n\n/**\n * Create a Hono middleware that canonicalises trailing slashes via 308 redirects.\n *\n * - `'ignore'` — returns `null`; routes match both `/foo` and `/foo/` natively\n * (Hono handles this when constructed with `strict: false`).\n * - `'always'` — non-trailing requests redirect to the trailing-slash form.\n * Paths whose last segment contains `.` (e.g. `/api/openapi.json`) are skipped.\n * - `'never'` — trailing requests redirect to the non-trailing form.\n *\n * Paths in the config's `exclude` list are never redirected — both forms\n * are served as requested (routes match either, `strict: false`). When\n * `getLocales` is provided, exclusions also match locale-prefixed request\n * paths (`/fr/callback` for exclude `'/callback'`). It is a thunk because\n * the middleware is created before the i18n module resolves its locales.\n *\n * Root (`/`) is always passed through unchanged.\n *\n * 308 is used so that POST/PUT/PATCH bodies survive the redirect.\n *\n * Location headers are emitted as path-relative URIs so the user agent\n * resolves them against the effective request URI — sidestepping scheme\n * mismatches behind HTTPS-terminating proxies that proxy HTTPS pages to an\n * HTTP-speaking backend (which would otherwise produce a mixed-content block).\n */\nexport function createTrailingSlashRedirect(\n config: TrailingSlashConfig,\n getLocales?: () => readonly string[] | undefined,\n): MiddlewareHandler<RouterEnv> | null {\n const options = resolveTrailingSlash(config)\n if (options.mode === 'ignore') return null\n\n return async (c, next) => {\n const url = new URL(c.req.url)\n const canonicalPath = applyTrailingSlash(url.pathname, options, getLocales?.())\n if (canonicalPath === url.pathname) return next()\n return c.redirect(`${canonicalPath}${url.search}`, REDIRECT_STATUS)\n }\n}\n","import type { Context, MiddlewareHandler } from 'hono'\nimport { inject } from '../di'\nimport type { Application } from '../application'\nimport type { Container } from '../di/container'\nimport { runWithContainer } from '../di/container-storage'\nimport { Singleton } from '../di/decorators'\nimport { CONTAINER_TOKEN, DI_TOKENS } from '../di/tokens'\nimport { createHttpExceptionContext } from '../errors/exception-context'\nimport type { ExceptionHandler } from '../errors/exception-handler'\nimport { OpenAPIHono } from '../i18n/validation/zod'\nimport { LOGGER_TOKENS, type LoggerService } from '../logger'\nimport { OPENAPI_TOKENS, type OpenAPIService } from '../openapi'\nimport type { Constructor } from '../types'\nimport { ROUTER_CONTEXT_KEYS } from './constants'\nimport { RouteNotFoundError, SchemaValidationError } from './errors'\nimport { RouterError } from './router.error'\nimport { createLoggerMiddleware, createMiddlewareChain, createTrailingSlashRedirect } from './middleware'\nimport type { Middleware } from './middleware.interface'\nimport { RouterContext } from './router-context'\nimport { ROUTER_TOKENS } from './router.tokens'\nimport type { LocalePathService } from './services/locale-path.service'\nimport { RouteRegistrationService } from './services/route-registration.service'\nimport type { RouterEnv, TrailingSlashConfig } from './types'\n\nconst isMiddlewareClass = (arg: unknown): arg is Constructor<Middleware> =>\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n typeof arg === 'function' && arg.prototype && 'handle' in arg.prototype\n\n\n/**\n * HonoApp — extends OpenAPIHono with Stratal-specific setup\n *\n * - Request scope middleware (child container per request)\n * - Global middleware (CORS, logging, error handling)\n * - defaultHook for validation errors\n * - `use()` overload for Stratal middleware classes\n * - `configure()` for OpenAPI, routes, and 404\n */\n@Singleton()\nexport class HonoApp extends OpenAPIHono<RouterEnv> {\n private configured = false\n private readonly _container: Container\n private readonly _logger: LoggerService\n\n /**\n * Reference to the original Hono `use` implementation.\n * Captured in constructor after super() sets it as an instance property.\n * Used by private methods to register middleware without going through the override.\n */\n private nativeUse!: typeof this.use\n\n constructor(\n @inject(CONTAINER_TOKEN) container: Container,\n @inject(LOGGER_TOKENS.LoggerService) logger: LoggerService,\n @inject(DI_TOKENS.Application) application: Application,\n ) {\n const trailingSlash: TrailingSlashConfig = application.config.trailingSlash ?? 'ignore'\n\n super({\n // Always non-strict: a registered `/foo` route matches both `/foo` and `/foo/`.\n // For the redirect modes, the trailing-slash middleware runs first and\n // canonicalises via 308 before matching reaches the registered route.\n strict: false,\n defaultHook: (result) => {\n if (!result.success) {\n throw new SchemaValidationError(result.error)\n }\n },\n })\n\n this._container = container\n this._logger = logger\n\n // Capture Hono's original `use` (set by super() as an instance property)\n this.nativeUse = this.use\n\n // Override `use` to support Stratal middleware classes alongside Hono-native handlers\n this.use = ((...args: unknown[]) => {\n if (isMiddlewareClass(args[0])) {\n this.nativeUse('*', createMiddlewareChain(args as Constructor<Middleware>[]))\n return this\n }\n\n if (typeof args[0] === 'string' && args.length > 1 && isMiddlewareClass(args[1])) {\n this.nativeUse(args[0], createMiddlewareChain(args.slice(1) as Constructor<Middleware>[]))\n return this\n }\n\n return (this.nativeUse as (...a: unknown[]) => unknown)(...args)\n }) as typeof this.use\n\n // Trailing-slash redirect runs first so redirected requests skip request-scope\n // and logger overhead. Locales are read lazily — the i18n module (and thus\n // LocalePathService) initialises after HonoApp is constructed.\n const trailingSlashRedirect = createTrailingSlashRedirect(trailingSlash, () =>\n this._container.tryResolve<LocalePathService>(ROUTER_TOKENS.LocalePathService)?.localePathConfig?.allLocales)\n if (trailingSlashRedirect) {\n this.nativeUse('*', trailingSlashRedirect)\n }\n\n // Internal setup — uses nativeUse to bypass the override\n this.setupRequestScope()\n this.applyGlobalMiddleware()\n }\n\n /**\n * Apply global middleware (logger + error handler).\n * Called by Application after locale middleware is applied by LocalePathService.\n */\n private applyGlobalMiddleware(): void {\n this.nativeUse('*', createLoggerMiddleware(this._logger) as MiddlewareHandler<RouterEnv>)\n this.onError((err, c) => this.handleException(c, err))\n }\n\n /**\n * Configure OpenAPI endpoints, controller routes, and 404 handler.\n * Called once by Application.initialize().\n */\n async configure(): Promise<void> {\n if (this.configured) throw new RouterError('HonoApp has already been configured')\n\n // OpenAPI endpoints\n const openAPIService = this._container.resolve<OpenAPIService>(OPENAPI_TOKENS.OpenAPIService)\n openAPIService.setupEndpoints(this, this._container)\n\n // Controller routes + global middleware\n const routeRegistrationService = this._container.resolve<RouteRegistrationService>(RouteRegistrationService)\n await routeRegistrationService.configure()\n\n // 404 handler (must be last)\n this.notFound((c) => { throw new RouteNotFoundError(c.req.path, c.req.method) })\n\n this.configured = true\n }\n\n private setupRequestScope(): void {\n this.nativeUse('*', async (c: Context<RouterEnv>, next: () => Promise<void>) => {\n const routerContext = new RouterContext(c)\n const requestContainer = this._container.createRequestScope(routerContext)\n c.set(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER, requestContainer)\n\n await runWithContainer(requestContainer, next)\n })\n }\n\n private handleException(c: Context<RouterEnv>, err: unknown) {\n // Fallback to global container if request scope setup failed before storing REQUEST_CONTAINER\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard: REQUEST_CONTAINER may be unset if request scope middleware throws\n const requestContainer = c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER) ?? this._container\n const handler = requestContainer.resolve<ExceptionHandler>(DI_TOKENS.ExceptionHandler)\n const ctx = createHttpExceptionContext(c)\n // Run the handler within the request container's async context so standalone\n // helpers like `route()` (which read the ambient container via getContainer)\n // resolve correctly. Errors thrown before the request-scope middleware's\n // `runWithContainer` body — e.g. route-param validation failures — otherwise\n // reach here outside any container scope, breaking redirect-back rendering.\n return runWithContainer(requestContainer, () => handler.handle(err, ctx))\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { n as getMetadata, t as defineMetadata } from "./metadata-DzzprcID.mjs";
2
- import { u as ROUTE_METADATA_KEYS } from "./exception-context-kEoMFwze.mjs";
2
+ import { u as ROUTE_METADATA_KEYS } from "./exception-context-D-kvney-.mjs";
3
3
  import { r as z } from "./zod-eKqqhZ5_.mjs";
4
4
  //#region src/router/decorators/http-method.decorator.ts
5
5
  /**
@@ -94,4 +94,4 @@ const All = createHttpMethodDecorator("all");
94
94
  //#endregion
95
95
  export { Post as a, Patch as i, Delete as n, Put as o, Get as r, All as t };
96
96
 
97
- //# sourceMappingURL=http-method.decorator-ByWZb9DO.mjs.map
97
+ //# sourceMappingURL=http-method.decorator-BljM8BDj.mjs.map