stratal 0.0.25 → 0.0.27

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 (134) hide show
  1. package/dist/bin/quarry.mjs +72 -6
  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-4HnKnC-G.d.mts → command-DoBD2Cwl.d.mts} +2 -2
  6. package/dist/{command-4HnKnC-G.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-DKeZ71aP.mjs → controller.decorator-YSTPQntu.mjs} +3 -3
  10. package/dist/{controller.decorator-DKeZ71aP.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-DgxLe8Xy.mjs → cron.module-C81HTzR7.mjs} +3 -3
  14. package/dist/{cron.module-DgxLe8Xy.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-B0NXIdRF.mjs → di-D7qmrAir.mjs} +44 -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-EUtwVfNm.mjs → errors-C01O2T-n.mjs} +19 -4
  24. package/dist/errors-C01O2T-n.mjs.map +1 -0
  25. package/dist/events/index.mjs +1 -1
  26. package/dist/{events-zbCMY_Gf.mjs → events-BhEQuT1X.mjs} +2 -2
  27. package/dist/{events-zbCMY_Gf.mjs.map → events-BhEQuT1X.mjs.map} +1 -1
  28. package/dist/{exception-context-Bd5Hk2c_.mjs → exception-context-D-kvney-.mjs} +2 -2
  29. package/dist/{exception-context-Bd5Hk2c_.mjs.map → exception-context-D-kvney-.mjs.map} +1 -1
  30. package/dist/{gateway-context-I0tKgGfh.mjs → gateway-context-m7kEzRa2.mjs} +4 -4
  31. package/dist/{gateway-context-I0tKgGfh.mjs.map → gateway-context-m7kEzRa2.mjs.map} +1 -1
  32. package/dist/guards/index.d.mts +1 -1
  33. package/dist/{hono-app-C7-1m9eK.mjs → hono-app-COAgmutc.mjs} +18 -11
  34. package/dist/hono-app-COAgmutc.mjs.map +1 -0
  35. package/dist/{http-method.decorator-xCuGoZPC.mjs → http-method.decorator-BljM8BDj.mjs} +2 -2
  36. package/dist/{http-method.decorator-xCuGoZPC.mjs.map → http-method.decorator-BljM8BDj.mjs.map} +1 -1
  37. package/dist/i18n/index.d.mts +10 -3
  38. package/dist/i18n/index.d.mts.map +1 -1
  39. package/dist/i18n/index.mjs +3 -3
  40. package/dist/{i18n.module-JOPUNpvi.mjs → i18n.module-B2DvWUPa.mjs} +24 -9
  41. package/dist/i18n.module-B2DvWUPa.mjs.map +1 -0
  42. package/dist/{index-BPJSBHhq.d.mts → index-CNuFQSNj.d.mts} +6 -4
  43. package/dist/{index-BPJSBHhq.d.mts.map → index-CNuFQSNj.d.mts.map} +1 -1
  44. package/dist/{index-C_t8VVQD.d.mts → index-uybm0bhQ.d.mts} +100 -6
  45. package/dist/index-uybm0bhQ.d.mts.map +1 -0
  46. package/dist/index.d.mts +3 -3
  47. package/dist/index.mjs +2 -2
  48. package/dist/{lazy-module-loader-DZQxOgRZ.d.mts → lazy-module-loader-M6YKudNL.d.mts} +2 -2
  49. package/dist/{lazy-module-loader-DZQxOgRZ.d.mts.map → lazy-module-loader-M6YKudNL.d.mts.map} +1 -1
  50. package/dist/{locale-path.service-DzA01Kvc.mjs → locale-path.service-CH0CaxwH.mjs} +3 -3
  51. package/dist/{locale-path.service-DzA01Kvc.mjs.map → locale-path.service-CH0CaxwH.mjs.map} +1 -1
  52. package/dist/{locale-url.service-DwTXZid3.mjs → locale-url.service-6bgia24_.mjs} +2 -2
  53. package/dist/{locale-url.service-DwTXZid3.mjs.map → locale-url.service-6bgia24_.mjs.map} +1 -1
  54. package/dist/logger/index.mjs +1 -1
  55. package/dist/module/index.d.mts +2 -2
  56. package/dist/module/index.mjs +2 -2
  57. package/dist/{module-registry-MfWmniZ7.mjs → module-registry-NxX5O0Qk.mjs} +4 -4
  58. package/dist/{module-registry-MfWmniZ7.mjs.map → module-registry-NxX5O0Qk.mjs.map} +1 -1
  59. package/dist/openapi/index.d.mts +2 -2
  60. package/dist/openapi/index.mjs +1 -1
  61. package/dist/{openapi-BKDwQKh-.mjs → openapi-CMwuCp31.mjs} +3 -3
  62. package/dist/{openapi-BKDwQKh-.mjs.map → openapi-CMwuCp31.mjs.map} +1 -1
  63. package/dist/{openapi.service-CM40bnBB.d.mts → openapi.service-2rvJBCEg.d.mts} +2 -2
  64. package/dist/{openapi.service-CM40bnBB.d.mts.map → openapi.service-2rvJBCEg.d.mts.map} +1 -1
  65. package/dist/quarry/index.d.mts +3 -3
  66. package/dist/quarry/index.mjs +1 -1
  67. package/dist/quarry/runner.d.mts +6 -6
  68. package/dist/quarry/runner.mjs +5 -5
  69. package/dist/{quarry-registry-CsStq0DB.d.mts → quarry-registry-DRnV-DDa.d.mts} +3 -3
  70. package/dist/{quarry-registry-CsStq0DB.d.mts.map → quarry-registry-DRnV-DDa.d.mts.map} +1 -1
  71. package/dist/{quarry.module-BOgcNJMU.mjs → quarry.module-CcGxU2dJ.mjs} +3 -3
  72. package/dist/{quarry.module-BOgcNJMU.mjs.map → quarry.module-CcGxU2dJ.mjs.map} +1 -1
  73. package/dist/queue/index.d.mts +1 -1
  74. package/dist/queue/index.mjs +2 -2
  75. package/dist/{queue.module-DSAH88gZ.mjs → queue.module-CEs4_kEM.mjs} +15 -8
  76. package/dist/{queue.module-DSAH88gZ.mjs.map → queue.module-CEs4_kEM.mjs.map} +1 -1
  77. package/dist/{r2-storage.provider-BJ4j23W6.mjs → r2-storage.provider-BoZmR6Ut.mjs} +2 -2
  78. package/dist/{r2-storage.provider-BJ4j23W6.mjs.map → r2-storage.provider-BoZmR6Ut.mjs.map} +1 -1
  79. package/dist/{rate-limit.decorator-B35jBEVD.mjs → rate-limit.decorator-Djs4oYDB.mjs} +2 -2
  80. package/dist/{rate-limit.decorator-B35jBEVD.mjs.map → rate-limit.decorator-Djs4oYDB.mjs.map} +1 -1
  81. package/dist/rate-limiter/index.d.mts +54 -47
  82. package/dist/rate-limiter/index.d.mts.map +1 -1
  83. package/dist/rate-limiter/index.mjs +16 -7
  84. package/dist/rate-limiter/index.mjs.map +1 -1
  85. package/dist/{route-registration.service-HN8WgIis.mjs → route-registration.service-CDPQKpm4.mjs} +9 -9
  86. package/dist/{route-registration.service-HN8WgIis.mjs.map → route-registration.service-CDPQKpm4.mjs.map} +1 -1
  87. package/dist/{route-registry-CtW26Suv.mjs → route-registry-BvLJisvK.mjs} +3 -3
  88. package/dist/{route-registry-CtW26Suv.mjs.map → route-registry-BvLJisvK.mjs.map} +1 -1
  89. package/dist/router/index.d.mts +2 -2
  90. package/dist/router/index.mjs +16 -16
  91. package/dist/{router-OCnf4VB6.mjs → router-DwyqEXgf.mjs} +13 -13
  92. package/dist/{router-OCnf4VB6.mjs.map → router-DwyqEXgf.mjs.map} +1 -1
  93. package/dist/{router-resolver-CMNuw-s0.mjs → router-resolver-sUV_jTrU.mjs} +2 -2
  94. package/dist/{router-resolver-CMNuw-s0.mjs.map → router-resolver-sUV_jTrU.mjs.map} +1 -1
  95. package/dist/seeder/index.d.mts +2 -2
  96. package/dist/seeder/index.mjs +3 -3
  97. package/dist/{seeder-DI-rUoZx.mjs → seeder-BPGY5rUb.mjs} +4 -4
  98. package/dist/{seeder-DI-rUoZx.mjs.map → seeder-BPGY5rUb.mjs.map} +1 -1
  99. package/dist/{seeder-registry-B5FwBeCU.mjs → seeder-registry-DEvCycsT.mjs} +3 -3
  100. package/dist/{seeder-registry-B5FwBeCU.mjs.map → seeder-registry-DEvCycsT.mjs.map} +1 -1
  101. package/dist/{seeder.module-DaY0RYoB.mjs → seeder.module-CIwQbdN4.mjs} +2 -2
  102. package/dist/{seeder.module-DaY0RYoB.mjs.map → seeder.module-CIwQbdN4.mjs.map} +1 -1
  103. package/dist/storage/index.d.mts +1 -1
  104. package/dist/storage/index.mjs +2 -2
  105. package/dist/storage/providers/index.mjs +1 -1
  106. package/dist/{storage-BE6-kzaN.mjs → storage-C30X81CS.mjs} +7 -7
  107. package/dist/{storage-BE6-kzaN.mjs.map → storage-C30X81CS.mjs.map} +1 -1
  108. package/dist/{storage.error-Dai3rShJ.mjs → storage.error-BStXPmO4.mjs} +2 -2
  109. package/dist/{storage.error-Dai3rShJ.mjs.map → storage.error-BStXPmO4.mjs.map} +1 -1
  110. package/dist/{stratal-DtAj21uF.mjs → stratal-BL6FKUM_.mjs} +84 -35
  111. package/dist/stratal-BL6FKUM_.mjs.map +1 -0
  112. package/dist/{stratal-Bmc6WT3k.d.mts → stratal-D5j_I14G.d.mts} +13 -3
  113. package/dist/stratal-D5j_I14G.d.mts.map +1 -0
  114. package/dist/trailing-slash-2SctvePW.mjs +80 -0
  115. package/dist/trailing-slash-2SctvePW.mjs.map +1 -0
  116. package/dist/{uri-h6ITRpqr.mjs → uri-iwofWJ_T.mjs} +12 -10
  117. package/dist/uri-iwofWJ_T.mjs.map +1 -0
  118. package/dist/{versioning.service-B4rtmgMQ.mjs → versioning.service-CCa2oYMJ.mjs} +3 -3
  119. package/dist/{versioning.service-B4rtmgMQ.mjs.map → versioning.service-CCa2oYMJ.mjs.map} +1 -1
  120. package/dist/websocket/index.d.mts +1 -1
  121. package/dist/websocket/index.mjs +1 -1
  122. package/dist/workers/index.d.mts +1 -1
  123. package/dist/workers/index.mjs +2 -2
  124. package/package.json +1 -1
  125. package/dist/di-B0NXIdRF.mjs.map +0 -1
  126. package/dist/errors-EUtwVfNm.mjs.map +0 -1
  127. package/dist/hono-app-C7-1m9eK.mjs.map +0 -1
  128. package/dist/i18n.module-JOPUNpvi.mjs.map +0 -1
  129. package/dist/index-C_t8VVQD.d.mts.map +0 -1
  130. package/dist/stratal-Bmc6WT3k.d.mts.map +0 -1
  131. package/dist/stratal-DtAj21uF.mjs.map +0 -1
  132. package/dist/trailing-slash-CFyw8nYu.mjs +0 -34
  133. package/dist/trailing-slash-CFyw8nYu.mjs.map +0 -1
  134. package/dist/uri-h6ITRpqr.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"router-resolver-CMNuw-s0.mjs","names":["internal.getGroups","internal.getDefaultEntry","internal.getGlobalMiddleware"],"sources":["../src/router/router-resolver.ts"],"sourcesContent":["import type { ZodObject } from '../i18n/validation/zod'\nimport type { Constructor } from '../types'\nimport type { Middleware } from './middleware.interface'\nimport type { Router, RouterEntry } from './router'\nimport * as internal from './router.internals'\n\n/**\n * Resolved configuration for a single controller.\n * Merges Router default entry, sub-group overrides, and inheritance rules.\n */\nexport interface ResolvedRouterConfig {\n prefix?: string\n domain?: string\n name?: string\n middleware: Constructor<Middleware>[]\n version?: string | string[]\n hideFromDocs?: boolean\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ZodObject generics require any for flexible shape parameter\n params?: ZodObject<any>\n}\n\n/**\n * Internal resolver that computes the effective Router config for each controller.\n *\n * Inheritance rules:\n * - `middleware`: parent middleware runs first, then child (concatenated)\n * - `prefix`: concatenated (parent + child)\n * - `name`: concatenated (parent + child)\n * - `domain`: child overrides parent\n * - `version`: child overrides parent\n * - `hideFromDocs`: child overrides parent\n *\n * @internal — not exported from stratal/router\n */\nexport class RouterResolver {\n private readonly routers: { router: Router; controllers: Constructor[] }[]\n\n constructor(routers: { router: Router; controllers: Constructor[] }[]) {\n this.routers = routers\n }\n\n /**\n * Resolve the effective config for a given controller class.\n * Searches through all module routers to find the one owning this controller.\n */\n resolveForController(controller: Constructor): ResolvedRouterConfig {\n for (const { router, controllers: moduleControllers } of this.routers) {\n if (!moduleControllers.includes(controller)) continue\n\n // Check if controller is in a sub-group\n for (const group of router[internal.getGroups]()) {\n if (group.controllers?.includes(controller)) {\n return this.mergeEntries(router[internal.getDefaultEntry](), group)\n }\n }\n\n // Controller is in the default scope (not in any sub-group)\n // But only if it's not claimed by any sub-group in this module\n const groupedControllers = new Set(\n router[internal.getGroups]().flatMap(g => g.controllers ?? [])\n )\n if (!groupedControllers.has(controller)) {\n return this.entryToConfig(router[internal.getDefaultEntry]())\n }\n }\n\n // Controller not found in any module's router — return empty config\n return { middleware: [] }\n }\n\n /**\n * Collect all global middleware registered via `router.use()` across all modules.\n */\n getGlobalMiddleware(): Constructor<Middleware>[] {\n const global: Constructor<Middleware>[] = []\n for (const { router } of this.routers) {\n global.push(...router[internal.getGlobalMiddleware]())\n }\n return global\n }\n\n /**\n * Merge parent default entry with child group entry following inheritance rules.\n */\n private mergeEntries(parent: RouterEntry, child: RouterEntry): ResolvedRouterConfig {\n return {\n // Concatenate: parent prefix + child prefix\n prefix: this.concatPrefixes(parent.prefix, child.prefix),\n // Override: child domain wins\n domain: child.domain ?? parent.domain,\n // Concatenate: parent name + child name\n name: this.concatNames(parent.name, child.name),\n // Concatenate: parent middleware first, then child\n middleware: [...parent.middleware, ...child.middleware],\n // Override: child version wins\n version: child.version ?? parent.version,\n // Override: child hideFromDocs wins\n hideFromDocs: child.hideFromDocs ?? parent.hideFromDocs,\n // Extend: parent params extended with child params\n params: this.mergeParams(parent.params, child.params),\n }\n }\n\n private entryToConfig(entry: RouterEntry): ResolvedRouterConfig {\n return {\n prefix: entry.prefix,\n domain: entry.domain,\n name: entry.name,\n middleware: [...entry.middleware],\n version: entry.version,\n hideFromDocs: entry.hideFromDocs,\n params: entry.params,\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ZodObject generics require any for flexible shape parameter\n private mergeParams(parent?: ZodObject<any>, child?: ZodObject<any>): ZodObject<any> | undefined {\n if (!parent && !child) return undefined\n if (!parent) return child\n if (!child) return parent\n // oxlint-disable-next-line typescript/no-explicit-any\n return parent.extend(child.shape) as ZodObject<any>\n }\n\n private concatPrefixes(parent?: string, child?: string): string | undefined {\n if (!parent && !child) return undefined\n if (!parent) return child\n if (!child) return parent\n // Normalize: remove trailing slash from parent, ensure child starts with /\n const p = parent.endsWith('/') ? parent.slice(0, -1) : parent\n const c = child.startsWith('/') ? child : `/${child}`\n return `${p}${c}`\n }\n\n private concatNames(parent?: string, child?: string): string | undefined {\n if (!parent && !child) return undefined\n if (!parent) return child\n if (!child) return parent\n return `${parent}${child}`\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkCA,IAAa,iBAAb,MAA4B;CAC1B;CAEA,YAAY,SAA2D;EACrE,KAAK,UAAU;CACjB;;;;;CAMA,qBAAqB,YAA+C;EAClE,KAAK,MAAM,EAAE,QAAQ,aAAa,uBAAuB,KAAK,SAAS;GACrE,IAAI,CAAC,kBAAkB,SAAS,UAAU,GAAG;GAG7C,KAAK,MAAM,SAAS,OAAOA,WAAoB,GAC7C,IAAI,MAAM,aAAa,SAAS,UAAU,GACxC,OAAO,KAAK,aAAa,OAAOC,iBAA0B,GAAG,KAAK;GAStE,IAAI,CAAC,IAH0B,IAC7B,OAAOD,WAAoB,EAAE,SAAQ,MAAK,EAAE,eAAe,CAAC,CAAC,CAEzC,EAAE,IAAI,UAAU,GACpC,OAAO,KAAK,cAAc,OAAOC,iBAA0B,CAAC;EAEhE;EAGA,OAAO,EAAE,YAAY,CAAC,EAAE;CAC1B;;;;CAKA,sBAAiD;EAC/C,MAAM,SAAoC,CAAC;EAC3C,KAAK,MAAM,EAAE,YAAY,KAAK,SAC5B,OAAO,KAAK,GAAG,OAAOC,qBAA8B,CAAC;EAEvD,OAAO;CACT;;;;CAKA,aAAqB,QAAqB,OAA0C;EAClF,OAAO;GAEL,QAAQ,KAAK,eAAe,OAAO,QAAQ,MAAM,MAAM;GAEvD,QAAQ,MAAM,UAAU,OAAO;GAE/B,MAAM,KAAK,YAAY,OAAO,MAAM,MAAM,IAAI;GAE9C,YAAY,CAAC,GAAG,OAAO,YAAY,GAAG,MAAM,UAAU;GAEtD,SAAS,MAAM,WAAW,OAAO;GAEjC,cAAc,MAAM,gBAAgB,OAAO;GAE3C,QAAQ,KAAK,YAAY,OAAO,QAAQ,MAAM,MAAM;EACtD;CACF;CAEA,cAAsB,OAA0C;EAC9D,OAAO;GACL,QAAQ,MAAM;GACd,QAAQ,MAAM;GACd,MAAM,MAAM;GACZ,YAAY,CAAC,GAAG,MAAM,UAAU;GAChC,SAAS,MAAM;GACf,cAAc,MAAM;GACpB,QAAQ,MAAM;EAChB;CACF;CAGA,YAAoB,QAAyB,OAAoD;EAC/F,IAAI,CAAC,UAAU,CAAC,OAAO,OAAO,KAAA;EAC9B,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI,CAAC,OAAO,OAAO;EAEnB,OAAO,OAAO,OAAO,MAAM,KAAK;CAClC;CAEA,eAAuB,QAAiB,OAAoC;EAC1E,IAAI,CAAC,UAAU,CAAC,OAAO,OAAO,KAAA;EAC9B,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI,CAAC,OAAO,OAAO;EAInB,OAAO,GAFG,OAAO,SAAS,GAAG,IAAI,OAAO,MAAM,GAAG,EAAE,IAAI,SAC7C,MAAM,WAAW,GAAG,IAAI,QAAQ,IAAI;CAEhD;CAEA,YAAoB,QAAiB,OAAoC;EACvE,IAAI,CAAC,UAAU,CAAC,OAAO,OAAO,KAAA;EAC9B,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI,CAAC,OAAO,OAAO;EACnB,OAAO,GAAG,SAAS;CACrB;AACF"}
