stratal 0.0.19 → 0.0.21

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 (163) hide show
  1. package/dist/{base-email.provider-mjynzewK.mjs → base-email.provider-CfQCA08m.mjs} +1 -1
  2. package/dist/{base-email.provider-mjynzewK.mjs.map → base-email.provider-CfQCA08m.mjs.map} +1 -1
  3. package/dist/bin/cloudflare-workers-loader.mjs.map +1 -1
  4. package/dist/bin/quarry.mjs +6 -0
  5. package/dist/bin/quarry.mjs.map +1 -1
  6. package/dist/cache/index.d.mts +2 -154
  7. package/dist/cache/index.d.mts.map +1 -1
  8. package/dist/cache/index.mjs +6 -6
  9. package/dist/cache/index.mjs.map +1 -1
  10. package/dist/cache.service-DsnKuNyO.d.mts +156 -0
  11. package/dist/cache.service-DsnKuNyO.d.mts.map +1 -0
  12. package/dist/cache.tokens-B7Rw1C9Q.mjs +6 -0
  13. package/dist/cache.tokens-B7Rw1C9Q.mjs.map +1 -0
  14. package/dist/command-BgSlsS4M.mjs.map +1 -1
  15. package/dist/{command-DsQq56Lp.d.mts → command-Cmmf0oHX.d.mts} +2 -2
  16. package/dist/{command-DsQq56Lp.d.mts.map → command-Cmmf0oHX.d.mts.map} +1 -1
  17. package/dist/config/index.d.mts +3 -3
  18. package/dist/config/index.mjs +5 -3
  19. package/dist/config/index.mjs.map +1 -1
  20. package/dist/{consumer-registry-Doom7BEh.d.mts → consumer-registry-B7yUNh0q.d.mts} +1 -1
  21. package/dist/{consumer-registry-Doom7BEh.d.mts.map → consumer-registry-B7yUNh0q.d.mts.map} +1 -1
  22. package/dist/{controller.decorator-LZY9aHYG.mjs → controller.decorator-B9vwn0zK.mjs} +3 -3
  23. package/dist/{controller.decorator-LZY9aHYG.mjs.map → controller.decorator-B9vwn0zK.mjs.map} +1 -1
  24. package/dist/cron/index.d.mts +2 -2
  25. package/dist/cron/index.mjs +1 -1
  26. package/dist/{cron-manager-RuPtFVLy.d.mts → cron-manager-CmTimEjf.d.mts} +3 -3
  27. package/dist/cron-manager-CmTimEjf.d.mts.map +1 -0
  28. package/dist/{cron-manager-C30t9UZM.mjs → cron-manager-DQSK8uoV.mjs} +10 -4
  29. package/dist/cron-manager-DQSK8uoV.mjs.map +1 -0
  30. package/dist/di/index.d.mts +1 -1
  31. package/dist/di/index.mjs +2 -2
  32. package/dist/email/index.d.mts +3 -3
  33. package/dist/email/index.mjs +13 -8
  34. package/dist/email/index.mjs.map +1 -1
  35. package/dist/{en-rHmW6vD9.mjs → en-DSH_bhh6.mjs} +7 -1
  36. package/dist/en-DSH_bhh6.mjs.map +1 -0
  37. package/dist/{env-CamWD-U1.d.mts → env-D1rcZ8_r.d.mts} +1 -1
  38. package/dist/env-D1rcZ8_r.d.mts.map +1 -0
  39. package/dist/errors/index.d.mts +1 -1
  40. package/dist/errors/index.mjs +1 -1
  41. package/dist/{errors-B4pYgYON.mjs → errors-COW9-Mar.mjs} +35 -10
  42. package/dist/errors-COW9-Mar.mjs.map +1 -0
  43. package/dist/{errors-BUyUfr2Z.mjs → errors-ORxu1-Bb.mjs} +2 -2
  44. package/dist/{errors-BUyUfr2Z.mjs.map → errors-ORxu1-Bb.mjs.map} +1 -1
  45. package/dist/events/index.d.mts +2 -2
  46. package/dist/events/index.mjs +1 -1
  47. package/dist/{events-COKixqnG.mjs → events-CzCV8jI8.mjs} +4 -2
  48. package/dist/{events-COKixqnG.mjs.map → events-CzCV8jI8.mjs.map} +1 -1
  49. package/dist/{gateway-context-cqZ8wMoi.mjs → gateway-context-CXmXtaUP.mjs} +5 -8
  50. package/dist/{gateway-context-cqZ8wMoi.mjs.map → gateway-context-CXmXtaUP.mjs.map} +1 -1
  51. package/dist/guards/index.d.mts +3 -3
  52. package/dist/guards/index.mjs +1 -1
  53. package/dist/{guards-DMbsAxSX.mjs → guards-DU1_J9YA.mjs} +2 -1
  54. package/dist/{guards-DMbsAxSX.mjs.map → guards-DU1_J9YA.mjs.map} +1 -1
  55. package/dist/{http-method.decorator-BT3ufnz8.mjs → http-method.decorator-BrgHMdLQ.mjs} +3 -3
  56. package/dist/{http-method.decorator-BT3ufnz8.mjs.map → http-method.decorator-BrgHMdLQ.mjs.map} +1 -1
  57. package/dist/i18n/index.d.mts +2 -2
  58. package/dist/i18n/index.mjs +2 -2
  59. package/dist/i18n/messages/en/index.d.mts +1 -1
  60. package/dist/i18n/messages/en/index.mjs +1 -1
  61. package/dist/i18n/utils/index.mjs +1 -1
  62. package/dist/i18n/validation/index.d.mts +2 -2
  63. package/dist/i18n/validation/index.mjs +2 -2
  64. package/dist/{i18n.module-CI_prYFD.mjs → i18n.module-CzXLW9Hy.mjs} +242 -50
  65. package/dist/i18n.module-CzXLW9Hy.mjs.map +1 -0
  66. package/dist/{index-SHx31sBJ.d.mts → index-7-hU3GTV.d.mts} +1 -1
  67. package/dist/{index-SHx31sBJ.d.mts.map → index-7-hU3GTV.d.mts.map} +1 -1
  68. package/dist/{index-B437eK7p.d.mts → index-Bnpfq6uk.d.mts} +58 -10
  69. package/dist/index-Bnpfq6uk.d.mts.map +1 -0
  70. package/dist/{index-DPFqRs8L.d.mts → index-ByOyTmqf.d.mts} +317 -205
  71. package/dist/index-ByOyTmqf.d.mts.map +1 -0
  72. package/dist/{index-DFhEeFfC.d.mts → index-C1KvMncZ.d.mts} +7 -1
  73. package/dist/{index-DFhEeFfC.d.mts.map → index-C1KvMncZ.d.mts.map} +1 -1
  74. package/dist/{index-CWRS7Ri3.d.mts → index-DBd_2wv8.d.mts} +1 -1
  75. package/dist/{index-CWRS7Ri3.d.mts.map → index-DBd_2wv8.d.mts.map} +1 -1
  76. package/dist/{index-Dnqm9ZB6.d.mts → index-DUzWs0z7.d.mts} +5 -5
  77. package/dist/{index-Dnqm9ZB6.d.mts.map → index-DUzWs0z7.d.mts.map} +1 -1
  78. package/dist/index.d.mts +3 -3
  79. package/dist/index.mjs +1 -1
  80. package/dist/is-command-C6a7WTPw.mjs.map +1 -1
  81. package/dist/is-seeder-CebjZCDn.mjs.map +1 -1
  82. package/dist/logger/index.d.mts +1 -1
  83. package/dist/logger/index.mjs +1 -1
  84. package/dist/{logger-V6Ms3QnQ.mjs → logger-DlV7NtvD.mjs} +8 -4
  85. package/dist/{logger-V6Ms3QnQ.mjs.map → logger-DlV7NtvD.mjs.map} +1 -1
  86. package/dist/macroable/index.d.mts +1 -1
  87. package/dist/macroable-BmufBshB.mjs.map +1 -1
  88. package/dist/module/index.d.mts +2 -2
  89. package/dist/module/index.mjs +1 -1
  90. package/dist/{module-qGE_1duv.mjs → module-BzLg57FK.mjs} +139 -5
  91. package/dist/module-BzLg57FK.mjs.map +1 -0
  92. package/dist/openapi/index.d.mts +3 -3
  93. package/dist/openapi/index.mjs +2 -2
  94. package/dist/{openapi-tools.service-CYWGuhue.mjs → openapi-tools.service-Zs-Ewv7F.mjs} +1 -1
  95. package/dist/{openapi-tools.service-CYWGuhue.mjs.map → openapi-tools.service-Zs-Ewv7F.mjs.map} +1 -1
  96. package/dist/{openapi.service-Bv_NioM9.d.mts → openapi.service-Bt9bCIrd.d.mts} +3 -3
  97. package/dist/{openapi.service-Bv_NioM9.d.mts.map → openapi.service-Bt9bCIrd.d.mts.map} +1 -1
  98. package/dist/quarry/index.d.mts +6 -6
  99. package/dist/quarry/index.mjs +2 -2
  100. package/dist/{quarry-registry-DFfRRkA7.mjs → quarry-registry-BwY2hOxm.mjs} +18 -7
  101. package/dist/{quarry-registry-DFfRRkA7.mjs.map → quarry-registry-BwY2hOxm.mjs.map} +1 -1
  102. package/dist/queue/index.d.mts +2 -2
  103. package/dist/queue/index.mjs +3 -2
  104. package/dist/queue/index.mjs.map +1 -1
  105. package/dist/{queue.module-P-G-nCYz.mjs → queue.module-BhCjZp6H.mjs} +13 -4
  106. package/dist/{queue.module-P-G-nCYz.mjs.map → queue.module-BhCjZp6H.mjs.map} +1 -1
  107. package/dist/{r2-storage.provider-LdzK9tfG.mjs → r2-storage.provider-DuonKeYm.mjs} +6 -3
  108. package/dist/{r2-storage.provider-LdzK9tfG.mjs.map → r2-storage.provider-DuonKeYm.mjs.map} +1 -1
  109. package/dist/rate-limit.decorator-6qzNcSOt.mjs +55 -0
  110. package/dist/rate-limit.decorator-6qzNcSOt.mjs.map +1 -0
  111. package/dist/rate-limiter/index.d.mts +420 -0
  112. package/dist/rate-limiter/index.d.mts.map +1 -0
  113. package/dist/rate-limiter/index.mjs +374 -0
  114. package/dist/rate-limiter/index.mjs.map +1 -0
  115. package/dist/{resend.provider-bwILp0WI.mjs → resend.provider-DB4IlFjG.mjs} +3 -2
  116. package/dist/{resend.provider-bwILp0WI.mjs.map → resend.provider-DB4IlFjG.mjs.map} +1 -1
  117. package/dist/router/index.d.mts +2 -2
  118. package/dist/router/index.mjs +6 -6
  119. package/dist/seeder/index.d.mts +3 -3
  120. package/dist/seeder/index.mjs +1 -1
  121. package/dist/{seeder-BcqIFa2X.mjs → seeder-zoEfEw9i.mjs} +6 -3
  122. package/dist/{seeder-BcqIFa2X.mjs.map → seeder-zoEfEw9i.mjs.map} +1 -1
  123. package/dist/{setup-CtekcwuO.mjs → setup-CefZKV_e.mjs} +1 -1
  124. package/dist/{setup-CtekcwuO.mjs.map → setup-CefZKV_e.mjs.map} +1 -1
  125. package/dist/{signed-url-COX7cCWR.mjs → signed-url-BQPbv2In.mjs} +1 -1
  126. package/dist/{signed-url-COX7cCWR.mjs.map → signed-url-BQPbv2In.mjs.map} +1 -1
  127. package/dist/{smtp.provider-B07yuARi.mjs → smtp.provider-B6D7zuWX.mjs} +3 -2
  128. package/dist/{smtp.provider-B07yuARi.mjs.map → smtp.provider-B6D7zuWX.mjs.map} +1 -1
  129. package/dist/storage/index.d.mts +3 -3
  130. package/dist/storage/index.mjs +2 -2
  131. package/dist/storage/providers/index.d.mts +2 -2
  132. package/dist/storage/providers/index.mjs +1 -1
  133. package/dist/{storage-P6X4h9So.mjs → storage-D8CBP72Z.mjs} +14 -9
  134. package/dist/{storage-P6X4h9So.mjs.map → storage-D8CBP72Z.mjs.map} +1 -1
  135. package/dist/{storage-provider.interface-CC1nniHk.d.mts → storage-provider.interface-Bd6vA4ak.d.mts} +2 -2
  136. package/dist/{storage-provider.interface-CC1nniHk.d.mts.map → storage-provider.interface-Bd6vA4ak.d.mts.map} +1 -1
  137. package/dist/{stratal-BCiwCFN9.mjs → stratal-CNwpbSZl.mjs} +12 -10
  138. package/dist/stratal-CNwpbSZl.mjs.map +1 -0
  139. package/dist/{types-DIWemRad.d.mts → types-cySNS_lp.d.mts} +1 -1
  140. package/dist/types-cySNS_lp.d.mts.map +1 -0
  141. package/dist/{usage-generator-MBcRo0Q2.mjs → usage-generator-BUdlhnCK.mjs} +1 -1
  142. package/dist/{usage-generator-MBcRo0Q2.mjs.map → usage-generator-BUdlhnCK.mjs.map} +1 -1
  143. package/dist/{validation-Dbg3ehdP.mjs → validation-DtJwAv7O.mjs} +62 -8
  144. package/dist/validation-DtJwAv7O.mjs.map +1 -0
  145. package/dist/websocket/index.d.mts +8 -3
  146. package/dist/websocket/index.d.mts.map +1 -1
  147. package/dist/websocket/index.mjs +1 -1
  148. package/dist/workers/index.d.mts +2 -2
  149. package/dist/workers/index.mjs +2 -2
  150. package/dist/workers/index.mjs.map +1 -1
  151. package/package.json +18 -14
  152. package/dist/cron-manager-C30t9UZM.mjs.map +0 -1
  153. package/dist/cron-manager-RuPtFVLy.d.mts.map +0 -1
  154. package/dist/en-rHmW6vD9.mjs.map +0 -1
  155. package/dist/env-CamWD-U1.d.mts.map +0 -1
  156. package/dist/errors-B4pYgYON.mjs.map +0 -1
  157. package/dist/i18n.module-CI_prYFD.mjs.map +0 -1
  158. package/dist/index-B437eK7p.d.mts.map +0 -1
  159. package/dist/index-DPFqRs8L.d.mts.map +0 -1
  160. package/dist/module-qGE_1duv.mjs.map +0 -1
  161. package/dist/stratal-BCiwCFN9.mjs.map +0 -1
  162. package/dist/types-DIWemRad.d.mts.map +0 -1
  163. package/dist/validation-Dbg3ehdP.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"setup-CtekcwuO.mjs","names":[],"sources":["../src/i18n/utils/setup.ts"],"sourcesContent":["/**\n * I18n Setup - Message Compiler Registration\n *\n * Registers a JIT (Just-In-Time) message compiler for @intlify/core-base\n * that works in Cloudflare Workers edge runtime.\n *\n * This must be called ONCE at application startup, before any I18nService instances are created.\n */\n\nimport { compile, registerMessageCompiler } from '@intlify/core-base'\n\nlet isRegistered = false\n\n/**\n * Setup JIT message compiler for i18n\n *\n * Registers a message compiler that uses JIT compilation mode.\n * In @intlify/core-base v10+, JIT mode is enabled by default, which generates\n * AST (Abstract Syntax Tree) instead of JavaScript code, making it compatible\n * with CSP-restricted environments like Cloudflare Workers.\n *\n * **IMPORTANT:** Call this function once at application startup before creating\n * any I18nService instances. Safe to call multiple times (only registers once).\n *\n * @example\n * ```typescript\n * // In application entry point (before app.initialize())\n * setupI18nCompiler()\n * ```\n */\nexport function setupI18nCompiler(): void {\n // Guard against multiple registrations\n if (isRegistered) {\n return\n }\n\n // Register the compile function from @intlify/core-base as the message compiler\n // In v11+, compile() uses JIT mode by default, generating AST instead of JavaScript code\n // This avoids CSP violations (no eval/new Function) in Cloudflare Workers\n registerMessageCompiler(compile)\n\n isRegistered = true\n}\n"],"mappings":";;;;;;;;;;AAWA,IAAI,eAAe;;;;;;;;;;;;;;;;;;AAmBnB,SAAgB,oBAA0B;AAExC,KAAI,aACF;AAMF,yBAAwB,QAAQ;AAEhC,gBAAe"}