1
+ {"version":3,"file":"router-resolver-sUV_jTrU.mjs","names":["internal.getGroups","internal.getDefaultEntry","internal.getGlobalMiddleware"],"sources":["../src/router/router-resolver.ts"],"sourcesContent":["import type { ZodObject } from '../i18n/validation/zod'\nimport type { Constructor } from '../types'\nimport type { Middleware } from './middleware.interface'\nimport type { Router, RouterEntry } from './router'\nimport * as internal from './router.internals'\n\n/**\n * Resolved configuration for a single controller.\n * Merges Router default entry, sub-group overrides, and inheritance rules.\n */\nexport interface ResolvedRouterConfig {\n prefix?: string\n domain?: string\n name?: string\n middleware: Constructor<Middleware>[]\n version?: string | string[]\n hideFromDocs?: boolean\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ZodObject generics require any for flexible shape parameter\n params?: ZodObject<any>\n}\n\n/**\n * Internal resolver that computes the effective Router config for each controller.\n *\n * Inheritance rules:\n * - `middleware`: parent middleware runs first, then child (concatenated)\n * - `prefix`: concatenated (parent + child)\n * - `name`: concatenated (parent + child)\n * - `domain`: child overrides parent\n * - `version`: child overrides parent\n * - `hideFromDocs`: child overrides parent\n *\n * @internal — not exported from stratal/router\n */\nexport class RouterResolver {\n private readonly routers: { router: Router; controllers: Constructor[] }[]\n\n constructor(routers: { router: Router; controllers: Constructor[] }[]) {\n this.routers = routers\n }\n\n /**\n * Resolve the effective config for a given controller class.\n * Searches through all module routers to find the one owning this controller.\n */\n resolveForController(controller: Constructor): ResolvedRouterConfig {\n for (const { router, controllers: moduleControllers } of this.routers) {\n if (!moduleControllers.includes(controller)) continue\n\n // Check if controller is in a sub-group\n for (const group of router[internal.getGroups]()) {\n if (group.controllers?.includes(controller)) {\n return this.mergeEntries(router[internal.getDefaultEntry](), group)\n }\n }\n\n // Controller is in the default scope (not in any sub-group)\n // But only if it's not claimed by any sub-group in this module\n const groupedControllers = new Set(\n router[internal.getGroups]().flatMap(g => g.controllers ?? [])\n )\n if (!groupedControllers.has(controller)) {\n return this.entryToConfig(router[internal.getDefaultEntry]())\n }\n }\n\n // Controller not found in any module's router — return empty config\n return { middleware: [] }\n }\n\n /**\n * Collect all global middleware registered via `router.use()` across all modules.\n */\n getGlobalMiddleware(): Constructor<Middleware>[] {\n const global: Constructor<Middleware>[] = []\n for (const { router } of this.routers) {\n global.push(...router[internal.getGlobalMiddleware]())\n }\n return global\n }\n\n /**\n * Merge parent default entry with child group entry following inheritance rules.\n */\n private mergeEntries(parent: RouterEntry, child: RouterEntry): ResolvedRouterConfig {\n return {\n // Concatenate: parent prefix + child prefix\n prefix: this.concatPrefixes(parent.prefix, child.prefix),\n // Override: child domain wins\n domain: child.domain ?? parent.domain,\n // Concatenate: parent name + child name\n name: this.concatNames(parent.name, child.name),\n // Concatenate: parent middleware first, then child\n middleware: [...parent.middleware, ...child.middleware],\n // Override: child version wins\n version: child.version ?? parent.version,\n // Override: child hideFromDocs wins\n hideFromDocs: child.hideFromDocs ?? parent.hideFromDocs,\n // Extend: parent params extended with child params\n params: this.mergeParams(parent.params, child.params),\n }\n }\n\n private entryToConfig(entry: RouterEntry): ResolvedRouterConfig {\n return {\n prefix: entry.prefix,\n domain: entry.domain,\n name: entry.name,\n middleware: [...entry.middleware],\n version: entry.version,\n hideFromDocs: entry.hideFromDocs,\n params: entry.params,\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ZodObject generics require any for flexible shape parameter\n private mergeParams(parent?: ZodObject<any>, child?: ZodObject<any>): ZodObject<any> | undefined {\n if (!parent && !child) return undefined\n if (!parent) return child\n if (!child) return parent\n // oxlint-disable-next-line typescript/no-explicit-any\n return parent.extend(child.shape) as ZodObject<any>\n }\n\n private concatPrefixes(parent?: string, child?: string): string | undefined {\n if (!parent && !child) return undefined\n if (!parent) return child\n if (!child) return parent\n // Normalize: remove trailing slash from parent, ensure child starts with /\n const p = parent.endsWith('/') ? parent.slice(0, -1) : parent\n const c = child.startsWith('/') ? child : `/${child}`\n return `${p}${c}`\n }\n\n private concatNames(parent?: string, child?: string): string | undefined {\n if (!parent && !child) return undefined\n if (!parent) return child\n if (!child) return parent\n return `${parent}${child}`\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkCA,IAAa,iBAAb,MAA4B;CAC1B;CAEA,YAAY,SAA2D;EACrE,KAAK,UAAU;CACjB;;;;;CAMA,qBAAqB,YAA+C;EAClE,KAAK,MAAM,EAAE,QAAQ,aAAa,uBAAuB,KAAK,SAAS;GACrE,IAAI,CAAC,kBAAkB,SAAS,UAAU,GAAG;GAG7C,KAAK,MAAM,SAAS,OAAOA,WAAoB,GAC7C,IAAI,MAAM,aAAa,SAAS,UAAU,GACxC,OAAO,KAAK,aAAa,OAAOC,iBAA0B,GAAG,KAAK;GAStE,IAAI,CAAC,IAH0B,IAC7B,OAAOD,WAAoB,EAAE,SAAQ,MAAK,EAAE,eAAe,CAAC,CAAC,CAEzC,EAAE,IAAI,UAAU,GACpC,OAAO,KAAK,cAAc,OAAOC,iBAA0B,CAAC;EAEhE;EAGA,OAAO,EAAE,YAAY,CAAC,EAAE;CAC1B;;;;CAKA,sBAAiD;EAC/C,MAAM,SAAoC,CAAC;EAC3C,KAAK,MAAM,EAAE,YAAY,KAAK,SAC5B,OAAO,KAAK,GAAG,OAAOC,qBAA8B,CAAC;EAEvD,OAAO;CACT;;;;CAKA,aAAqB,QAAqB,OAA0C;EAClF,OAAO;GAEL,QAAQ,KAAK,eAAe,OAAO,QAAQ,MAAM,MAAM;GAEvD,QAAQ,MAAM,UAAU,OAAO;GAE/B,MAAM,KAAK,YAAY,OAAO,MAAM,MAAM,IAAI;GAE9C,YAAY,CAAC,GAAG,OAAO,YAAY,GAAG,MAAM,UAAU;GAEtD,SAAS,MAAM,WAAW,OAAO;GAEjC,cAAc,MAAM,gBAAgB,OAAO;GAE3C,QAAQ,KAAK,YAAY,OAAO,QAAQ,MAAM,MAAM;EACtD;CACF;CAEA,cAAsB,OAA0C;EAC9D,OAAO;GACL,QAAQ,MAAM;GACd,QAAQ,MAAM;GACd,MAAM,MAAM;GACZ,YAAY,CAAC,GAAG,MAAM,UAAU;GAChC,SAAS,MAAM;GACf,cAAc,MAAM;GACpB,QAAQ,MAAM;EAChB;CACF;CAGA,YAAoB,QAAyB,OAAoD;EAC/F,IAAI,CAAC,UAAU,CAAC,OAAO,OAAO,KAAA;EAC9B,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI,CAAC,OAAO,OAAO;EAEnB,OAAO,OAAO,OAAO,MAAM,KAAK;CAClC;CAEA,eAAuB,QAAiB,OAAoC;EAC1E,IAAI,CAAC,UAAU,CAAC,OAAO,OAAO,KAAA;EAC9B,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI,CAAC,OAAO,OAAO;EAInB,OAAO,GAFG,OAAO,SAAS,GAAG,IAAI,OAAO,MAAM,GAAG,EAAE,IAAI,SAC7C,MAAM,WAAW,GAAG,IAAI,QAAQ,IAAI;CAEhD;CAEA,YAAoB,QAAiB,OAAoC;EACvE,IAAI,CAAC,UAAU,CAAC,OAAO,OAAO,KAAA;EAC9B,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI,CAAC,OAAO,OAAO;EACnB,OAAO,GAAG,SAAS;CACrB;AACF"}
@@ -1,6 +1,6 @@
1
- import { Dr as ApplicationError, Y as Container, ct as Application } from "../index-C_t8VVQD.mjs";
1
+ import { $ as Container, Ir as ApplicationError, ft as Application } from "../index-uybm0bhQ.mjs";
2
2
  import { t as Constructor } from "../types-CmV_9xBD.mjs";
3
- import { t as Command } from "../command-4HnKnC-G.mjs";
3
+ import { t as Command } from "../command-DoBD2Cwl.mjs";
4
4
 
5
5
  //#region src/seeder/seeder.d.ts
6
6
  declare const SEEDER_INTERNALS: unique symbol;
@@ -1,5 +1,5 @@
1
1
  import { n as SEEDER_INTERNALS, r as Seeder, t as isSeeder } from "../is-seeder-Gvh_AM71.mjs";
2
- import { n as SeederRegistry, r as SeederError, t as SEEDER_TOKENS } from "../seeder-registry-B5FwBeCU.mjs";
3
- import { n as DbSeedListCommand, t as DbSeedCommand } from "../seeder-DI-rUoZx.mjs";
4
- import { t as SeederModule } from "../seeder.module-DaY0RYoB.mjs";
2
+ import { n as SeederRegistry, r as SeederError, t as SEEDER_TOKENS } from "../seeder-registry-DEvCycsT.mjs";
3
+ import { n as DbSeedListCommand, t as DbSeedCommand } from "../seeder-BPGY5rUb.mjs";
4
+ import { t as SeederModule } from "../seeder.module-CIwQbdN4.mjs";
5
5
  export { DbSeedCommand, DbSeedListCommand, SEEDER_INTERNALS, SEEDER_TOKENS, Seeder, SeederError, SeederModule, SeederRegistry, isSeeder };
@@ -1,8 +1,8 @@
1
- import { d as inject } from "./di-B0NXIdRF.mjs";
1
+ import { p as inject } from "./di-D7qmrAir.mjs";
2
2
  import { n as __decorateParam, t as __decorate } from "./decorate-CuAoSZvs.mjs";
3
3
  import { t as Command } from "./command-BvmUAPPQ.mjs";
4
- import { t as SEEDER_TOKENS } from "./seeder-registry-B5FwBeCU.mjs";
5
- import "./seeder.module-DaY0RYoB.mjs";
4
+ import { t as SEEDER_TOKENS } from "./seeder-registry-DEvCycsT.mjs";
5
+ import "./seeder.module-CIwQbdN4.mjs";
6
6
  //#region src/seeder/commands/db-seed-list.command.ts
7
7
  let DbSeedListCommand = class DbSeedListCommand extends Command {
8
8
  seeders;
@@ -78,4 +78,4 @@ DbSeedCommand = __decorate([__decorateParam(0, inject(SEEDER_TOKENS.SeederRegist
78
78
  //#endregion
79
79
  export { DbSeedListCommand as n, DbSeedCommand as t };
80
80
 
81
- //# sourceMappingURL=seeder-DI-rUoZx.mjs.map
81
+ //# sourceMappingURL=seeder-BPGY5rUb.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"seeder-DI-rUoZx.mjs","names":[],"sources":["../src/seeder/commands/db-seed-list.command.ts","../src/seeder/commands/db-seed.command.ts"],"sourcesContent":["import { inject } from '../../di'\nimport { Command } from '../../quarry/command'\nimport { type SeederRegistry, SEEDER_TOKENS } from '../seeder-registry'\n\nexport class DbSeedListCommand extends Command {\n static command = 'db:seed:list'\n static description = 'List available database seeders'\n\n constructor(@inject(SEEDER_TOKENS.SeederRegistry) private seeders: SeederRegistry) {\n super()\n }\n\n handle(): undefined | number {\n const list = this.seeders.list()\n if (list.length === 0) {\n this.info('No seeders found')\n return 0\n }\n this.table(['Class'], list.map(s => [s.className]))\n\n return undefined\n }\n}\n","import { inject } from '../../di'\nimport { Command } from '../../quarry/command'\nimport { type SeederRegistry, SEEDER_TOKENS } from '../seeder-registry'\n\nexport class DbSeedCommand extends Command {\n static command = 'db:seed {names* : Seeder class names} {--a|all : Run all seeders} {--dry-run : Preview without executing}'\n static description = 'Run database seeders'\n\n constructor(@inject(SEEDER_TOKENS.SeederRegistry) private seeders: SeederRegistry) {\n super()\n }\n\n async handle(): Promise<number | undefined> {\n const names = this.array('names')\n const all = this.boolean('all')\n const dryRun = this.boolean('dry-run')\n\n if (names.length > 0 && all) {\n this.warn(`Ignoring \"${names.join(', ')}\" because --all takes precedence`)\n }\n\n if (names.length === 0 && !all) {\n this.fail('Specify one or more seeder class names or use --all')\n return 1\n }\n\n if (dryRun) {\n if (all) {\n const list = this.seeders.list()\n this.info('Dry run — would execute:')\n for (const s of list) {\n this.info(` ${s.className}`)\n }\n } else {\n this.info('Dry run — would execute:')\n for (const name of names) {\n const SeederClass = this.seeders.find(name)\n if (!SeederClass) {\n this.fail(`Seeder \"${name}\" not found`)\n return 1\n }\n this.info(` ${SeederClass.name}`)\n }\n }\n return 0\n }\n\n if (all) {\n await this.seeders.runAll()\n this.success('All seeders completed')\n } else {\n for (const name of names) {\n const SeederClass = this.seeders.find(name)\n if (!SeederClass) {\n this.fail(`Seeder \"${name}\" not found`)\n return 1\n }\n await this.seeders.run(SeederClass)\n this.success(`Seeder \"${name}\" completed`)\n }\n }\n\n return 0\n }\n}\n"],"mappings":";;;;;;AAIO,IAAA,oBAAA,MAAM,0BAA0B,QAAQ;CAIa;CAH1D,OAAO,UAAU;CACjB,OAAO,cAAc;CAErB,YAAY,SAAuE;EACjF,MAAM;EADkD,KAAA,UAAA;CAE1D;CAEA,SAA6B;EAC3B,MAAM,OAAO,KAAK,QAAQ,KAAK;EAC/B,IAAI,KAAK,WAAW,GAAG;GACrB,KAAK,KAAK,kBAAkB;GAC5B,OAAO;EACT;EACA,KAAK,MAAM,CAAC,OAAO,GAAG,KAAK,KAAI,MAAK,CAAC,EAAE,SAAS,CAAC,CAAC;CAGpD;AACF;mDAde,OAAO,cAAc,cAAc,CAAA,CAAA,GAAA,iBAAA;;;ACJ3C,IAAA,gBAAA,MAAM,sBAAsB,QAAQ;CAIiB;CAH1D,OAAO,UAAU;CACjB,OAAO,cAAc;CAErB,YAAY,SAAuE;EACjF,MAAM;EADkD,KAAA,UAAA;CAE1D;CAEA,MAAM,SAAsC;EAC1C,MAAM,QAAQ,KAAK,MAAM,OAAO;EAChC,MAAM,MAAM,KAAK,QAAQ,KAAK;EAC9B,MAAM,SAAS,KAAK,QAAQ,SAAS;EAErC,IAAI,MAAM,SAAS,KAAK,KACtB,KAAK,KAAK,aAAa,MAAM,KAAK,IAAI,EAAE,iCAAiC;EAG3E,IAAI,MAAM,WAAW,KAAK,CAAC,KAAK;GAC9B,KAAK,KAAK,qDAAqD;GAC/D,OAAO;EACT;EAEA,IAAI,QAAQ;GACV,IAAI,KAAK;IACP,MAAM,OAAO,KAAK,QAAQ,KAAK;IAC/B,KAAK,KAAK,0BAA0B;IACpC,KAAK,MAAM,KAAK,MACd,KAAK,KAAK,KAAK,EAAE,WAAW;GAEhC,OAAO;IACL,KAAK,KAAK,0BAA0B;IACpC,KAAK,MAAM,QAAQ,OAAO;KACxB,MAAM,cAAc,KAAK,QAAQ,KAAK,IAAI;KAC1C,IAAI,CAAC,aAAa;MAChB,KAAK,KAAK,WAAW,KAAK,YAAY;MACtC,OAAO;KACT;KACA,KAAK,KAAK,KAAK,YAAY,MAAM;IACnC;GACF;GACA,OAAO;EACT;EAEA,IAAI,KAAK;GACP,MAAM,KAAK,QAAQ,OAAO;GAC1B,KAAK,QAAQ,uBAAuB;EACtC,OACE,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,cAAc,KAAK,QAAQ,KAAK,IAAI;GAC1C,IAAI,CAAC,aAAa;IAChB,KAAK,KAAK,WAAW,KAAK,YAAY;IACtC,OAAO;GACT;GACA,MAAM,KAAK,QAAQ,IAAI,WAAW;GAClC,KAAK,QAAQ,WAAW,KAAK,YAAY;EAC3C;EAGF,OAAO;CACT;AACF;+CAxDe,OAAO,cAAc,cAAc,CAAA,CAAA,GAAA,aAAA"}
1
+ {"version":3,"file":"seeder-BPGY5rUb.mjs","names":[],"sources":["../src/seeder/commands/db-seed-list.command.ts","../src/seeder/commands/db-seed.command.ts"],"sourcesContent":["import { inject } from '../../di'\nimport { Command } from '../../quarry/command'\nimport { type SeederRegistry, SEEDER_TOKENS } from '../seeder-registry'\n\nexport class DbSeedListCommand extends Command {\n static command = 'db:seed:list'\n static description = 'List available database seeders'\n\n constructor(@inject(SEEDER_TOKENS.SeederRegistry) private seeders: SeederRegistry) {\n super()\n }\n\n handle(): undefined | number {\n const list = this.seeders.list()\n if (list.length === 0) {\n this.info('No seeders found')\n return 0\n }\n this.table(['Class'], list.map(s => [s.className]))\n\n return undefined\n }\n}\n","import { inject } from '../../di'\nimport { Command } from '../../quarry/command'\nimport { type SeederRegistry, SEEDER_TOKENS } from '../seeder-registry'\n\nexport class DbSeedCommand extends Command {\n static command = 'db:seed {names* : Seeder class names} {--a|all : Run all seeders} {--dry-run : Preview without executing}'\n static description = 'Run database seeders'\n\n constructor(@inject(SEEDER_TOKENS.SeederRegistry) private seeders: SeederRegistry) {\n super()\n }\n\n async handle(): Promise<number | undefined> {\n const names = this.array('names')\n const all = this.boolean('all')\n const dryRun = this.boolean('dry-run')\n\n if (names.length > 0 && all) {\n this.warn(`Ignoring \"${names.join(', ')}\" because --all takes precedence`)\n }\n\n if (names.length === 0 && !all) {\n this.fail('Specify one or more seeder class names or use --all')\n return 1\n }\n\n if (dryRun) {\n if (all) {\n const list = this.seeders.list()\n this.info('Dry run — would execute:')\n for (const s of list) {\n this.info(` ${s.className}`)\n }\n } else {\n this.info('Dry run — would execute:')\n for (const name of names) {\n const SeederClass = this.seeders.find(name)\n if (!SeederClass) {\n this.fail(`Seeder \"${name}\" not found`)\n return 1\n }\n this.info(` ${SeederClass.name}`)\n }\n }\n return 0\n }\n\n if (all) {\n await this.seeders.runAll()\n this.success('All seeders completed')\n } else {\n for (const name of names) {\n const SeederClass = this.seeders.find(name)\n if (!SeederClass) {\n this.fail(`Seeder \"${name}\" not found`)\n return 1\n }\n await this.seeders.run(SeederClass)\n this.success(`Seeder \"${name}\" completed`)\n }\n }\n\n return 0\n }\n}\n"],"mappings":";;;;;;AAIO,IAAA,oBAAA,MAAM,0BAA0B,QAAQ;CAIa;CAH1D,OAAO,UAAU;CACjB,OAAO,cAAc;CAErB,YAAY,SAAuE;EACjF,MAAM;EADkD,KAAA,UAAA;CAE1D;CAEA,SAA6B;EAC3B,MAAM,OAAO,KAAK,QAAQ,KAAK;EAC/B,IAAI,KAAK,WAAW,GAAG;GACrB,KAAK,KAAK,kBAAkB;GAC5B,OAAO;EACT;EACA,KAAK,MAAM,CAAC,OAAO,GAAG,KAAK,KAAI,MAAK,CAAC,EAAE,SAAS,CAAC,CAAC;CAGpD;AACF;mDAde,OAAO,cAAc,cAAc,CAAA,CAAA,GAAA,iBAAA;;;ACJ3C,IAAA,gBAAA,MAAM,sBAAsB,QAAQ;CAIiB;CAH1D,OAAO,UAAU;CACjB,OAAO,cAAc;CAErB,YAAY,SAAuE;EACjF,MAAM;EADkD,KAAA,UAAA;CAE1D;CAEA,MAAM,SAAsC;EAC1C,MAAM,QAAQ,KAAK,MAAM,OAAO;EAChC,MAAM,MAAM,KAAK,QAAQ,KAAK;EAC9B,MAAM,SAAS,KAAK,QAAQ,SAAS;EAErC,IAAI,MAAM,SAAS,KAAK,KACtB,KAAK,KAAK,aAAa,MAAM,KAAK,IAAI,EAAE,iCAAiC;EAG3E,IAAI,MAAM,WAAW,KAAK,CAAC,KAAK;GAC9B,KAAK,KAAK,qDAAqD;GAC/D,OAAO;EACT;EAEA,IAAI,QAAQ;GACV,IAAI,KAAK;IACP,MAAM,OAAO,KAAK,QAAQ,KAAK;IAC/B,KAAK,KAAK,0BAA0B;IACpC,KAAK,MAAM,KAAK,MACd,KAAK,KAAK,KAAK,EAAE,WAAW;GAEhC,OAAO;IACL,KAAK,KAAK,0BAA0B;IACpC,KAAK,MAAM,QAAQ,OAAO;KACxB,MAAM,cAAc,KAAK,QAAQ,KAAK,IAAI;KAC1C,IAAI,CAAC,aAAa;MAChB,KAAK,KAAK,WAAW,KAAK,YAAY;MACtC,OAAO;KACT;KACA,KAAK,KAAK,KAAK,YAAY,MAAM;IACnC;GACF;GACA,OAAO;EACT;EAEA,IAAI,KAAK;GACP,MAAM,KAAK,QAAQ,OAAO;GAC1B,KAAK,QAAQ,uBAAuB;EACtC,OACE,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,cAAc,KAAK,QAAQ,KAAK,IAAI;GAC1C,IAAI,CAAC,aAAa;IAChB,KAAK,KAAK,WAAW,KAAK,YAAY;IACtC,OAAO;GACT;GACA,MAAM,KAAK,QAAQ,IAAI,WAAW;GAClC,KAAK,QAAQ,WAAW,KAAK,YAAY;EAC3C;EAGF,OAAO;CACT;AACF;+CAxDe,OAAO,cAAc,cAAc,CAAA,CAAA,GAAA,aAAA"}
@@ -1,7 +1,7 @@
1
- import { d as inject, r as DI_TOKENS, s as Singleton } from "./di-B0NXIdRF.mjs";
1
+ import { l as Singleton, p as inject, r as DI_TOKENS } 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
- import "./errors-EUtwVfNm.mjs";
4
+ import "./errors-C01O2T-n.mjs";
5
5
  import { n as SEEDER_INTERNALS } from "./is-seeder-Gvh_AM71.mjs";
6
6
  //#region src/seeder/seeder.error.ts
7
7
  var SeederError = class extends ApplicationError {};
@@ -54,4 +54,4 @@ SeederRegistry = __decorate([Singleton(SEEDER_TOKENS.SeederRegistry), __decorate
54
54
  //#endregion
55
55
  export { SeederRegistry as n, SeederError as r, SEEDER_TOKENS as t };
56
56
 
57
- //# sourceMappingURL=seeder-registry-B5FwBeCU.mjs.map
57
+ //# sourceMappingURL=seeder-registry-DEvCycsT.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"seeder-registry-B5FwBeCU.mjs","names":[],"sources":["../src/seeder/seeder.error.ts","../src/seeder/seeder-registry.ts"],"sourcesContent":["import { ApplicationError } from '../errors'\n\nexport class SeederError extends ApplicationError {}\n","import type { Application } from '../application'\nimport type { Container } from '../di/container'\nimport { inject, Singleton } from '../di/decorators'\nimport { DI_TOKENS } from '../di/tokens'\nimport type { Constructor } from '../types'\nimport { SeederError } from './seeder.error'\nimport { type Seeder, SEEDER_INTERNALS } from './seeder'\n\nexport const SEEDER_TOKENS = {\n SeederRegistry: Symbol.for('stratal:seeders:registry'),\n} as const\n\n@Singleton(SEEDER_TOKENS.SeederRegistry)\nexport class SeederRegistry {\n private seeders = new Set<Constructor<Seeder>>()\n private nameIndex = new Map<string, Constructor<Seeder>>()\n\n constructor(@inject(DI_TOKENS.Application) private app: Application) { }\n\n register(SeederClass: Constructor<Seeder>): void {\n const existing = this.nameIndex.get(SeederClass.name)\n if (existing && existing !== SeederClass) {\n throw new SeederError(`Seeder name collision: \"${SeederClass.name}\" is already registered`)\n }\n this.seeders.add(SeederClass)\n this.nameIndex.set(SeederClass.name, SeederClass)\n }\n\n async run(SeederClass: Constructor<Seeder>, options?: { container?: Container }): Promise<void> {\n if (!this.seeders.has(SeederClass)) {\n throw new SeederError(`Seeder \"${SeederClass.name}\" is not registered`)\n }\n\n const execute = async (container: Container) => {\n const seeder = container.resolve<Seeder>(SeederClass)\n seeder[SEEDER_INTERNALS] = {\n run: (cls) => this.run(cls, { container }),\n container,\n }\n await seeder.run()\n }\n\n if (options?.container) {\n await execute(options.container)\n } else {\n const mockContext = this.app.createMockRouterContext('en')\n await this.app.container.runInRequestScope(mockContext, execute)\n }\n }\n\n async runAll(options?: { container?: Container }): Promise<void> {\n for (const SeederClass of this.seeders) {\n await this.run(SeederClass, options)\n }\n }\n\n find(name: string): Constructor<Seeder> | undefined {\n return this.nameIndex.get(name)\n }\n\n has(SeederClass: Constructor<Seeder>): boolean {\n return this.seeders.has(SeederClass)\n }\n\n list(): { className: string }[] {\n return [...this.seeders].map(cls => ({ className: cls.name }))\n }\n}\n"],"mappings":";;;;;;AAEA,IAAa,cAAb,cAAiC,iBAAiB,CAAC;;;ACMnD,MAAa,gBAAgB,EAC3B,gBAAgB,OAAO,IAAI,0BAA0B,EACvD;AAGO,IAAA,iBAAA,MAAM,eAAe;CAIyB;CAHnD,0BAAkB,IAAI,IAAyB;CAC/C,4BAAoB,IAAI,IAAiC;CAEzD,YAAY,KAAyD;EAAlB,KAAA,MAAA;CAAoB;CAEvE,SAAS,aAAwC;EAC/C,MAAM,WAAW,KAAK,UAAU,IAAI,YAAY,IAAI;EACpD,IAAI,YAAY,aAAa,aAC3B,MAAM,IAAI,YAAY,2BAA2B,YAAY,KAAK,wBAAwB;EAE5F,KAAK,QAAQ,IAAI,WAAW;EAC5B,KAAK,UAAU,IAAI,YAAY,MAAM,WAAW;CAClD;CAEA,MAAM,IAAI,aAAkC,SAAoD;EAC9F,IAAI,CAAC,KAAK,QAAQ,IAAI,WAAW,GAC/B,MAAM,IAAI,YAAY,WAAW,YAAY,KAAK,oBAAoB;EAGxE,MAAM,UAAU,OAAO,cAAyB;GAC9C,MAAM,SAAS,UAAU,QAAgB,WAAW;GACpD,OAAO,oBAAoB;IACzB,MAAM,QAAQ,KAAK,IAAI,KAAK,EAAE,UAAU,CAAC;IACzC;GACF;GACA,MAAM,OAAO,IAAI;EACnB;EAEA,IAAI,SAAS,WACX,MAAM,QAAQ,QAAQ,SAAS;OAC1B;GACL,MAAM,cAAc,KAAK,IAAI,wBAAwB,IAAI;GACzD,MAAM,KAAK,IAAI,UAAU,kBAAkB,aAAa,OAAO;EACjE;CACF;CAEA,MAAM,OAAO,SAAoD;EAC/D,KAAK,MAAM,eAAe,KAAK,SAC7B,MAAM,KAAK,IAAI,aAAa,OAAO;CAEvC;CAEA,KAAK,MAA+C;EAClD,OAAO,KAAK,UAAU,IAAI,IAAI;CAChC;CAEA,IAAI,aAA2C;EAC7C,OAAO,KAAK,QAAQ,IAAI,WAAW;CACrC;CAEA,OAAgC;EAC9B,OAAO,CAAC,GAAG,KAAK,OAAO,EAAE,KAAI,SAAQ,EAAE,WAAW,IAAI,KAAK,EAAE;CAC/D;AACF;6BAvDC,UAAU,cAAc,cAAc,GAAA,gBAAA,GAKxB,OAAO,UAAU,WAAW,CAAA,CAAA,GAAA,cAAA"}
1
+ {"version":3,"file":"seeder-registry-DEvCycsT.mjs","names":[],"sources":["../src/seeder/seeder.error.ts","../src/seeder/seeder-registry.ts"],"sourcesContent":["import { ApplicationError } from '../errors'\n\nexport class SeederError extends ApplicationError {}\n","import type { Application } from '../application'\nimport type { Container } from '../di/container'\nimport { inject, Singleton } from '../di/decorators'\nimport { DI_TOKENS } from '../di/tokens'\nimport type { Constructor } from '../types'\nimport { SeederError } from './seeder.error'\nimport { type Seeder, SEEDER_INTERNALS } from './seeder'\n\nexport const SEEDER_TOKENS = {\n SeederRegistry: Symbol.for('stratal:seeders:registry'),\n} as const\n\n@Singleton(SEEDER_TOKENS.SeederRegistry)\nexport class SeederRegistry {\n private seeders = new Set<Constructor<Seeder>>()\n private nameIndex = new Map<string, Constructor<Seeder>>()\n\n constructor(@inject(DI_TOKENS.Application) private app: Application) { }\n\n register(SeederClass: Constructor<Seeder>): void {\n const existing = this.nameIndex.get(SeederClass.name)\n if (existing && existing !== SeederClass) {\n throw new SeederError(`Seeder name collision: \"${SeederClass.name}\" is already registered`)\n }\n this.seeders.add(SeederClass)\n this.nameIndex.set(SeederClass.name, SeederClass)\n }\n\n async run(SeederClass: Constructor<Seeder>, options?: { container?: Container }): Promise<void> {\n if (!this.seeders.has(SeederClass)) {\n throw new SeederError(`Seeder \"${SeederClass.name}\" is not registered`)\n }\n\n const execute = async (container: Container) => {\n const seeder = container.resolve<Seeder>(SeederClass)\n seeder[SEEDER_INTERNALS] = {\n run: (cls) => this.run(cls, { container }),\n container,\n }\n await seeder.run()\n }\n\n if (options?.container) {\n await execute(options.container)\n } else {\n const mockContext = this.app.createMockRouterContext('en')\n await this.app.container.runInRequestScope(mockContext, execute)\n }\n }\n\n async runAll(options?: { container?: Container }): Promise<void> {\n for (const SeederClass of this.seeders) {\n await this.run(SeederClass, options)\n }\n }\n\n find(name: string): Constructor<Seeder> | undefined {\n return this.nameIndex.get(name)\n }\n\n has(SeederClass: Constructor<Seeder>): boolean {\n return this.seeders.has(SeederClass)\n }\n\n list(): { className: string }[] {\n return [...this.seeders].map(cls => ({ className: cls.name }))\n }\n}\n"],"mappings":";;;;;;AAEA,IAAa,cAAb,cAAiC,iBAAiB,CAAC;;;ACMnD,MAAa,gBAAgB,EAC3B,gBAAgB,OAAO,IAAI,0BAA0B,EACvD;AAGO,IAAA,iBAAA,MAAM,eAAe;CAIyB;CAHnD,0BAAkB,IAAI,IAAyB;CAC/C,4BAAoB,IAAI,IAAiC;CAEzD,YAAY,KAAyD;EAAlB,KAAA,MAAA;CAAoB;CAEvE,SAAS,aAAwC;EAC/C,MAAM,WAAW,KAAK,UAAU,IAAI,YAAY,IAAI;EACpD,IAAI,YAAY,aAAa,aAC3B,MAAM,IAAI,YAAY,2BAA2B,YAAY,KAAK,wBAAwB;EAE5F,KAAK,QAAQ,IAAI,WAAW;EAC5B,KAAK,UAAU,IAAI,YAAY,MAAM,WAAW;CAClD;CAEA,MAAM,IAAI,aAAkC,SAAoD;EAC9F,IAAI,CAAC,KAAK,QAAQ,IAAI,WAAW,GAC/B,MAAM,IAAI,YAAY,WAAW,YAAY,KAAK,oBAAoB;EAGxE,MAAM,UAAU,OAAO,cAAyB;GAC9C,MAAM,SAAS,UAAU,QAAgB,WAAW;GACpD,OAAO,oBAAoB;IACzB,MAAM,QAAQ,KAAK,IAAI,KAAK,EAAE,UAAU,CAAC;IACzC;GACF;GACA,MAAM,OAAO,IAAI;EACnB;EAEA,IAAI,SAAS,WACX,MAAM,QAAQ,QAAQ,SAAS;OAC1B;GACL,MAAM,cAAc,KAAK,IAAI,wBAAwB,IAAI;GACzD,MAAM,KAAK,IAAI,UAAU,kBAAkB,aAAa,OAAO;EACjE;CACF;CAEA,MAAM,OAAO,SAAoD;EAC/D,KAAK,MAAM,eAAe,KAAK,SAC7B,MAAM,KAAK,IAAI,aAAa,OAAO;CAEvC;CAEA,KAAK,MAA+C;EAClD,OAAO,KAAK,UAAU,IAAI,IAAI;CAChC;CAEA,IAAI,aAA2C;EAC7C,OAAO,KAAK,QAAQ,IAAI,WAAW;CACrC;CAEA,OAAgC;EAC9B,OAAO,CAAC,GAAG,KAAK,OAAO,EAAE,KAAI,SAAQ,EAAE,WAAW,IAAI,KAAK,EAAE;CAC/D;AACF;6BAvDC,UAAU,cAAc,cAAc,GAAA,gBAAA,GAKxB,OAAO,UAAU,WAAW,CAAA,CAAA,GAAA,cAAA"}
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./chunk-BBjsoOtd.mjs";
2
2
  import { t as __decorate } from "./decorate-CuAoSZvs.mjs";
3
3
  import { n as Module } from "./module.decorator-CYHY6pG5.mjs";
4
- import { n as SeederRegistry, t as SEEDER_TOKENS } from "./seeder-registry-B5FwBeCU.mjs";
4
+ import { n as SeederRegistry, t as SEEDER_TOKENS } from "./seeder-registry-DEvCycsT.mjs";
5
5
  //#region src/seeder/seeder.module.ts
6
6
  var seeder_module_exports = /* @__PURE__ */ __exportAll({ SeederModule: () => SeederModule });
7
7
  let SeederModule = class SeederModule {};
@@ -12,4 +12,4 @@ SeederModule = __decorate([Module({ providers: [{
12
12
  //#endregion
13
13
  export { seeder_module_exports as n, SeederModule as t };
14
14
 
15
- //# sourceMappingURL=seeder.module-DaY0RYoB.mjs.map
15
+ //# sourceMappingURL=seeder.module-CIwQbdN4.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"seeder.module-DaY0RYoB.mjs","names":[],"sources":["../src/seeder/seeder.module.ts"],"sourcesContent":["import { Module } from '../module/module.decorator'\nimport { SEEDER_TOKENS, SeederRegistry } from './seeder-registry'\n\n/**\n * Registers the seeder registry (`SEEDER_TOKENS.SeederRegistry`).\n *\n * Eager: resolved synchronously at bootstrap (`registerSeeders`) and by the\n * test harness (`@stratal/testing`), so it cannot be lazily loaded.\n * {@link SeederRegistry} injects the `Application` via `DI_TOKENS.Application`.\n */\n@Module({\n providers: [\n { provide: SEEDER_TOKENS.SeederRegistry, useClass: SeederRegistry },\n ],\n})\nexport class SeederModule { }\n"],"mappings":";;;;;;AAeO,IAAA,eAAA,MAAM,aAAa,CAAE;2BAL3B,OAAO,EACN,WAAW,CACT;CAAE,SAAS,cAAc;CAAgB,UAAU;AAAe,CACpE,EACF,CAAC,CAAA,GAAA,YAAA"}
1
+ {"version":3,"file":"seeder.module-CIwQbdN4.mjs","names":[],"sources":["../src/seeder/seeder.module.ts"],"sourcesContent":["import { Module } from '../module/module.decorator'\nimport { SEEDER_TOKENS, SeederRegistry } from './seeder-registry'\n\n/**\n * Registers the seeder registry (`SEEDER_TOKENS.SeederRegistry`).\n *\n * Eager: resolved synchronously at bootstrap (`registerSeeders`) and by the\n * test harness (`@stratal/testing`), so it cannot be lazily loaded.\n * {@link SeederRegistry} injects the `Application` via `DI_TOKENS.Application`.\n */\n@Module({\n providers: [\n { provide: SEEDER_TOKENS.SeederRegistry, useClass: SeederRegistry },\n ],\n})\nexport class SeederModule { }\n"],"mappings":";;;;;;AAeO,IAAA,eAAA,MAAM,aAAa,CAAE;2BAL3B,OAAO,EACN,WAAW,CACT;CAAE,SAAS,cAAc;CAAgB,UAAU;AAAe,CACpE,EACF,CAAC,CAAA,GAAA,YAAA"}
@@ -1,4 +1,4 @@
1
- import { Dn as AsyncModuleOptions, Dr as ApplicationError, c as HttpException, kn as DynamicModule, rt as RouterContext } from "../index-C_t8VVQD.mjs";
1
+ import { Fn as DynamicModule, Ir as ApplicationError, Nn as AsyncModuleOptions, l as HttpException, st as RouterContext } from "../index-uybm0bhQ.mjs";
2
2
  import { t as StratalEnv } from "../env-ug22bJj7.mjs";
3
3
  import { _ as StorageEntry, a as uploadResultSchema, c as getPresignedUrlInputSchema, d as fileExistsInputSchema, f as DownloadResult, g as StorageConfig, h as PresignedUrlConfig, i as UploadResult, l as presignedUrlResultSchema, m as deleteFileInputSchema, n as StreamingBlobPayloadInputTypes, o as GetPresignedUrlInput, p as DeleteFileInput, r as UploadOptions, s as PresignedUrlResult, t as IStorageProvider, u as FileExistsInput, v as StorageRouteConfig } from "../storage-provider.interface-ClUwxz4S.mjs";
4
4
 
@@ -1,3 +1,3 @@
1
- import { t as StorageError } from "../storage.error-Dai3rShJ.mjs";
2
- import { a as presignedUrlResultSchema, c as StorageController, d as StorageService, f as StorageManagerService, i as getPresignedUrlInputSchema, l as FileNotFoundError, n as FileTooLargeError, o as fileExistsInputSchema, p as STORAGE_TOKENS, r as uploadResultSchema, s as deleteFileInputSchema, t as InvalidFileTypeError, u as StorageModule } from "../storage-BE6-kzaN.mjs";
1
+ import { t as StorageError } from "../storage.error-BStXPmO4.mjs";
2
+ import { a as presignedUrlResultSchema, c as StorageController, d as StorageService, f as StorageManagerService, i as getPresignedUrlInputSchema, l as FileNotFoundError, n as FileTooLargeError, o as fileExistsInputSchema, p as STORAGE_TOKENS, r as uploadResultSchema, s as deleteFileInputSchema, t as InvalidFileTypeError, u as StorageModule } from "../storage-C30X81CS.mjs";
3
3
  export { FileNotFoundError, FileTooLargeError, InvalidFileTypeError, STORAGE_TOKENS, StorageController, StorageError, StorageManagerService, StorageModule, StorageService, deleteFileInputSchema, fileExistsInputSchema, getPresignedUrlInputSchema, presignedUrlResultSchema, uploadResultSchema };
@@ -1,2 +1,2 @@
1
- import { t as R2StorageProvider } from "../../r2-storage.provider-BJ4j23W6.mjs";
1
+ import { t as R2StorageProvider } from "../../r2-storage.provider-BoZmR6Ut.mjs";
2
2
  export { R2StorageProvider };
@@ -1,13 +1,13 @@
1
- import { d as inject, o as Request, r as DI_TOKENS, s as Singleton } from "./di-B0NXIdRF.mjs";
1
+ import { c as Request, l as Singleton, p as inject, r as DI_TOKENS } from "./di-D7qmrAir.mjs";
2
2
  import { n as __decorateParam, t as __decorate } from "./decorate-CuAoSZvs.mjs";
3
- import { u as HttpException } from "./errors-EUtwVfNm.mjs";
3
+ import { d as HttpException } from "./errors-C01O2T-n.mjs";
4
4
  import { n as Module } from "./module.decorator-CYHY6pG5.mjs";
5
5
  import "./module/index.mjs";
6
- import { t as StorageError } from "./storage.error-Dai3rShJ.mjs";
6
+ import { t as StorageError } from "./storage.error-BStXPmO4.mjs";
7
7
  import { r as z } from "./zod-eKqqhZ5_.mjs";
8
8
  import { t as withZodI18n } from "./validation-CpOjviyT.mjs";
9
- import { t as Controller } from "./controller.decorator-DKeZ71aP.mjs";
10
- import { n as Delete, o as Put, r as Get } from "./http-method.decorator-xCuGoZPC.mjs";
9
+ import { t as Controller } from "./controller.decorator-YSTPQntu.mjs";
10
+ import { n as Delete, o as Put, r as Get } from "./http-method.decorator-BljM8BDj.mjs";
11
11
  //#region src/storage/storage.tokens.ts
12
12
  /**
13
13
  * Dependency injection tokens for the Storage module
@@ -68,7 +68,7 @@ let StorageManagerService = class StorageManagerService {
68
68
  * @returns Storage provider instance
69
69
  */
70
70
  async createProvider(config) {
71
- const { R2StorageProvider } = await import("./r2-storage.provider-BJ4j23W6.mjs").then((n) => n.n);
71
+ const { R2StorageProvider } = await import("./r2-storage.provider-BoZmR6Ut.mjs").then((n) => n.n);
72
72
  const bucket = this.env[config.binding];
73
73
  if (!bucket) throw new StorageError(`R2 binding "${config.binding}" was not found in the environment`);
74
74
  return new R2StorageProvider(config, bucket, this.env, this.options.route);
@@ -493,4 +493,4 @@ var InvalidFileTypeError = class extends HttpException {
493
493
  //#endregion
494
494
  export { presignedUrlResultSchema as a, StorageController as c, StorageService as d, StorageManagerService as f, getPresignedUrlInputSchema as i, FileNotFoundError as l, FileTooLargeError as n, fileExistsInputSchema as o, STORAGE_TOKENS as p, uploadResultSchema as r, deleteFileInputSchema as s, InvalidFileTypeError as t, StorageModule as u };
495
495
 
496
- //# sourceMappingURL=storage-BE6-kzaN.mjs.map
496
+ //# sourceMappingURL=storage-C30X81CS.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage-BE6-kzaN.mjs","names":[],"sources":["../src/storage/storage.tokens.ts","../src/storage/services/storage-manager.service.ts","../src/storage/services/storage.service.ts","../src/storage/storage.module.ts","../src/storage/errors/file-not-found.error.ts","../src/storage/controllers/storage.controller.ts","../src/storage/contracts/delete-file.input.ts","../src/storage/contracts/file-exists.input.ts","../src/storage/contracts/get-presigned-url.input.ts","../src/storage/contracts/upload-file.input.ts","../src/storage/errors/file-too-large.error.ts","../src/storage/errors/invalid-file-type.error.ts"],"sourcesContent":["/**\n * Dependency injection tokens for the Storage module\n * Using Symbol-based tokens to avoid magic strings\n */\nexport const STORAGE_TOKENS = {\n Options: Symbol.for('stratal:storage:options'),\n StorageService: Symbol.for('stratal:storage:service'),\n StorageManager: Symbol.for('stratal:storage:manager'),\n} as const\n","import { inject } from '../../di'\nimport { Singleton } from '../../di/decorators'\nimport { DI_TOKENS } from '../../di/tokens'\nimport { type StratalEnv } from '../../env'\nimport { StorageError } from '../storage.error'\nimport type { IStorageProvider } from '../providers/storage-provider.interface'\nimport { STORAGE_TOKENS } from '../storage.tokens'\nimport type { StorageConfig, StorageEntry } from '../types'\n\n/**\n * Storage Manager Service\n * Manages multiple storage providers (one per disk)\n * Handles lazy initialization and caching of R2 providers\n */\n@Singleton(STORAGE_TOKENS.StorageManager)\nexport class StorageManagerService {\n private readonly providers = new Map<string, IStorageProvider>()\n private readonly creationPromises = new Map<string, Promise<IStorageProvider>>()\n private readonly diskConfigs = new Map<string, StorageEntry>()\n\n constructor(\n @inject(STORAGE_TOKENS.Options)\n private readonly options: StorageConfig,\n @inject(DI_TOKENS.CloudflareEnv)\n private readonly env: StratalEnv\n ) {\n this.initializeDiskConfigs()\n }\n\n /**\n * Initialize disk configurations from options\n */\n private initializeDiskConfigs(): void {\n for (const entry of this.options.storage) {\n this.diskConfigs.set(entry.disk, entry)\n }\n }\n\n /**\n * Get provider for a specific disk\n * Lazily initializes provider on first access\n * @param diskName - Name of the disk\n * @returns Storage provider instance\n */\n async getProvider(diskName: string): Promise<IStorageProvider> {\n // Return cached provider if exists\n const cached = this.providers.get(diskName)\n if (cached) {\n return cached\n }\n\n // Return in-flight creation promise to deduplicate concurrent calls\n const inflight = this.creationPromises.get(diskName)\n if (inflight) {\n return inflight\n }\n\n // Get disk configuration\n const diskConfig = this.diskConfigs.get(diskName)\n if (!diskConfig) {\n throw new StorageError(`Disk \"${diskName}\" is not configured`)\n }\n\n // Create provider and deduplicate concurrent calls\n const promise = this.createProvider(diskConfig).then((provider) => {\n this.providers.set(diskName, provider)\n this.creationPromises.delete(diskName)\n return provider\n }).catch((error: unknown) => {\n this.creationPromises.delete(diskName)\n throw error\n })\n\n this.creationPromises.set(diskName, promise)\n\n return promise\n }\n\n /**\n * Create an R2 provider instance\n * Dynamically imports R2StorageProvider to support code splitting\n * @param config - Storage entry configuration\n * @returns Storage provider instance\n */\n private async createProvider(config: StorageEntry): Promise<IStorageProvider> {\n const { R2StorageProvider } = await import('../providers/r2-storage.provider')\n const bucket = this.env[config.binding as keyof StratalEnv] as unknown as R2Bucket | undefined\n if (!bucket) {\n throw new StorageError(`R2 binding \"${config.binding}\" was not found in the environment`)\n }\n return new R2StorageProvider(config, bucket, this.env, this.options.route)\n }\n\n /**\n * Get disk configuration\n * @param diskName - Name of the disk\n * @returns Storage entry configuration\n */\n getDiskConfig(diskName: string): StorageEntry {\n const config = this.diskConfigs.get(diskName)\n if (!config) {\n throw new StorageError(`Disk \"${diskName}\" is not configured`)\n }\n return config\n }\n\n /**\n * Check if a disk exists\n * @param diskName - Name of the disk\n * @returns True if disk exists, false otherwise\n */\n hasDisk(diskName: string): boolean {\n return this.diskConfigs.has(diskName)\n }\n\n /**\n * Get all available disk names\n * @returns Array of disk names\n */\n getAvailableDisks(): string[] {\n return Array.from(this.diskConfigs.keys())\n }\n}\n","import { inject } from '../../di'\nimport { Request } from '../../di/decorators'\nimport type { DownloadResult, PresignedUrlResult, UploadOptions, UploadResult } from '../contracts'\nimport { StorageError } from '../storage.error'\nimport type { StreamingBlobPayloadInputTypes } from '../providers/storage-provider.interface'\nimport { STORAGE_TOKENS } from '../storage.tokens'\nimport type { StorageConfig } from '../types'\nimport { type StorageManagerService } from './storage-manager.service'\n\n/**\n * Storage Service\n *\n * Main facade for storage operations.\n * Request-scoped for proper isolation.\n *\n * @example\n * ```typescript\n * @inject(STORAGE_TOKENS.StorageService)\n * private readonly storage: StorageService\n *\n * await this.storage.upload(file, 'documents/report.pdf')\n * ```\n */\n@Request(STORAGE_TOKENS.StorageService)\nexport class StorageService {\n constructor(\n @inject(STORAGE_TOKENS.StorageManager)\n protected readonly storageManager: StorageManagerService,\n @inject(STORAGE_TOKENS.Options)\n protected readonly options: StorageConfig\n ) { }\n\n /**\n * Upload content to storage\n * @param body - Content to upload (stream, buffer, or string)\n * @param relativePath - Relative path within the disk\n * @param options - Upload options including size and mime type\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Upload result with metadata\n */\n async upload(\n body: StreamingBlobPayloadInputTypes,\n relativePath: string,\n options: UploadOptions,\n disk?: string\n ): Promise<UploadResult> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n return provider.upload(body, fullPath, options)\n }\n\n /**\n * Download a file from storage\n * @param relativePath - Relative path within the disk\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Download result with stream and metadata\n */\n async download(relativePath: string, disk?: string): Promise<DownloadResult> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n return provider.download(fullPath)\n }\n\n /**\n * Delete a file from storage\n * @param relativePath - Relative path within the disk\n * @param disk - Optional disk name (uses default if not provided)\n */\n async delete(relativePath: string, disk?: string): Promise<void> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n await provider.delete(fullPath)\n }\n\n /**\n * Check if a file exists in storage\n * @param relativePath - Relative path within the disk\n * @param disk - Optional disk name (uses default if not provided)\n * @returns True if file exists, false otherwise\n */\n async exists(relativePath: string, disk?: string): Promise<boolean> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n return provider.exists(fullPath)\n }\n\n /**\n * Generate a presigned download URL\n * @param relativePath - Relative path within the disk\n * @param expiresIn - Optional expiry time in seconds (uses default if not provided)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Presigned URL result\n */\n async getPresignedDownloadUrl(\n relativePath: string,\n expiresIn?: number,\n disk?: string\n ): Promise<PresignedUrlResult> {\n return this.getPresignedUrl(relativePath, 'GET', expiresIn, disk)\n }\n\n /**\n * Generate a presigned upload URL\n * @param relativePath - Relative path within the disk\n * @param expiresIn - Optional expiry time in seconds (uses default if not provided)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Presigned URL result\n */\n async getPresignedUploadUrl(\n relativePath: string,\n expiresIn?: number,\n disk?: string\n ): Promise<PresignedUrlResult> {\n return this.getPresignedUrl(relativePath, 'PUT', expiresIn, disk)\n }\n\n /**\n * Generate a presigned delete URL\n * @param relativePath - Relative path within the disk\n * @param expiresIn - Optional expiry time in seconds (uses default if not provided)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Presigned URL result\n */\n async getPresignedDeleteUrl(\n relativePath: string,\n expiresIn?: number,\n disk?: string\n ): Promise<PresignedUrlResult> {\n return this.getPresignedUrl(relativePath, 'DELETE', expiresIn, disk)\n }\n\n /**\n * Generate a presigned URL for any method\n * @param relativePath - Relative path within the disk\n * @param method - HTTP method (GET, PUT, DELETE, HEAD)\n * @param expiresIn - Optional expiry time in seconds (uses default if not provided)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Presigned URL result\n */\n protected async getPresignedUrl(\n relativePath: string,\n method: 'GET' | 'PUT' | 'DELETE' | 'HEAD',\n expiresIn?: number,\n disk?: string\n ): Promise<PresignedUrlResult> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n const validatedExpiresIn = this.validateExpiresIn(expiresIn)\n\n return provider.getPresignedUrl(fullPath, method, validatedExpiresIn)\n }\n\n /**\n * Resolve disk name (use default if not provided)\n * @param disk - Optional disk name\n * @returns Resolved disk name\n */\n protected resolveDisk(disk?: string): string {\n const diskName = disk ?? this.options.defaultStorageDisk\n\n if (!this.storageManager.hasDisk(diskName)) {\n throw new StorageError(`Disk \"${diskName}\" is not configured`)\n }\n\n return diskName\n }\n\n /**\n * Build full path with disk root and path template substitution\n * @param relativePath - Relative path within the disk\n * @param diskName - Name of the disk\n * @returns Full path including disk root\n */\n protected buildFullPath(relativePath: string, diskName: string): string {\n const diskConfig = this.storageManager.getDiskConfig(diskName)\n let root = diskConfig.root || ''\n\n // Substitute template variables\n root = this.substituteTemplateVariables(root)\n\n // Combine root and relative path\n const fullPath = `${root}/${relativePath}`.replace(/\\/+/g, '/').replace(/^\\//, '')\n\n return fullPath\n }\n\n /**\n * Substitute template variables in path\n * Override this method in subclasses to add custom substitutions\n *\n * @param path - Path with template variables\n * @returns Path with substituted variables\n */\n protected substituteTemplateVariables(path: string): string {\n let result = path\n\n // Substitute {date}, {year}, {month}\n const now = new Date()\n result = result.replace(/{date}/g, now.toISOString().split('T')[0])\n result = result.replace(/{year}/g, now.getFullYear().toString())\n result = result.replace(/{month}/g, (now.getMonth() + 1).toString().padStart(2, '0'))\n\n return result\n }\n\n /**\n * Validate expiry time for presigned URLs\n * @param expiresIn - Optional expiry time in seconds\n * @returns Validated expiry time\n */\n protected validateExpiresIn(expiresIn?: number): number {\n const presignedUrlConfig = this.options.presignedUrl\n const validatedExpiresIn = expiresIn ?? presignedUrlConfig.defaultExpiry\n\n const minExpiry = 1\n const maxExpiry = presignedUrlConfig.maxExpiry\n\n if (validatedExpiresIn < minExpiry || validatedExpiresIn > maxExpiry) {\n throw new StorageError(`Presigned URL expiry ${validatedExpiresIn}s is out of range (${minExpiry}–${maxExpiry}s)`)\n }\n\n return validatedExpiresIn\n }\n\n /**\n * Get all available disk names\n * @returns Array of disk names\n */\n getAvailableDisks(): string[] {\n return this.storageManager.getAvailableDisks()\n }\n\n /**\n * Chunked upload for streaming data without known size\n * Uses multipart upload under the hood - handles retries and large files\n *\n * Use this method when:\n * - Content-Length is unknown or unreliable\n * - Uploading from streams that can't be rewound\n * - Need automatic retry handling for transient failures\n *\n * @param body - Content to upload (stream or buffer)\n * @param relativePath - Relative path within the disk\n * @param options - Upload options (mimeType required, size optional)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Upload result with metadata\n */\n async chunkedUpload(\n body: StreamingBlobPayloadInputTypes,\n relativePath: string,\n options: Omit<UploadOptions, 'size'> & { size?: number },\n disk?: string\n ): Promise<UploadResult> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n return provider.chunkedUpload(body, fullPath, options)\n }\n}\n","/**\n * Storage Module\n * Provides file storage capabilities using Cloudflare R2\n * Supports multiple disk configurations with dynamic path templates\n */\n\nimport { Module } from '../module'\nimport type { AsyncModuleOptions, DynamicModule } from '../module/types'\nimport { StorageManagerService } from './services/storage-manager.service'\nimport { StorageService } from './services/storage.service'\nimport { STORAGE_TOKENS } from './storage.tokens'\nimport type { StorageConfig } from './types'\n\n/**\n * Storage module options\n * Same as StorageConfig from types.ts\n */\nexport type StorageModuleOptions = StorageConfig\n\n@Module({\n providers: [\n { provide: STORAGE_TOKENS.StorageManager, useClass: StorageManagerService },\n { provide: STORAGE_TOKENS.StorageService, useClass: StorageService },\n ],\n})\nexport class StorageModule {\n /**\n * Configure StorageModule with static options\n *\n * @example\n * ```typescript\n * StorageModule.forRoot({\n * storage: [{ disk: 'uploads', binding: 'MY_BUCKET', root: 'uploads' }],\n * defaultStorageDisk: 'uploads',\n * presignedUrl: { defaultExpiry: 3600, maxExpiry: 86400 }\n * })\n * ```\n */\n static forRoot(options: StorageModuleOptions): DynamicModule {\n return {\n module: StorageModule,\n providers: [\n { provide: STORAGE_TOKENS.Options, useValue: options },\n ],\n }\n }\n\n /**\n * Configure StorageModule with async factory\n *\n * Use when configuration depends on other services.\n *\n * @example\n * ```typescript\n * StorageModule.forRootAsync({\n * inject: [storageConfig.KEY],\n * useFactory: (storage) => ({\n * storage: storage.storage,\n * defaultStorageDisk: storage.defaultStorageDisk,\n * presignedUrl: storage.presignedUrl\n * })\n * })\n * ```\n */\n static forRootAsync(options: AsyncModuleOptions<StorageModuleOptions>): DynamicModule {\n return {\n module: StorageModule,\n providers: [\n {\n provide: STORAGE_TOKENS.Options,\n useFactory: options.useFactory,\n inject: options.inject,\n },\n ],\n }\n }\n}\n","import { HttpException } from '../../errors'\n\nexport class FileNotFoundError extends HttpException {\n constructor(path?: string) {\n super(404, path ? `File not found: \"${path}\"` : 'File not found')\n }\n}\n","import { inject } from '../../di'\nimport { z } from '../../i18n/validation'\nimport { Controller } from '../../router/decorators/controller.decorator'\nimport { Delete, Get, Put } from '../../router/decorators/http-method.decorator'\nimport { type RouterContext } from '../../router/router-context'\nimport { FileNotFoundError } from '../errors/file-not-found.error'\nimport type { StorageService } from '../services/storage.service'\nimport { STORAGE_TOKENS } from '../storage.tokens'\n\nconst diskParam = z.object({\n disk: z.string(),\n})\n\n/**\n * Storage Controller\n *\n * Auto-registered controller that proxies R2 operations behind signed URLs.\n * Signature verification is applied via VerifySignatureMiddleware on the module's\n * configureRoutes() method.\n *\n * Routes:\n * - GET /storage/:disk/* → download file\n * - PUT /storage/:disk/* → upload file\n * - DELETE /storage/:disk/* → delete file\n */\n@Controller('/storage', { hideFromDocs: true })\nexport class StorageController {\n constructor(\n @inject(STORAGE_TOKENS.StorageService)\n private readonly storage: StorageService\n ) {}\n\n @Get('/:disk/*', { hideFromDocs: true, params: diskParam })\n async download(ctx: RouterContext): Promise<Response> {\n const disk = ctx.param('disk')\n const path = extractWildcardPath(ctx)\n const result = await this.storage.download(path, disk)\n\n const stream = result.toStream()\n if (!stream) {\n throw new FileNotFoundError(path)\n }\n\n return new Response(stream, {\n headers: {\n 'Content-Type': result.contentType,\n 'Content-Length': String(result.size),\n 'Content-Disposition': 'inline',\n },\n })\n }\n\n @Put('/:disk/*', { hideFromDocs: true, params: diskParam })\n async upload(ctx: RouterContext): Promise<Response> {\n const disk = ctx.param('disk')\n const path = extractWildcardPath(ctx)\n\n const body = ctx.c.req.raw.body\n const contentType = ctx.header('content-type') ?? 'application/octet-stream'\n const contentLength = ctx.header('content-length')\n\n await this.storage.upload(body, path, {\n mimeType: contentType,\n size: contentLength ? parseInt(contentLength, 10) : 0,\n }, disk)\n\n return ctx.json({ path, disk }, 200)\n }\n\n @Delete('/:disk/*', { hideFromDocs: true, params: diskParam })\n async destroy(ctx: RouterContext): Promise<Response> {\n const disk = ctx.param('disk')\n const path = extractWildcardPath(ctx)\n\n await this.storage.delete(path, disk)\n\n return ctx.c.body(null, 204)\n }\n}\n\n/**\n * Extract the wildcard path from the Hono context.\n * Hono stores wildcard params under the key matching the path pattern.\n */\nfunction extractWildcardPath(ctx: RouterContext): string {\n // Hono exposes wildcard capture as the raw path after the matched prefix\n const url = new URL(ctx.c.req.url)\n const fullPath = url.pathname\n // Remove /storage/:disk/ prefix to get the file path\n const parts = fullPath.split('/')\n // ['', 'storage', 'disk', ...rest]\n return parts.slice(3).join('/')\n}\n","import { z, withZodI18n } from '../../i18n/validation'\n\nexport const deleteFileInputSchema = z.object({\n path: z.string().min(1, withZodI18n('zodI18n.errors.custom.filePathRequired')),\n disk: z.string().optional(),\n})\n\nexport type DeleteFileInput = z.infer<typeof deleteFileInputSchema>\n","import { z, withZodI18n } from '../../i18n/validation'\n\nexport const fileExistsInputSchema = z.object({\n path: z.string().min(1, withZodI18n('zodI18n.errors.custom.filePathRequired')),\n disk: z.string().optional(),\n})\n\nexport type FileExistsInput = z.infer<typeof fileExistsInputSchema>\n","import { z, withZodI18n } from '../../i18n/validation'\n\nexport const getPresignedUrlInputSchema = z.object({\n path: z.string().min(1, withZodI18n('zodI18n.errors.custom.filePathRequired')),\n method: z.enum(['GET', 'PUT', 'DELETE', 'HEAD']).default('GET'),\n expiresIn: z.number().int().min(1).max(604800).optional(),\n disk: z.string().optional(),\n})\n\nexport type GetPresignedUrlInput = z.infer<typeof getPresignedUrlInputSchema>\n\nexport const presignedUrlResultSchema = z.object({\n url: z.string().url(),\n expiresIn: z.number(),\n expiresAt: z.date(),\n method: z.enum(['GET', 'PUT', 'DELETE', 'HEAD']),\n})\n\nexport type PresignedUrlResult = z.infer<typeof presignedUrlResultSchema>\n","import { z } from '../../i18n/validation'\n\n/**\n * Upload options for streaming uploads\n */\nexport interface UploadOptions {\n /**\n * Size of the content in bytes\n */\n size: number\n /**\n * MIME type of the content\n */\n mimeType?: string\n /**\n * Custom metadata to store with the object (S3-specific)\n * Stored as S3 object metadata headers\n */\n metadata?: Record<string, string>\n /**\n * Object tagging for lifecycle policies (S3-specific)\n * Format: key=value (e.g., \"Tus-Completed=true\")\n */\n tagging?: string\n}\n\nexport const uploadResultSchema = z.object({\n path: z.string(),\n disk: z.string(),\n fullPath: z.string(),\n size: z.number(),\n mimeType: z.string(),\n uploadedAt: z.date(),\n})\n\nexport type UploadResult = z.infer<typeof uploadResultSchema>\n","import { HttpException } from '../../errors'\n\nexport class FileTooLargeError extends HttpException {\n constructor(public readonly size?: number, public readonly maxSize?: number) {\n super(413, 'File too large')\n }\n}\n","import { HttpException } from '../../errors'\n\nexport class InvalidFileTypeError extends HttpException {\n constructor(public readonly mimeType?: string) {\n super(422, 'Invalid file type')\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAIA,MAAa,iBAAiB;CAC5B,SAAS,OAAO,IAAI,yBAAyB;CAC7C,gBAAgB,OAAO,IAAI,yBAAyB;CACpD,gBAAgB,OAAO,IAAI,yBAAyB;AACtD;;;ACOO,IAAA,wBAAA,MAAM,sBAAsB;CAOd;CAEA;CARnB,4BAA6B,IAAI,IAA8B;CAC/D,mCAAoC,IAAI,IAAuC;CAC/E,8BAA+B,IAAI,IAA0B;CAE7D,YACE,SAEA,KAEA;EAHiB,KAAA,UAAA;EAEA,KAAA,MAAA;EAEjB,KAAK,sBAAsB;CAC7B;;;;CAKA,wBAAsC;EACpC,KAAK,MAAM,SAAS,KAAK,QAAQ,SAC/B,KAAK,YAAY,IAAI,MAAM,MAAM,KAAK;CAE1C;;;;;;;CAQA,MAAM,YAAY,UAA6C;EAE7D,MAAM,SAAS,KAAK,UAAU,IAAI,QAAQ;EAC1C,IAAI,QACF,OAAO;EAIT,MAAM,WAAW,KAAK,iBAAiB,IAAI,QAAQ;EACnD,IAAI,UACF,OAAO;EAIT,MAAM,aAAa,KAAK,YAAY,IAAI,QAAQ;EAChD,IAAI,CAAC,YACH,MAAM,IAAI,aAAa,SAAS,SAAS,oBAAoB;EAI/D,MAAM,UAAU,KAAK,eAAe,UAAU,EAAE,MAAM,aAAa;GACjE,KAAK,UAAU,IAAI,UAAU,QAAQ;GACrC,KAAK,iBAAiB,OAAO,QAAQ;GACrC,OAAO;EACT,CAAC,EAAE,OAAO,UAAmB;GAC3B,KAAK,iBAAiB,OAAO,QAAQ;GACrC,MAAM;EACR,CAAC;EAED,KAAK,iBAAiB,IAAI,UAAU,OAAO;EAE3C,OAAO;CACT;;;;;;;CAQA,MAAc,eAAe,QAAiD;EAC5E,MAAM,EAAE,sBAAsB,MAAM,OAAO,sCAAA,MAAA,MAAA,EAAA,CAAA;EAC3C,MAAM,SAAS,KAAK,IAAI,OAAO;EAC/B,IAAI,CAAC,QACH,MAAM,IAAI,aAAa,eAAe,OAAO,QAAQ,mCAAmC;EAE1F,OAAO,IAAI,kBAAkB,QAAQ,QAAQ,KAAK,KAAK,KAAK,QAAQ,KAAK;CAC3E;;;;;;CAOA,cAAc,UAAgC;EAC5C,MAAM,SAAS,KAAK,YAAY,IAAI,QAAQ;EAC5C,IAAI,CAAC,QACH,MAAM,IAAI,aAAa,SAAS,SAAS,oBAAoB;EAE/D,OAAO;CACT;;;;;;CAOA,QAAQ,UAA2B;EACjC,OAAO,KAAK,YAAY,IAAI,QAAQ;CACtC;;;;;CAMA,oBAA8B;EAC5B,OAAO,MAAM,KAAK,KAAK,YAAY,KAAK,CAAC;CAC3C;AACF;;CA5GC,UAAU,eAAe,cAAc;oBAOnC,OAAO,eAAe,OAAO,CAAA;oBAE7B,OAAO,UAAU,aAAa,CAAA;;;;ACC5B,IAAA,iBAAA,MAAM,eAAe;CAGL;CAEA;CAJrB,YACE,gBAEA,SAEA;EAHmB,KAAA,iBAAA;EAEA,KAAA,UAAA;CACjB;;;;;;;;;CAUJ,MAAM,OACJ,MACA,cACA,SACA,MACuB;EACvB,MAAM,WAAW,KAAK,YAAY,IAAI;EACtC,MAAM,WAAW,MAAM,KAAK,eAAe,YAAY,QAAQ;EAC/D,MAAM,WAAW,KAAK,cAAc,cAAc,QAAQ;EAE1D,OAAO,SAAS,OAAO,MAAM,UAAU,OAAO;CAChD;;;;;;;CAQA,MAAM,SAAS,cAAsB,MAAwC;EAC3E,MAAM,WAAW,KAAK,YAAY,IAAI;EACtC,MAAM,WAAW,MAAM,KAAK,eAAe,YAAY,QAAQ;EAC/D,MAAM,WAAW,KAAK,cAAc,cAAc,QAAQ;EAE1D,OAAO,SAAS,SAAS,QAAQ;CACnC;;;;;;CAOA,MAAM,OAAO,cAAsB,MAA8B;EAC/D,MAAM,WAAW,KAAK,YAAY,IAAI;EACtC,MAAM,WAAW,MAAM,KAAK,eAAe,YAAY,QAAQ;EAC/D,MAAM,WAAW,KAAK,cAAc,cAAc,QAAQ;EAE1D,MAAM,SAAS,OAAO,QAAQ;CAChC;;;;;;;CAQA,MAAM,OAAO,cAAsB,MAAiC;EAClE,MAAM,WAAW,KAAK,YAAY,IAAI;EACtC,MAAM,WAAW,MAAM,KAAK,eAAe,YAAY,QAAQ;EAC/D,MAAM,WAAW,KAAK,cAAc,cAAc,QAAQ;EAE1D,OAAO,SAAS,OAAO,QAAQ;CACjC;;;;;;;;CASA,MAAM,wBACJ,cACA,WACA,MAC6B;EAC7B,OAAO,KAAK,gBAAgB,cAAc,OAAO,WAAW,IAAI;CAClE;;;;;;;;CASA,MAAM,sBACJ,cACA,WACA,MAC6B;EAC7B,OAAO,KAAK,gBAAgB,cAAc,OAAO,WAAW,IAAI;CAClE;;;;;;;;CASA,MAAM,sBACJ,cACA,WACA,MAC6B;EAC7B,OAAO,KAAK,gBAAgB,cAAc,UAAU,WAAW,IAAI;CACrE;;;;;;;;;CAUA,MAAgB,gBACd,cACA,QACA,WACA,MAC6B;EAC7B,MAAM,WAAW,KAAK,YAAY,IAAI;EACtC,MAAM,WAAW,MAAM,KAAK,eAAe,YAAY,QAAQ;EAC/D,MAAM,WAAW,KAAK,cAAc,cAAc,QAAQ;EAC1D,MAAM,qBAAqB,KAAK,kBAAkB,SAAS;EAE3D,OAAO,SAAS,gBAAgB,UAAU,QAAQ,kBAAkB;CACtE;;;;;;CAOA,YAAsB,MAAuB;EAC3C,MAAM,WAAW,QAAQ,KAAK,QAAQ;EAEtC,IAAI,CAAC,KAAK,eAAe,QAAQ,QAAQ,GACvC,MAAM,IAAI,aAAa,SAAS,SAAS,oBAAoB;EAG/D,OAAO;CACT;;;;;;;CAQA,cAAwB,cAAsB,UAA0B;EAEtE,IAAI,OADe,KAAK,eAAe,cAAc,QACjC,EAAE,QAAQ;EAG9B,OAAO,KAAK,4BAA4B,IAAI;EAK5C,OAFiB,GAAG,KAAK,GAAG,eAAe,QAAQ,QAAQ,GAAG,EAAE,QAAQ,OAAO,EAEjE;CAChB;;;;;;;;CASA,4BAAsC,MAAsB;EAC1D,IAAI,SAAS;EAGb,MAAM,sBAAM,IAAI,KAAK;EACrB,SAAS,OAAO,QAAQ,WAAW,IAAI,YAAY,EAAE,MAAM,GAAG,EAAE,EAAE;EAClE,SAAS,OAAO,QAAQ,WAAW,IAAI,YAAY,EAAE,SAAS,CAAC;EAC/D,SAAS,OAAO,QAAQ,aAAa,IAAI,SAAS,IAAI,GAAG,SAAS,EAAE,SAAS,GAAG,GAAG,CAAC;EAEpF,OAAO;CACT;;;;;;CAOA,kBAA4B,WAA4B;EACtD,MAAM,qBAAqB,KAAK,QAAQ;EACxC,MAAM,qBAAqB,aAAa,mBAAmB;EAE3D,MAAM,YAAY;EAClB,MAAM,YAAY,mBAAmB;EAErC,IAAI,qBAAqB,aAAa,qBAAqB,WACzD,MAAM,IAAI,aAAa,wBAAwB,mBAAmB,qBAAqB,UAAU,GAAG,UAAU,GAAG;EAGnH,OAAO;CACT;;;;;CAMA,oBAA8B;EAC5B,OAAO,KAAK,eAAe,kBAAkB;CAC/C;;;;;;;;;;;;;;;;CAiBA,MAAM,cACJ,MACA,cACA,SACA,MACuB;EACvB,MAAM,WAAW,KAAK,YAAY,IAAI;EACtC,MAAM,WAAW,MAAM,KAAK,eAAe,YAAY,QAAQ;EAC/D,MAAM,WAAW,KAAK,cAAc,cAAc,QAAQ;EAE1D,OAAO,SAAS,cAAc,MAAM,UAAU,OAAO;CACvD;AACF;;CArPC,QAAQ,eAAe,cAAc;oBAGjC,OAAO,eAAe,cAAc,CAAA;oBAEpC,OAAO,eAAe,OAAO,CAAA;;;;;;;;;;ACH3B,IAAA,gBAAA,iBAAA,MAAM,cAAc;;;;;;;;;;;;;CAazB,OAAO,QAAQ,SAA8C;EAC3D,OAAO;GACL,QAAA;GACA,WAAW,CACT;IAAE,SAAS,eAAe;IAAS,UAAU;GAAQ,CACvD;EACF;CACF;;;;;;;;;;;;;;;;;;CAmBA,OAAO,aAAa,SAAkE;EACpF,OAAO;GACL,QAAA;GACA,WAAW,CACT;IACE,SAAS,eAAe;IACxB,YAAY,QAAQ;IACpB,QAAQ,QAAQ;GAClB,CACF;EACF;CACF;AACF;6CAzDC,OAAO,EACN,WAAW,CACT;CAAE,SAAS,eAAe;CAAgB,UAAU;AAAsB,GAC1E;CAAE,SAAS,eAAe;CAAgB,UAAU;AAAe,CACrE,EACF,CAAC,CAAA,GAAA,aAAA;;;ACtBD,IAAa,oBAAb,cAAuC,cAAc;CACnD,YAAY,MAAe;EACzB,MAAM,KAAK,OAAO,oBAAoB,KAAK,KAAK,gBAAgB;CAClE;AACF;;;ACGA,MAAM,YAAY,EAAE,OAAO,EACzB,MAAM,EAAE,OAAO,EACjB,CAAC;AAeM,IAAA,oBAAA,MAAM,kBAAkB;CAGV;CAFnB,YACE,SAEA;EADiB,KAAA,UAAA;CAChB;CAEH,MACM,SAAS,KAAuC;EACpD,MAAM,OAAO,IAAI,MAAM,MAAM;EAC7B,MAAM,OAAO,oBAAoB,GAAG;EACpC,MAAM,SAAS,MAAM,KAAK,QAAQ,SAAS,MAAM,IAAI;EAErD,MAAM,SAAS,OAAO,SAAS;EAC/B,IAAI,CAAC,QACH,MAAM,IAAI,kBAAkB,IAAI;EAGlC,OAAO,IAAI,SAAS,QAAQ,EAC1B,SAAS;GACP,gBAAgB,OAAO;GACvB,kBAAkB,OAAO,OAAO,IAAI;GACpC,uBAAuB;EACzB,EACF,CAAC;CACH;CAEA,MACM,OAAO,KAAuC;EAClD,MAAM,OAAO,IAAI,MAAM,MAAM;EAC7B,MAAM,OAAO,oBAAoB,GAAG;EAEpC,MAAM,OAAO,IAAI,EAAE,IAAI,IAAI;EAC3B,MAAM,cAAc,IAAI,OAAO,cAAc,KAAK;EAClD,MAAM,gBAAgB,IAAI,OAAO,gBAAgB;EAEjD,MAAM,KAAK,QAAQ,OAAO,MAAM,MAAM;GACpC,UAAU;GACV,MAAM,gBAAgB,SAAS,eAAe,EAAE,IAAI;EACtD,GAAG,IAAI;EAEP,OAAO,IAAI,KAAK;GAAE;GAAM;EAAK,GAAG,GAAG;CACrC;CAEA,MACM,QAAQ,KAAuC;EACnD,MAAM,OAAO,IAAI,MAAM,MAAM;EAC7B,MAAM,OAAO,oBAAoB,GAAG;EAEpC,MAAM,KAAK,QAAQ,OAAO,MAAM,IAAI;EAEpC,OAAO,IAAI,EAAE,KAAK,MAAM,GAAG;CAC7B;AACF;YA9CG,IAAI,YAAY;CAAE,cAAc;CAAM,QAAQ;AAAU,CAAC,CAAA,GAAA,kBAAA,WAAA,YAAA,IAAA;YAoBzD,IAAI,YAAY;CAAE,cAAc;CAAM,QAAQ;AAAU,CAAC,CAAA,GAAA,kBAAA,WAAA,UAAA,IAAA;YAiBzD,OAAO,YAAY;CAAE,cAAc;CAAM,QAAQ;AAAU,CAAC,CAAA,GAAA,kBAAA,WAAA,WAAA,IAAA;gCA5C9D,WAAW,YAAY,EAAE,cAAc,KAAK,CAAC,GAAA,gBAAA,GAGzC,OAAO,eAAe,cAAc,CAAA,CAAA,GAAA,iBAAA;;;;;AAwDzC,SAAS,oBAAoB,KAA4B;CAOvD,OAJiB,IADD,IAAI,IAAI,EAAE,IAAI,GACX,EAAE,SAEE,MAAM,GAElB,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG;AAChC;;;AC1FA,MAAa,wBAAwB,EAAE,OAAO;CAC5C,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,YAAY,wCAAwC,CAAC;CAC7E,MAAM,EAAE,OAAO,EAAE,SAAS;AAC5B,CAAC;;;ACHD,MAAa,wBAAwB,EAAE,OAAO;CAC5C,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,YAAY,wCAAwC,CAAC;CAC7E,MAAM,EAAE,OAAO,EAAE,SAAS;AAC5B,CAAC;;;ACHD,MAAa,6BAA6B,EAAE,OAAO;CACjD,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,YAAY,wCAAwC,CAAC;CAC7E,QAAQ,EAAE,KAAK;EAAC;EAAO;EAAO;EAAU;CAAM,CAAC,EAAE,QAAQ,KAAK;CAC9D,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,MAAM,EAAE,SAAS;CACxD,MAAM,EAAE,OAAO,EAAE,SAAS;AAC5B,CAAC;AAID,MAAa,2BAA2B,EAAE,OAAO;CAC/C,KAAK,EAAE,OAAO,EAAE,IAAI;CACpB,WAAW,EAAE,OAAO;CACpB,WAAW,EAAE,KAAK;CAClB,QAAQ,EAAE,KAAK;EAAC;EAAO;EAAO;EAAU;CAAM,CAAC;AACjD,CAAC;;;ACUD,MAAa,qBAAqB,EAAE,OAAO;CACzC,MAAM,EAAE,OAAO;CACf,MAAM,EAAE,OAAO;CACf,UAAU,EAAE,OAAO;CACnB,MAAM,EAAE,OAAO;CACf,UAAU,EAAE,OAAO;CACnB,YAAY,EAAE,KAAK;AACrB,CAAC;;;AC/BD,IAAa,oBAAb,cAAuC,cAAc;CACvB;CAA+B;CAA3D,YAAY,MAA+B,SAAkC;EAC3E,MAAM,KAAK,gBAAgB;EADD,KAAA,OAAA;EAA+B,KAAA,UAAA;CAE3D;AACF;;;ACJA,IAAa,uBAAb,cAA0C,cAAc;CAC1B;CAA5B,YAAY,UAAmC;EAC7C,MAAM,KAAK,mBAAmB;EADJ,KAAA,WAAA;CAE5B;AACF"}
1
+ {"version":3,"file":"storage-C30X81CS.mjs","names":[],"sources":["../src/storage/storage.tokens.ts","../src/storage/services/storage-manager.service.ts","../src/storage/services/storage.service.ts","../src/storage/storage.module.ts","../src/storage/errors/file-not-found.error.ts","../src/storage/controllers/storage.controller.ts","../src/storage/contracts/delete-file.input.ts","../src/storage/contracts/file-exists.input.ts","../src/storage/contracts/get-presigned-url.input.ts","../src/storage/contracts/upload-file.input.ts","../src/storage/errors/file-too-large.error.ts","../src/storage/errors/invalid-file-type.error.ts"],"sourcesContent":["/**\n * Dependency injection tokens for the Storage module\n * Using Symbol-based tokens to avoid magic strings\n */\nexport const STORAGE_TOKENS = {\n Options: Symbol.for('stratal:storage:options'),\n StorageService: Symbol.for('stratal:storage:service'),\n StorageManager: Symbol.for('stratal:storage:manager'),\n} as const\n","import { inject } from '../../di'\nimport { Singleton } from '../../di/decorators'\nimport { DI_TOKENS } from '../../di/tokens'\nimport { type StratalEnv } from '../../env'\nimport { StorageError } from '../storage.error'\nimport type { IStorageProvider } from '../providers/storage-provider.interface'\nimport { STORAGE_TOKENS } from '../storage.tokens'\nimport type { StorageConfig, StorageEntry } from '../types'\n\n/**\n * Storage Manager Service\n * Manages multiple storage providers (one per disk)\n * Handles lazy initialization and caching of R2 providers\n */\n@Singleton(STORAGE_TOKENS.StorageManager)\nexport class StorageManagerService {\n private readonly providers = new Map<string, IStorageProvider>()\n private readonly creationPromises = new Map<string, Promise<IStorageProvider>>()\n private readonly diskConfigs = new Map<string, StorageEntry>()\n\n constructor(\n @inject(STORAGE_TOKENS.Options)\n private readonly options: StorageConfig,\n @inject(DI_TOKENS.CloudflareEnv)\n private readonly env: StratalEnv\n ) {\n this.initializeDiskConfigs()\n }\n\n /**\n * Initialize disk configurations from options\n */\n private initializeDiskConfigs(): void {\n for (const entry of this.options.storage) {\n this.diskConfigs.set(entry.disk, entry)\n }\n }\n\n /**\n * Get provider for a specific disk\n * Lazily initializes provider on first access\n * @param diskName - Name of the disk\n * @returns Storage provider instance\n */\n async getProvider(diskName: string): Promise<IStorageProvider> {\n // Return cached provider if exists\n const cached = this.providers.get(diskName)\n if (cached) {\n return cached\n }\n\n // Return in-flight creation promise to deduplicate concurrent calls\n const inflight = this.creationPromises.get(diskName)\n if (inflight) {\n return inflight\n }\n\n // Get disk configuration\n const diskConfig = this.diskConfigs.get(diskName)\n if (!diskConfig) {\n throw new StorageError(`Disk \"${diskName}\" is not configured`)\n }\n\n // Create provider and deduplicate concurrent calls\n const promise = this.createProvider(diskConfig).then((provider) => {\n this.providers.set(diskName, provider)\n this.creationPromises.delete(diskName)\n return provider\n }).catch((error: unknown) => {\n this.creationPromises.delete(diskName)\n throw error\n })\n\n this.creationPromises.set(diskName, promise)\n\n return promise\n }\n\n /**\n * Create an R2 provider instance\n * Dynamically imports R2StorageProvider to support code splitting\n * @param config - Storage entry configuration\n * @returns Storage provider instance\n */\n private async createProvider(config: StorageEntry): Promise<IStorageProvider> {\n const { R2StorageProvider } = await import('../providers/r2-storage.provider')\n const bucket = this.env[config.binding as keyof StratalEnv] as unknown as R2Bucket | undefined\n if (!bucket) {\n throw new StorageError(`R2 binding \"${config.binding}\" was not found in the environment`)\n }\n return new R2StorageProvider(config, bucket, this.env, this.options.route)\n }\n\n /**\n * Get disk configuration\n * @param diskName - Name of the disk\n * @returns Storage entry configuration\n */\n getDiskConfig(diskName: string): StorageEntry {\n const config = this.diskConfigs.get(diskName)\n if (!config) {\n throw new StorageError(`Disk \"${diskName}\" is not configured`)\n }\n return config\n }\n\n /**\n * Check if a disk exists\n * @param diskName - Name of the disk\n * @returns True if disk exists, false otherwise\n */\n hasDisk(diskName: string): boolean {\n return this.diskConfigs.has(diskName)\n }\n\n /**\n * Get all available disk names\n * @returns Array of disk names\n */\n getAvailableDisks(): string[] {\n return Array.from(this.diskConfigs.keys())\n }\n}\n","import { inject } from '../../di'\nimport { Request } from '../../di/decorators'\nimport type { DownloadResult, PresignedUrlResult, UploadOptions, UploadResult } from '../contracts'\nimport { StorageError } from '../storage.error'\nimport type { StreamingBlobPayloadInputTypes } from '../providers/storage-provider.interface'\nimport { STORAGE_TOKENS } from '../storage.tokens'\nimport type { StorageConfig } from '../types'\nimport { type StorageManagerService } from './storage-manager.service'\n\n/**\n * Storage Service\n *\n * Main facade for storage operations.\n * Request-scoped for proper isolation.\n *\n * @example\n * ```typescript\n * @inject(STORAGE_TOKENS.StorageService)\n * private readonly storage: StorageService\n *\n * await this.storage.upload(file, 'documents/report.pdf')\n * ```\n */\n@Request(STORAGE_TOKENS.StorageService)\nexport class StorageService {\n constructor(\n @inject(STORAGE_TOKENS.StorageManager)\n protected readonly storageManager: StorageManagerService,\n @inject(STORAGE_TOKENS.Options)\n protected readonly options: StorageConfig\n ) { }\n\n /**\n * Upload content to storage\n * @param body - Content to upload (stream, buffer, or string)\n * @param relativePath - Relative path within the disk\n * @param options - Upload options including size and mime type\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Upload result with metadata\n */\n async upload(\n body: StreamingBlobPayloadInputTypes,\n relativePath: string,\n options: UploadOptions,\n disk?: string\n ): Promise<UploadResult> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n return provider.upload(body, fullPath, options)\n }\n\n /**\n * Download a file from storage\n * @param relativePath - Relative path within the disk\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Download result with stream and metadata\n */\n async download(relativePath: string, disk?: string): Promise<DownloadResult> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n return provider.download(fullPath)\n }\n\n /**\n * Delete a file from storage\n * @param relativePath - Relative path within the disk\n * @param disk - Optional disk name (uses default if not provided)\n */\n async delete(relativePath: string, disk?: string): Promise<void> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n await provider.delete(fullPath)\n }\n\n /**\n * Check if a file exists in storage\n * @param relativePath - Relative path within the disk\n * @param disk - Optional disk name (uses default if not provided)\n * @returns True if file exists, false otherwise\n */\n async exists(relativePath: string, disk?: string): Promise<boolean> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n return provider.exists(fullPath)\n }\n\n /**\n * Generate a presigned download URL\n * @param relativePath - Relative path within the disk\n * @param expiresIn - Optional expiry time in seconds (uses default if not provided)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Presigned URL result\n */\n async getPresignedDownloadUrl(\n relativePath: string,\n expiresIn?: number,\n disk?: string\n ): Promise<PresignedUrlResult> {\n return this.getPresignedUrl(relativePath, 'GET', expiresIn, disk)\n }\n\n /**\n * Generate a presigned upload URL\n * @param relativePath - Relative path within the disk\n * @param expiresIn - Optional expiry time in seconds (uses default if not provided)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Presigned URL result\n */\n async getPresignedUploadUrl(\n relativePath: string,\n expiresIn?: number,\n disk?: string\n ): Promise<PresignedUrlResult> {\n return this.getPresignedUrl(relativePath, 'PUT', expiresIn, disk)\n }\n\n /**\n * Generate a presigned delete URL\n * @param relativePath - Relative path within the disk\n * @param expiresIn - Optional expiry time in seconds (uses default if not provided)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Presigned URL result\n */\n async getPresignedDeleteUrl(\n relativePath: string,\n expiresIn?: number,\n disk?: string\n ): Promise<PresignedUrlResult> {\n return this.getPresignedUrl(relativePath, 'DELETE', expiresIn, disk)\n }\n\n /**\n * Generate a presigned URL for any method\n * @param relativePath - Relative path within the disk\n * @param method - HTTP method (GET, PUT, DELETE, HEAD)\n * @param expiresIn - Optional expiry time in seconds (uses default if not provided)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Presigned URL result\n */\n protected async getPresignedUrl(\n relativePath: string,\n method: 'GET' | 'PUT' | 'DELETE' | 'HEAD',\n expiresIn?: number,\n disk?: string\n ): Promise<PresignedUrlResult> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n const validatedExpiresIn = this.validateExpiresIn(expiresIn)\n\n return provider.getPresignedUrl(fullPath, method, validatedExpiresIn)\n }\n\n /**\n * Resolve disk name (use default if not provided)\n * @param disk - Optional disk name\n * @returns Resolved disk name\n */\n protected resolveDisk(disk?: string): string {\n const diskName = disk ?? this.options.defaultStorageDisk\n\n if (!this.storageManager.hasDisk(diskName)) {\n throw new StorageError(`Disk \"${diskName}\" is not configured`)\n }\n\n return diskName\n }\n\n /**\n * Build full path with disk root and path template substitution\n * @param relativePath - Relative path within the disk\n * @param diskName - Name of the disk\n * @returns Full path including disk root\n */\n protected buildFullPath(relativePath: string, diskName: string): string {\n const diskConfig = this.storageManager.getDiskConfig(diskName)\n let root = diskConfig.root || ''\n\n // Substitute template variables\n root = this.substituteTemplateVariables(root)\n\n // Combine root and relative path\n const fullPath = `${root}/${relativePath}`.replace(/\\/+/g, '/').replace(/^\\//, '')\n\n return fullPath\n }\n\n /**\n * Substitute template variables in path\n * Override this method in subclasses to add custom substitutions\n *\n * @param path - Path with template variables\n * @returns Path with substituted variables\n */\n protected substituteTemplateVariables(path: string): string {\n let result = path\n\n // Substitute {date}, {year}, {month}\n const now = new Date()\n result = result.replace(/{date}/g, now.toISOString().split('T')[0])\n result = result.replace(/{year}/g, now.getFullYear().toString())\n result = result.replace(/{month}/g, (now.getMonth() + 1).toString().padStart(2, '0'))\n\n return result\n }\n\n /**\n * Validate expiry time for presigned URLs\n * @param expiresIn - Optional expiry time in seconds\n * @returns Validated expiry time\n */\n protected validateExpiresIn(expiresIn?: number): number {\n const presignedUrlConfig = this.options.presignedUrl\n const validatedExpiresIn = expiresIn ?? presignedUrlConfig.defaultExpiry\n\n const minExpiry = 1\n const maxExpiry = presignedUrlConfig.maxExpiry\n\n if (validatedExpiresIn < minExpiry || validatedExpiresIn > maxExpiry) {\n throw new StorageError(`Presigned URL expiry ${validatedExpiresIn}s is out of range (${minExpiry}–${maxExpiry}s)`)\n }\n\n return validatedExpiresIn\n }\n\n /**\n * Get all available disk names\n * @returns Array of disk names\n */\n getAvailableDisks(): string[] {\n return this.storageManager.getAvailableDisks()\n }\n\n /**\n * Chunked upload for streaming data without known size\n * Uses multipart upload under the hood - handles retries and large files\n *\n * Use this method when:\n * - Content-Length is unknown or unreliable\n * - Uploading from streams that can't be rewound\n * - Need automatic retry handling for transient failures\n *\n * @param body - Content to upload (stream or buffer)\n * @param relativePath - Relative path within the disk\n * @param options - Upload options (mimeType required, size optional)\n * @param disk - Optional disk name (uses default if not provided)\n * @returns Upload result with metadata\n */\n async chunkedUpload(\n body: StreamingBlobPayloadInputTypes,\n relativePath: string,\n options: Omit<UploadOptions, 'size'> & { size?: number },\n disk?: string\n ): Promise<UploadResult> {\n const diskName = this.resolveDisk(disk)\n const provider = await this.storageManager.getProvider(diskName)\n const fullPath = this.buildFullPath(relativePath, diskName)\n\n return provider.chunkedUpload(body, fullPath, options)\n }\n}\n","/**\n * Storage Module\n * Provides file storage capabilities using Cloudflare R2\n * Supports multiple disk configurations with dynamic path templates\n */\n\nimport { Module } from '../module'\nimport type { AsyncModuleOptions, DynamicModule } from '../module/types'\nimport { StorageManagerService } from './services/storage-manager.service'\nimport { StorageService } from './services/storage.service'\nimport { STORAGE_TOKENS } from './storage.tokens'\nimport type { StorageConfig } from './types'\n\n/**\n * Storage module options\n * Same as StorageConfig from types.ts\n */\nexport type StorageModuleOptions = StorageConfig\n\n@Module({\n providers: [\n { provide: STORAGE_TOKENS.StorageManager, useClass: StorageManagerService },\n { provide: STORAGE_TOKENS.StorageService, useClass: StorageService },\n ],\n})\nexport class StorageModule {\n /**\n * Configure StorageModule with static options\n *\n * @example\n * ```typescript\n * StorageModule.forRoot({\n * storage: [{ disk: 'uploads', binding: 'MY_BUCKET', root: 'uploads' }],\n * defaultStorageDisk: 'uploads',\n * presignedUrl: { defaultExpiry: 3600, maxExpiry: 86400 }\n * })\n * ```\n */\n static forRoot(options: StorageModuleOptions): DynamicModule {\n return {\n module: StorageModule,\n providers: [\n { provide: STORAGE_TOKENS.Options, useValue: options },\n ],\n }\n }\n\n /**\n * Configure StorageModule with async factory\n *\n * Use when configuration depends on other services.\n *\n * @example\n * ```typescript\n * StorageModule.forRootAsync({\n * inject: [storageConfig.KEY],\n * useFactory: (storage) => ({\n * storage: storage.storage,\n * defaultStorageDisk: storage.defaultStorageDisk,\n * presignedUrl: storage.presignedUrl\n * })\n * })\n * ```\n */\n static forRootAsync(options: AsyncModuleOptions<StorageModuleOptions>): DynamicModule {\n return {\n module: StorageModule,\n providers: [\n {\n provide: STORAGE_TOKENS.Options,\n useFactory: options.useFactory,\n inject: options.inject,\n },\n ],\n }\n }\n}\n","import { HttpException } from '../../errors'\n\nexport class FileNotFoundError extends HttpException {\n constructor(path?: string) {\n super(404, path ? `File not found: \"${path}\"` : 'File not found')\n }\n}\n","import { inject } from '../../di'\nimport { z } from '../../i18n/validation'\nimport { Controller } from '../../router/decorators/controller.decorator'\nimport { Delete, Get, Put } from '../../router/decorators/http-method.decorator'\nimport { type RouterContext } from '../../router/router-context'\nimport { FileNotFoundError } from '../errors/file-not-found.error'\nimport type { StorageService } from '../services/storage.service'\nimport { STORAGE_TOKENS } from '../storage.tokens'\n\nconst diskParam = z.object({\n disk: z.string(),\n})\n\n/**\n * Storage Controller\n *\n * Auto-registered controller that proxies R2 operations behind signed URLs.\n * Signature verification is applied via VerifySignatureMiddleware on the module's\n * configureRoutes() method.\n *\n * Routes:\n * - GET /storage/:disk/* → download file\n * - PUT /storage/:disk/* → upload file\n * - DELETE /storage/:disk/* → delete file\n */\n@Controller('/storage', { hideFromDocs: true })\nexport class StorageController {\n constructor(\n @inject(STORAGE_TOKENS.StorageService)\n private readonly storage: StorageService\n ) {}\n\n @Get('/:disk/*', { hideFromDocs: true, params: diskParam })\n async download(ctx: RouterContext): Promise<Response> {\n const disk = ctx.param('disk')\n const path = extractWildcardPath(ctx)\n const result = await this.storage.download(path, disk)\n\n const stream = result.toStream()\n if (!stream) {\n throw new FileNotFoundError(path)\n }\n\n return new Response(stream, {\n headers: {\n 'Content-Type': result.contentType,\n 'Content-Length': String(result.size),\n 'Content-Disposition': 'inline',\n },\n })\n }\n\n @Put('/:disk/*', { hideFromDocs: true, params: diskParam })\n async upload(ctx: RouterContext): Promise<Response> {\n const disk = ctx.param('disk')\n const path = extractWildcardPath(ctx)\n\n const body = ctx.c.req.raw.body\n const contentType = ctx.header('content-type') ?? 'application/octet-stream'\n const contentLength = ctx.header('content-length')\n\n await this.storage.upload(body, path, {\n mimeType: contentType,\n size: contentLength ? parseInt(contentLength, 10) : 0,\n }, disk)\n\n return ctx.json({ path, disk }, 200)\n }\n\n @Delete('/:disk/*', { hideFromDocs: true, params: diskParam })\n async destroy(ctx: RouterContext): Promise<Response> {\n const disk = ctx.param('disk')\n const path = extractWildcardPath(ctx)\n\n await this.storage.delete(path, disk)\n\n return ctx.c.body(null, 204)\n }\n}\n\n/**\n * Extract the wildcard path from the Hono context.\n * Hono stores wildcard params under the key matching the path pattern.\n */\nfunction extractWildcardPath(ctx: RouterContext): string {\n // Hono exposes wildcard capture as the raw path after the matched prefix\n const url = new URL(ctx.c.req.url)\n const fullPath = url.pathname\n // Remove /storage/:disk/ prefix to get the file path\n const parts = fullPath.split('/')\n // ['', 'storage', 'disk', ...rest]\n return parts.slice(3).join('/')\n}\n","import { z, withZodI18n } from '../../i18n/validation'\n\nexport const deleteFileInputSchema = z.object({\n path: z.string().min(1, withZodI18n('zodI18n.errors.custom.filePathRequired')),\n disk: z.string().optional(),\n})\n\nexport type DeleteFileInput = z.infer<typeof deleteFileInputSchema>\n","import { z, withZodI18n } from '../../i18n/validation'\n\nexport const fileExistsInputSchema = z.object({\n path: z.string().min(1, withZodI18n('zodI18n.errors.custom.filePathRequired')),\n disk: z.string().optional(),\n})\n\nexport type FileExistsInput = z.infer<typeof fileExistsInputSchema>\n","import { z, withZodI18n } from '../../i18n/validation'\n\nexport const getPresignedUrlInputSchema = z.object({\n path: z.string().min(1, withZodI18n('zodI18n.errors.custom.filePathRequired')),\n method: z.enum(['GET', 'PUT', 'DELETE', 'HEAD']).default('GET'),\n expiresIn: z.number().int().min(1).max(604800).optional(),\n disk: z.string().optional(),\n})\n\nexport type GetPresignedUrlInput = z.infer<typeof getPresignedUrlInputSchema>\n\nexport const presignedUrlResultSchema = z.object({\n url: z.string().url(),\n expiresIn: z.number(),\n expiresAt: z.date(),\n method: z.enum(['GET', 'PUT', 'DELETE', 'HEAD']),\n})\n\nexport type PresignedUrlResult = z.infer<typeof presignedUrlResultSchema>\n","import { z } from '../../i18n/validation'\n\n/**\n * Upload options for streaming uploads\n */\nexport interface UploadOptions {\n /**\n * Size of the content in bytes\n */\n size: number\n /**\n * MIME type of the content\n */\n mimeType?: string\n /**\n * Custom metadata to store with the object (S3-specific)\n * Stored as S3 object metadata headers\n */\n metadata?: Record<string, string>\n /**\n * Object tagging for lifecycle policies (S3-specific)\n * Format: key=value (e.g., \"Tus-Completed=true\")\n */\n tagging?: string\n}\n\nexport const uploadResultSchema = z.object({\n path: z.string(),\n disk: z.string(),\n fullPath: z.string(),\n size: z.number(),\n mimeType: z.string(),\n uploadedAt: z.date(),\n})\n\nexport type UploadResult = z.infer<typeof uploadResultSchema>\n","import { HttpException } from '../../errors'\n\nexport class FileTooLargeError extends HttpException {\n constructor(public readonly size?: number, public readonly maxSize?: number) {\n super(413, 'File too large')\n }\n}\n","import { HttpException } from '../../errors'\n\nexport class InvalidFileTypeError extends HttpException {\n constructor(public readonly mimeType?: string) {\n super(422, 'Invalid file type')\n }\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { a as ApplicationError } from "./container-storage-BmOJ4_Na.mjs";
2
- import "./errors-EUtwVfNm.mjs";
2
+ import "./errors-C01O2T-n.mjs";
3
3
  //#region src/storage/storage.error.ts
4
4
  var StorageError = class extends ApplicationError {};
5
5
  //#endregion
6
6
  export { StorageError as t };
7
7
 
8
- //# sourceMappingURL=storage.error-Dai3rShJ.mjs.map
8
+ //# sourceMappingURL=storage.error-BStXPmO4.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.error-Dai3rShJ.mjs","names":[],"sources":["../src/storage/storage.error.ts"],"sourcesContent":["import { ApplicationError } from '../errors'\n\nexport class StorageError extends ApplicationError {}\n"],"mappings":";;;AAEA,IAAa,eAAb,cAAkC,iBAAiB,CAAC"}
1
+ {"version":3,"file":"storage.error-BStXPmO4.mjs","names":[],"sources":["../src/storage/storage.error.ts"],"sourcesContent":["import { ApplicationError } from '../errors'\n\nexport class StorageError extends ApplicationError {}\n"],"mappings":";;;AAEA,IAAa,eAAb,cAAkC,iBAAiB,CAAC"}
@@ -1,11 +1,11 @@
1
- import { r as DI_TOKENS, t as Container, v as ROUTER_TOKENS } from "./di-B0NXIdRF.mjs";
1
+ import { b as ROUTER_TOKENS, r as DI_TOKENS, t as Container } from "./di-D7qmrAir.mjs";
2
2
  import { n as getContainer, r as runWithContainer, t as containerStorage } from "./container-storage-BmOJ4_Na.mjs";
3
3
  import { JsonFormatter, LOGGER_TOKENS, LoggerService, PrettyFormatter } from "./logger/index.mjs";
4
- import { a as DefaultExceptionHandler, r as StratalNotInitializedError } from "./errors-EUtwVfNm.mjs";
5
- import { i as createQueueExceptionContext, n as createCronExceptionContext, t as createCliExceptionContext } from "./exception-context-Bd5Hk2c_.mjs";
6
- import { a as getListenerHandlers } from "./events-zbCMY_Gf.mjs";
7
- import { d as LazyModuleLoader, t as ModuleRegistry } from "./module-registry-MfWmniZ7.mjs";
8
- import { t as SEEDER_TOKENS } from "./seeder-registry-B5FwBeCU.mjs";
4
+ import { i as StratalNotInitializedError, o as DefaultExceptionHandler, r as StratalSupersededError } from "./errors-C01O2T-n.mjs";
5
+ import { i as createQueueExceptionContext, n as createCronExceptionContext, t as createCliExceptionContext } from "./exception-context-D-kvney-.mjs";
6
+ import { a as getListenerHandlers } from "./events-BhEQuT1X.mjs";
7
+ import { d as LazyModuleLoader, t as ModuleRegistry } from "./module-registry-NxX5O0Qk.mjs";
8
+ import { t as SEEDER_TOKENS } from "./seeder-registry-DEvCycsT.mjs";
9
9
  //#region src/application.ts
10
10
  var Application = class {
11
11
  _container;
@@ -48,10 +48,17 @@ var Application = class {
48
48
  }
49
49
  async initialize() {
50
50
  if (this.initialized) return;
51
- await runWithContainer(this._container, () => this.initializeInternal());
51
+ try {
52
+ await runWithContainer(this._container, () => this.initializeInternal());
53
+ } catch (error) {
54
+ await this.teardown().catch((teardownError) => {
55
+ console.error("[stratal] Teardown after failed initialization also failed:", teardownError);
56
+ });
57
+ throw error;
58
+ }
52
59
  }
53
60
  async initializeInternal() {
54
- const [{ QuarryModule }, { SeederModule }] = await Promise.all([import("./quarry.module-BOgcNJMU.mjs").then((n) => n.n), import("./seeder.module-DaY0RYoB.mjs").then((n) => n.n)]);
61
+ const [{ QuarryModule }, { SeederModule }] = await Promise.all([import("./quarry.module-CcGxU2dJ.mjs").then((n) => n.n), import("./seeder.module-CIwQbdN4.mjs").then((n) => n.n)]);
55
62
  this.moduleRegistry.registerAll([QuarryModule, SeederModule]);
56
63
  this.moduleRegistry.register(this.appConfig.module);
57
64
  await this.moduleRegistry.initialize();
@@ -64,14 +71,14 @@ var Application = class {
64
71
  }
65
72
  async registerRoutingServices() {
66
73
  const [{ HonoApp }, { RouteRegistry }, { RouterResolver }, { VersioningService }, { LocalePathService }, { LocaleUrlService }, { RouteRegistrationService }, { Uri }] = await Promise.all([
67
- import("./hono-app-C7-1m9eK.mjs").then((n) => n.n),
68
- import("./route-registry-CtW26Suv.mjs").then((n) => n.n),
69
- import("./router-resolver-CMNuw-s0.mjs"),
70
- import("./versioning.service-B4rtmgMQ.mjs").then((n) => n.n),
71
- import("./locale-path.service-DzA01Kvc.mjs").then((n) => n.n),
72
- import("./locale-url.service-DwTXZid3.mjs").then((n) => n.n),
73
- import("./route-registration.service-HN8WgIis.mjs").then((n) => n.n),
74
- import("./uri-h6ITRpqr.mjs").then((n) => n.r)
74
+ import("./hono-app-COAgmutc.mjs").then((n) => n.n),
75
+ import("./route-registry-BvLJisvK.mjs").then((n) => n.n),
76
+ import("./router-resolver-sUV_jTrU.mjs"),
77
+ import("./versioning.service-CCa2oYMJ.mjs").then((n) => n.n),
78
+ import("./locale-path.service-CH0CaxwH.mjs").then((n) => n.n),
79
+ import("./locale-url.service-6bgia24_.mjs").then((n) => n.n),
80
+ import("./route-registration.service-CDPQKpm4.mjs").then((n) => n.n),
81
+ import("./uri-iwofWJ_T.mjs").then((n) => n.r)
75
82
  ]);
76
83
  this._container.register(ROUTER_TOKENS.VersioningService, VersioningService);
77
84
  this._container.register(ROUTER_TOKENS.HonoApp, HonoApp);
@@ -92,7 +99,7 @@ var Application = class {
92
99
  */
93
100
  initializeEventListeners() {
94
101
  this.eventsInitPromise ??= runWithContainer(this._container, async () => {
95
- const { EventsModule } = await import("./events-zbCMY_Gf.mjs").then((n) => n.n);
102
+ const { EventsModule } = await import("./events-BhEQuT1X.mjs").then((n) => n.n);
96
103
  await this.moduleRegistry.registerLazy(EventsModule);
97
104
  this.registerEventListeners();
98
105
  });
@@ -122,7 +129,7 @@ var Application = class {
122
129
  initializeQueue() {
123
130
  this.queueInitPromise ??= runWithContainer(this._container, async () => {
124
131
  await this.ensureI18n();
125
- const { QueueModule } = await import("./queue.module-DSAH88gZ.mjs").then((n) => n.n);
132
+ const { QueueModule } = await import("./queue.module-CEs4_kEM.mjs").then((n) => n.n);
126
133
  this.moduleRegistry.register(QueueModule);
127
134
  this.consumerRegistry = this._container.resolve(DI_TOKENS.ConsumerRegistry);
128
135
  await this.registerQueueConsumers();
@@ -137,7 +144,7 @@ var Application = class {
137
144
  */
138
145
  ensureI18n() {
139
146
  this.i18nInitPromise ??= runWithContainer(this._container, async () => {
140
- const { I18nModule } = await import("./i18n.module-JOPUNpvi.mjs").then((n) => n.n);
147
+ const { I18nModule } = await import("./i18n.module-B2DvWUPa.mjs").then((n) => n.n);
141
148
  await this.moduleRegistry.registerLazy(I18nModule);
142
149
  });
143
150
  return this.i18nInitPromise;
@@ -148,7 +155,7 @@ var Application = class {
148
155
  */
149
156
  ensureCron() {
150
157
  this.cronInitPromise ??= runWithContainer(this._container, async () => {
151
- const { CronModule } = await import("./cron.module-DgxLe8Xy.mjs").then((n) => n.n);
158
+ const { CronModule } = await import("./cron.module-C81HTzR7.mjs").then((n) => n.n);
152
159
  this.moduleRegistry.register(CronModule);
153
160
  this.cronManager = this._container.resolve(DI_TOKENS.Cron);
154
161
  this.registerCronJobs(this.cronManager);
@@ -203,9 +210,13 @@ var Application = class {
203
210
  async shutdown() {
204
211
  if (!this.initialized) return;
205
212
  this.initialized = false;
213
+ await this.teardown();
214
+ }
215
+ /** Releases everything boot acquired: module resources, then the container. */
216
+ async teardown() {
206
217
  await this.moduleRegistry.shutdown();
207
218
  this._container.resolve(LOGGER_TOKENS.LoggerService).info("Disposing container...");
208
- this._container.dispose();
219
+ await this._container.dispose();
209
220
  }
210
221
  async handleCommand(name, input) {
211
222
  await this.initializeRouting();
@@ -316,16 +327,31 @@ var Stratal = class Stratal {
316
327
  initPromise;
317
328
  static _application = null;
318
329
  static _generation = 0;
319
- static _previousInstance = null;
330
+ static _current = null;
331
+ /**
332
+ * Serializes generation teardown: each new instance appends the previous
333
+ * instance's shutdown here, and `prepareApp` awaits the chain before
334
+ * allocating the new Application. Old and new DI graphs never coexist,
335
+ * so repeated Vite HMR reloads can't accumulate live object graphs.
336
+ */
337
+ static _teardownChain = Promise.resolve();
320
338
  constructor(config) {
321
339
  this.fetch = this.fetch.bind(this);
322
340
  this.queue = this.queue.bind(this);
323
341
  this.scheduled = this.scheduled.bind(this);
324
342
  const generation = ++Stratal._generation;
325
- if (Stratal._previousInstance) Stratal._previousInstance.shutdown();
326
- Stratal._previousInstance = this;
327
- this.initPromise = this.prepareApp(config, generation);
343
+ const previous = Stratal._current;
344
+ Stratal._current = this;
345
+ if (previous) Stratal._teardownChain = Stratal._teardownChain.then(() => previous.shutdown()).catch((error) => {
346
+ console.error("[stratal] Failed to shut down superseded instance:", error);
347
+ });
348
+ const teardownBarrier = Stratal._teardownChain;
349
+ this.initPromise = this.prepareApp(config, generation, teardownBarrier);
328
350
  Stratal._application = this.initPromise;
351
+ this.initPromise.catch((error) => {
352
+ if (error instanceof StratalSupersededError) return;
353
+ console.error("[stratal] Initialization failed:", error);
354
+ });
329
355
  }
330
356
  async fetch(request, env, ctx) {
331
357
  return (await (await this.ensureReady()).ensureHono()).fetch(request, env, ctx);
@@ -337,7 +363,7 @@ var Stratal = class Stratal {
337
363
  return (await this.ensureReady()).handleScheduled(controller);
338
364
  }
339
365
  get hono() {
340
- return this.initPromise.then((app) => app.ensureHono());
366
+ return this.ensureReady().then((app) => app.ensureHono());
341
367
  }
342
368
  async shutdown() {
343
369
  try {
@@ -347,24 +373,47 @@ var Stratal = class Stratal {
347
373
  await this.app.shutdown();
348
374
  this.app = null;
349
375
  }
376
+ if (Stratal._current === this) {
377
+ Stratal._current = null;
378
+ Stratal._application = null;
379
+ }
350
380
  }
351
381
  /**
352
382
  * @internal
353
383
  * Resolves the Application instance from the static singleton.
354
384
  * Used by worker base classes (DurableObject, Workflow, WorkerEntrypoint)
355
385
  * to access the DI container without going through Cloudflare RPC.
386
+ * Generations superseded mid-boot reject with {@link StratalSupersededError};
387
+ * this retries against the replacing generation so callers always land on
388
+ * the live Application.
356
389
  */
357
- static resolveApplication() {
358
- if (!Stratal._application) throw new StratalNotInitializedError();
359
- return Stratal._application;
390
+ static async resolveApplication() {
391
+ let current = Stratal._application;
392
+ while (current) try {
393
+ return await current;
394
+ } catch (error) {
395
+ if (error instanceof StratalSupersededError && Stratal._application !== current) {
396
+ current = Stratal._application;
397
+ continue;
398
+ }
399
+ throw error;
400
+ }
401
+ throw new StratalNotInitializedError();
360
402
  }
361
403
  async ensureReady() {
362
- this.app ??= await this.initPromise;
363
- return this.app;
404
+ if (this.app) return this.app;
405
+ try {
406
+ this.app = await this.initPromise;
407
+ return this.app;
408
+ } catch (error) {
409
+ if (error instanceof StratalSupersededError) return Stratal.resolveApplication();
410
+ throw error;
411
+ }
364
412
  }
365
- async prepareApp(config, generation) {
413
+ async prepareApp(config, generation, teardownBarrier) {
366
414
  const { env, waitUntil } = await import("cloudflare:workers");
367
- if (generation !== Stratal._generation) return new Promise(() => {});
415
+ await teardownBarrier;
416
+ if (generation !== Stratal._generation) throw new StratalSupersededError(generation);
368
417
  const app = new Application({
369
418
  ...config,
370
419
  env,
@@ -373,7 +422,7 @@ var Stratal = class Stratal {
373
422
  await app.initialize();
374
423
  if (generation !== Stratal._generation) {
375
424
  await app.shutdown();
376
- return new Promise(() => {});
425
+ throw new StratalSupersededError(generation);
377
426
  }
378
427
  return app;
379
428
  }
@@ -381,4 +430,4 @@ var Stratal = class Stratal {
381
430
  //#endregion
382
431
  export { Application as n, Stratal as t };
383
432
 
384
- //# sourceMappingURL=stratal-DtAj21uF.mjs.map
433
+ //# sourceMappingURL=stratal-BL6FKUM_.mjs.map