1
+ {"version":3,"file":"setup-CefZKV_e.mjs","names":[],"sources":["../src/i18n/utils/setup.ts"],"sourcesContent":["/**\n * I18n Setup - Message Compiler Registration\n *\n * Registers a JIT (Just-In-Time) message compiler for @intlify/core-base\n * that works in Cloudflare Workers edge runtime.\n *\n * This must be called ONCE at application startup, before any I18nService instances are created.\n */\n\nimport { compile, registerMessageCompiler } from '@intlify/core-base'\n\nlet isRegistered = false\n\n/**\n * Setup JIT message compiler for i18n\n *\n * Registers a message compiler that uses JIT compilation mode.\n * In @intlify/core-base v10+, JIT mode is enabled by default, which generates\n * AST (Abstract Syntax Tree) instead of JavaScript code, making it compatible\n * with CSP-restricted environments like Cloudflare Workers.\n *\n * **IMPORTANT:** Call this function once at application startup before creating\n * any I18nService instances. Safe to call multiple times (only registers once).\n *\n * @example\n * ```typescript\n * // In application entry point (before app.initialize())\n * setupI18nCompiler()\n * ```\n */\nexport function setupI18nCompiler(): void {\n // Guard against multiple registrations\n if (isRegistered) {\n return\n }\n\n // Register the compile function from @intlify/core-base as the message compiler\n // In v11+, compile() uses JIT mode by default, generating AST instead of JavaScript code\n // This avoids CSP violations (no eval/new Function) in Cloudflare Workers\n registerMessageCompiler(compile)\n\n isRegistered = true\n}\n"],"mappings":";;;;;;;;;;AAWA,IAAI,eAAe;;;;;;;;;;;;;;;;;;AAmBnB,SAAgB,oBAA0B;CAExC,IAAI,cACF;CAMF,wBAAwB,QAAQ;CAEhC,eAAe"}
@@ -71,4 +71,4 @@ async function verifySignedUrl(url, secret) {
71
71
  //#endregion
72
72
  export { verifySignedUrl as n, signUrl as t };
73
73
 
74
- //# sourceMappingURL=signed-url-COX7cCWR.mjs.map
74
+ //# sourceMappingURL=signed-url-BQPbv2In.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"signed-url-COX7cCWR.mjs","names":[],"sources":["../src/router/signed-url.ts"],"sourcesContent":["/**\n * Signed URL utilities using HMAC-SHA256 via Web Crypto API.\n *\n * Follows the Cloudflare Workers signing pattern:\n * https://developers.cloudflare.com/workers/examples/signing-requests/\n *\n * Uses `crypto.subtle.verify()` for timing-attack-safe comparison.\n */\n\n/**\n * Options for signing a URL.\n */\nexport interface SignedUrlOptions {\n /** Time-to-live in seconds. URL expires after this duration. */\n expiresIn?: number\n}\n\n/**\n * Import a signing key for HMAC-SHA256.\n */\nasync function importKey(secret: string): Promise<CryptoKey> {\n const encoder = new TextEncoder()\n return crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign', 'verify']\n )\n}\n\n/**\n * Encode an ArrayBuffer as base64url (URL-safe base64).\n */\nfunction toBase64Url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer)\n let binary = ''\n for (const byte of bytes) {\n binary += String.fromCharCode(byte)\n }\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Sign a URL with HMAC-SHA256.\n *\n * Appends `signature` and optionally `expires` query parameters.\n * The signature covers the pathname + search (excluding the signature params themselves).\n *\n * @param url - Full URL or path to sign\n * @param secret - HMAC secret key (e.g., from env.APP_SECRET)\n * @param options - Optional expiration\n * @returns URL string with `signature` (and `expires`) query params appended\n */\nexport async function signUrl(url: string, secret: string, options?: SignedUrlOptions): Promise<string> {\n const parsedUrl = new URL(url, 'https://placeholder.local')\n const key = await importKey(secret)\n\n // Add expiry if specified\n if (options?.expiresIn) {\n const expires = Math.floor(Date.now() / 1000) + options.expiresIn\n parsedUrl.searchParams.set('expires', String(expires))\n }\n\n // Sign: pathname + sorted search params (without signature)\n const dataToSign = `${parsedUrl.pathname}?${parsedUrl.searchParams.toString()}`\n const encoder = new TextEncoder()\n const signatureBuffer = await crypto.subtle.sign('HMAC', key, encoder.encode(dataToSign))\n const signature = toBase64Url(signatureBuffer)\n\n parsedUrl.searchParams.set('signature', signature)\n // Return just the path + query for relative URLs, full URL for absolute\n return url.startsWith('http') ? parsedUrl.toString() : `${parsedUrl.pathname}?${parsedUrl.searchParams.toString()}`\n}\n\n/**\n * Verify a signed URL using `crypto.subtle.verify()` (timing-attack-safe).\n *\n * @param url - Full URL or path with signature query param\n * @param secret - HMAC secret key (same key used for signing)\n * @returns true if signature is valid and not expired\n */\nexport async function verifySignedUrl(url: string, secret: string): Promise<boolean> {\n const parsedUrl = new URL(url, 'https://placeholder.local')\n const signature = parsedUrl.searchParams.get('signature')\n if (!signature) return false\n\n // Check expiry\n const expires = parsedUrl.searchParams.get('expires')\n if (expires) {\n const expiryTime = parseInt(expires, 10)\n if (isNaN(expiryTime) || Math.floor(Date.now() / 1000) > expiryTime) {\n return false\n }\n }\n\n // Reconstruct the data that was signed (without signature param)\n parsedUrl.searchParams.delete('signature')\n const dataToVerify = `${parsedUrl.pathname}?${parsedUrl.searchParams.toString()}`\n\n // Decode base64url signature\n const base64 = signature.replace(/-/g, '+').replace(/_/g, '/')\n const binaryStr = atob(base64)\n const signatureBytes = new Uint8Array(binaryStr.length)\n for (let i = 0; i < binaryStr.length; i++) {\n signatureBytes[i] = binaryStr.charCodeAt(i)\n }\n\n const key = await importKey(secret)\n const encoder = new TextEncoder()\n\n // Use crypto.subtle.verify() for timing-attack-safe comparison\n return crypto.subtle.verify('HMAC', key, signatureBytes, encoder.encode(dataToVerify))\n}\n"],"mappings":";;;;AAoBA,eAAe,UAAU,QAAoC;CAC3D,MAAM,UAAU,IAAI,aAAa;AACjC,QAAO,OAAO,OAAO,UACnB,OACA,QAAQ,OAAO,OAAO,EACtB;EAAE,MAAM;EAAQ,MAAM;EAAW,EACjC,OACA,CAAC,QAAQ,SAAS,CACnB;;;;;AAMH,SAAS,YAAY,QAA6B;CAChD,MAAM,QAAQ,IAAI,WAAW,OAAO;CACpC,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,MACjB,WAAU,OAAO,aAAa,KAAK;AAErC,QAAO,KAAK,OAAO,CAAC,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,GAAG;;;;;;;;;;;;;AAchF,eAAsB,QAAQ,KAAa,QAAgB,SAA6C;CACtG,MAAM,YAAY,IAAI,IAAI,KAAK,4BAA4B;CAC3D,MAAM,MAAM,MAAM,UAAU,OAAO;AAGnC,KAAI,SAAS,WAAW;EACtB,MAAM,UAAU,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,GAAG,QAAQ;AACxD,YAAU,aAAa,IAAI,WAAW,OAAO,QAAQ,CAAC;;CAIxD,MAAM,aAAa,GAAG,UAAU,SAAS,GAAG,UAAU,aAAa,UAAU;CAC7E,MAAM,UAAU,IAAI,aAAa;CAEjC,MAAM,YAAY,YAAY,MADA,OAAO,OAAO,KAAK,QAAQ,KAAK,QAAQ,OAAO,WAAW,CAAC,CAC3C;AAE9C,WAAU,aAAa,IAAI,aAAa,UAAU;AAElD,QAAO,IAAI,WAAW,OAAO,GAAG,UAAU,UAAU,GAAG,GAAG,UAAU,SAAS,GAAG,UAAU,aAAa,UAAU;;;;;;;;;AAUnH,eAAsB,gBAAgB,KAAa,QAAkC;CACnF,MAAM,YAAY,IAAI,IAAI,KAAK,4BAA4B;CAC3D,MAAM,YAAY,UAAU,aAAa,IAAI,YAAY;AACzD,KAAI,CAAC,UAAW,QAAO;CAGvB,MAAM,UAAU,UAAU,aAAa,IAAI,UAAU;AACrD,KAAI,SAAS;EACX,MAAM,aAAa,SAAS,SAAS,GAAG;AACxC,MAAI,MAAM,WAAW,IAAI,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,GAAG,WACvD,QAAO;;AAKX,WAAU,aAAa,OAAO,YAAY;CAC1C,MAAM,eAAe,GAAG,UAAU,SAAS,GAAG,UAAU,aAAa,UAAU;CAG/E,MAAM,SAAS,UAAU,QAAQ,MAAM,IAAI,CAAC,QAAQ,MAAM,IAAI;CAC9D,MAAM,YAAY,KAAK,OAAO;CAC9B,MAAM,iBAAiB,IAAI,WAAW,UAAU,OAAO;AACvD,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,IACpC,gBAAe,KAAK,UAAU,WAAW,EAAE;CAG7C,MAAM,MAAM,MAAM,UAAU,OAAO;CACnC,MAAM,UAAU,IAAI,aAAa;AAGjC,QAAO,OAAO,OAAO,OAAO,QAAQ,KAAK,gBAAgB,QAAQ,OAAO,aAAa,CAAC"}
1
+ {"version":3,"file":"signed-url-BQPbv2In.mjs","names":[],"sources":["../src/router/signed-url.ts"],"sourcesContent":["/**\n * Signed URL utilities using HMAC-SHA256 via Web Crypto API.\n *\n * Follows the Cloudflare Workers signing pattern:\n * https://developers.cloudflare.com/workers/examples/signing-requests/\n *\n * Uses `crypto.subtle.verify()` for timing-attack-safe comparison.\n */\n\n/**\n * Options for signing a URL.\n */\nexport interface SignedUrlOptions {\n /** Time-to-live in seconds. URL expires after this duration. */\n expiresIn?: number\n}\n\n/**\n * Import a signing key for HMAC-SHA256.\n */\nasync function importKey(secret: string): Promise<CryptoKey> {\n const encoder = new TextEncoder()\n return crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign', 'verify']\n )\n}\n\n/**\n * Encode an ArrayBuffer as base64url (URL-safe base64).\n */\nfunction toBase64Url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer)\n let binary = ''\n for (const byte of bytes) {\n binary += String.fromCharCode(byte)\n }\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Sign a URL with HMAC-SHA256.\n *\n * Appends `signature` and optionally `expires` query parameters.\n * The signature covers the pathname + search (excluding the signature params themselves).\n *\n * @param url - Full URL or path to sign\n * @param secret - HMAC secret key (e.g., from env.APP_SECRET)\n * @param options - Optional expiration\n * @returns URL string with `signature` (and `expires`) query params appended\n */\nexport async function signUrl(url: string, secret: string, options?: SignedUrlOptions): Promise<string> {\n const parsedUrl = new URL(url, 'https://placeholder.local')\n const key = await importKey(secret)\n\n // Add expiry if specified\n if (options?.expiresIn) {\n const expires = Math.floor(Date.now() / 1000) + options.expiresIn\n parsedUrl.searchParams.set('expires', String(expires))\n }\n\n // Sign: pathname + sorted search params (without signature)\n const dataToSign = `${parsedUrl.pathname}?${parsedUrl.searchParams.toString()}`\n const encoder = new TextEncoder()\n const signatureBuffer = await crypto.subtle.sign('HMAC', key, encoder.encode(dataToSign))\n const signature = toBase64Url(signatureBuffer)\n\n parsedUrl.searchParams.set('signature', signature)\n // Return just the path + query for relative URLs, full URL for absolute\n return url.startsWith('http') ? parsedUrl.toString() : `${parsedUrl.pathname}?${parsedUrl.searchParams.toString()}`\n}\n\n/**\n * Verify a signed URL using `crypto.subtle.verify()` (timing-attack-safe).\n *\n * @param url - Full URL or path with signature query param\n * @param secret - HMAC secret key (same key used for signing)\n * @returns true if signature is valid and not expired\n */\nexport async function verifySignedUrl(url: string, secret: string): Promise<boolean> {\n const parsedUrl = new URL(url, 'https://placeholder.local')\n const signature = parsedUrl.searchParams.get('signature')\n if (!signature) return false\n\n // Check expiry\n const expires = parsedUrl.searchParams.get('expires')\n if (expires) {\n const expiryTime = parseInt(expires, 10)\n if (isNaN(expiryTime) || Math.floor(Date.now() / 1000) > expiryTime) {\n return false\n }\n }\n\n // Reconstruct the data that was signed (without signature param)\n parsedUrl.searchParams.delete('signature')\n const dataToVerify = `${parsedUrl.pathname}?${parsedUrl.searchParams.toString()}`\n\n // Decode base64url signature\n const base64 = signature.replace(/-/g, '+').replace(/_/g, '/')\n const binaryStr = atob(base64)\n const signatureBytes = new Uint8Array(binaryStr.length)\n for (let i = 0; i < binaryStr.length; i++) {\n signatureBytes[i] = binaryStr.charCodeAt(i)\n }\n\n const key = await importKey(secret)\n const encoder = new TextEncoder()\n\n // Use crypto.subtle.verify() for timing-attack-safe comparison\n return crypto.subtle.verify('HMAC', key, signatureBytes, encoder.encode(dataToVerify))\n}\n"],"mappings":";;;;AAoBA,eAAe,UAAU,QAAoC;CAC3D,MAAM,UAAU,IAAI,aAAa;CACjC,OAAO,OAAO,OAAO,UACnB,OACA,QAAQ,OAAO,OAAO,EACtB;EAAE,MAAM;EAAQ,MAAM;EAAW,EACjC,OACA,CAAC,QAAQ,SAAS,CACnB;;;;;AAMH,SAAS,YAAY,QAA6B;CAChD,MAAM,QAAQ,IAAI,WAAW,OAAO;CACpC,IAAI,SAAS;CACb,KAAK,MAAM,QAAQ,OACjB,UAAU,OAAO,aAAa,KAAK;CAErC,OAAO,KAAK,OAAO,CAAC,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,GAAG;;;;;;;;;;;;;AAchF,eAAsB,QAAQ,KAAa,QAAgB,SAA6C;CACtG,MAAM,YAAY,IAAI,IAAI,KAAK,4BAA4B;CAC3D,MAAM,MAAM,MAAM,UAAU,OAAO;CAGnC,IAAI,SAAS,WAAW;EACtB,MAAM,UAAU,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,GAAG,QAAQ;EACxD,UAAU,aAAa,IAAI,WAAW,OAAO,QAAQ,CAAC;;CAIxD,MAAM,aAAa,GAAG,UAAU,SAAS,GAAG,UAAU,aAAa,UAAU;CAC7E,MAAM,UAAU,IAAI,aAAa;CAEjC,MAAM,YAAY,YAAY,MADA,OAAO,OAAO,KAAK,QAAQ,KAAK,QAAQ,OAAO,WAAW,CAAC,CAC3C;CAE9C,UAAU,aAAa,IAAI,aAAa,UAAU;CAElD,OAAO,IAAI,WAAW,OAAO,GAAG,UAAU,UAAU,GAAG,GAAG,UAAU,SAAS,GAAG,UAAU,aAAa,UAAU;;;;;;;;;AAUnH,eAAsB,gBAAgB,KAAa,QAAkC;CACnF,MAAM,YAAY,IAAI,IAAI,KAAK,4BAA4B;CAC3D,MAAM,YAAY,UAAU,aAAa,IAAI,YAAY;CACzD,IAAI,CAAC,WAAW,OAAO;CAGvB,MAAM,UAAU,UAAU,aAAa,IAAI,UAAU;CACrD,IAAI,SAAS;EACX,MAAM,aAAa,SAAS,SAAS,GAAG;EACxC,IAAI,MAAM,WAAW,IAAI,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,GAAG,YACvD,OAAO;;CAKX,UAAU,aAAa,OAAO,YAAY;CAC1C,MAAM,eAAe,GAAG,UAAU,SAAS,GAAG,UAAU,aAAa,UAAU;CAG/E,MAAM,SAAS,UAAU,QAAQ,MAAM,IAAI,CAAC,QAAQ,MAAM,IAAI;CAC9D,MAAM,YAAY,KAAK,OAAO;CAC9B,MAAM,iBAAiB,IAAI,WAAW,UAAU,OAAO;CACvD,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KACpC,eAAe,KAAK,UAAU,WAAW,EAAE;CAG7C,MAAM,MAAM,MAAM,UAAU,OAAO;CACnC,MAAM,UAAU,IAAI,aAAa;CAGjC,OAAO,OAAO,OAAO,OAAO,QAAQ,KAAK,gBAAgB,QAAQ,OAAO,aAAa,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { EmailSmtpConnectionFailedError, SmtpConfigurationMissingError, SmtpHostMissingError } from "./email/index.mjs";
2
- import { t as BaseEmailProvider } from "./base-email.provider-mjynzewK.mjs";
2
+ import { t as BaseEmailProvider } from "./base-email.provider-CfQCA08m.mjs";
3
3
  import * as nodemailer from "nodemailer";
4
4
  import { Readable } from "stream";
5
5
  //#region src/email/providers/smtp.provider.ts
@@ -10,6 +10,7 @@ import { Readable } from "stream";
10
10
  * Supports any SMTP server configured via SMTP_URL
11
11
  */
12
12
  var SmtpProvider = class extends BaseEmailProvider {
13
+ options;
13
14
  transporter;
14
15
  defaultFrom;
15
16
  constructor(options) {
@@ -72,4 +73,4 @@ var SmtpProvider = class extends BaseEmailProvider {
72
73
  //#endregion
73
74
  export { SmtpProvider };
74
75
 
75
- //# sourceMappingURL=smtp.provider-B07yuARi.mjs.map
76
+ //# sourceMappingURL=smtp.provider-B6D7zuWX.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"smtp.provider-B07yuARi.mjs","names":[],"sources":["../src/email/providers/smtp.provider.ts"],"sourcesContent":["import type { Transporter } from 'nodemailer'\nimport * as nodemailer from 'nodemailer'\nimport type SMTPTransport from 'nodemailer/lib/smtp-transport'\nimport { Readable } from 'stream'\nimport type { ResolvedEmailAttachment, ResolvedEmailMessage } from '../contracts'\nimport type { EmailModuleOptions } from '../email.module'\nimport { EmailSmtpConnectionFailedError, SmtpConfigurationMissingError, SmtpHostMissingError } from '../errors'\nimport { BaseEmailProvider } from './base-email.provider'\nimport type { EmailSendResult } from './email-provider.interface'\n\n/**\n * SMTP Email Provider\n *\n * Implementation of IEmailProvider using SMTP/nodemailer\n * Supports any SMTP server configured via SMTP_URL\n */\nexport class SmtpProvider extends BaseEmailProvider {\n private readonly transporter: Transporter<SMTPTransport.SentMessageInfo>\n private readonly defaultFrom: { name: string; email: string }\n\n constructor(\n private readonly options: EmailModuleOptions\n ) {\n super()\n\n // Validate SMTP configuration\n if (!this.options.smtp) {\n throw new SmtpConfigurationMissingError()\n }\n\n if (!this.options.smtp.host) {\n throw new SmtpHostMissingError()\n }\n\n // Create nodemailer transporter\n this.transporter = nodemailer.createTransport({\n host: this.options.smtp.host,\n port: this.options.smtp.port,\n secure: this.options.smtp.secure,\n auth: this.options.smtp.username && this.options.smtp.password\n ? {\n user: this.options.smtp.username,\n pass: this.options.smtp.password,\n }\n : undefined,\n })\n\n this.defaultFrom = this.options.from\n }\n\n async send(message: ResolvedEmailMessage): Promise<EmailSendResult> {\n try {\n const from = message.from\n ? `${message.from.name} <${message.from.email}>`\n : `${this.defaultFrom.name} <${this.defaultFrom.email}>`\n\n const info = await this.transporter.sendMail({\n from,\n to: Array.isArray(message.to) ? message.to.join(', ') : message.to,\n subject: message.subject,\n html: message.html,\n text: message.text,\n replyTo: message.replyTo,\n cc: message.cc?.join(', '),\n bcc: message.bcc?.join(', '),\n attachments: message.attachments?.map(attachment => ({\n filename: attachment.filename,\n content: this.toNodeStream(attachment.content),\n contentType: attachment.contentType,\n })),\n })\n\n return {\n messageId: info.messageId,\n accepted: true,\n metadata: {\n provider: 'smtp',\n response: info.response,\n },\n }\n } catch {\n throw new EmailSmtpConnectionFailedError(\n this.options.smtp?.host ?? '',\n this.options.smtp?.port ?? 587\n )\n }\n }\n\n /**\n * Convert attachment content to Node.js stream format\n *\n * Nodemailer expects Node.js Readable streams, not web ReadableStream.\n * Buffer is passed through as-is since nodemailer supports it directly.\n */\n private toNodeStream(content: ResolvedEmailAttachment['content']): Buffer | Readable {\n if (Buffer.isBuffer(content)) {\n return content\n }\n // Convert web ReadableStream to Node.js Readable\n return Readable.fromWeb(content as unknown as Parameters<typeof Readable.fromWeb>[0])\n }\n}\n"],"mappings":";;;;;;;;;;;AAgBA,IAAa,eAAb,cAAkC,kBAAkB;CAClD;CACA;CAEA,YACE,SACA;AACA,SAAO;AAFU,OAAA,UAAA;AAKjB,MAAI,CAAC,KAAK,QAAQ,KAChB,OAAM,IAAI,+BAA+B;AAG3C,MAAI,CAAC,KAAK,QAAQ,KAAK,KACrB,OAAM,IAAI,sBAAsB;AAIlC,OAAK,cAAc,WAAW,gBAAgB;GAC5C,MAAM,KAAK,QAAQ,KAAK;GACxB,MAAM,KAAK,QAAQ,KAAK;GACxB,QAAQ,KAAK,QAAQ,KAAK;GAC1B,MAAM,KAAK,QAAQ,KAAK,YAAY,KAAK,QAAQ,KAAK,WAClD;IACA,MAAM,KAAK,QAAQ,KAAK;IACxB,MAAM,KAAK,QAAQ,KAAK;IACzB,GACC,KAAA;GACL,CAAC;AAEF,OAAK,cAAc,KAAK,QAAQ;;CAGlC,MAAM,KAAK,SAAyD;AAClE,MAAI;GACF,MAAM,OAAO,QAAQ,OACjB,GAAG,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,MAAM,KAC5C,GAAG,KAAK,YAAY,KAAK,IAAI,KAAK,YAAY,MAAM;GAExD,MAAM,OAAO,MAAM,KAAK,YAAY,SAAS;IAC3C;IACA,IAAI,MAAM,QAAQ,QAAQ,GAAG,GAAG,QAAQ,GAAG,KAAK,KAAK,GAAG,QAAQ;IAChE,SAAS,QAAQ;IACjB,MAAM,QAAQ;IACd,MAAM,QAAQ;IACd,SAAS,QAAQ;IACjB,IAAI,QAAQ,IAAI,KAAK,KAAK;IAC1B,KAAK,QAAQ,KAAK,KAAK,KAAK;IAC5B,aAAa,QAAQ,aAAa,KAAI,gBAAe;KACnD,UAAU,WAAW;KACrB,SAAS,KAAK,aAAa,WAAW,QAAQ;KAC9C,aAAa,WAAW;KACzB,EAAE;IACJ,CAAC;AAEF,UAAO;IACL,WAAW,KAAK;IAChB,UAAU;IACV,UAAU;KACR,UAAU;KACV,UAAU,KAAK;KAChB;IACF;UACK;AACN,SAAM,IAAI,+BACR,KAAK,QAAQ,MAAM,QAAQ,IAC3B,KAAK,QAAQ,MAAM,QAAQ,IAC5B;;;;;;;;;CAUL,aAAqB,SAAgE;AACnF,MAAI,OAAO,SAAS,QAAQ,CAC1B,QAAO;AAGT,SAAO,SAAS,QAAQ,QAA6D"}
1
+ {"version":3,"file":"smtp.provider-B6D7zuWX.mjs","names":[],"sources":["../src/email/providers/smtp.provider.ts"],"sourcesContent":["import type { Transporter } from 'nodemailer'\nimport * as nodemailer from 'nodemailer'\nimport type SMTPTransport from 'nodemailer/lib/smtp-transport'\nimport { Readable } from 'stream'\nimport type { ResolvedEmailAttachment, ResolvedEmailMessage } from '../contracts'\nimport type { EmailModuleOptions } from '../email.module'\nimport { EmailSmtpConnectionFailedError, SmtpConfigurationMissingError, SmtpHostMissingError } from '../errors'\nimport { BaseEmailProvider } from './base-email.provider'\nimport type { EmailSendResult } from './email-provider.interface'\n\n/**\n * SMTP Email Provider\n *\n * Implementation of IEmailProvider using SMTP/nodemailer\n * Supports any SMTP server configured via SMTP_URL\n */\nexport class SmtpProvider extends BaseEmailProvider {\n private readonly transporter: Transporter<SMTPTransport.SentMessageInfo>\n private readonly defaultFrom: { name: string; email: string }\n\n constructor(\n private readonly options: EmailModuleOptions\n ) {\n super()\n\n // Validate SMTP configuration\n if (!this.options.smtp) {\n throw new SmtpConfigurationMissingError()\n }\n\n if (!this.options.smtp.host) {\n throw new SmtpHostMissingError()\n }\n\n // Create nodemailer transporter\n this.transporter = nodemailer.createTransport({\n host: this.options.smtp.host,\n port: this.options.smtp.port,\n secure: this.options.smtp.secure,\n auth: this.options.smtp.username && this.options.smtp.password\n ? {\n user: this.options.smtp.username,\n pass: this.options.smtp.password,\n }\n : undefined,\n })\n\n this.defaultFrom = this.options.from\n }\n\n async send(message: ResolvedEmailMessage): Promise<EmailSendResult> {\n try {\n const from = message.from\n ? `${message.from.name} <${message.from.email}>`\n : `${this.defaultFrom.name} <${this.defaultFrom.email}>`\n\n const info = await this.transporter.sendMail({\n from,\n to: Array.isArray(message.to) ? message.to.join(', ') : message.to,\n subject: message.subject,\n html: message.html,\n text: message.text,\n replyTo: message.replyTo,\n cc: message.cc?.join(', '),\n bcc: message.bcc?.join(', '),\n attachments: message.attachments?.map(attachment => ({\n filename: attachment.filename,\n content: this.toNodeStream(attachment.content),\n contentType: attachment.contentType,\n })),\n })\n\n return {\n messageId: info.messageId,\n accepted: true,\n metadata: {\n provider: 'smtp',\n response: info.response,\n },\n }\n } catch {\n throw new EmailSmtpConnectionFailedError(\n this.options.smtp?.host ?? '',\n this.options.smtp?.port ?? 587\n )\n }\n }\n\n /**\n * Convert attachment content to Node.js stream format\n *\n * Nodemailer expects Node.js Readable streams, not web ReadableStream.\n * Buffer is passed through as-is since nodemailer supports it directly.\n */\n private toNodeStream(content: ResolvedEmailAttachment['content']): Buffer | Readable {\n if (Buffer.isBuffer(content)) {\n return content\n }\n // Convert web ReadableStream to Node.js Readable\n return Readable.fromWeb(content as unknown as Parameters<typeof Readable.fromWeb>[0])\n }\n}\n"],"mappings":";;;;;;;;;;;AAgBA,IAAa,eAAb,cAAkC,kBAAkB;CAK/B;CAJnB;CACA;CAEA,YACE,SACA;EACA,OAAO;EAFU,KAAA,UAAA;EAKjB,IAAI,CAAC,KAAK,QAAQ,MAChB,MAAM,IAAI,+BAA+B;EAG3C,IAAI,CAAC,KAAK,QAAQ,KAAK,MACrB,MAAM,IAAI,sBAAsB;EAIlC,KAAK,cAAc,WAAW,gBAAgB;GAC5C,MAAM,KAAK,QAAQ,KAAK;GACxB,MAAM,KAAK,QAAQ,KAAK;GACxB,QAAQ,KAAK,QAAQ,KAAK;GAC1B,MAAM,KAAK,QAAQ,KAAK,YAAY,KAAK,QAAQ,KAAK,WAClD;IACA,MAAM,KAAK,QAAQ,KAAK;IACxB,MAAM,KAAK,QAAQ,KAAK;IACzB,GACC,KAAA;GACL,CAAC;EAEF,KAAK,cAAc,KAAK,QAAQ;;CAGlC,MAAM,KAAK,SAAyD;EAClE,IAAI;GACF,MAAM,OAAO,QAAQ,OACjB,GAAG,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,MAAM,KAC5C,GAAG,KAAK,YAAY,KAAK,IAAI,KAAK,YAAY,MAAM;GAExD,MAAM,OAAO,MAAM,KAAK,YAAY,SAAS;IAC3C;IACA,IAAI,MAAM,QAAQ,QAAQ,GAAG,GAAG,QAAQ,GAAG,KAAK,KAAK,GAAG,QAAQ;IAChE,SAAS,QAAQ;IACjB,MAAM,QAAQ;IACd,MAAM,QAAQ;IACd,SAAS,QAAQ;IACjB,IAAI,QAAQ,IAAI,KAAK,KAAK;IAC1B,KAAK,QAAQ,KAAK,KAAK,KAAK;IAC5B,aAAa,QAAQ,aAAa,KAAI,gBAAe;KACnD,UAAU,WAAW;KACrB,SAAS,KAAK,aAAa,WAAW,QAAQ;KAC9C,aAAa,WAAW;KACzB,EAAE;IACJ,CAAC;GAEF,OAAO;IACL,WAAW,KAAK;IAChB,UAAU;IACV,UAAU;KACR,UAAU;KACV,UAAU,KAAK;KAChB;IACF;UACK;GACN,MAAM,IAAI,+BACR,KAAK,QAAQ,MAAM,QAAQ,IAC3B,KAAK,QAAQ,MAAM,QAAQ,IAC5B;;;;;;;;;CAUL,aAAqB,SAAgE;EACnF,IAAI,OAAO,SAAS,QAAQ,EAC1B,OAAO;EAGT,OAAO,SAAS,QAAQ,QAA6D"}
@@ -1,6 +1,6 @@
1
- import { V as RouterContext, d as ApplicationError, hn as DynamicModule, pn as AsyncModuleOptions } from "../index-DPFqRs8L.mjs";
2
- import { t as StratalEnv } from "../env-CamWD-U1.mjs";
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-CC1nniHk.mjs";
1
+ import { V as RouterContext, d as ApplicationError, gn as AsyncModuleOptions, vn as DynamicModule } from "../index-ByOyTmqf.mjs";
2
+ import { t as StratalEnv } from "../env-D1rcZ8_r.mjs";
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-Bd6vA4ak.mjs";
4
4
 
5
5
  //#region src/storage/storage.module.d.ts
6
6
  /**
@@ -1,3 +1,3 @@
1
- import { a as InvalidFileTypeError, c as FileNotFoundError, i as PresignedUrlInvalidExpiryError, l as DiskNotConfiguredError, n as R2PresignedUrlSecretMissingError, o as InvalidDiskError, r as R2BindingNotFoundError, s as FileTooLargeError, t as StorageResponseBodyMissingError } from "../errors-BUyUfr2Z.mjs";
2
- import { a as deleteFileInputSchema, c as StorageService, i as fileExistsInputSchema, l as StorageManagerService, n as getPresignedUrlInputSchema, o as StorageController, r as presignedUrlResultSchema, s as StorageModule, t as uploadResultSchema, u as STORAGE_TOKENS } from "../storage-P6X4h9So.mjs";
1
+ import { a as InvalidFileTypeError, c as FileNotFoundError, i as PresignedUrlInvalidExpiryError, l as DiskNotConfiguredError, n as R2PresignedUrlSecretMissingError, o as InvalidDiskError, r as R2BindingNotFoundError, s as FileTooLargeError, t as StorageResponseBodyMissingError } from "../errors-ORxu1-Bb.mjs";
2
+ import { a as deleteFileInputSchema, c as StorageService, i as fileExistsInputSchema, l as StorageManagerService, n as getPresignedUrlInputSchema, o as StorageController, r as presignedUrlResultSchema, s as StorageModule, t as uploadResultSchema, u as STORAGE_TOKENS } from "../storage-D8CBP72Z.mjs";
3
3
  export { DiskNotConfiguredError, FileNotFoundError, FileTooLargeError, InvalidDiskError, InvalidFileTypeError, PresignedUrlInvalidExpiryError, R2BindingNotFoundError, R2PresignedUrlSecretMissingError, STORAGE_TOKENS, StorageController, StorageManagerService, StorageModule, StorageResponseBodyMissingError, StorageService, deleteFileInputSchema, fileExistsInputSchema, getPresignedUrlInputSchema, presignedUrlResultSchema, uploadResultSchema };
@@ -1,5 +1,5 @@
1
- import { t as StratalEnv } from "../../env-CamWD-U1.mjs";
2
- import { _ as StorageEntry, f as DownloadResult, i as UploadResult, n as StreamingBlobPayloadInputTypes, r as UploadOptions, s as PresignedUrlResult, t as IStorageProvider, v as StorageRouteConfig } from "../../storage-provider.interface-CC1nniHk.mjs";
1
+ import { t as StratalEnv } from "../../env-D1rcZ8_r.mjs";
2
+ import { _ as StorageEntry, f as DownloadResult, i as UploadResult, n as StreamingBlobPayloadInputTypes, r as UploadOptions, s as PresignedUrlResult, t as IStorageProvider, v as StorageRouteConfig } from "../../storage-provider.interface-Bd6vA4ak.mjs";
3
3
 
4
4
  //#region src/storage/providers/multipart-provider.interface.d.ts
5
5
  /**
@@ -1,2 +1,2 @@
1
- import { t as R2StorageProvider } from "../../r2-storage.provider-LdzK9tfG.mjs";
1
+ import { t as R2StorageProvider } from "../../r2-storage.provider-DuonKeYm.mjs";
2
2
  export { R2StorageProvider };
@@ -1,10 +1,10 @@
1
- import { A as Scope } from "./errors-B4pYgYON.mjs";
2
- import { a as __decorate, f as DI_TOKENS, o as __decorateParam, p as Transient, s as __decorateMetadata } from "./logger-V6Ms3QnQ.mjs";
3
- import { C as Module } from "./module-qGE_1duv.mjs";
4
- import { a as withI18n, i as z } from "./validation-Dbg3ehdP.mjs";
5
- import { t as Controller } from "./controller.decorator-LZY9aHYG.mjs";
6
- import { n as Delete, o as Put, r as Get } from "./http-method.decorator-BT3ufnz8.mjs";
7
- import { c as FileNotFoundError, i as PresignedUrlInvalidExpiryError, l as DiskNotConfiguredError, o as InvalidDiskError, r as R2BindingNotFoundError } from "./errors-BUyUfr2Z.mjs";
1
+ import { A as Scope } from "./errors-COW9-Mar.mjs";
2
+ import { a as __decorate, f as DI_TOKENS, o as __decorateParam, p as Transient, s as __decorateMetadata } from "./logger-DlV7NtvD.mjs";
3
+ import { k as Module } from "./module-BzLg57FK.mjs";
4
+ import { i as z, s as withI18n } from "./validation-DtJwAv7O.mjs";
5
+ import { t as Controller } from "./controller.decorator-B9vwn0zK.mjs";
6
+ import { n as Delete, o as Put, r as Get } from "./http-method.decorator-BrgHMdLQ.mjs";
7
+ import { c as FileNotFoundError, i as PresignedUrlInvalidExpiryError, l as DiskNotConfiguredError, o as InvalidDiskError, r as R2BindingNotFoundError } from "./errors-ORxu1-Bb.mjs";
8
8
  import { inject } from "tsyringe";
9
9
  //#region src/storage/storage.tokens.ts
10
10
  /**
@@ -19,6 +19,8 @@ const STORAGE_TOKENS = {
19
19
  //#endregion
20
20
  //#region src/storage/services/storage-manager.service.ts
21
21
  let StorageManagerService = class StorageManagerService {
22
+ options;
23
+ env;
22
24
  providers = /* @__PURE__ */ new Map();
23
25
  creationPromises = /* @__PURE__ */ new Map();
24
26
  diskConfigs = /* @__PURE__ */ new Map();
@@ -64,7 +66,7 @@ let StorageManagerService = class StorageManagerService {
64
66
  * @returns Storage provider instance
65
67
  */
66
68
  async createProvider(config) {
67
- const { R2StorageProvider } = await import("./r2-storage.provider-LdzK9tfG.mjs").then((n) => n.n);
69
+ const { R2StorageProvider } = await import("./r2-storage.provider-DuonKeYm.mjs").then((n) => n.n);
68
70
  const bucket = this.env[config.binding];
69
71
  if (!bucket) throw new R2BindingNotFoundError(config.binding);
70
72
  return new R2StorageProvider(config, bucket, this.env, this.options.route);
@@ -104,6 +106,8 @@ StorageManagerService = __decorate([
104
106
  //#endregion
105
107
  //#region src/storage/services/storage.service.ts
106
108
  let StorageService = class StorageService {
109
+ storageManager;
110
+ options;
107
111
  constructor(storageManager, options) {
108
112
  this.storageManager = storageManager;
109
113
  this.options = options;
@@ -356,6 +360,7 @@ StorageModule = _StorageModule = __decorate([Module({ providers: [{
356
360
  //#region src/storage/controllers/storage.controller.ts
357
361
  const diskParam = z.object({ disk: z.string() });
358
362
  let StorageController = class StorageController {
363
+ storage;
359
364
  constructor(storage) {
360
365
  this.storage = storage;
361
366
  }
@@ -481,4 +486,4 @@ const uploadResultSchema = z.object({
481
486
  //#endregion
482
487
  export { deleteFileInputSchema as a, StorageService as c, fileExistsInputSchema as i, StorageManagerService as l, getPresignedUrlInputSchema as n, StorageController as o, presignedUrlResultSchema as r, StorageModule as s, uploadResultSchema as t, STORAGE_TOKENS as u };
483
488
 
484
- //# sourceMappingURL=storage-P6X4h9So.mjs.map
489
+ //# sourceMappingURL=storage-D8CBP72Z.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage-P6X4h9So.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/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"],"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 'tsyringe'\nimport { Transient } from '../../di/decorators'\nimport { DI_TOKENS } from '../../di/tokens'\nimport { type StratalEnv } from '../../env'\nimport { DiskNotConfiguredError, R2BindingNotFoundError } from '../errors'\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@Transient(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 DiskNotConfiguredError(diskName)\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 R2BindingNotFoundError(config.binding)\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 DiskNotConfiguredError(diskName)\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 'tsyringe'\nimport { Transient } from '../../di/decorators'\nimport type { DownloadResult, PresignedUrlResult, UploadOptions, UploadResult } from '../contracts'\nimport {\n InvalidDiskError,\n PresignedUrlInvalidExpiryError,\n} from '../errors'\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@Transient(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 InvalidDiskError(diskName)\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 PresignedUrlInvalidExpiryError(validatedExpiresIn, minExpiry, maxExpiry)\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 { Scope } from '../di/types'\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, scope: Scope.Singleton },\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 { inject } from 'tsyringe'\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'\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, withI18n } from '../../i18n/validation'\n\nexport const deleteFileInputSchema = z.object({\n path: z.string().min(1, withI18n('zodI18n.errors.custom.filePathRequired')),\n disk: z.string().optional(),\n})\n\nexport type DeleteFileInput = z.infer<typeof deleteFileInputSchema>\n","import { z, withI18n } from '../../i18n/validation'\n\nexport const fileExistsInputSchema = z.object({\n path: z.string().min(1, withI18n('zodI18n.errors.custom.filePathRequired')),\n disk: z.string().optional(),\n})\n\nexport type FileExistsInput = z.infer<typeof fileExistsInputSchema>\n","import { z, withI18n } from '../../i18n/validation'\n\nexport const getPresignedUrlInputSchema = z.object({\n path: z.string().min(1, withI18n('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"],"mappings":"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"}
1
+ {"version":3,"file":"storage-D8CBP72Z.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/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"],"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 'tsyringe'\nimport { Transient } from '../../di/decorators'\nimport { DI_TOKENS } from '../../di/tokens'\nimport { type StratalEnv } from '../../env'\nimport { DiskNotConfiguredError, R2BindingNotFoundError } from '../errors'\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@Transient(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 DiskNotConfiguredError(diskName)\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 R2BindingNotFoundError(config.binding)\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 DiskNotConfiguredError(diskName)\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 'tsyringe'\nimport { Transient } from '../../di/decorators'\nimport type { DownloadResult, PresignedUrlResult, UploadOptions, UploadResult } from '../contracts'\nimport {\n InvalidDiskError,\n PresignedUrlInvalidExpiryError,\n} from '../errors'\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@Transient(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 InvalidDiskError(diskName)\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 PresignedUrlInvalidExpiryError(validatedExpiresIn, minExpiry, maxExpiry)\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 { Scope } from '../di/types'\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, scope: Scope.Singleton },\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 { inject } from 'tsyringe'\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'\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, withI18n } from '../../i18n/validation'\n\nexport const deleteFileInputSchema = z.object({\n path: z.string().min(1, withI18n('zodI18n.errors.custom.filePathRequired')),\n disk: z.string().optional(),\n})\n\nexport type DeleteFileInput = z.infer<typeof deleteFileInputSchema>\n","import { z, withI18n } from '../../i18n/validation'\n\nexport const fileExistsInputSchema = z.object({\n path: z.string().min(1, withI18n('zodI18n.errors.custom.filePathRequired')),\n disk: z.string().optional(),\n})\n\nexport type FileExistsInput = z.infer<typeof fileExistsInputSchema>\n","import { z, withI18n } from '../../i18n/validation'\n\nexport const getPresignedUrlInputSchema = z.object({\n path: z.string().min(1, withI18n('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"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { o as z } from "./index-B437eK7p.mjs";
1
+ import { o as z } from "./index-Bnpfq6uk.mjs";
2
2
 
3
3
  //#region src/storage/types.d.ts
4
4
  /**
@@ -199,4 +199,4 @@ interface IStorageProvider {
199
199
  }
200
200
  //#endregion
201
201
  export { StorageEntry as _, uploadResultSchema as a, getPresignedUrlInputSchema as c, fileExistsInputSchema as d, DownloadResult as f, StorageConfig as g, PresignedUrlConfig as h, UploadResult as i, presignedUrlResultSchema as l, deleteFileInputSchema as m, StreamingBlobPayloadInputTypes as n, GetPresignedUrlInput as o, DeleteFileInput as p, UploadOptions as r, PresignedUrlResult as s, IStorageProvider as t, FileExistsInput as u, StorageRouteConfig as v };
202
- //# sourceMappingURL=storage-provider.interface-CC1nniHk.d.mts.map
202
+ //# sourceMappingURL=storage-provider.interface-Bd6vA4ak.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage-provider.interface-CC1nniHk.d.mts","names":[],"sources":["../src/storage/types.ts","../src/storage/contracts/delete-file.input.ts","../src/storage/contracts/download-result.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/providers/storage-provider.interface.ts"],"mappings":";;;;;;AAIA;UAAiB,YAAA;EAChB,IAAA;EAD4B;EAG5B,OAAA;EACA,IAAA;AAAA;;;AAMD;UAAiB,kBAAA;EAChB,aAAA;EACA,SAAA;AAAA;AAMD;;;AAAA,UAAiB,kBAAA;EAIR;EAFR,QAAA;EAQ6B;EAN7B,QAAA;AAAA;;;;UAMgB,aAAA;EAChB,OAAA,EAAS,YAAA;EACT,kBAAA;EACA,YAAA,EAAc,kBAAA;EAAd;EAEA,KAAA,GAAQ,kBAAA;AAAA;;;cCnCI,qBAAA,EAAqB,CAAA,CAAA,SAAA;;;;KAKtB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,qBAAA;;;;;;ADH7C;;;UEEiB,cAAA;EFDhB;;;EEKC,QAAA,gBAAwB,cAAA,CAAe,UAAA;EAEvC,QAAA,gBAAwB,OAAA;EAExB,aAAA,gBAA6B,OAAA,CAAQ,UAAA;EFAJ;;;EEKjC,WAAA;EFGe;;;EEEf,IAAA;EFEO;AAMT;;;EEFE,QAAA,GAAW,MAAA;AAAA;;;cC5BA,qBAAA,EAAqB,CAAA,CAAA,SAAA;;;;KAKtB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,qBAAA;;;cCLhC,0BAAA,EAA0B,CAAA,CAAA,SAAA;;;;;;;;;;;KAO3B,oBAAA,GAAuB,CAAA,CAAE,KAAA,QAAa,0BAAA;AAAA,cAErC,wBAAA,EAAwB,CAAA,CAAA,SAAA;;;;;;;;;;;KAOzB,kBAAA,GAAqB,CAAA,CAAE,KAAA,QAAa,wBAAA;;;;;AJdhD;UKCiB,aAAA;;;;EAIf,IAAA;ELDD;;;EKKC,QAAA;ELCiC;;;;EKIjC,QAAA,GAAW,MAAA;ELIsB;;;;EKCjC,OAAA;AAAA;AAAA,cAGW,kBAAA,EAAkB,CAAA,CAAA,SAAA;;;;;;;;KASnB,YAAA,GAAe,CAAA,CAAE,KAAA,QAAa,kBAAA;;;;;AL/B1C;;KMEY,8BAAA,GACR,cAAA,GACA,WAAA,GACA,eAAA,YAEA,IAAA;;;;;UAOa,gBAAA;ENVZ;AAML;;;;;AAQA;EMIE,MAAA,CAAO,IAAA,EAAM,8BAAA,EAAgC,IAAA,UAAc,OAAA,EAAS,aAAA,GAAgB,OAAA,CAAQ,YAAA;;;;ANM9F;;EMCE,QAAA,CAAS,IAAA,WAAe,OAAA,CAAQ,cAAA;ENAxB;;;;EMMR,MAAA,CAAO,IAAA,WAAe,OAAA;ENNvB;;;;;EMaC,MAAA,CAAO,IAAA,WAAe,OAAA;ENTf;;;;;;ACnCT;EKqDE,eAAA,CACE,IAAA,UACA,MAAA,qCACA,SAAA,WACC,OAAA,CAAQ,kBAAA;;;;;;;;;EAUX,aAAA,CACE,IAAA,EAAM,8BAAA,EACN,IAAA,UACA,OAAA,EAAS,IAAA,CAAK,aAAA;IAA2B,IAAA;EAAA,IACxC,OAAA,CAAQ,YAAA;AAAA"}
1
+ {"version":3,"file":"storage-provider.interface-Bd6vA4ak.d.mts","names":[],"sources":["../src/storage/types.ts","../src/storage/contracts/delete-file.input.ts","../src/storage/contracts/download-result.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/providers/storage-provider.interface.ts"],"mappings":";;;;;;AAIA;UAAiB,YAAA;EAChB,IAAA;EAD4B;EAG5B,OAAA;EACA,IAAA;AAAA;;;AAMD;UAAiB,kBAAA;EAChB,aAAA;EACA,SAAA;AAAA;AAMD;;;AAAA,UAAiB,kBAAA;EAIR;EAFR,QAAA;EAQ6B;EAN7B,QAAA;AAAA;;;;UAMgB,aAAA;EAChB,OAAA,EAAS,YAAA;EACT,kBAAA;EACA,YAAA,EAAc,kBAAA;EAAd;EAEA,KAAA,GAAQ,kBAAA;AAAA;;;cCnCI,qBAAA,EAAqB,CAAA,CAAA,SAAA;;;;KAKtB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,qBAAA;;;;;;ADH7C;;;UEEiB,cAAA;EFDhB;;;EEKC,QAAA,gBAAwB,cAAA,CAAe,UAAA;EAEvC,QAAA,gBAAwB,OAAA;EAExB,aAAA,gBAA6B,OAAA,CAAQ,UAAA;EFAJ;;;EEKjC,WAAA;EFGe;;;EEEf,IAAA;EFEO;AAMT;;;EEFE,QAAA,GAAW,MAAA;AAAA;;;cC5BA,qBAAA,EAAqB,CAAA,CAAA,SAAA;;;;KAKtB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,qBAAA;;;cCLhC,0BAAA,EAA0B,CAAA,CAAA,SAAA;;;;;;;;;;;KAO3B,oBAAA,GAAuB,CAAA,CAAE,KAAA,QAAa,0BAAA;AAAA,cAErC,wBAAA,EAAwB,CAAA,CAAA,SAAA;;;;;;;;;;;KAOzB,kBAAA,GAAqB,CAAA,CAAE,KAAA,QAAa,wBAAA;;;;;AJdhD;UKCiB,aAAA;;;;EAIf,IAAA;ELDD;;;EKKC,QAAA;ELCiC;;;;EKIjC,QAAA,GAAW,MAAA;ELIsB;;;;EKCjC,OAAA;AAAA;AAAA,cAGW,kBAAA,EAAkB,CAAA,CAAA,SAAA;;;;;;;;KASnB,YAAA,GAAe,CAAA,CAAE,KAAA,QAAa,kBAAA;;;;;AL/B1C;;KMEY,8BAAA,GACR,cAAA,GACA,WAAA,GACA,eAAA,YAEA,IAAA;;;;;UAOa,gBAAA;ENVZ;AAML;;;;;AAQA;EMIE,MAAA,CAAO,IAAA,EAAM,8BAAA,EAAgC,IAAA,UAAc,OAAA,EAAS,aAAA,GAAgB,OAAA,CAAQ,YAAA;;;;ANM9F;;EMCE,QAAA,CAAS,IAAA,WAAe,OAAA,CAAQ,cAAA;ENAxB;;;;EMMR,MAAA,CAAO,IAAA,WAAe,OAAA;ENNvB;;;;;EMaC,MAAA,CAAO,IAAA,WAAe,OAAA;ENTf;;;;;;ACnCT;EKqDE,eAAA,CACE,IAAA,UACA,MAAA,qCACA,SAAA,WACC,OAAA,CAAQ,kBAAA;;;;;;;;;EAUX,aAAA,CACE,IAAA,EAAM,8BAAA,EACN,IAAA,UACA,OAAA,EAAS,IAAA,CAAK,aAAA;IAA2B,IAAA;EAAA,IACxC,OAAA,CAAQ,YAAA;AAAA"}
@@ -1,20 +1,22 @@
1
- import { A as Scope, D as runWithContainer, H as ApplicationError, V as ROUTER_TOKENS, g as DefaultExceptionHandler, i as createCronExceptionContext, j as Container, o as createQueueExceptionContext, r as createCliExceptionContext, t as StratalNotInitializedError } from "./errors-B4pYgYON.mjs";
2
- import { a as __decorate, f as DI_TOKENS, i as LoggerService, n as PrettyFormatter, o as __decorateParam, r as JsonFormatter, s as __decorateMetadata, t as ConsoleTransport, u as LOGGER_TOKENS } from "./logger-V6Ms3QnQ.mjs";
3
- import { a as getGroups, i as getGlobalMiddleware, r as getDefaultEntry, t as ModuleRegistry } from "./module-qGE_1duv.mjs";
4
- import { r as getListenerHandlers, t as EventRegistry } from "./events-COKixqnG.mjs";
1
+ import { A as Scope, D as runWithContainer, H as ApplicationError, V as ROUTER_TOKENS, g as DefaultExceptionHandler, i as createCronExceptionContext, j as Container, o as createQueueExceptionContext, r as createCliExceptionContext, t as StratalNotInitializedError } from "./errors-COW9-Mar.mjs";
2
+ import { a as __decorate, f as DI_TOKENS, i as LoggerService, n as PrettyFormatter, o as __decorateParam, r as JsonFormatter, s as __decorateMetadata, t as ConsoleTransport, u as LOGGER_TOKENS } from "./logger-DlV7NtvD.mjs";
3
+ import { a as getGroups, i as getGlobalMiddleware, r as getDefaultEntry, t as ModuleRegistry } from "./module-BzLg57FK.mjs";
4
+ import { r as getListenerHandlers, t as EventRegistry } from "./events-CzCV8jI8.mjs";
5
5
  import { t as Command } from "./command-BgSlsS4M.mjs";
6
6
  import { CacheModule } from "./cache/index.mjs";
7
- import { t as CronManager } from "./cron-manager-C30t9UZM.mjs";
8
- import { A as OpenAPIModule, c as LocalePathService, i as Uri, l as HonoApp, o as RouteRegistry, s as VersioningService, t as I18nModule, u as RouteRegistrationService } from "./i18n.module-CI_prYFD.mjs";
9
- import { a as QueueListCommand, c as HelpCommand, d as ApiCommand, i as RouteListCommand, o as McpToolsCommand, r as ScheduleListCommand, s as McpServeCommand, t as QuarryRegistry, u as EventListCommand } from "./quarry-registry-DFfRRkA7.mjs";
10
- import { t as QueueModule } from "./queue.module-P-G-nCYz.mjs";
11
- import { i as SeederRegistry, n as DbSeedListCommand, r as SEEDER_TOKENS, t as DbSeedCommand } from "./seeder-BcqIFa2X.mjs";
7
+ import { t as CronManager } from "./cron-manager-DQSK8uoV.mjs";
8
+ import { A as OpenAPIModule, c as LocalePathService, i as Uri, l as HonoApp, o as RouteRegistry, s as VersioningService, t as I18nModule, u as RouteRegistrationService } from "./i18n.module-CzXLW9Hy.mjs";
9
+ import { a as QueueListCommand, c as HelpCommand, d as ApiCommand, i as RouteListCommand, o as McpToolsCommand, r as ScheduleListCommand, s as McpServeCommand, t as QuarryRegistry, u as EventListCommand } from "./quarry-registry-BwY2hOxm.mjs";
10
+ import { t as QueueModule } from "./queue.module-BhCjZp6H.mjs";
11
+ import { i as SeederRegistry, n as DbSeedListCommand, r as SEEDER_TOKENS, t as DbSeedCommand } from "./seeder-zoEfEw9i.mjs";
12
12
  import { container, inject, injectable } from "tsyringe";
13
13
  import "reflect-metadata";
14
14
  import { writeFileSync } from "node:fs";
15
15
  import { resolve } from "node:path";
16
16
  //#region src/quarry/commands/route-types.command.ts
17
17
  let RouteTypesCommand = class RouteTypesCommand extends Command {
18
+ registry;
19
+ localePathService;
18
20
  static command = "route:types {--output=src/stratal.d.ts : Output file path}";
19
21
  static description = "Generate TypeScript types for named routes";
20
22
  constructor(registry, localePathService) {
@@ -530,4 +532,4 @@ var Stratal = class Stratal {
530
532
  //#endregion
531
533
  export { Application as n, Stratal as t };
532
534
 
533
- //# sourceMappingURL=stratal-BCiwCFN9.mjs.map
535
+ //# sourceMappingURL=stratal-CNwpbSZl.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stratal-CNwpbSZl.mjs","names":["internal.getGroups","internal.getDefaultEntry","internal.getGlobalMiddleware","tsyringeRootContainer"],"sources":["../src/quarry/commands/route-types.command.ts","../src/router/router-resolver.ts","../src/application.ts","../src/stratal.ts"],"sourcesContent":["import { writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { inject } from 'tsyringe'\nimport type { RouteRegistry, RegisteredRoute } from '../../router/route-registry'\nimport { ROUTER_TOKENS } from '../../router/router.tokens'\nimport type { LocalePathService } from '../../router/services/locale-path.service'\nimport { Command } from '../command'\n\n/**\n * Generate TypeScript types for named routes.\n *\n * Outputs a `stratal.d.ts` file with `StratalRouteMap` augmentation\n * that provides autocomplete and type-safe params for `route()` and `ctx.route()`.\n *\n * @example\n * ```bash\n * quarry route:types # → src/stratal.d.ts\n * quarry route:types --output=types/routes.d.ts\n * ```\n */\nexport class RouteTypesCommand extends Command {\n static command = 'route:types {--output=src/stratal.d.ts : Output file path}'\n static description = 'Generate TypeScript types for named routes'\n\n constructor(\n @inject(ROUTER_TOKENS.RouteRegistry) private registry: RouteRegistry,\n @inject(ROUTER_TOKENS.LocalePathService) private localePathService: LocalePathService,\n ) {\n super()\n }\n\n handle(): number | undefined {\n const outputPath = resolve(this.string('output') || 'src/stratal.d.ts')\n const namedRoutes = this.registry.named()\n\n if (namedRoutes.length === 0) {\n this.warn('No named routes found. Add name to your @Route() or @Get()/@Post() decorators.')\n return 0\n }\n\n const content = this.generateDeclaration(namedRoutes)\n writeFileSync(outputPath, content, 'utf-8')\n this.info(`Generated route types for ${namedRoutes.length} named routes → ${outputPath}`)\n\n return undefined\n }\n\n /**\n * Generate the StratalRouteMap declaration content.\n */\n private generateDeclaration(routes: RegisteredRoute[]): string {\n const localeConfig = this.localePathService.enabled ? this.localePathService.localePathConfig : null\n const localeType = localeConfig\n ? localeConfig.allLocales.map(l => `'${l}'`).join(' | ')\n : null\n const localeOptional = localeConfig ? this.localePathService.prefixDefaultLocale !== true : false\n\n const entries = routes\n .filter((r): r is RegisteredRoute & { name: string } => r.name !== undefined)\n .sort((a, b) => a.name.localeCompare(b.name))\n .map(route => {\n const paramEntries = [\n ...route.paramNames.map(p => `${p}: string`),\n ...route.domainParamNames.map(p => `${p}: string`),\n ]\n\n if (localeType && route.localePaths?.length) {\n const optionalMarker = localeOptional ? '?' : ''\n paramEntries.push(`locale${optionalMarker}: StratalLocale`)\n }\n\n // Extra keys become query-string params at runtime, so allow any\n // string key. Typed path/domain params above still take precedence.\n const indexSignature = localeType && route.localePaths?.length\n ? '[key: string]: string | StratalLocale | undefined'\n : '[key: string]: string | undefined'\n paramEntries.push(indexSignature)\n\n const paramsType = `{ ${paramEntries.join('; ')} }`\n return ` '${route.name}': { params: ${paramsType} }`\n })\n .join('\\n')\n\n const lines = [\n '// Auto-generated by `quarry route:types` — do not edit manually',\n \"declare module 'stratal/router' {\",\n ]\n\n if (localeType) {\n lines.push(` type StratalLocale = ${localeType}`)\n lines.push('')\n }\n\n lines.push(\n ' interface StratalRouteMap {',\n entries,\n ' }',\n '}',\n )\n\n return [\n ...lines,\n '',\n 'export {}',\n '',\n ].join('\\n')\n }\n}\n","import type { ZodObject } from '../i18n/validation'\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","import { injectable, container as tsyringeRootContainer } from 'tsyringe'\nimport { CacheModule } from './cache'\nimport type { CronJob } from './cron/cron-job'\nimport { CronManager } from './cron/cron-manager'\nimport { Container } from './di/container'\nimport { runWithContainer } from './di/container-storage'\nimport { DI_TOKENS } from './di/tokens'\nimport { Scope } from './di/types'\nimport { type StratalEnv } from './env'\nimport { ApplicationError } from './errors'\nimport { DefaultExceptionHandler } from './errors/default-exception-handler'\nimport { createCliExceptionContext, createCronExceptionContext, createQueueExceptionContext } from './errors/exception-context'\nimport type { ExceptionHandler } from './errors/exception-handler'\nimport type { EventHandler } from './events'\nimport { EventRegistry, getListenerHandlers } from './events'\nimport type { StratalExecutionContext } from './execution-context'\nimport { I18nModule } from './i18n/i18n.module'\nimport { ConsoleTransport, JsonFormatter, LOGGER_TOKENS, LoggerService, LogLevel, PrettyFormatter } from './logger'\nimport { ModuleRegistry } from './module/module-registry'\nimport type { DynamicModule, ModuleClass } from './module/types'\nimport { OpenAPIModule } from './openapi'\nimport type { Command } from './quarry/command'\nimport { ApiCommand } from './quarry/commands/api.command'\nimport { EventListCommand } from './quarry/commands/event-list.command'\nimport { HelpCommand } from './quarry/commands/help.command'\nimport { McpServeCommand } from './quarry/commands/mcp-serve.command'\nimport { McpToolsCommand } from './quarry/commands/mcp-tools.command'\nimport { QueueListCommand } from './quarry/commands/queue-list.command'\nimport { RouteListCommand } from './quarry/commands/route-list.command'\nimport { RouteTypesCommand } from './quarry/commands/route-types.command'\nimport { ScheduleListCommand } from './quarry/commands/schedule-list.command'\nimport { QuarryRegistry } from './quarry/quarry-registry'\nimport type { CommandInput, CommandResult } from './quarry/types'\nimport { type ConsumerRegistry } from './queue/consumer-registry'\nimport type { IQueueConsumer, QueueMessage } from './queue/queue-consumer'\nimport { type QueueManager } from './queue/queue-manager'\nimport { QueueModule } from './queue/queue.module'\nimport { type RouterContext } from './router'\nimport { HonoApp } from './router/hono-app'\nimport { RouteRegistry } from './router/route-registry'\nimport { RouterResolver } from './router/router-resolver'\nimport { ROUTER_TOKENS } from './router/router.tokens'\nimport { LocalePathService } from './router/services/locale-path.service'\nimport { RouteRegistrationService } from './router/services/route-registration.service'\nimport { VersioningService } from './router/services/versioning.service'\nimport type { TrailingSlashMode, VersioningOptions } from './router/types'\nimport { Uri } from './router/uri'\nimport { DbSeedCommand, DbSeedListCommand, SEEDER_TOKENS, SeederRegistry, type Seeder } from './seeder'\nimport type { Constructor } from './types'\n\nexport interface ApplicationConfig {\n /** Root application module */\n module: ModuleClass | DynamicModule\n /** Logging configuration. Defaults: level=INFO, formatter='json' */\n logging?: {\n level?: LogLevel\n formatter?: 'json' | 'pretty'\n }\n /**\n * API versioning configuration.\n * When provided, enables URI-based versioning for controllers.\n */\n versioning?: VersioningOptions\n /**\n * Trailing-slash handling for incoming requests.\n *\n * Defaults to `'ignore'` — both `/foo` and `/foo/` resolve to the same route.\n *\n * - `'ignore'` — match both, no redirect.\n * - `'always'` — non-trailing requests redirect (308) to the trailing-slash form.\n * - `'never'` — trailing requests redirect (308) to the non-trailing form.\n */\n trailingSlash?: TrailingSlashMode\n /**\n * Custom exception handler class.\n *\n * Extend {@link ExceptionHandler} and override `register()` to configure\n * custom reporting, rendering, and post-processing of exceptions.\n *\n * When not provided, {@link DefaultExceptionHandler} is used (standard\n * severity-based logging and JSON error responses).\n *\n * @example\n * ```typescript\n * new Stratal({\n * module: AppModule,\n * exceptionHandler: AppExceptionHandler,\n * })\n * ```\n */\n exceptionHandler?: Constructor<ExceptionHandler>\n}\n\nexport interface ApplicationOptions extends ApplicationConfig {\n env: StratalEnv\n ctx: StratalExecutionContext\n}\n\n/**\n * Application\n *\n * Main application class managing the two-tier container hierarchy:\n * - Global Container: All services (singletons via tsyringe native)\n * - Request Container: Child of global, context-enriched instances per request\n *\n * @example\n * ```typescript\n * const app = new Application({ module: AppModule, env, ctx })\n * await app.initialize()\n *\n * // Access container via getter\n * const service = app.container.resolve(MY_TOKEN)\n *\n * // Handle HTTP request (via HonoApp)\n * // Handle queue batch\n * await app.handleQueue(batch, 'my-queue')\n * ```\n */\nexport class Application {\n /**\n * Unified Container - manages all DI operations\n */\n private _container: Container\n\n private honoApp!: HonoApp\n private moduleRegistry: ModuleRegistry\n private consumerRegistry!: ConsumerRegistry\n private cronManager!: CronManager\n private quarry!: QuarryRegistry\n private initialized = false\n private routingInitPromise: Promise<void> | null = null\n private handlerInitPromise: Promise<void> | null = null\n\n readonly env: StratalEnv\n private readonly appConfig: ApplicationConfig\n\n constructor({ env, ctx, ...config }: ApplicationOptions) {\n this.env = env\n this.appConfig = config\n\n ApplicationError.captureStackTraces = env.ENVIRONMENT !== 'production'\n\n // Create unified Container with explicit child container\n this._container = new Container({\n container: tsyringeRootContainer.createChildContainer()\n })\n\n // Register globally — env and ctx always available\n this._container.registerValue(DI_TOKENS.Application, this)\n this._container.registerValue(DI_TOKENS.CloudflareEnv, env)\n this._container.registerValue(DI_TOKENS.ExecutionContext, ctx)\n\n // Register core infrastructure inline\n this.registerLoggerService()\n this.registerCoreServices()\n\n // Create ModuleRegistry with our Container\n const logger = this._container.resolve<LoggerService>(LOGGER_TOKENS.LoggerService)\n this.moduleRegistry = new ModuleRegistry(this._container, logger)\n\n // Register ModuleRegistry in container so modules can access it in onInitialize\n this._container.registerValue(DI_TOKENS.ModuleRegistry, this.moduleRegistry)\n }\n\n /**\n * Get the Container instance\n */\n get container(): Container {\n return this._container\n }\n\n /**\n * Lazily initialize routing and return the HonoApp instance.\n *\n * Routing (service registration, HonoApp resolution, route configuration)\n * is deferred so that `scheduled` and `queue` handlers don't pay the CPU\n * cost of route setup on cold start.\n */\n async ensureHono(): Promise<HonoApp> {\n await this.initializeRouting()\n return this.honoApp\n }\n\n /**\n * Get the application configuration\n */\n get config(): ApplicationConfig {\n return this.appConfig\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) {\n return\n }\n\n // Wrap in AsyncLocalStorage so getContainer() works for route() and other standalone functions\n await runWithContainer(this._container, () => this.initializeInternal())\n }\n\n private async initializeInternal(): Promise<void> {\n // Phase 1: Register core infrastructure modules (internal)\n // OpenAPIModule is deferred to initializeRouting() (only needed for fetch)\n this.moduleRegistry.registerAll([\n I18nModule,\n QueueModule,\n CacheModule,\n ])\n\n // Phase 2: Register user's root module (traverses imports)\n this.moduleRegistry.register(this.appConfig.module)\n\n // Phase 3: Initialize all modules\n await this.moduleRegistry.initialize()\n\n // Phase 3.5: Initialize ExceptionHandler and call module onException hooks\n this.initializeExceptionHandler()\n\n // Phase 4: Resolve managers from container\n this.consumerRegistry = this._container.resolve<ConsumerRegistry>(DI_TOKENS.ConsumerRegistry)\n this.cronManager = this._container.resolve<CronManager>(DI_TOKENS.Cron)\n this.quarry = this._container.resolve<QuarryRegistry>(DI_TOKENS.Quarry)\n\n // Phase 5: Register cron jobs, seeders, and commands (cheap — stores class refs)\n // Queue consumers and event listeners are deferred (they resolve instances\n // from the container, which is expensive). Routing is also deferred.\n this.registerCronJobs()\n this.registerSeeders()\n this.registerCommands()\n\n this.initialized = true\n }\n\n /**\n * Register routing services as singletons in the container.\n * Called after module initialization so I18N_TOKENS.Options is available.\n */\n private registerRoutingServices(): void {\n // VersioningService — resolves version prefixes from appConfig.versioning\n this._container.register(ROUTER_TOKENS.VersioningService, VersioningService, Scope.Singleton)\n\n // HonoApp — the Hono application instance (must be before LocalePathService)\n this._container.register(ROUTER_TOKENS.HonoApp, HonoApp, Scope.Singleton)\n\n // LocalePathService — computes LocalePathConfig and applies locale middleware to HonoApp\n this._container.register(ROUTER_TOKENS.LocalePathService, LocalePathService, Scope.Singleton)\n\n // RouteRegistry — single source of truth, expands routes via services above\n this._container.register(ROUTER_TOKENS.RouteRegistry, RouteRegistry, Scope.Singleton)\n\n // Uri — URL generation service (request-scoped for access to RouterContext)\n this._container.register(ROUTER_TOKENS.Uri, Uri, Scope.Request)\n\n // RouterResolver — merges Router configs from modules\n const routerConfigs = this.moduleRegistry.getAllRouterConfigs()\n const routerResolver = routerConfigs.length > 0 ? new RouterResolver(routerConfigs) : null\n this._container.registerValue(ROUTER_TOKENS.RouterResolver, routerResolver)\n\n // RouteRegistrationService — transient, resolved in HonoApp.configure()\n this._container.register(RouteRegistrationService, RouteRegistrationService)\n }\n\n /**\n * Wire up queue consumers and event listeners.\n * Called lazily on first fetch/queue — not during scheduled handling.\n */\n private initializeHandlers(): Promise<void> {\n this.handlerInitPromise ??= runWithContainer(this._container, () => {\n this.registerQueueConsumers()\n this.registerEventListeners()\n return Promise.resolve()\n })\n return this.handlerInitPromise\n }\n\n /**\n * Register routing services, resolve HonoApp, and configure routes.\n * Called lazily on first fetch — not during scheduled/queue handling.\n */\n private initializeRouting(): Promise<void> {\n this.routingInitPromise ??= runWithContainer(this._container, async () => {\n await this.initializeHandlers()\n this.moduleRegistry.register(OpenAPIModule as unknown as ModuleClass)\n this.registerRoutingServices()\n this.honoApp = this._container.resolve<HonoApp>(ROUTER_TOKENS.HonoApp)\n await this.honoApp.configure()\n })\n return this.routingInitPromise\n }\n\n /**\n * Resolve a service from the container\n */\n resolve<T>(token: symbol): T {\n try {\n return this._container.resolve(token)\n } catch (error) {\n const handler = this._container.resolve<ExceptionHandler>(DI_TOKENS.ExceptionHandler)\n const ctx = createCliExceptionContext('resolve')\n // Fire-and-forget — reporting happens via waitUntil internally\n void handler.handle(error, ctx)\n throw error\n }\n }\n\n /**\n * Handle queue batch processing\n */\n async handleQueue(batch: MessageBatch, queueName: string): Promise<void> {\n await this.initializeHandlers()\n\n const firstMessage = batch.messages[0]?.body as QueueMessage | undefined\n const locale = firstMessage?.metadata?.locale ?? 'en'\n const mockRouterContext = this.createMockRouterContext(locale)\n\n await this._container.runInRequestScope(mockRouterContext, async (requestContainer) => {\n try {\n const queueManager = requestContainer.resolve<QueueManager>(DI_TOKENS.Queue)\n await queueManager.processBatch(queueName, batch)\n } catch (error) {\n const handler = requestContainer.resolve<ExceptionHandler>(DI_TOKENS.ExceptionHandler)\n await handler.handle(error, createQueueExceptionContext(queueName))\n throw error\n }\n })\n }\n\n /**\n * Handle scheduled cron trigger\n */\n async handleScheduled(controller: ScheduledController): Promise<void> {\n const mockRouterContext = this.createMockRouterContext('en')\n\n await this._container.runInRequestScope(mockRouterContext, async (requestContainer) => {\n try {\n await this.cronManager.executeScheduled(controller, requestContainer)\n } catch (error) {\n const handler = requestContainer.resolve<ExceptionHandler>(DI_TOKENS.ExceptionHandler)\n await handler.handle(error, createCronExceptionContext())\n throw error\n }\n })\n }\n\n /**\n * Create mock RouterContext for queue/cron/seeder processing\n */\n createMockRouterContext(locale = 'en'): RouterContext {\n return {\n getLocale: () => locale,\n setLocale: () => { /* no-op */ },\n getContainer: () => this._container,\n } as unknown as RouterContext\n }\n\n async shutdown(): Promise<void> {\n if (!this.initialized) return\n this.initialized = false\n\n await this.moduleRegistry.shutdown()\n\n const logger = this._container.resolve<LoggerService>(LOGGER_TOKENS.LoggerService)\n logger.info('Disposing container...')\n\n await this._container.dispose()\n }\n\n /**\n * Execute a command by name in a request-scoped container.\n */\n async handleCommand(name: string, input?: CommandInput): Promise<CommandResult> {\n await this.initializeRouting()\n const mockContext = this.createMockRouterContext('en')\n return this._container.runInRequestScope(mockContext, async () => {\n return this.quarry.call(name, input)\n })\n }\n\n private registerCommands(): void {\n // Built-in commands (always available)\n const builtinCommands: Constructor<Command>[] = [\n HelpCommand,\n DbSeedCommand, DbSeedListCommand,\n RouteListCommand, RouteTypesCommand, EventListCommand,\n ScheduleListCommand, QueueListCommand,\n McpServeCommand, McpToolsCommand, ApiCommand,\n ]\n for (const Cmd of builtinCommands) {\n injectable()(Cmd)\n this._container.register(Cmd, Cmd, Scope.Singleton)\n this.quarry.register(Cmd)\n }\n\n // User commands from modules\n const commands = this.moduleRegistry.getAllCommands()\n if (commands.length === 0) {\n return\n }\n\n for (const CommandClass of commands) {\n this.quarry.register(CommandClass as Constructor<Command>)\n }\n }\n\n private registerSeeders(): void {\n const seeders = this.moduleRegistry.getAllSeeders()\n if (seeders.length === 0) return\n const registry = this._container.resolve<SeederRegistry>(SEEDER_TOKENS.SeederRegistry)\n for (const SeederClass of seeders) {\n registry.register(SeederClass as Constructor<Seeder>)\n }\n }\n\n private registerQueueConsumers(): void {\n for (const ConsumerClass of this.moduleRegistry.getAllConsumers()) {\n const consumer = this._container.resolve(ConsumerClass) as IQueueConsumer\n this.consumerRegistry.register(consumer)\n }\n }\n\n private registerCronJobs(): void {\n for (const JobClass of this.moduleRegistry.getAllJobs()) {\n // Resolve temporarily to read the schedule property.\n // The delay() proxy on DB dependencies is created but never triggered\n // since we only access the schedule string.\n const tempJob = this._container.resolve(JobClass) as CronJob\n this.cronManager.registerJob(tempJob.schedule, JobClass as Constructor<CronJob>)\n }\n }\n\n /**\n * Auto-wire `@Listener()` classes with the EventRegistry.\n */\n private registerEventListeners(): void {\n const listeners = this.moduleRegistry.getAllListeners()\n if (listeners.length === 0) {\n return\n }\n\n const eventRegistry = this._container.resolve<EventRegistry>(DI_TOKENS.EventRegistry)\n\n for (const ListenerClass of listeners) {\n const instance = this._container.resolve(ListenerClass) as Record<string, ((...args: unknown[]) => unknown)>\n const handlers = getListenerHandlers(ListenerClass)\n\n for (const { methodName, event, options } of handlers) {\n eventRegistry.on(event, instance[methodName].bind(instance) as EventHandler, options)\n }\n }\n }\n\n /**\n * Register LoggerService and dependencies\n */\n private registerLoggerService(): void {\n const logLevel = this.appConfig.logging?.level ?? LogLevel.INFO\n const formatter = this.appConfig.logging?.formatter ?? 'json'\n\n this._container.registerValue(LOGGER_TOKENS.LogLevelOptions, logLevel)\n\n this._container\n .when(() => formatter === 'pretty')\n .use(LOGGER_TOKENS.Formatter)\n .give(PrettyFormatter)\n .otherwise(JsonFormatter)\n\n this._container.registerSingleton(LOGGER_TOKENS.ConsoleTransport, ConsoleTransport)\n this._container.registerFactory(LOGGER_TOKENS.Transports, (c) => [c.resolve(LOGGER_TOKENS.ConsoleTransport)])\n this._container.registerSingleton(LOGGER_TOKENS.LoggerService, LoggerService)\n }\n\n /**\n * Register core services with explicit scope\n */\n private registerCoreServices(): void {\n this._container.registerSingleton(DI_TOKENS.Cron, CronManager)\n this._container.registerSingleton(\n DI_TOKENS.ExceptionHandler,\n (this.appConfig.exceptionHandler ?? DefaultExceptionHandler) as Constructor,\n )\n this._container.registerSingleton(DI_TOKENS.EventRegistry, EventRegistry)\n this._container.registerSingleton(DI_TOKENS.Quarry, QuarryRegistry)\n this._container.registerValue(SEEDER_TOKENS.SeederRegistry, new SeederRegistry(this))\n }\n\n /**\n * Initialize the ExceptionHandler: call register(), then module onException hooks.\n */\n private initializeExceptionHandler(): void {\n const handler = this._container.resolve<ExceptionHandler>(DI_TOKENS.ExceptionHandler)\n handler.register()\n this.moduleRegistry.configureExceptionHandlers(handler)\n }\n}\n","import 'reflect-metadata'\n\nimport { Application, type ApplicationConfig } from './application'\nimport type { StratalEnv } from './env'\nimport { StratalNotInitializedError } from './errors'\nimport type { HonoApp } from './router/hono-app'\n\n/**\n * Stratal — Hono-style entry point for Cloudflare Workers.\n *\n * Eagerly bootstraps the Application at construction time, dynamically\n * importing `cloudflare:workers` for env and waitUntil.\n *\n * @example\n * ```typescript\n * import { Stratal } from 'stratal'\n * import { AppModule } from './app.module'\n *\n * export default new Stratal({ module: AppModule })\n * ```\n */\nexport class Stratal<Env extends StratalEnv = StratalEnv> {\n private app: Application | null = null\n private initPromise: Promise<Application>\n\n private static _application: Promise<Application> | null = null\n private static _generation = 0\n private static _previousInstance: Stratal | null = null\n\n constructor(config: ApplicationConfig) {\n this.fetch = this.fetch.bind(this)\n this.queue = this.queue.bind(this)\n this.scheduled = this.scheduled.bind(this)\n\n // Invalidate any in-flight initialization from a previous instance (Vite HMR reload)\n const generation = ++Stratal._generation\n\n if (Stratal._previousInstance) {\n void Stratal._previousInstance.shutdown()\n }\n Stratal._previousInstance = this\n\n this.initPromise = this.prepareApp(config, generation)\n Stratal._application = this.initPromise\n }\n\n async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {\n const app = await this.ensureReady()\n const hono = await app.ensureHono()\n return hono.fetch(request, env, ctx)\n }\n\n async queue(batch: MessageBatch): Promise<void> {\n const app = await this.ensureReady()\n return app.handleQueue(batch, batch.queue)\n }\n\n async scheduled(controller: ScheduledController): Promise<void> {\n const app = await this.ensureReady()\n return app.handleScheduled(controller)\n }\n\n get hono(): Promise<HonoApp> {\n return this.initPromise.then(app => app.ensureHono())\n }\n\n async shutdown(): Promise<void> {\n try { this.app = await this.initPromise } catch { /* ignore */ }\n if (this.app) {\n await this.app.shutdown()\n this.app = null\n }\n }\n\n /**\n * @internal\n * Resolves the Application instance from the static singleton.\n * Used by worker base classes (DurableObject, Workflow, WorkerEntrypoint)\n * to access the DI container without going through Cloudflare RPC.\n */\n static resolveApplication(): Promise<Application> {\n if (!Stratal._application) {\n throw new StratalNotInitializedError()\n }\n return Stratal._application\n }\n\n private async ensureReady(): Promise<Application> {\n this.app ??= await this.initPromise;\n return this.app\n }\n\n private async prepareApp(config: ApplicationConfig, generation: number): Promise<Application> {\n const { env, waitUntil } = await import('cloudflare:workers')\n\n // After async import, check if a newer instance has replaced us (Vite HMR reload)\n if (generation !== Stratal._generation) {\n return new Promise<Application>(() => {\n //\n }) // Never resolves — avoids cross-request promise warning\n }\n\n const app = new Application({ ...config, env: env as Env, ctx: { waitUntil } })\n await app.initialize()\n\n // Check again after initialization completes\n if (generation !== Stratal._generation) {\n await app.shutdown()\n return new Promise<Application>(() => {\n //\n })\n }\n\n return app\n }\n}\n"],"mappings":"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"}
@@ -16,4 +16,4 @@
16
16
  type Constructor<T = object> = new (...args: any[]) => T;
17
17
  //#endregion
18
18
  export { Constructor as t };
19
- //# sourceMappingURL=types-DIWemRad.d.mts.map
19
+ //# sourceMappingURL=types-cySNS_lp.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-cySNS_lp.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;AAeA;;;;;;;;;;;;;KAAY,WAAA,uBAAkC,IAAA,YAAgB,CAAA"}
@@ -155,4 +155,4 @@ function formatTable(rows) {
155
155
  //#endregion
156
156
  export { usage_generator_exports as n, generateUsage as t };
157
157
 
158
- //# sourceMappingURL=usage-generator-MBcRo0Q2.mjs.map
158
+ //# sourceMappingURL=usage-generator-BUdlhnCK.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"usage-generator-MBcRo0Q2.mjs","names":[],"sources":["../src/quarry/usage-generator.ts"],"sourcesContent":["import { bold, cyan, dim, dimWhite, yellow } from './colors'\nimport type { ParsedSignature } from './types'\n\n/**\n * Generate formatted help/usage text from a parsed signature.\n *\n * Pure function, edge-compatible.\n */\nexport function generateUsage(signature: ParsedSignature, description?: string): string {\n const lines: string[] = []\n\n // Usage line\n lines.push(`${bold('Usage:')} ${bold(cyan('quarry ' + buildUsageLine(signature)))}`)\n\n // Description\n if (description) {\n lines.push('')\n lines.push(description)\n }\n\n // Arguments section\n if (signature.arguments.length > 0) {\n lines.push('')\n lines.push(bold(yellow('Arguments:')))\n const argRows = signature.arguments.map((arg) => {\n const label = arg.name\n const parts: string[] = []\n\n if (arg.description) parts.push(arg.description)\n\n if (arg.isArray) {\n parts.push('(variadic)')\n } else if (arg.required) {\n parts.push('(required)')\n } else if (arg.default !== undefined) {\n parts.push(dim(`(default: ${arg.default})`))\n } else {\n parts.push('(optional)')\n }\n\n return [label, parts.join(' ')] as const\n })\n lines.push(...formatTable(argRows))\n }\n\n // Options section\n if (signature.options.length > 0) {\n lines.push('')\n lines.push(bold(yellow('Options:')))\n const optRows = signature.options.map((opt) => {\n const flagParts: string[] = []\n if (opt.alias) flagParts.push(`-${opt.alias},`)\n flagParts.push(`--${opt.name}`)\n\n const label = flagParts.join(' ')\n const parts: string[] = []\n\n if (opt.description) parts.push(opt.description)\n if (!opt.isFlag && !opt.description) parts.push('Accepts a value')\n if (opt.isFlag && !opt.description) parts.push('Boolean flag')\n if (opt.isArray) parts.push('(multiple)')\n if (opt.default !== undefined) parts.push(dim(`(default: ${opt.default})`))\n\n return [label, parts.join(' ')] as const\n })\n lines.push(...formatTable(optRows))\n }\n\n return lines.join('\\n')\n}\n\nexport interface ListingOptions {\n binaryName?: string\n binaryLabel?: string\n binaryVersion?: string\n}\n\n/**\n * Generate a compact command listing with visual hierarchy.\n */\nexport function generateListing(\n commands: { name: string; description?: string; aliases: string[] }[],\n signatures: Map<string, ParsedSignature>,\n options?: ListingOptions,\n): string {\n const bin = options?.binaryName ?? 'quarry'\n const label = options?.binaryLabel ?? 'Quarry CLI'\n const version = options?.binaryVersion\n\n const lines: string[] = []\n\n // Header\n lines.push(bold(`${label}${version ? ` v${version}` : ''}`))\n lines.push('')\n\n // Usage\n lines.push(bold(yellow('Usage')))\n lines.push(` $ ${bin} <command> [options]`)\n lines.push('')\n\n // Commands\n if (commands.length === 0) {\n lines.push('No registered commands.')\n return lines.join('\\n')\n }\n\n lines.push(bold(yellow('Commands')))\n\n const termWidth = typeof process !== 'undefined'\n ? (process.stdout.columns as number | undefined) ?? 80\n : 80\n\n for (let i = 0; i < commands.length; i++) {\n const cmd = commands[i]\n const sig = signatures.get(cmd.name)\n const sigParts: string[] = [cyan(cmd.name)]\n\n if (cmd.aliases.length > 0) {\n sigParts.push(cyan(`(alias: ${cmd.aliases.join(', ')})`))\n }\n\n if (sig) {\n const inlineParts: string[] = []\n for (const arg of sig.arguments) {\n inlineParts.push(formatArgPlaceholder(arg))\n }\n\n for (const opt of sig.options) {\n const flagStr = opt.alias ? `-${opt.alias},--${opt.name}` : `--${opt.name}`\n inlineParts.push(`[${flagStr}]`)\n }\n\n if (inlineParts.length > 0) {\n sigParts.push(dim(inlineParts.join(' ')))\n }\n }\n\n const sigLine = ' ' + sigParts.join(' ')\n lines.push(...wrapLine(sigLine, termWidth, ' '))\n\n if (cmd.description) {\n lines.push(` ${dimWhite(cmd.description)}`)\n }\n\n if (i < commands.length - 1) {\n lines.push('')\n }\n }\n\n lines.push('')\n\n // Footer\n lines.push(dimWhite(`Run ${bin} help <command> for detailed information.`))\n\n return lines.join('\\n')\n}\n\n/** Format a single argument into its placeholder representation (e.g. `<name>`, `[name=default]`). */\nfunction formatArgPlaceholder(arg: ParsedSignature['arguments'][number]): string {\n if (arg.isArray) return `<${arg.name}...>`\n if (arg.required) return `<${arg.name}>`\n if (arg.default !== undefined) return `[${arg.name}=${arg.default}]`\n return `[${arg.name}]`\n}\n\n/** Build the inline usage line showing the command name with argument and option placeholders. */\nfunction buildUsageLine(signature: ParsedSignature): string {\n const parts = [signature.name]\n\n for (const arg of signature.arguments) {\n parts.push(formatArgPlaceholder(arg))\n }\n\n for (const opt of signature.options) {\n const flagStr = opt.alias ? `-${opt.alias},--${opt.name}` : `--${opt.name}`\n\n if (opt.isFlag) {\n parts.push(`[${flagStr}]`)\n } else if (opt.isArray) {\n parts.push(`[${flagStr} <value...>]`)\n } else {\n parts.push(`[${flagStr} <value>]`)\n }\n }\n\n return parts.join(' ')\n}\n\n// eslint-disable-next-line no-control-regex\nconst ANSI_RE = /\\x1b\\[[0-9;]*m/g\n\n/** Remove ANSI escape sequences from a string. */\nfunction stripAnsi(s: string): string {\n return s.replace(ANSI_RE, '')\n}\n\n// eslint-disable-next-line no-control-regex\nconst TOKEN_RE = /(?:\\x1b\\[[0-9;]*m)*[^\\s\\x1b]+(?:\\x1b\\[[0-9;]*m)*/g\n\n/** Wrap a single line at word boundaries, preserving ANSI codes across wrapped segments. */\nfunction wrapLine(text: string, maxWidth: number, continuationIndent: string): string[] {\n const visibleLen = stripAnsi(text).length\n if (visibleLen <= maxWidth) return [text]\n\n const tokens = text.match(TOKEN_RE) ?? [text]\n\n const lines: string[] = []\n let currentLine = ''\n let currentVisibleLen = 0\n\n for (const token of tokens) {\n const tokenVisible = stripAnsi(token).length\n const separator = currentLine === '' ? '' : ' '\n const separatorLen = separator.length\n\n if (currentLine !== '' && currentVisibleLen + separatorLen + tokenVisible > maxWidth) {\n lines.push(currentLine)\n currentLine = continuationIndent + token\n currentVisibleLen = continuationIndent.length + tokenVisible\n } else {\n currentLine += separator + token\n currentVisibleLen += separatorLen + tokenVisible\n }\n }\n\n if (currentLine !== '') {\n lines.push(currentLine)\n }\n\n return lines\n}\n\n/** Format label/description pairs into aligned two-column table rows. */\nfunction formatTable(rows: readonly (readonly [string, string])[]): string[] {\n if (rows.length === 0) return []\n\n const maxLabel = Math.max(...rows.map(([label]) => stripAnsi(label).length))\n const padding = 4\n\n return rows.map(([label, desc]) => {\n const visibleLen = stripAnsi(label).length\n const pad = ' '.repeat(maxLabel - visibleLen + padding)\n return ` ${label}${pad}${desc}`\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"usage-generator-BUdlhnCK.mjs","names":[],"sources":["../src/quarry/usage-generator.ts"],"sourcesContent":["import { bold, cyan, dim, dimWhite, yellow } from './colors'\nimport type { ParsedSignature } from './types'\n\n/**\n * Generate formatted help/usage text from a parsed signature.\n *\n * Pure function, edge-compatible.\n */\nexport function generateUsage(signature: ParsedSignature, description?: string): string {\n const lines: string[] = []\n\n // Usage line\n lines.push(`${bold('Usage:')} ${bold(cyan('quarry ' + buildUsageLine(signature)))}`)\n\n // Description\n if (description) {\n lines.push('')\n lines.push(description)\n }\n\n // Arguments section\n if (signature.arguments.length > 0) {\n lines.push('')\n lines.push(bold(yellow('Arguments:')))\n const argRows = signature.arguments.map((arg) => {\n const label = arg.name\n const parts: string[] = []\n\n if (arg.description) parts.push(arg.description)\n\n if (arg.isArray) {\n parts.push('(variadic)')\n } else if (arg.required) {\n parts.push('(required)')\n } else if (arg.default !== undefined) {\n parts.push(dim(`(default: ${arg.default})`))\n } else {\n parts.push('(optional)')\n }\n\n return [label, parts.join(' ')] as const\n })\n lines.push(...formatTable(argRows))\n }\n\n // Options section\n if (signature.options.length > 0) {\n lines.push('')\n lines.push(bold(yellow('Options:')))\n const optRows = signature.options.map((opt) => {\n const flagParts: string[] = []\n if (opt.alias) flagParts.push(`-${opt.alias},`)\n flagParts.push(`--${opt.name}`)\n\n const label = flagParts.join(' ')\n const parts: string[] = []\n\n if (opt.description) parts.push(opt.description)\n if (!opt.isFlag && !opt.description) parts.push('Accepts a value')\n if (opt.isFlag && !opt.description) parts.push('Boolean flag')\n if (opt.isArray) parts.push('(multiple)')\n if (opt.default !== undefined) parts.push(dim(`(default: ${opt.default})`))\n\n return [label, parts.join(' ')] as const\n })\n lines.push(...formatTable(optRows))\n }\n\n return lines.join('\\n')\n}\n\nexport interface ListingOptions {\n binaryName?: string\n binaryLabel?: string\n binaryVersion?: string\n}\n\n/**\n * Generate a compact command listing with visual hierarchy.\n */\nexport function generateListing(\n commands: { name: string; description?: string; aliases: string[] }[],\n signatures: Map<string, ParsedSignature>,\n options?: ListingOptions,\n): string {\n const bin = options?.binaryName ?? 'quarry'\n const label = options?.binaryLabel ?? 'Quarry CLI'\n const version = options?.binaryVersion\n\n const lines: string[] = []\n\n // Header\n lines.push(bold(`${label}${version ? ` v${version}` : ''}`))\n lines.push('')\n\n // Usage\n lines.push(bold(yellow('Usage')))\n lines.push(` $ ${bin} <command> [options]`)\n lines.push('')\n\n // Commands\n if (commands.length === 0) {\n lines.push('No registered commands.')\n return lines.join('\\n')\n }\n\n lines.push(bold(yellow('Commands')))\n\n const termWidth = typeof process !== 'undefined'\n ? (process.stdout.columns as number | undefined) ?? 80\n : 80\n\n for (let i = 0; i < commands.length; i++) {\n const cmd = commands[i]\n const sig = signatures.get(cmd.name)\n const sigParts: string[] = [cyan(cmd.name)]\n\n if (cmd.aliases.length > 0) {\n sigParts.push(cyan(`(alias: ${cmd.aliases.join(', ')})`))\n }\n\n if (sig) {\n const inlineParts: string[] = []\n for (const arg of sig.arguments) {\n inlineParts.push(formatArgPlaceholder(arg))\n }\n\n for (const opt of sig.options) {\n const flagStr = opt.alias ? `-${opt.alias},--${opt.name}` : `--${opt.name}`\n inlineParts.push(`[${flagStr}]`)\n }\n\n if (inlineParts.length > 0) {\n sigParts.push(dim(inlineParts.join(' ')))\n }\n }\n\n const sigLine = ' ' + sigParts.join(' ')\n lines.push(...wrapLine(sigLine, termWidth, ' '))\n\n if (cmd.description) {\n lines.push(` ${dimWhite(cmd.description)}`)\n }\n\n if (i < commands.length - 1) {\n lines.push('')\n }\n }\n\n lines.push('')\n\n // Footer\n lines.push(dimWhite(`Run ${bin} help <command> for detailed information.`))\n\n return lines.join('\\n')\n}\n\n/** Format a single argument into its placeholder representation (e.g. `<name>`, `[name=default]`). */\nfunction formatArgPlaceholder(arg: ParsedSignature['arguments'][number]): string {\n if (arg.isArray) return `<${arg.name}...>`\n if (arg.required) return `<${arg.name}>`\n if (arg.default !== undefined) return `[${arg.name}=${arg.default}]`\n return `[${arg.name}]`\n}\n\n/** Build the inline usage line showing the command name with argument and option placeholders. */\nfunction buildUsageLine(signature: ParsedSignature): string {\n const parts = [signature.name]\n\n for (const arg of signature.arguments) {\n parts.push(formatArgPlaceholder(arg))\n }\n\n for (const opt of signature.options) {\n const flagStr = opt.alias ? `-${opt.alias},--${opt.name}` : `--${opt.name}`\n\n if (opt.isFlag) {\n parts.push(`[${flagStr}]`)\n } else if (opt.isArray) {\n parts.push(`[${flagStr} <value...>]`)\n } else {\n parts.push(`[${flagStr} <value>]`)\n }\n }\n\n return parts.join(' ')\n}\n\n// eslint-disable-next-line no-control-regex\nconst ANSI_RE = /\\x1b\\[[0-9;]*m/g\n\n/** Remove ANSI escape sequences from a string. */\nfunction stripAnsi(s: string): string {\n return s.replace(ANSI_RE, '')\n}\n\n// eslint-disable-next-line no-control-regex\nconst TOKEN_RE = /(?:\\x1b\\[[0-9;]*m)*[^\\s\\x1b]+(?:\\x1b\\[[0-9;]*m)*/g\n\n/** Wrap a single line at word boundaries, preserving ANSI codes across wrapped segments. */\nfunction wrapLine(text: string, maxWidth: number, continuationIndent: string): string[] {\n const visibleLen = stripAnsi(text).length\n if (visibleLen <= maxWidth) return [text]\n\n const tokens = text.match(TOKEN_RE) ?? [text]\n\n const lines: string[] = []\n let currentLine = ''\n let currentVisibleLen = 0\n\n for (const token of tokens) {\n const tokenVisible = stripAnsi(token).length\n const separator = currentLine === '' ? '' : ' '\n const separatorLen = separator.length\n\n if (currentLine !== '' && currentVisibleLen + separatorLen + tokenVisible > maxWidth) {\n lines.push(currentLine)\n currentLine = continuationIndent + token\n currentVisibleLen = continuationIndent.length + tokenVisible\n } else {\n currentLine += separator + token\n currentVisibleLen += separatorLen + tokenVisible\n }\n }\n\n if (currentLine !== '') {\n lines.push(currentLine)\n }\n\n return lines\n}\n\n/** Format label/description pairs into aligned two-column table rows. */\nfunction formatTable(rows: readonly (readonly [string, string])[]): string[] {\n if (rows.length === 0) return []\n\n const maxLabel = Math.max(...rows.map(([label]) => stripAnsi(label).length))\n const padding = 4\n\n return rows.map(([label, desc]) => {\n const visibleLen = stripAnsi(label).length\n const pad = ' '.repeat(maxLabel - visibleLen + padding)\n return ` ${label}${pad}${desc}`\n })\n}\n"],"mappings":";;;;;;;;;;;;AAQA,SAAgB,cAAc,WAA4B,aAA8B;CACtF,MAAM,QAAkB,EAAE;CAG1B,MAAM,KAAK,GAAG,KAAK,SAAS,CAAC,GAAG,KAAK,KAAK,YAAY,eAAe,UAAU,CAAC,CAAC,GAAG;CAGpF,IAAI,aAAa;EACf,MAAM,KAAK,GAAG;EACd,MAAM,KAAK,YAAY;;CAIzB,IAAI,UAAU,UAAU,SAAS,GAAG;EAClC,MAAM,KAAK,GAAG;EACd,MAAM,KAAK,KAAK,OAAO,aAAa,CAAC,CAAC;EACtC,MAAM,UAAU,UAAU,UAAU,KAAK,QAAQ;GAC/C,MAAM,QAAQ,IAAI;GAClB,MAAM,QAAkB,EAAE;GAE1B,IAAI,IAAI,aAAa,MAAM,KAAK,IAAI,YAAY;GAEhD,IAAI,IAAI,SACN,MAAM,KAAK,aAAa;QACnB,IAAI,IAAI,UACb,MAAM,KAAK,aAAa;QACnB,IAAI,IAAI,YAAY,KAAA,GACzB,MAAM,KAAK,IAAI,aAAa,IAAI,QAAQ,GAAG,CAAC;QAE5C,MAAM,KAAK,aAAa;GAG1B,OAAO,CAAC,OAAO,MAAM,KAAK,IAAI,CAAC;IAC/B;EACF,MAAM,KAAK,GAAG,YAAY,QAAQ,CAAC;;CAIrC,IAAI,UAAU,QAAQ,SAAS,GAAG;EAChC,MAAM,KAAK,GAAG;EACd,MAAM,KAAK,KAAK,OAAO,WAAW,CAAC,CAAC;EACpC,MAAM,UAAU,UAAU,QAAQ,KAAK,QAAQ;GAC7C,MAAM,YAAsB,EAAE;GAC9B,IAAI,IAAI,OAAO,UAAU,KAAK,IAAI,IAAI,MAAM,GAAG;GAC/C,UAAU,KAAK,KAAK,IAAI,OAAO;GAE/B,MAAM,QAAQ,UAAU,KAAK,IAAI;GACjC,MAAM,QAAkB,EAAE;GAE1B,IAAI,IAAI,aAAa,MAAM,KAAK,IAAI,YAAY;GAChD,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,aAAa,MAAM,KAAK,kBAAkB;GAClE,IAAI,IAAI,UAAU,CAAC,IAAI,aAAa,MAAM,KAAK,eAAe;GAC9D,IAAI,IAAI,SAAS,MAAM,KAAK,aAAa;GACzC,IAAI,IAAI,YAAY,KAAA,GAAW,MAAM,KAAK,IAAI,aAAa,IAAI,QAAQ,GAAG,CAAC;GAE3E,OAAO,CAAC,OAAO,MAAM,KAAK,IAAI,CAAC;IAC/B;EACF,MAAM,KAAK,GAAG,YAAY,QAAQ,CAAC;;CAGrC,OAAO,MAAM,KAAK,KAAK;;;;;AAYzB,SAAgB,gBACd,UACA,YACA,SACQ;CACR,MAAM,MAAM,SAAS,cAAc;CACnC,MAAM,QAAQ,SAAS,eAAe;CACtC,MAAM,UAAU,SAAS;CAEzB,MAAM,QAAkB,EAAE;CAG1B,MAAM,KAAK,KAAK,GAAG,QAAQ,UAAU,KAAK,YAAY,KAAK,CAAC;CAC5D,MAAM,KAAK,GAAG;CAGd,MAAM,KAAK,KAAK,OAAO,QAAQ,CAAC,CAAC;CACjC,MAAM,KAAK,OAAO,IAAI,sBAAsB;CAC5C,MAAM,KAAK,GAAG;CAGd,IAAI,SAAS,WAAW,GAAG;EACzB,MAAM,KAAK,0BAA0B;EACrC,OAAO,MAAM,KAAK,KAAK;;CAGzB,MAAM,KAAK,KAAK,OAAO,WAAW,CAAC,CAAC;CAEpC,MAAM,YAAY,OAAO,YAAY,cAChC,QAAQ,OAAO,WAAkC,KAClD;CAEJ,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,MAAM,SAAS;EACrB,MAAM,MAAM,WAAW,IAAI,IAAI,KAAK;EACpC,MAAM,WAAqB,CAAC,KAAK,IAAI,KAAK,CAAC;EAE3C,IAAI,IAAI,QAAQ,SAAS,GACvB,SAAS,KAAK,KAAK,WAAW,IAAI,QAAQ,KAAK,KAAK,CAAC,GAAG,CAAC;EAG3D,IAAI,KAAK;GACP,MAAM,cAAwB,EAAE;GAChC,KAAK,MAAM,OAAO,IAAI,WACpB,YAAY,KAAK,qBAAqB,IAAI,CAAC;GAG7C,KAAK,MAAM,OAAO,IAAI,SAAS;IAC7B,MAAM,UAAU,IAAI,QAAQ,IAAI,IAAI,MAAM,KAAK,IAAI,SAAS,KAAK,IAAI;IACrE,YAAY,KAAK,IAAI,QAAQ,GAAG;;GAGlC,IAAI,YAAY,SAAS,GACvB,SAAS,KAAK,IAAI,YAAY,KAAK,IAAI,CAAC,CAAC;;EAI7C,MAAM,UAAU,OAAO,SAAS,KAAK,IAAI;EACzC,MAAM,KAAK,GAAG,SAAS,SAAS,WAAW,SAAS,CAAC;EAErD,IAAI,IAAI,aACN,MAAM,KAAK,OAAO,SAAS,IAAI,YAAY,GAAG;EAGhD,IAAI,IAAI,SAAS,SAAS,GACxB,MAAM,KAAK,GAAG;;CAIlB,MAAM,KAAK,GAAG;CAGd,MAAM,KAAK,SAAS,OAAO,IAAI,2CAA2C,CAAC;CAE3E,OAAO,MAAM,KAAK,KAAK;;;AAIzB,SAAS,qBAAqB,KAAmD;CAC/E,IAAI,IAAI,SAAS,OAAO,IAAI,IAAI,KAAK;CACrC,IAAI,IAAI,UAAU,OAAO,IAAI,IAAI,KAAK;CACtC,IAAI,IAAI,YAAY,KAAA,GAAW,OAAO,IAAI,IAAI,KAAK,GAAG,IAAI,QAAQ;CAClE,OAAO,IAAI,IAAI,KAAK;;;AAItB,SAAS,eAAe,WAAoC;CAC1D,MAAM,QAAQ,CAAC,UAAU,KAAK;CAE9B,KAAK,MAAM,OAAO,UAAU,WAC1B,MAAM,KAAK,qBAAqB,IAAI,CAAC;CAGvC,KAAK,MAAM,OAAO,UAAU,SAAS;EACnC,MAAM,UAAU,IAAI,QAAQ,IAAI,IAAI,MAAM,KAAK,IAAI,SAAS,KAAK,IAAI;EAErE,IAAI,IAAI,QACN,MAAM,KAAK,IAAI,QAAQ,GAAG;OACrB,IAAI,IAAI,SACb,MAAM,KAAK,IAAI,QAAQ,cAAc;OAErC,MAAM,KAAK,IAAI,QAAQ,WAAW;;CAItC,OAAO,MAAM,KAAK,IAAI;;AAIxB,MAAM,UAAU;;AAGhB,SAAS,UAAU,GAAmB;CACpC,OAAO,EAAE,QAAQ,SAAS,GAAG;;AAI/B,MAAM,WAAW;;AAGjB,SAAS,SAAS,MAAc,UAAkB,oBAAsC;CAEtF,IADmB,UAAU,KAAK,CAAC,UACjB,UAAU,OAAO,CAAC,KAAK;CAEzC,MAAM,SAAS,KAAK,MAAM,SAAS,IAAI,CAAC,KAAK;CAE7C,MAAM,QAAkB,EAAE;CAC1B,IAAI,cAAc;CAClB,IAAI,oBAAoB;CAExB,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,eAAe,UAAU,MAAM,CAAC;EACtC,MAAM,YAAY,gBAAgB,KAAK,KAAK;EAC5C,MAAM,eAAe,UAAU;EAE/B,IAAI,gBAAgB,MAAM,oBAAoB,eAAe,eAAe,UAAU;GACpF,MAAM,KAAK,YAAY;GACvB,cAAc,qBAAqB;GACnC,oBAAoB,mBAAmB,SAAS;SAC3C;GACL,eAAe,YAAY;GAC3B,qBAAqB,eAAe;;;CAIxC,IAAI,gBAAgB,IAClB,MAAM,KAAK,YAAY;CAGzB,OAAO;;;AAIT,SAAS,YAAY,MAAwD;CAC3E,IAAI,KAAK,WAAW,GAAG,OAAO,EAAE;CAEhC,MAAM,WAAW,KAAK,IAAI,GAAG,KAAK,KAAK,CAAC,WAAW,UAAU,MAAM,CAAC,OAAO,CAAC;CAC5E,MAAM,UAAU;CAEhB,OAAO,KAAK,KAAK,CAAC,OAAO,UAAU;EACjC,MAAM,aAAa,UAAU,MAAM,CAAC;EAEpC,OAAO,KAAK,QADA,IAAI,OAAO,WAAW,aAAa,QACxB,GAAG;GAC1B"}