stratal 0.0.20 → 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 (113) hide show
  1. package/dist/base-email.provider-CfQCA08m.mjs.map +1 -1
  2. package/dist/bin/cloudflare-workers-loader.mjs.map +1 -1
  3. package/dist/bin/quarry.mjs.map +1 -1
  4. package/dist/cache/index.d.mts +1 -1
  5. package/dist/cache/index.mjs +5 -3
  6. package/dist/cache/index.mjs.map +1 -1
  7. package/dist/command-BgSlsS4M.mjs.map +1 -1
  8. package/dist/{command-Bu-PjJrX.d.mts → command-Cmmf0oHX.d.mts} +2 -2
  9. package/dist/{command-Bu-PjJrX.d.mts.map → command-Cmmf0oHX.d.mts.map} +1 -1
  10. package/dist/config/index.d.mts +1 -1
  11. package/dist/config/index.mjs +5 -3
  12. package/dist/config/index.mjs.map +1 -1
  13. package/dist/{controller.decorator-DQzenvSN.mjs → controller.decorator-B9vwn0zK.mjs} +3 -3
  14. package/dist/{controller.decorator-DQzenvSN.mjs.map → controller.decorator-B9vwn0zK.mjs.map} +1 -1
  15. package/dist/cron/index.d.mts +2 -2
  16. package/dist/cron/index.mjs +1 -1
  17. package/dist/{cron-manager-BEsH1mjW.d.mts → cron-manager-CmTimEjf.d.mts} +2 -2
  18. package/dist/cron-manager-CmTimEjf.d.mts.map +1 -0
  19. package/dist/{cron-manager-7Symz_TE.mjs → cron-manager-DQSK8uoV.mjs} +10 -4
  20. package/dist/cron-manager-DQSK8uoV.mjs.map +1 -0
  21. package/dist/di/index.d.mts +1 -1
  22. package/dist/di/index.mjs +2 -2
  23. package/dist/email/index.d.mts +2 -2
  24. package/dist/email/index.mjs +12 -7
  25. package/dist/email/index.mjs.map +1 -1
  26. package/dist/errors/index.d.mts +1 -1
  27. package/dist/errors/index.mjs +1 -1
  28. package/dist/{errors-BdyV5PnY.mjs → errors-COW9-Mar.mjs} +16 -2
  29. package/dist/{errors-BdyV5PnY.mjs.map → errors-COW9-Mar.mjs.map} +1 -1
  30. package/dist/{errors-Da3Pz2X7.mjs → errors-ORxu1-Bb.mjs} +2 -2
  31. package/dist/{errors-Da3Pz2X7.mjs.map → errors-ORxu1-Bb.mjs.map} +1 -1
  32. package/dist/events/index.mjs +1 -1
  33. package/dist/{events-COKixqnG.mjs → events-CzCV8jI8.mjs} +4 -2
  34. package/dist/{events-COKixqnG.mjs.map → events-CzCV8jI8.mjs.map} +1 -1
  35. package/dist/{gateway-context-CdJjpUCW.mjs → gateway-context-CXmXtaUP.mjs} +4 -3
  36. package/dist/{gateway-context-CdJjpUCW.mjs.map → gateway-context-CXmXtaUP.mjs.map} +1 -1
  37. package/dist/guards/index.d.mts +1 -1
  38. package/dist/guards/index.mjs +1 -1
  39. package/dist/{guards-DUk_Kzst.mjs → guards-DU1_J9YA.mjs} +2 -1
  40. package/dist/{guards-DUk_Kzst.mjs.map → guards-DU1_J9YA.mjs.map} +1 -1
  41. package/dist/{http-method.decorator-DXwxAfb_.mjs → http-method.decorator-BrgHMdLQ.mjs} +2 -2
  42. package/dist/{http-method.decorator-DXwxAfb_.mjs.map → http-method.decorator-BrgHMdLQ.mjs.map} +1 -1
  43. package/dist/i18n/index.d.mts +1 -1
  44. package/dist/i18n/index.mjs +2 -2
  45. package/dist/{i18n.module-BBlNNlcG.mjs → i18n.module-CzXLW9Hy.mjs} +59 -19
  46. package/dist/i18n.module-CzXLW9Hy.mjs.map +1 -0
  47. package/dist/{index-D0US0X14.d.mts → index-ByOyTmqf.d.mts} +6 -3
  48. package/dist/{index-D0US0X14.d.mts.map → index-ByOyTmqf.d.mts.map} +1 -1
  49. package/dist/{index-CjaQ6_tZ.d.mts → index-DUzWs0z7.d.mts} +2 -2
  50. package/dist/{index-CjaQ6_tZ.d.mts.map → index-DUzWs0z7.d.mts.map} +1 -1
  51. package/dist/index.d.mts +1 -1
  52. package/dist/index.mjs +1 -1
  53. package/dist/is-command-C6a7WTPw.mjs.map +1 -1
  54. package/dist/is-seeder-CebjZCDn.mjs.map +1 -1
  55. package/dist/logger/index.mjs +1 -1
  56. package/dist/{logger-V6Ms3QnQ.mjs → logger-DlV7NtvD.mjs} +8 -4
  57. package/dist/{logger-V6Ms3QnQ.mjs.map → logger-DlV7NtvD.mjs.map} +1 -1
  58. package/dist/macroable-BmufBshB.mjs.map +1 -1
  59. package/dist/module/index.d.mts +1 -1
  60. package/dist/module/index.mjs +1 -1
  61. package/dist/{module-Dk2qTa77.mjs → module-BzLg57FK.mjs} +10 -4
  62. package/dist/{module-Dk2qTa77.mjs.map → module-BzLg57FK.mjs.map} +1 -1
  63. package/dist/openapi/index.d.mts +2 -2
  64. package/dist/openapi/index.mjs +1 -1
  65. package/dist/openapi-tools.service-Zs-Ewv7F.mjs.map +1 -1
  66. package/dist/{openapi.service-BLgvn3hJ.d.mts → openapi.service-Bt9bCIrd.d.mts} +2 -2
  67. package/dist/{openapi.service-BLgvn3hJ.d.mts.map → openapi.service-Bt9bCIrd.d.mts.map} +1 -1
  68. package/dist/quarry/index.d.mts +4 -4
  69. package/dist/quarry/index.mjs +1 -1
  70. package/dist/{quarry-registry-DNEej-Db.mjs → quarry-registry-BwY2hOxm.mjs} +15 -4
  71. package/dist/{quarry-registry-DNEej-Db.mjs.map → quarry-registry-BwY2hOxm.mjs.map} +1 -1
  72. package/dist/queue/index.d.mts +1 -1
  73. package/dist/queue/index.mjs +3 -2
  74. package/dist/queue/index.mjs.map +1 -1
  75. package/dist/{queue.module-BCdCiySt.mjs → queue.module-BhCjZp6H.mjs} +13 -4
  76. package/dist/{queue.module-BCdCiySt.mjs.map → queue.module-BhCjZp6H.mjs.map} +1 -1
  77. package/dist/{r2-storage.provider-Co6F0ZYV.mjs → r2-storage.provider-DuonKeYm.mjs} +5 -2
  78. package/dist/{r2-storage.provider-Co6F0ZYV.mjs.map → r2-storage.provider-DuonKeYm.mjs.map} +1 -1
  79. package/dist/{rate-limit.decorator--o6Q6p9w.mjs → rate-limit.decorator-6qzNcSOt.mjs} +2 -2
  80. package/dist/{rate-limit.decorator--o6Q6p9w.mjs.map → rate-limit.decorator-6qzNcSOt.mjs.map} +1 -1
  81. package/dist/rate-limiter/index.d.mts +1 -1
  82. package/dist/rate-limiter/index.mjs +13 -4
  83. package/dist/rate-limiter/index.mjs.map +1 -1
  84. package/dist/{resend.provider-M6qRLrcy.mjs → resend.provider-DB4IlFjG.mjs} +2 -1
  85. package/dist/{resend.provider-M6qRLrcy.mjs.map → resend.provider-DB4IlFjG.mjs.map} +1 -1
  86. package/dist/router/index.d.mts +1 -1
  87. package/dist/router/index.mjs +5 -5
  88. package/dist/seeder/index.d.mts +2 -2
  89. package/dist/seeder/index.mjs +1 -1
  90. package/dist/{seeder-CJAOHEIo.mjs → seeder-zoEfEw9i.mjs} +6 -3
  91. package/dist/{seeder-CJAOHEIo.mjs.map → seeder-zoEfEw9i.mjs.map} +1 -1
  92. package/dist/setup-CefZKV_e.mjs.map +1 -1
  93. package/dist/signed-url-BQPbv2In.mjs.map +1 -1
  94. package/dist/{smtp.provider-w0Ve52Xg.mjs → smtp.provider-B6D7zuWX.mjs} +2 -1
  95. package/dist/{smtp.provider-w0Ve52Xg.mjs.map → smtp.provider-B6D7zuWX.mjs.map} +1 -1
  96. package/dist/storage/index.d.mts +1 -1
  97. package/dist/storage/index.mjs +2 -2
  98. package/dist/storage/providers/index.mjs +1 -1
  99. package/dist/{storage-1zw-6Yiz.mjs → storage-D8CBP72Z.mjs} +13 -8
  100. package/dist/{storage-1zw-6Yiz.mjs.map → storage-D8CBP72Z.mjs.map} +1 -1
  101. package/dist/{stratal-DeEcGgdq.mjs → stratal-CNwpbSZl.mjs} +12 -10
  102. package/dist/{stratal-DeEcGgdq.mjs.map → stratal-CNwpbSZl.mjs.map} +1 -1
  103. package/dist/usage-generator-BUdlhnCK.mjs.map +1 -1
  104. package/dist/validation-DtJwAv7O.mjs.map +1 -1
  105. package/dist/websocket/index.d.mts +1 -1
  106. package/dist/websocket/index.mjs +1 -1
  107. package/dist/workers/index.d.mts +1 -1
  108. package/dist/workers/index.mjs +2 -2
  109. package/dist/workers/index.mjs.map +1 -1
  110. package/package.json +13 -13
  111. package/dist/cron-manager-7Symz_TE.mjs.map +0 -1
  112. package/dist/cron-manager-BEsH1mjW.d.mts.map +0 -1
  113. package/dist/i18n.module-BBlNNlcG.mjs.map +0 -1
@@ -1,9 +1,9 @@
1
- import { A as Scope } from "../errors-BdyV5PnY.mjs";
2
- import { a as __decorate, d as CONTAINER_TOKEN, f as DI_TOKENS, o as __decorateParam, p as Transient, s as __decorateMetadata } from "../logger-V6Ms3QnQ.mjs";
1
+ import { A as Scope } from "../errors-COW9-Mar.mjs";
2
+ import { a as __decorate, d as CONTAINER_TOKEN, f as DI_TOKENS, o as __decorateParam, p as Transient, s as __decorateMetadata } from "../logger-DlV7NtvD.mjs";
3
3
  import { t as Macroable } from "../macroable-BmufBshB.mjs";
4
- import { C as RATE_LIMITER_TOKENS, D as TooManyRequestsError, E as RateLimiterNotDefinedError, S as createThrottleMiddleware, T as RateLimiterNotConfiguredError, k as Module, w as RateLimiterModuleNotImportedError } from "../module-Dk2qTa77.mjs";
4
+ import { C as RATE_LIMITER_TOKENS, D as TooManyRequestsError, E as RateLimiterNotDefinedError, S as createThrottleMiddleware, T as RateLimiterNotConfiguredError, k as Module, w as RateLimiterModuleNotImportedError } from "../module-BzLg57FK.mjs";
5
5
  import { t as CACHE_TOKENS } from "../cache.tokens-B7Rw1C9Q.mjs";
6
- import { n as getRateLimits, t as RateLimit } from "../rate-limit.decorator--o6Q6p9w.mjs";
6
+ import { n as getRateLimits, t as RateLimit } from "../rate-limit.decorator-6qzNcSOt.mjs";
7
7
  import { inject } from "tsyringe";
8
8
  //#region src/rate-limiter/limit.ts
9
9
  /**
@@ -24,6 +24,9 @@ import { inject } from "tsyringe";
24
24
  * ```
25
25
  */
26
26
  var Limit = class Limit {
27
+ windowSeconds;
28
+ max;
29
+ disabled;
27
30
  _key;
28
31
  _customResponse;
29
32
  constructor(windowSeconds, max, disabled = false) {
@@ -77,6 +80,7 @@ var Limit = class Limit {
77
80
  //#endregion
78
81
  //#region src/rate-limiter/rate-limiter-registry.ts
79
82
  let RateLimiterRegistry = class RateLimiterRegistry extends Macroable {
83
+ store;
80
84
  resolvers = /* @__PURE__ */ new Map();
81
85
  constructor(store) {
82
86
  super();
@@ -197,6 +201,7 @@ RateLimiterRegistry = __decorate([
197
201
  * accuracy across edges.
198
202
  */
199
203
  var KvRateLimiterStore = class {
204
+ cache;
200
205
  constructor(cache) {
201
206
  this.cache = cache;
202
207
  }
@@ -248,6 +253,10 @@ var InMemoryRateLimiterStore = class {
248
253
  //#endregion
249
254
  //#region src/rate-limiter/stores/store-factory.ts
250
255
  let RateLimiterStoreFactory = class RateLimiterStoreFactory {
256
+ env;
257
+ cache;
258
+ container;
259
+ options;
251
260
  constructor(env, cache, container, options) {
252
261
  this.env = env;
253
262
  this.cache = cache;
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/rate-limiter/limit.ts","../../src/rate-limiter/rate-limiter-registry.ts","../../src/rate-limiter/stores/kv-store.ts","../../src/rate-limiter/stores/memory-store.ts","../../src/rate-limiter/stores/store-factory.ts","../../src/rate-limiter/rate-limiter.module.ts"],"sourcesContent":["import type { RouterContext } from '../router/router-context'\n\n/**\n * Standard rate-limit response headers passed to custom response handlers.\n * Keys are canonical HTTP header names; values are the stringified counts.\n */\nexport interface RateLimitHeaders {\n 'Retry-After': string\n 'X-RateLimit-Limit': string\n 'X-RateLimit-Remaining': string\n 'X-RateLimit-Reset': string\n}\n\n/**\n * Custom response handler invoked when a limit is exceeded and the user\n * supplied `.response()` on the {@link Limit}. Receives the request context\n * and the precomputed standard headers (which the handler can spread, drop,\n * or override).\n */\nexport type RateLimitResponseHandler = (\n ctx: RouterContext,\n headers: RateLimitHeaders,\n) => Response | Promise<Response>\n\n/**\n * A single rate limit window.\n *\n * Build via the static factories (`perSecond`, `perMinute`, `perMinutes`,\n * `perHour`, `perDay`, `none`). Chain `.by(key)` to scope per-actor and\n * `.response(handler)` to override the default 429 response.\n *\n * Returned (singly or as an array) by limiter resolvers registered via\n * `RateLimiterRegistry.for()`.\n *\n * @example\n * ```typescript\n * Limit.perMinute(60).by(ctx.header('cf-connecting-ip') ?? 'global')\n * Limit.perHour(100).by(userId)\n * Limit.none() // bypass\n * ```\n */\nexport class Limit {\n private _key?: string\n private _customResponse?: RateLimitResponseHandler\n\n private constructor(\n public readonly windowSeconds: number,\n public readonly max: number,\n public readonly disabled = false,\n ) {}\n\n static perSecond(max: number): Limit {\n return new Limit(1, max)\n }\n\n static perSeconds(seconds: number, max: number): Limit {\n return new Limit(seconds, max)\n }\n\n static perMinute(max: number): Limit {\n return new Limit(60, max)\n }\n\n static perMinutes(minutes: number, max: number): Limit {\n return new Limit(minutes * 60, max)\n }\n\n static perHour(max: number): Limit {\n return new Limit(60 * 60, max)\n }\n\n static perDay(max: number): Limit {\n return new Limit(24 * 60 * 60, max)\n }\n\n /** Bypass the limiter entirely for this request. */\n static none(): Limit {\n return new Limit(0, 0, true)\n }\n\n /** Scope this limit to a specific actor (user id, IP, tenant, etc.). */\n by(key: string | number): this {\n this._key = String(key)\n return this\n }\n\n /**\n * Override the default 429 response. The handler receives the standard\n * `RateLimitHeaders` so it can spread them onto its own Response or omit\n * them as it sees fit.\n */\n response(handler: RateLimitResponseHandler): this {\n this._customResponse = handler\n return this\n }\n\n get key(): string | undefined {\n return this._key\n }\n\n get customResponse(): RateLimitResponseHandler | undefined {\n return this._customResponse\n }\n}\n","import { inject } from 'tsyringe'\nimport { Transient } from '../di/decorators'\nimport { Macroable } from '../macroable'\nimport type { Next } from '../router/middleware.interface'\nimport type { RouterContext } from '../router/router-context'\nimport { RateLimiterNotDefinedError, TooManyRequestsError } from './errors'\nimport type { Limit, RateLimitHeaders } from './limit'\nimport { RATE_LIMITER_TOKENS } from './rate-limiter.tokens'\nimport type { IRateLimiterStore, RateLimitHit } from './stores/rate-limiter-store.interface'\n\n/**\n * Resolver function registered via {@link RateLimiterRegistry.for}. Receives\n * the request context and returns the limit (or limits) that apply to this\n * request. Return `Limit.none()` to bypass for the current request.\n */\nexport type LimitResolver = (\n ctx: RouterContext,\n) => Limit | Limit[] | Promise<Limit | Limit[]>\n\ninterface StoredHit {\n count: number\n resetAt: number\n}\n\n/**\n * Central registry of named rate limiters and the request-time enforcement\n * pipeline. Resolved as a singleton; consumed by `ThrottleMiddleware`.\n *\n * Register limiters in a module's `onInitialize` hook:\n * ```typescript\n * @Module({})\n * export class RateLimitsModule implements OnInitialize {\n * onInitialize({ container }: ModuleContext): void {\n * const limiter = container.resolve<RateLimiterRegistry>(RATE_LIMITER_TOKENS.Registry)\n * limiter.for('api', (ctx) => Limit.perMinute(60).by(ctx.header('cf-connecting-ip') ?? '*'))\n * }\n * }\n * ```\n *\n * Extensible via `Macroable`: adapter packages (e.g. `@stratal/framework/auth`)\n * can attach extra registration methods such as `forPath()` for better-auth\n * `customRules` interop.\n */\n// IMPORTANT: do not pass a token to @Transient — that would self-register\n// the class globally at module-load time, making the Registry resolvable\n// even when the user never imported RateLimiterModule. We rely on\n// RateLimiterModule providers being the only binding source, so\n// `{ isOptional: true }` in ThrottleMiddleware correctly returns undefined\n// when the module is missing.\n@Transient()\nexport class RateLimiterRegistry extends Macroable {\n private readonly resolvers = new Map<string, LimitResolver>()\n\n constructor(\n @inject(RATE_LIMITER_TOKENS.Store) private readonly store: IRateLimiterStore,\n ) {\n super()\n }\n\n /**\n * Register a named limiter. Names must be unique; calling `for()` again\n * with the same name overwrites the previous resolver (matches Laravel\n * `RateLimiter::for` semantics — last definition wins).\n */\n for(name: string, resolver: LimitResolver): void {\n this.resolvers.set(name, resolver)\n }\n\n has(name: string): boolean {\n return this.resolvers.has(name)\n }\n\n /**\n * Enforce the named limiter for the current request. Called by\n * `ThrottleMiddleware` (the per-name class produced by\n * `createThrottleMiddleware`). Resolves the limiter, increments the store\n * for each non-bypassed limit, sets `X-RateLimit-*` headers on success, and\n * either invokes the limit's custom `.response()` or throws\n * {@link TooManyRequestsError} when a limit is exceeded.\n */\n async handle(name: string, ctx: RouterContext, next: Next): Promise<Response | void> {\n const resolver = this.resolvers.get(name)\n if (!resolver) {\n throw new RateLimiterNotDefinedError(name)\n }\n\n const resolved = await resolver(ctx)\n const limits = Array.isArray(resolved) ? resolved : [resolved]\n const active = limits.filter((l) => !l.disabled)\n\n if (active.length === 0) {\n return next()\n }\n\n let mostRestrictive: { limit: Limit; remaining: number; resetAt: number } | undefined\n let exceeded: { limit: Limit; resetAt: number } | undefined\n\n for (const limit of active) {\n const key = this.makeKey(name, limit.windowSeconds, limit.key)\n const hit = await this.hit(key, limit.windowSeconds)\n\n if (hit.count > limit.max) {\n if (!exceeded || hit.resetAt > exceeded.resetAt) {\n exceeded = { limit, resetAt: hit.resetAt }\n }\n continue\n }\n\n const remaining = limit.max - hit.count\n if (!mostRestrictive || remaining < mostRestrictive.remaining) {\n mostRestrictive = { limit, remaining, resetAt: hit.resetAt }\n }\n }\n\n if (exceeded) {\n const headers = this.makeHeaders(exceeded.limit.max, 0, exceeded.resetAt)\n if (exceeded.limit.customResponse) {\n return exceeded.limit.customResponse(ctx, headers)\n }\n throw new TooManyRequestsError({\n retryAfter: Number(headers['Retry-After']),\n limit: exceeded.limit.max,\n resetAt: exceeded.resetAt,\n })\n }\n\n await next()\n\n if (mostRestrictive) {\n const headers = this.makeHeaders(\n mostRestrictive.limit.max,\n mostRestrictive.remaining,\n mostRestrictive.resetAt,\n )\n // Hono populates ctx.c.res after next() — same pattern as logger.middleware.ts.\n const downstream = ctx.c.res\n downstream.headers.set('X-RateLimit-Limit', headers['X-RateLimit-Limit'])\n downstream.headers.set('X-RateLimit-Remaining', headers['X-RateLimit-Remaining'])\n downstream.headers.set('X-RateLimit-Reset', headers['X-RateLimit-Reset'])\n }\n }\n\n /**\n * Get-modify-set increment over the typed KV store. Not atomic across\n * concurrent edge requests on KV — see `KvRateLimiterStore`'s caveat.\n */\n private async hit(key: string, windowSeconds: number): Promise<RateLimitHit> {\n const now = Date.now()\n const existing = await this.store.get<StoredHit>(key)\n\n let next: StoredHit\n if (!existing || existing.resetAt <= now) {\n next = { count: 1, resetAt: now + windowSeconds * 1000 }\n } else {\n next = { count: existing.count + 1, resetAt: existing.resetAt }\n }\n\n const ttlSeconds = Math.max(1, Math.ceil((next.resetAt - now) / 1000))\n await this.store.set(key, next, ttlSeconds)\n return next\n }\n\n private makeKey(name: string, windowSeconds: number, by: string | undefined): string {\n const actor = by ?? '*'\n return `rl:${name}:${windowSeconds}:${actor}`\n }\n\n private makeHeaders(limit: number, remaining: number, resetAt: number): RateLimitHeaders {\n const retryAfter = Math.max(1, Math.ceil((resetAt - Date.now()) / 1000))\n return {\n 'Retry-After': String(retryAfter),\n 'X-RateLimit-Limit': String(limit),\n 'X-RateLimit-Remaining': String(Math.max(0, remaining)),\n 'X-RateLimit-Reset': String(Math.ceil(resetAt / 1000)),\n }\n }\n}\n","import type { CacheService } from '../../cache/services/cache.service'\nimport type { IRateLimiterStore } from './rate-limiter-store.interface'\n\n/**\n * Cloudflare KV-backed typed KV store.\n *\n * KV's minimum `expirationTtl` is 60 seconds; sub-60s windows are still\n * enforced by the registry's algorithm via the persisted `resetAt`, but\n * the key itself may live in KV longer than the logical window.\n *\n * KV has no native atomic increment, so concurrent writes from different\n * edge locations may undercount under high contention. That's an inherent\n * KV tradeoff — pick `{ useClass: MyDurableObjectStore }` for strict\n * accuracy across edges.\n */\nexport class KvRateLimiterStore implements IRateLimiterStore {\n constructor(private readonly cache: CacheService) {}\n\n async get<T>(key: string): Promise<T | null> {\n return (await this.cache.get<T>(key, 'json')) ?? null\n }\n\n async set<T>(key: string, value: T, ttlSeconds: number): Promise<void> {\n const ttl = Math.max(60, Math.ceil(ttlSeconds))\n await this.cache.put(key, JSON.stringify(value), { expirationTtl: ttl })\n }\n\n async delete(key: string): Promise<void> {\n await this.cache.delete(key)\n }\n}\n","import type { IRateLimiterStore } from './rate-limiter-store.interface'\n\ninterface Entry {\n value: unknown\n expiresAt: number\n}\n\n/**\n * In-process Map-backed typed KV store.\n *\n * Suitable for tests and single-isolate scenarios. Not safe across\n * Cloudflare Worker isolates — entries reset whenever a fresh isolate\n * spins up. Use `KvRateLimiterStore` (or a custom Durable Object store)\n * for production. Expiry is lazy: stale entries are dropped on the next\n * `get`.\n */\nexport class InMemoryRateLimiterStore implements IRateLimiterStore {\n private readonly entries = new Map<string, Entry>()\n\n get<T>(key: string): Promise<T | null> {\n const entry = this.entries.get(key)\n if (!entry) return Promise.resolve(null)\n if (entry.expiresAt <= Date.now()) {\n this.entries.delete(key)\n return Promise.resolve(null)\n }\n return Promise.resolve(entry.value as T)\n }\n\n set<T>(key: string, value: T, ttlSeconds: number): Promise<void> {\n this.entries.set(key, { value, expiresAt: Date.now() + ttlSeconds * 1000 })\n return Promise.resolve()\n }\n\n delete(key: string): Promise<void> {\n this.entries.delete(key)\n return Promise.resolve()\n }\n}\n","import { inject } from 'tsyringe'\nimport { CACHE_TOKENS } from '../../cache/cache.tokens'\nimport type { CacheService } from '../../cache/services/cache.service'\nimport { CONTAINER_TOKEN, type Container } from '../../di'\nimport { Transient } from '../../di/decorators'\nimport { DI_TOKENS } from '../../di/tokens'\nimport type { StratalEnv } from '../../env'\nimport type { Constructor } from '../../types'\nimport { RateLimiterNotConfiguredError } from '../errors'\nimport { RATE_LIMITER_TOKENS } from '../rate-limiter.tokens'\nimport { KvRateLimiterStore } from './kv-store'\nimport { InMemoryRateLimiterStore } from './memory-store'\nimport type { IRateLimiterStore } from './rate-limiter-store.interface'\n\n/**\n * Configuration for `RateLimiterModule.forRoot()`. Picks the backing store.\n *\n * - `'kv'`: Cloudflare KV. `binding` names the KVNamespace on `StratalEnv`.\n * - `'memory'`: in-process Map. Tests / single-isolate only.\n * - `{ useClass }`: any class implementing `IRateLimiterStore`. Resolved\n * from the DI container (so the class can declare its own `@inject` deps,\n * e.g. a Durable Object namespace from `StratalEnv`).\n */\nexport type RateLimiterModuleOptions =\n | { store: 'kv'; binding: keyof StratalEnv }\n | { store: 'memory' }\n | { store: { useClass: Constructor<IRateLimiterStore> } }\n\n// IMPORTANT: see RateLimiterRegistry — no token on @Transient so the\n// factory isn't globally bound at class-load time. Module providers are\n// the sole binding source, which keeps the \"module not imported\" detection\n// in ThrottleMiddleware working.\n@Transient()\nexport class RateLimiterStoreFactory {\n constructor(\n @inject(DI_TOKENS.CloudflareEnv) private readonly env: StratalEnv,\n @inject(CACHE_TOKENS.CacheService) private readonly cache: CacheService,\n @inject(CONTAINER_TOKEN) private readonly container: Container,\n @inject(RATE_LIMITER_TOKENS.Options, { isOptional: true })\n private readonly options?: RateLimiterModuleOptions,\n ) {}\n\n create(): IRateLimiterStore {\n if (!this.options) {\n throw new RateLimiterNotConfiguredError()\n }\n\n const { store } = this.options\n\n if (store === 'memory') {\n return new InMemoryRateLimiterStore()\n }\n\n if (store === 'kv') {\n const binding = this.env[this.options.binding] as KVNamespace | undefined\n if (!binding) {\n throw new RateLimiterNotConfiguredError()\n }\n return new KvRateLimiterStore(this.cache.withBinding(binding))\n }\n\n return this.container.resolve<IRateLimiterStore>(store.useClass)\n }\n}\n","import { Scope } from '../di/types'\nimport type { ExceptionHandler } from '../errors/exception-handler'\nimport { Module } from '../module'\nimport type { AsyncModuleOptions, DynamicModule, ModuleContext, OnException, OnInitialize } from '../module/types'\nimport { TooManyRequestsError } from './errors'\nimport { RateLimiterRegistry } from './rate-limiter-registry'\nimport { RATE_LIMITER_TOKENS } from './rate-limiter.tokens'\nimport { RateLimiterStoreFactory, type RateLimiterModuleOptions } from './stores/store-factory'\nimport type { IRateLimiterStore } from './stores/rate-limiter-store.interface'\n\n/**\n * Rate limiter module — opt-in, NOT registered automatically by Application.\n *\n * Usage:\n * ```typescript\n * @Module({\n * imports: [RateLimiterModule.forRoot({ store: 'kv', binding: 'RATE_LIMITS' })],\n * })\n * export class AppModule {}\n * ```\n *\n * Define limiters in any module's `onInitialize` hook by resolving\n * `RATE_LIMITER_TOKENS.Registry` from the container. Apply them with\n * `router.throttle('name')` or `@RateLimit('name')`.\n *\n * The module:\n * - eagerly validates the store at app boot (`onInitialize` resolves the\n * factory and calls `create()`); a missing `forRoot` surfaces\n * `RateLimiterNotConfiguredError` before any request is served.\n * - registers a `respond()` callback on the `ExceptionHandler` (via\n * `onException`) that injects `Retry-After` and `X-RateLimit-*` headers\n * on every {@link TooManyRequestsError} response, regardless of whether\n * the body was rendered as JSON, HTML, or via Inertia.\n */\n@Module({\n providers: [\n { provide: RATE_LIMITER_TOKENS.Registry, useClass: RateLimiterRegistry, scope: Scope.Singleton },\n { provide: RATE_LIMITER_TOKENS.StoreFactory, useClass: RateLimiterStoreFactory, scope: Scope.Singleton },\n {\n provide: RATE_LIMITER_TOKENS.Store,\n useFactory: (factory: RateLimiterStoreFactory) => factory.create(),\n inject: [RATE_LIMITER_TOKENS.StoreFactory],\n scope: Scope.Singleton,\n },\n ],\n})\nexport class RateLimiterModule implements OnInitialize, OnException {\n /**\n * Configure the rate limiter with a store choice.\n *\n * @param options - `{ store: 'kv', binding }` | `{ store: 'memory' }` | `{ store: { useClass } }`\n */\n static forRoot(options: RateLimiterModuleOptions): DynamicModule {\n return {\n module: RateLimiterModule,\n providers: [\n { provide: RATE_LIMITER_TOKENS.Options, useValue: options },\n ],\n }\n }\n\n /**\n * Async configuration. Use when store options depend on other services\n * (e.g. ConfigService).\n */\n static forRootAsync(options: AsyncModuleOptions<RateLimiterModuleOptions>): DynamicModule {\n return {\n module: RateLimiterModule,\n providers: [\n {\n provide: RATE_LIMITER_TOKENS.Options,\n useFactory: options.useFactory,\n inject: options.inject,\n },\n ],\n }\n }\n\n /**\n * Eagerly resolve the store so a missing `forRoot()` fails at boot,\n * not on the first throttled request. Also registers a per-app marker\n * value so ThrottleMiddleware can distinguish \"module imported into this\n * app\" from \"module class loaded somewhere in the process\" (the latter is\n * indistinguishable at the DI layer because @Module's `registry()` call\n * binds providers globally at decoration time).\n */\n onInitialize({ container }: ModuleContext): void {\n container.resolve<IRateLimiterStore>(RATE_LIMITER_TOKENS.Store)\n container.registerValue(RATE_LIMITER_TOKENS.ModuleMarker, { imported: true })\n }\n\n /**\n * Inject `Retry-After` + `X-RateLimit-*` headers on every 429 response.\n * The body itself is rendered by `ExceptionHandler.defaultRender` —\n * content-negotiated, so HTML clients (browsers, Inertia) and JSON\n * clients both get the right shape.\n */\n onException(handler: ExceptionHandler): void {\n handler.dontReport([TooManyRequestsError])\n handler.respond((response, error) => {\n if (!(error instanceof TooManyRequestsError)) return response\n response.headers.set('Retry-After', String(error.info.retryAfter))\n response.headers.set('X-RateLimit-Limit', String(error.info.limit))\n response.headers.set('X-RateLimit-Remaining', '0')\n response.headers.set('X-RateLimit-Reset', String(Math.ceil(error.info.resetAt / 1000)))\n return response\n })\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/rate-limiter/limit.ts","../../src/rate-limiter/rate-limiter-registry.ts","../../src/rate-limiter/stores/kv-store.ts","../../src/rate-limiter/stores/memory-store.ts","../../src/rate-limiter/stores/store-factory.ts","../../src/rate-limiter/rate-limiter.module.ts"],"sourcesContent":["import type { RouterContext } from '../router/router-context'\n\n/**\n * Standard rate-limit response headers passed to custom response handlers.\n * Keys are canonical HTTP header names; values are the stringified counts.\n */\nexport interface RateLimitHeaders {\n 'Retry-After': string\n 'X-RateLimit-Limit': string\n 'X-RateLimit-Remaining': string\n 'X-RateLimit-Reset': string\n}\n\n/**\n * Custom response handler invoked when a limit is exceeded and the user\n * supplied `.response()` on the {@link Limit}. Receives the request context\n * and the precomputed standard headers (which the handler can spread, drop,\n * or override).\n */\nexport type RateLimitResponseHandler = (\n ctx: RouterContext,\n headers: RateLimitHeaders,\n) => Response | Promise<Response>\n\n/**\n * A single rate limit window.\n *\n * Build via the static factories (`perSecond`, `perMinute`, `perMinutes`,\n * `perHour`, `perDay`, `none`). Chain `.by(key)` to scope per-actor and\n * `.response(handler)` to override the default 429 response.\n *\n * Returned (singly or as an array) by limiter resolvers registered via\n * `RateLimiterRegistry.for()`.\n *\n * @example\n * ```typescript\n * Limit.perMinute(60).by(ctx.header('cf-connecting-ip') ?? 'global')\n * Limit.perHour(100).by(userId)\n * Limit.none() // bypass\n * ```\n */\nexport class Limit {\n private _key?: string\n private _customResponse?: RateLimitResponseHandler\n\n private constructor(\n public readonly windowSeconds: number,\n public readonly max: number,\n public readonly disabled = false,\n ) {}\n\n static perSecond(max: number): Limit {\n return new Limit(1, max)\n }\n\n static perSeconds(seconds: number, max: number): Limit {\n return new Limit(seconds, max)\n }\n\n static perMinute(max: number): Limit {\n return new Limit(60, max)\n }\n\n static perMinutes(minutes: number, max: number): Limit {\n return new Limit(minutes * 60, max)\n }\n\n static perHour(max: number): Limit {\n return new Limit(60 * 60, max)\n }\n\n static perDay(max: number): Limit {\n return new Limit(24 * 60 * 60, max)\n }\n\n /** Bypass the limiter entirely for this request. */\n static none(): Limit {\n return new Limit(0, 0, true)\n }\n\n /** Scope this limit to a specific actor (user id, IP, tenant, etc.). */\n by(key: string | number): this {\n this._key = String(key)\n return this\n }\n\n /**\n * Override the default 429 response. The handler receives the standard\n * `RateLimitHeaders` so it can spread them onto its own Response or omit\n * them as it sees fit.\n */\n response(handler: RateLimitResponseHandler): this {\n this._customResponse = handler\n return this\n }\n\n get key(): string | undefined {\n return this._key\n }\n\n get customResponse(): RateLimitResponseHandler | undefined {\n return this._customResponse\n }\n}\n","import { inject } from 'tsyringe'\nimport { Transient } from '../di/decorators'\nimport { Macroable } from '../macroable'\nimport type { Next } from '../router/middleware.interface'\nimport type { RouterContext } from '../router/router-context'\nimport { RateLimiterNotDefinedError, TooManyRequestsError } from './errors'\nimport type { Limit, RateLimitHeaders } from './limit'\nimport { RATE_LIMITER_TOKENS } from './rate-limiter.tokens'\nimport type { IRateLimiterStore, RateLimitHit } from './stores/rate-limiter-store.interface'\n\n/**\n * Resolver function registered via {@link RateLimiterRegistry.for}. Receives\n * the request context and returns the limit (or limits) that apply to this\n * request. Return `Limit.none()` to bypass for the current request.\n */\nexport type LimitResolver = (\n ctx: RouterContext,\n) => Limit | Limit[] | Promise<Limit | Limit[]>\n\ninterface StoredHit {\n count: number\n resetAt: number\n}\n\n/**\n * Central registry of named rate limiters and the request-time enforcement\n * pipeline. Resolved as a singleton; consumed by `ThrottleMiddleware`.\n *\n * Register limiters in a module's `onInitialize` hook:\n * ```typescript\n * @Module({})\n * export class RateLimitsModule implements OnInitialize {\n * onInitialize({ container }: ModuleContext): void {\n * const limiter = container.resolve<RateLimiterRegistry>(RATE_LIMITER_TOKENS.Registry)\n * limiter.for('api', (ctx) => Limit.perMinute(60).by(ctx.header('cf-connecting-ip') ?? '*'))\n * }\n * }\n * ```\n *\n * Extensible via `Macroable`: adapter packages (e.g. `@stratal/framework/auth`)\n * can attach extra registration methods such as `forPath()` for better-auth\n * `customRules` interop.\n */\n// IMPORTANT: do not pass a token to @Transient — that would self-register\n// the class globally at module-load time, making the Registry resolvable\n// even when the user never imported RateLimiterModule. We rely on\n// RateLimiterModule providers being the only binding source, so\n// `{ isOptional: true }` in ThrottleMiddleware correctly returns undefined\n// when the module is missing.\n@Transient()\nexport class RateLimiterRegistry extends Macroable {\n private readonly resolvers = new Map<string, LimitResolver>()\n\n constructor(\n @inject(RATE_LIMITER_TOKENS.Store) private readonly store: IRateLimiterStore,\n ) {\n super()\n }\n\n /**\n * Register a named limiter. Names must be unique; calling `for()` again\n * with the same name overwrites the previous resolver (matches Laravel\n * `RateLimiter::for` semantics — last definition wins).\n */\n for(name: string, resolver: LimitResolver): void {\n this.resolvers.set(name, resolver)\n }\n\n has(name: string): boolean {\n return this.resolvers.has(name)\n }\n\n /**\n * Enforce the named limiter for the current request. Called by\n * `ThrottleMiddleware` (the per-name class produced by\n * `createThrottleMiddleware`). Resolves the limiter, increments the store\n * for each non-bypassed limit, sets `X-RateLimit-*` headers on success, and\n * either invokes the limit's custom `.response()` or throws\n * {@link TooManyRequestsError} when a limit is exceeded.\n */\n async handle(name: string, ctx: RouterContext, next: Next): Promise<Response | void> {\n const resolver = this.resolvers.get(name)\n if (!resolver) {\n throw new RateLimiterNotDefinedError(name)\n }\n\n const resolved = await resolver(ctx)\n const limits = Array.isArray(resolved) ? resolved : [resolved]\n const active = limits.filter((l) => !l.disabled)\n\n if (active.length === 0) {\n return next()\n }\n\n let mostRestrictive: { limit: Limit; remaining: number; resetAt: number } | undefined\n let exceeded: { limit: Limit; resetAt: number } | undefined\n\n for (const limit of active) {\n const key = this.makeKey(name, limit.windowSeconds, limit.key)\n const hit = await this.hit(key, limit.windowSeconds)\n\n if (hit.count > limit.max) {\n if (!exceeded || hit.resetAt > exceeded.resetAt) {\n exceeded = { limit, resetAt: hit.resetAt }\n }\n continue\n }\n\n const remaining = limit.max - hit.count\n if (!mostRestrictive || remaining < mostRestrictive.remaining) {\n mostRestrictive = { limit, remaining, resetAt: hit.resetAt }\n }\n }\n\n if (exceeded) {\n const headers = this.makeHeaders(exceeded.limit.max, 0, exceeded.resetAt)\n if (exceeded.limit.customResponse) {\n return exceeded.limit.customResponse(ctx, headers)\n }\n throw new TooManyRequestsError({\n retryAfter: Number(headers['Retry-After']),\n limit: exceeded.limit.max,\n resetAt: exceeded.resetAt,\n })\n }\n\n await next()\n\n if (mostRestrictive) {\n const headers = this.makeHeaders(\n mostRestrictive.limit.max,\n mostRestrictive.remaining,\n mostRestrictive.resetAt,\n )\n // Hono populates ctx.c.res after next() — same pattern as logger.middleware.ts.\n const downstream = ctx.c.res\n downstream.headers.set('X-RateLimit-Limit', headers['X-RateLimit-Limit'])\n downstream.headers.set('X-RateLimit-Remaining', headers['X-RateLimit-Remaining'])\n downstream.headers.set('X-RateLimit-Reset', headers['X-RateLimit-Reset'])\n }\n }\n\n /**\n * Get-modify-set increment over the typed KV store. Not atomic across\n * concurrent edge requests on KV — see `KvRateLimiterStore`'s caveat.\n */\n private async hit(key: string, windowSeconds: number): Promise<RateLimitHit> {\n const now = Date.now()\n const existing = await this.store.get<StoredHit>(key)\n\n let next: StoredHit\n if (!existing || existing.resetAt <= now) {\n next = { count: 1, resetAt: now + windowSeconds * 1000 }\n } else {\n next = { count: existing.count + 1, resetAt: existing.resetAt }\n }\n\n const ttlSeconds = Math.max(1, Math.ceil((next.resetAt - now) / 1000))\n await this.store.set(key, next, ttlSeconds)\n return next\n }\n\n private makeKey(name: string, windowSeconds: number, by: string | undefined): string {\n const actor = by ?? '*'\n return `rl:${name}:${windowSeconds}:${actor}`\n }\n\n private makeHeaders(limit: number, remaining: number, resetAt: number): RateLimitHeaders {\n const retryAfter = Math.max(1, Math.ceil((resetAt - Date.now()) / 1000))\n return {\n 'Retry-After': String(retryAfter),\n 'X-RateLimit-Limit': String(limit),\n 'X-RateLimit-Remaining': String(Math.max(0, remaining)),\n 'X-RateLimit-Reset': String(Math.ceil(resetAt / 1000)),\n }\n }\n}\n","import type { CacheService } from '../../cache/services/cache.service'\nimport type { IRateLimiterStore } from './rate-limiter-store.interface'\n\n/**\n * Cloudflare KV-backed typed KV store.\n *\n * KV's minimum `expirationTtl` is 60 seconds; sub-60s windows are still\n * enforced by the registry's algorithm via the persisted `resetAt`, but\n * the key itself may live in KV longer than the logical window.\n *\n * KV has no native atomic increment, so concurrent writes from different\n * edge locations may undercount under high contention. That's an inherent\n * KV tradeoff — pick `{ useClass: MyDurableObjectStore }` for strict\n * accuracy across edges.\n */\nexport class KvRateLimiterStore implements IRateLimiterStore {\n constructor(private readonly cache: CacheService) {}\n\n async get<T>(key: string): Promise<T | null> {\n return (await this.cache.get<T>(key, 'json')) ?? null\n }\n\n async set<T>(key: string, value: T, ttlSeconds: number): Promise<void> {\n const ttl = Math.max(60, Math.ceil(ttlSeconds))\n await this.cache.put(key, JSON.stringify(value), { expirationTtl: ttl })\n }\n\n async delete(key: string): Promise<void> {\n await this.cache.delete(key)\n }\n}\n","import type { IRateLimiterStore } from './rate-limiter-store.interface'\n\ninterface Entry {\n value: unknown\n expiresAt: number\n}\n\n/**\n * In-process Map-backed typed KV store.\n *\n * Suitable for tests and single-isolate scenarios. Not safe across\n * Cloudflare Worker isolates — entries reset whenever a fresh isolate\n * spins up. Use `KvRateLimiterStore` (or a custom Durable Object store)\n * for production. Expiry is lazy: stale entries are dropped on the next\n * `get`.\n */\nexport class InMemoryRateLimiterStore implements IRateLimiterStore {\n private readonly entries = new Map<string, Entry>()\n\n get<T>(key: string): Promise<T | null> {\n const entry = this.entries.get(key)\n if (!entry) return Promise.resolve(null)\n if (entry.expiresAt <= Date.now()) {\n this.entries.delete(key)\n return Promise.resolve(null)\n }\n return Promise.resolve(entry.value as T)\n }\n\n set<T>(key: string, value: T, ttlSeconds: number): Promise<void> {\n this.entries.set(key, { value, expiresAt: Date.now() + ttlSeconds * 1000 })\n return Promise.resolve()\n }\n\n delete(key: string): Promise<void> {\n this.entries.delete(key)\n return Promise.resolve()\n }\n}\n","import { inject } from 'tsyringe'\nimport { CACHE_TOKENS } from '../../cache/cache.tokens'\nimport type { CacheService } from '../../cache/services/cache.service'\nimport { CONTAINER_TOKEN, type Container } from '../../di'\nimport { Transient } from '../../di/decorators'\nimport { DI_TOKENS } from '../../di/tokens'\nimport type { StratalEnv } from '../../env'\nimport type { Constructor } from '../../types'\nimport { RateLimiterNotConfiguredError } from '../errors'\nimport { RATE_LIMITER_TOKENS } from '../rate-limiter.tokens'\nimport { KvRateLimiterStore } from './kv-store'\nimport { InMemoryRateLimiterStore } from './memory-store'\nimport type { IRateLimiterStore } from './rate-limiter-store.interface'\n\n/**\n * Configuration for `RateLimiterModule.forRoot()`. Picks the backing store.\n *\n * - `'kv'`: Cloudflare KV. `binding` names the KVNamespace on `StratalEnv`.\n * - `'memory'`: in-process Map. Tests / single-isolate only.\n * - `{ useClass }`: any class implementing `IRateLimiterStore`. Resolved\n * from the DI container (so the class can declare its own `@inject` deps,\n * e.g. a Durable Object namespace from `StratalEnv`).\n */\nexport type RateLimiterModuleOptions =\n | { store: 'kv'; binding: keyof StratalEnv }\n | { store: 'memory' }\n | { store: { useClass: Constructor<IRateLimiterStore> } }\n\n// IMPORTANT: see RateLimiterRegistry — no token on @Transient so the\n// factory isn't globally bound at class-load time. Module providers are\n// the sole binding source, which keeps the \"module not imported\" detection\n// in ThrottleMiddleware working.\n@Transient()\nexport class RateLimiterStoreFactory {\n constructor(\n @inject(DI_TOKENS.CloudflareEnv) private readonly env: StratalEnv,\n @inject(CACHE_TOKENS.CacheService) private readonly cache: CacheService,\n @inject(CONTAINER_TOKEN) private readonly container: Container,\n @inject(RATE_LIMITER_TOKENS.Options, { isOptional: true })\n private readonly options?: RateLimiterModuleOptions,\n ) {}\n\n create(): IRateLimiterStore {\n if (!this.options) {\n throw new RateLimiterNotConfiguredError()\n }\n\n const { store } = this.options\n\n if (store === 'memory') {\n return new InMemoryRateLimiterStore()\n }\n\n if (store === 'kv') {\n const binding = this.env[this.options.binding] as KVNamespace | undefined\n if (!binding) {\n throw new RateLimiterNotConfiguredError()\n }\n return new KvRateLimiterStore(this.cache.withBinding(binding))\n }\n\n return this.container.resolve<IRateLimiterStore>(store.useClass)\n }\n}\n","import { Scope } from '../di/types'\nimport type { ExceptionHandler } from '../errors/exception-handler'\nimport { Module } from '../module'\nimport type { AsyncModuleOptions, DynamicModule, ModuleContext, OnException, OnInitialize } from '../module/types'\nimport { TooManyRequestsError } from './errors'\nimport { RateLimiterRegistry } from './rate-limiter-registry'\nimport { RATE_LIMITER_TOKENS } from './rate-limiter.tokens'\nimport { RateLimiterStoreFactory, type RateLimiterModuleOptions } from './stores/store-factory'\nimport type { IRateLimiterStore } from './stores/rate-limiter-store.interface'\n\n/**\n * Rate limiter module — opt-in, NOT registered automatically by Application.\n *\n * Usage:\n * ```typescript\n * @Module({\n * imports: [RateLimiterModule.forRoot({ store: 'kv', binding: 'RATE_LIMITS' })],\n * })\n * export class AppModule {}\n * ```\n *\n * Define limiters in any module's `onInitialize` hook by resolving\n * `RATE_LIMITER_TOKENS.Registry` from the container. Apply them with\n * `router.throttle('name')` or `@RateLimit('name')`.\n *\n * The module:\n * - eagerly validates the store at app boot (`onInitialize` resolves the\n * factory and calls `create()`); a missing `forRoot` surfaces\n * `RateLimiterNotConfiguredError` before any request is served.\n * - registers a `respond()` callback on the `ExceptionHandler` (via\n * `onException`) that injects `Retry-After` and `X-RateLimit-*` headers\n * on every {@link TooManyRequestsError} response, regardless of whether\n * the body was rendered as JSON, HTML, or via Inertia.\n */\n@Module({\n providers: [\n { provide: RATE_LIMITER_TOKENS.Registry, useClass: RateLimiterRegistry, scope: Scope.Singleton },\n { provide: RATE_LIMITER_TOKENS.StoreFactory, useClass: RateLimiterStoreFactory, scope: Scope.Singleton },\n {\n provide: RATE_LIMITER_TOKENS.Store,\n useFactory: (factory: RateLimiterStoreFactory) => factory.create(),\n inject: [RATE_LIMITER_TOKENS.StoreFactory],\n scope: Scope.Singleton,\n },\n ],\n})\nexport class RateLimiterModule implements OnInitialize, OnException {\n /**\n * Configure the rate limiter with a store choice.\n *\n * @param options - `{ store: 'kv', binding }` | `{ store: 'memory' }` | `{ store: { useClass } }`\n */\n static forRoot(options: RateLimiterModuleOptions): DynamicModule {\n return {\n module: RateLimiterModule,\n providers: [\n { provide: RATE_LIMITER_TOKENS.Options, useValue: options },\n ],\n }\n }\n\n /**\n * Async configuration. Use when store options depend on other services\n * (e.g. ConfigService).\n */\n static forRootAsync(options: AsyncModuleOptions<RateLimiterModuleOptions>): DynamicModule {\n return {\n module: RateLimiterModule,\n providers: [\n {\n provide: RATE_LIMITER_TOKENS.Options,\n useFactory: options.useFactory,\n inject: options.inject,\n },\n ],\n }\n }\n\n /**\n * Eagerly resolve the store so a missing `forRoot()` fails at boot,\n * not on the first throttled request. Also registers a per-app marker\n * value so ThrottleMiddleware can distinguish \"module imported into this\n * app\" from \"module class loaded somewhere in the process\" (the latter is\n * indistinguishable at the DI layer because @Module's `registry()` call\n * binds providers globally at decoration time).\n */\n onInitialize({ container }: ModuleContext): void {\n container.resolve<IRateLimiterStore>(RATE_LIMITER_TOKENS.Store)\n container.registerValue(RATE_LIMITER_TOKENS.ModuleMarker, { imported: true })\n }\n\n /**\n * Inject `Retry-After` + `X-RateLimit-*` headers on every 429 response.\n * The body itself is rendered by `ExceptionHandler.defaultRender` —\n * content-negotiated, so HTML clients (browsers, Inertia) and JSON\n * clients both get the right shape.\n */\n onException(handler: ExceptionHandler): void {\n handler.dontReport([TooManyRequestsError])\n handler.respond((response, error) => {\n if (!(error instanceof TooManyRequestsError)) return response\n response.headers.set('Retry-After', String(error.info.retryAfter))\n response.headers.set('X-RateLimit-Limit', String(error.info.limit))\n response.headers.set('X-RateLimit-Remaining', '0')\n response.headers.set('X-RateLimit-Reset', String(Math.ceil(error.info.resetAt / 1000)))\n return response\n })\n }\n}\n"],"mappings":"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"}
@@ -9,6 +9,7 @@ import { Resend } from "resend";
9
9
  * Docs: https://resend.com/docs
10
10
  */
11
11
  var ResendProvider = class extends BaseEmailProvider {
12
+ options;
12
13
  client;
13
14
  defaultFrom;
14
15
  constructor(options) {
@@ -64,4 +65,4 @@ var ResendProvider = class extends BaseEmailProvider {
64
65
  //#endregion
65
66
  export { ResendProvider };
66
67
 
67
- //# sourceMappingURL=resend.provider-M6qRLrcy.mjs.map
68
+ //# sourceMappingURL=resend.provider-DB4IlFjG.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"resend.provider-M6qRLrcy.mjs","names":[],"sources":["../src/email/providers/resend.provider.ts"],"sourcesContent":["import { Resend } from 'resend'\nimport type { ResolvedEmailAttachment, ResolvedEmailMessage } from '../contracts'\nimport type { EmailModuleOptions } from '../email.module'\nimport { EmailResendApiFailedError, ResendApiKeyMissingError } from '../errors'\nimport { BaseEmailProvider } from './base-email.provider'\nimport type { EmailSendResult } from './email-provider.interface'\n\n/**\n * Resend Email Provider\n *\n * Implementation of IEmailProvider using Resend API\n * Docs: https://resend.com/docs\n */\nexport class ResendProvider extends BaseEmailProvider {\n private readonly client: Resend\n private readonly defaultFrom: { name: string; email: string }\n\n constructor(\n private readonly options: EmailModuleOptions\n ) {\n super()\n\n // Validate Resend API key\n if (!this.options.apiKey) {\n throw new ResendApiKeyMissingError()\n }\n\n this.client = new Resend(this.options.apiKey)\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 to = Array.isArray(message.to) ? message.to : [message.to]\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- Resend SDK types\n const response = await this.client.emails.send({\n from,\n to,\n subject: message.subject,\n html: message.html,\n text: message.text,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment -- Resend template extension\n ...((message as any).template && { template: (message as any).template }),\n ...(message.replyTo && { replyTo: message.replyTo }),\n ...(message.cc && { cc: message.cc }),\n ...(message.bcc && { bcc: message.bcc }),\n ...(message.attachments && {\n attachments: await Promise.all(\n message.attachments.map(async attachment => ({\n filename: attachment.filename,\n content: await this.toBuffer(attachment.content),\n }))\n ),\n }),\n })\n\n if (response.error) {\n throw new EmailResendApiFailedError()\n }\n\n return {\n messageId: response.data.id,\n accepted: true,\n metadata: {\n provider: 'resend',\n },\n }\n } catch (error) {\n if (error instanceof EmailResendApiFailedError || error instanceof ResendApiKeyMissingError) {\n throw error\n }\n\n throw new EmailResendApiFailedError()\n }\n }\n\n /**\n * Convert attachment content to Buffer\n *\n * Resend SDK expects Buffer for attachment content.\n * If content is already a Buffer, return as-is.\n * If content is a ReadableStream, convert to Buffer.\n */\n private async toBuffer(content: ResolvedEmailAttachment['content']): Promise<Buffer> {\n if (Buffer.isBuffer(content)) {\n return content\n }\n // Convert ReadableStream to Buffer\n const response = new Response(content)\n const arrayBuffer = await response.arrayBuffer()\n return Buffer.from(arrayBuffer)\n }\n}\n"],"mappings":";;;;;;;;;;AAaA,IAAa,iBAAb,cAAoC,kBAAkB;CACpD;CACA;CAEA,YACE,SACA;AACA,SAAO;AAFU,OAAA,UAAA;AAKjB,MAAI,CAAC,KAAK,QAAQ,OAChB,OAAM,IAAI,0BAA0B;AAGtC,OAAK,SAAS,IAAI,OAAO,KAAK,QAAQ,OAAO;AAC7C,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,KAAK,MAAM,QAAQ,QAAQ,GAAG,GAAG,QAAQ,KAAK,CAAC,QAAQ,GAAG;GAGhE,MAAM,WAAW,MAAM,KAAK,OAAO,OAAO,KAAK;IAC7C;IACA;IACA,SAAS,QAAQ;IACjB,MAAM,QAAQ;IACd,MAAM,QAAQ;IAEd,GAAK,QAAgB,YAAY,EAAE,UAAW,QAAgB,UAAU;IACxE,GAAI,QAAQ,WAAW,EAAE,SAAS,QAAQ,SAAS;IACnD,GAAI,QAAQ,MAAM,EAAE,IAAI,QAAQ,IAAI;IACpC,GAAI,QAAQ,OAAO,EAAE,KAAK,QAAQ,KAAK;IACvC,GAAI,QAAQ,eAAe,EACzB,aAAa,MAAM,QAAQ,IACzB,QAAQ,YAAY,IAAI,OAAM,gBAAe;KAC3C,UAAU,WAAW;KACrB,SAAS,MAAM,KAAK,SAAS,WAAW,QAAQ;KACjD,EAAE,CACJ,EACF;IACF,CAAC;AAEF,OAAI,SAAS,MACX,OAAM,IAAI,2BAA2B;AAGvC,UAAO;IACL,WAAW,SAAS,KAAK;IACzB,UAAU;IACV,UAAU,EACR,UAAU,UACX;IACF;WACM,OAAO;AACd,OAAI,iBAAiB,6BAA6B,iBAAiB,yBACjE,OAAM;AAGR,SAAM,IAAI,2BAA2B;;;;;;;;;;CAWzC,MAAc,SAAS,SAA8D;AACnF,MAAI,OAAO,SAAS,QAAQ,CAC1B,QAAO;EAIT,MAAM,cAAc,MAAM,IADL,SAAS,QACI,CAAC,aAAa;AAChD,SAAO,OAAO,KAAK,YAAY"}
1
+ {"version":3,"file":"resend.provider-DB4IlFjG.mjs","names":[],"sources":["../src/email/providers/resend.provider.ts"],"sourcesContent":["import { Resend } from 'resend'\nimport type { ResolvedEmailAttachment, ResolvedEmailMessage } from '../contracts'\nimport type { EmailModuleOptions } from '../email.module'\nimport { EmailResendApiFailedError, ResendApiKeyMissingError } from '../errors'\nimport { BaseEmailProvider } from './base-email.provider'\nimport type { EmailSendResult } from './email-provider.interface'\n\n/**\n * Resend Email Provider\n *\n * Implementation of IEmailProvider using Resend API\n * Docs: https://resend.com/docs\n */\nexport class ResendProvider extends BaseEmailProvider {\n private readonly client: Resend\n private readonly defaultFrom: { name: string; email: string }\n\n constructor(\n private readonly options: EmailModuleOptions\n ) {\n super()\n\n // Validate Resend API key\n if (!this.options.apiKey) {\n throw new ResendApiKeyMissingError()\n }\n\n this.client = new Resend(this.options.apiKey)\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 to = Array.isArray(message.to) ? message.to : [message.to]\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- Resend SDK types\n const response = await this.client.emails.send({\n from,\n to,\n subject: message.subject,\n html: message.html,\n text: message.text,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment -- Resend template extension\n ...((message as any).template && { template: (message as any).template }),\n ...(message.replyTo && { replyTo: message.replyTo }),\n ...(message.cc && { cc: message.cc }),\n ...(message.bcc && { bcc: message.bcc }),\n ...(message.attachments && {\n attachments: await Promise.all(\n message.attachments.map(async attachment => ({\n filename: attachment.filename,\n content: await this.toBuffer(attachment.content),\n }))\n ),\n }),\n })\n\n if (response.error) {\n throw new EmailResendApiFailedError()\n }\n\n return {\n messageId: response.data.id,\n accepted: true,\n metadata: {\n provider: 'resend',\n },\n }\n } catch (error) {\n if (error instanceof EmailResendApiFailedError || error instanceof ResendApiKeyMissingError) {\n throw error\n }\n\n throw new EmailResendApiFailedError()\n }\n }\n\n /**\n * Convert attachment content to Buffer\n *\n * Resend SDK expects Buffer for attachment content.\n * If content is already a Buffer, return as-is.\n * If content is a ReadableStream, convert to Buffer.\n */\n private async toBuffer(content: ResolvedEmailAttachment['content']): Promise<Buffer> {\n if (Buffer.isBuffer(content)) {\n return content\n }\n // Convert ReadableStream to Buffer\n const response = new Response(content)\n const arrayBuffer = await response.arrayBuffer()\n return Buffer.from(arrayBuffer)\n }\n}\n"],"mappings":";;;;;;;;;;AAaA,IAAa,iBAAb,cAAoC,kBAAkB;CAKjC;CAJnB;CACA;CAEA,YACE,SACA;EACA,OAAO;EAFU,KAAA,UAAA;EAKjB,IAAI,CAAC,KAAK,QAAQ,QAChB,MAAM,IAAI,0BAA0B;EAGtC,KAAK,SAAS,IAAI,OAAO,KAAK,QAAQ,OAAO;EAC7C,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,KAAK,MAAM,QAAQ,QAAQ,GAAG,GAAG,QAAQ,KAAK,CAAC,QAAQ,GAAG;GAGhE,MAAM,WAAW,MAAM,KAAK,OAAO,OAAO,KAAK;IAC7C;IACA;IACA,SAAS,QAAQ;IACjB,MAAM,QAAQ;IACd,MAAM,QAAQ;IAEd,GAAK,QAAgB,YAAY,EAAE,UAAW,QAAgB,UAAU;IACxE,GAAI,QAAQ,WAAW,EAAE,SAAS,QAAQ,SAAS;IACnD,GAAI,QAAQ,MAAM,EAAE,IAAI,QAAQ,IAAI;IACpC,GAAI,QAAQ,OAAO,EAAE,KAAK,QAAQ,KAAK;IACvC,GAAI,QAAQ,eAAe,EACzB,aAAa,MAAM,QAAQ,IACzB,QAAQ,YAAY,IAAI,OAAM,gBAAe;KAC3C,UAAU,WAAW;KACrB,SAAS,MAAM,KAAK,SAAS,WAAW,QAAQ;KACjD,EAAE,CACJ,EACF;IACF,CAAC;GAEF,IAAI,SAAS,OACX,MAAM,IAAI,2BAA2B;GAGvC,OAAO;IACL,WAAW,SAAS,KAAK;IACzB,UAAU;IACV,UAAU,EACR,UAAU,UACX;IACF;WACM,OAAO;GACd,IAAI,iBAAiB,6BAA6B,iBAAiB,0BACjE,MAAM;GAGR,MAAM,IAAI,2BAA2B;;;;;;;;;;CAWzC,MAAc,SAAS,SAA8D;EACnF,IAAI,OAAO,SAAS,QAAQ,EAC1B,OAAO;EAIT,MAAM,cAAc,MAAM,IADL,SAAS,QACI,CAAC,aAAa;EAChD,OAAO,OAAO,KAAK,YAAY"}
@@ -1,2 +1,2 @@
1
- import { $ as DuplicateRouteNameError, $n as TrailingSlashMode, At as Delete, Bn as ControllerOptions, Bt as extractDomainParamNames, Cr as RoutePrefixes, Ct as successMessageSchema, Dt as getRouteDecoratedMethods, Er as StratalRouteMap, Et as Route, Ft as Controller, G as buildRouteUrl, Gn as RouteBodyObject, Gt as toOpenAPIPath, H as SignedUriOptions, Hn as ExplicitRouteMetadata, Ht as generateConventionRouteName, It as getControllerOptions, Jn as RouteResponse, Jt as RouteRegistrationService, Kn as RouteConfig, Kt as route, Lt as getControllerRoute, Mt as Patch, Nt as Post, Ot as getRouteMetadata, Pt as Put, Q as DomainMismatchError, Qn as SecurityScheme, Qt as VersioningService, Rt as getControllerVersion, Sr as RouteParams, St as paginationQuerySchema, Tr as SerializedRoutes, Tt as validationErrorResponseSchema, U as Uri, Un as LocalePathConfig, Ut as getPathSpecificityScore, V as RouterContext, Vn as ConventionRouteMetadata, Vt as extractParamNames, W as UriOptions, Wn as RouteBody, Wt as sortRoutesBySpecificity, X as SSEStreamingApi, Xn as RouterEnv, Xt as RouteRegistrationInput, Y as SSEMessage, Yn as RouteResponseObject, Yt as RegisteredRoute, Z as StreamingApi, Zn as RouterVariables, Zt as RouteRegistry, _t as createDomainMiddleware, an as HonoApp, ar as VERSION_NEUTRAL, at as RouteNameNotFoundError, br as RouteMatcher, bt as errorResponseSchema, cn as IController, ct as RouteNotFoundError, dt as HonoAppAlreadyConfiguredError, en as RouteConfigurable, er as VersioningOptions, et as InvalidSignatureError, ft as ControllerRegistrationError, gt as VerifySignatureMiddleware, ht as verifySignedUrl, in as ResolvedPath, ir as SECURITY_SCHEMES, it as ResponseValidationError, jt as Get, kt as All, lt as OpenAPIValidationError, mt as signUrl, nn as RouterGroupConfig, nr as ROUTER_CONTEXT_KEYS, nt as MissingEnvironmentVariableError, on as Middleware, ot as RouterUseScopeError, pt as SignedUrlOptions, qn as RouteMetadata, qt as ROUTER_TOKENS, rn as LocalePathService, rr as ROUTE_METADATA_KEYS, rt as MissingRouteParamError, sn as Next, st as SchemaValidationError, tn as Router, tr as HTTP_METHODS, tt as MiddlewareNextCalledMultipleTimesError, ut as OpenAPIRouteRegistrationError, vt as parseDomainPattern, wr as SerializedRoute, wt as uuidParamSchema, xr as RouteName, xt as paginatedResponseSchema, yr as CurrentRoute, yt as commonErrorSchemas, zt as createMiddlewareChain } from "../index-D0US0X14.mjs";
1
+ import { $ as DuplicateRouteNameError, $n as TrailingSlashMode, At as Delete, Bn as ControllerOptions, Bt as extractDomainParamNames, Cr as RoutePrefixes, Ct as successMessageSchema, Dt as getRouteDecoratedMethods, Er as StratalRouteMap, Et as Route, Ft as Controller, G as buildRouteUrl, Gn as RouteBodyObject, Gt as toOpenAPIPath, H as SignedUriOptions, Hn as ExplicitRouteMetadata, Ht as generateConventionRouteName, It as getControllerOptions, Jn as RouteResponse, Jt as RouteRegistrationService, Kn as RouteConfig, Kt as route, Lt as getControllerRoute, Mt as Patch, Nt as Post, Ot as getRouteMetadata, Pt as Put, Q as DomainMismatchError, Qn as SecurityScheme, Qt as VersioningService, Rt as getControllerVersion, Sr as RouteParams, St as paginationQuerySchema, Tr as SerializedRoutes, Tt as validationErrorResponseSchema, U as Uri, Un as LocalePathConfig, Ut as getPathSpecificityScore, V as RouterContext, Vn as ConventionRouteMetadata, Vt as extractParamNames, W as UriOptions, Wn as RouteBody, Wt as sortRoutesBySpecificity, X as SSEStreamingApi, Xn as RouterEnv, Xt as RouteRegistrationInput, Y as SSEMessage, Yn as RouteResponseObject, Yt as RegisteredRoute, Z as StreamingApi, Zn as RouterVariables, Zt as RouteRegistry, _t as createDomainMiddleware, an as HonoApp, ar as VERSION_NEUTRAL, at as RouteNameNotFoundError, br as RouteMatcher, bt as errorResponseSchema, cn as IController, ct as RouteNotFoundError, dt as HonoAppAlreadyConfiguredError, en as RouteConfigurable, er as VersioningOptions, et as InvalidSignatureError, ft as ControllerRegistrationError, gt as VerifySignatureMiddleware, ht as verifySignedUrl, in as ResolvedPath, ir as SECURITY_SCHEMES, it as ResponseValidationError, jt as Get, kt as All, lt as OpenAPIValidationError, mt as signUrl, nn as RouterGroupConfig, nr as ROUTER_CONTEXT_KEYS, nt as MissingEnvironmentVariableError, on as Middleware, ot as RouterUseScopeError, pt as SignedUrlOptions, qn as RouteMetadata, qt as ROUTER_TOKENS, rn as LocalePathService, rr as ROUTE_METADATA_KEYS, rt as MissingRouteParamError, sn as Next, st as SchemaValidationError, tn as Router, tr as HTTP_METHODS, tt as MiddlewareNextCalledMultipleTimesError, ut as OpenAPIRouteRegistrationError, vt as parseDomainPattern, wr as SerializedRoute, wt as uuidParamSchema, xr as RouteName, xt as paginatedResponseSchema, yr as CurrentRoute, yt as commonErrorSchemas, zt as createMiddlewareChain } from "../index-ByOyTmqf.mjs";
2
2
  export { All, Controller, ControllerOptions, ControllerRegistrationError, ConventionRouteMetadata, CurrentRoute, Delete, DomainMismatchError, DuplicateRouteNameError, ExplicitRouteMetadata, Get, HTTP_METHODS, HonoApp, HonoAppAlreadyConfiguredError, IController, InvalidSignatureError, LocalePathConfig, LocalePathService, Middleware, MiddlewareNextCalledMultipleTimesError, MissingEnvironmentVariableError, MissingRouteParamError, Next, OpenAPIRouteRegistrationError, OpenAPIValidationError, Patch, Post, Put, ROUTER_CONTEXT_KEYS, ROUTER_TOKENS, ROUTE_METADATA_KEYS, RegisteredRoute, ResolvedPath, ResponseValidationError, Route, RouteBody, RouteBodyObject, RouteConfig, RouteConfigurable, RouteMatcher, RouteMetadata, RouteName, RouteNameNotFoundError, RouteNotFoundError, RouteParams, RoutePrefixes, RouteRegistrationInput, RouteRegistrationService, RouteRegistry, RouteResponse, RouteResponseObject, Router, RouterContext, RouterEnv, RouterGroupConfig, RouterUseScopeError, RouterVariables, SECURITY_SCHEMES, SSEMessage, SSEStreamingApi, SchemaValidationError, SecurityScheme, SerializedRoute, SerializedRoutes, SignedUriOptions, SignedUrlOptions, StratalRouteMap, StreamingApi, TrailingSlashMode, Uri, UriOptions, VERSION_NEUTRAL, VerifySignatureMiddleware, VersioningOptions, VersioningService, buildRouteUrl, commonErrorSchemas, createDomainMiddleware, createMiddlewareChain, errorResponseSchema, extractDomainParamNames, extractParamNames, generateConventionRouteName, getControllerOptions, getControllerRoute, getControllerVersion, getPathSpecificityScore, getRouteDecoratedMethods, getRouteMetadata, paginatedResponseSchema, paginationQuerySchema, parseDomainPattern, route, signUrl, sortRoutesBySpecificity, successMessageSchema, toOpenAPIPath, uuidParamSchema, validationErrorResponseSchema, verifySignedUrl };
@@ -1,7 +1,7 @@
1
- import { V as ROUTER_TOKENS, d as ROUTER_CONTEXT_KEYS, f as ROUTE_METADATA_KEYS, l as HTTP_METHODS, m as VERSION_NEUTRAL, p as SECURITY_SCHEMES, s as RouterContext } from "../errors-BdyV5PnY.mjs";
2
- import { _ as OpenAPIValidationError, b as ControllerRegistrationError, c as InvalidSignatureError, d as MissingRouteParamError, f as ResponseValidationError, g as RouteNotFoundError, h as SchemaValidationError, l as MiddlewareNextCalledMultipleTimesError, m as RouterUseScopeError, n as Router, o as DomainMismatchError, p as RouteNameNotFoundError, s as DuplicateRouteNameError, u as MissingEnvironmentVariableError, v as OpenAPIRouteRegistrationError, y as HonoAppAlreadyConfiguredError } from "../module-Dk2qTa77.mjs";
3
- import { C as validationErrorResponseSchema, D as createMiddlewareChain, E as getRouteMetadata, O as createDomainMiddleware, S as uuidParamSchema, T as getRouteDecoratedMethods, _ as commonErrorSchemas, a as buildRouteUrl, b as paginationQuerySchema, c as LocalePathService, d as extractDomainParamNames, f as extractParamNames, g as toOpenAPIPath, h as sortRoutesBySpecificity, i as Uri, k as parseDomainPattern, l as HonoApp, m as getPathSpecificityScore, n as VerifySignatureMiddleware, o as RouteRegistry, p as generateConventionRouteName, r as route, s as VersioningService, u as RouteRegistrationService, v as errorResponseSchema, w as Route, x as successMessageSchema, y as paginatedResponseSchema } from "../i18n.module-BBlNNlcG.mjs";
4
- import { i as getControllerVersion, n as getControllerOptions, r as getControllerRoute, t as Controller } from "../controller.decorator-DQzenvSN.mjs";
5
- import { a as Post, i as Patch, n as Delete, o as Put, r as Get, t as All } from "../http-method.decorator-DXwxAfb_.mjs";
1
+ import { V as ROUTER_TOKENS, d as ROUTER_CONTEXT_KEYS, f as ROUTE_METADATA_KEYS, l as HTTP_METHODS, m as VERSION_NEUTRAL, p as SECURITY_SCHEMES, s as RouterContext } from "../errors-COW9-Mar.mjs";
2
+ import { _ as OpenAPIValidationError, b as ControllerRegistrationError, c as InvalidSignatureError, d as MissingRouteParamError, f as ResponseValidationError, g as RouteNotFoundError, h as SchemaValidationError, l as MiddlewareNextCalledMultipleTimesError, m as RouterUseScopeError, n as Router, o as DomainMismatchError, p as RouteNameNotFoundError, s as DuplicateRouteNameError, u as MissingEnvironmentVariableError, v as OpenAPIRouteRegistrationError, y as HonoAppAlreadyConfiguredError } from "../module-BzLg57FK.mjs";
3
+ import { C as validationErrorResponseSchema, D as createMiddlewareChain, E as getRouteMetadata, O as createDomainMiddleware, S as uuidParamSchema, T as getRouteDecoratedMethods, _ as commonErrorSchemas, a as buildRouteUrl, b as paginationQuerySchema, c as LocalePathService, d as extractDomainParamNames, f as extractParamNames, g as toOpenAPIPath, h as sortRoutesBySpecificity, i as Uri, k as parseDomainPattern, l as HonoApp, m as getPathSpecificityScore, n as VerifySignatureMiddleware, o as RouteRegistry, p as generateConventionRouteName, r as route, s as VersioningService, u as RouteRegistrationService, v as errorResponseSchema, w as Route, x as successMessageSchema, y as paginatedResponseSchema } from "../i18n.module-CzXLW9Hy.mjs";
4
+ import { i as getControllerVersion, n as getControllerOptions, r as getControllerRoute, t as Controller } from "../controller.decorator-B9vwn0zK.mjs";
5
+ import { a as Post, i as Patch, n as Delete, o as Put, r as Get, t as All } from "../http-method.decorator-BrgHMdLQ.mjs";
6
6
  import { n as verifySignedUrl, t as signUrl } from "../signed-url-BQPbv2In.mjs";
7
7
  export { All, Controller, ControllerRegistrationError, Delete, DomainMismatchError, DuplicateRouteNameError, Get, HTTP_METHODS, HonoApp, HonoAppAlreadyConfiguredError, InvalidSignatureError, LocalePathService, MiddlewareNextCalledMultipleTimesError, MissingEnvironmentVariableError, MissingRouteParamError, OpenAPIRouteRegistrationError, OpenAPIValidationError, Patch, Post, Put, ROUTER_CONTEXT_KEYS, ROUTER_TOKENS, ROUTE_METADATA_KEYS, ResponseValidationError, Route, RouteNameNotFoundError, RouteNotFoundError, RouteRegistrationService, RouteRegistry, Router, RouterContext, RouterUseScopeError, SECURITY_SCHEMES, SchemaValidationError, Uri, VERSION_NEUTRAL, VerifySignatureMiddleware, VersioningService, buildRouteUrl, commonErrorSchemas, createDomainMiddleware, createMiddlewareChain, errorResponseSchema, extractDomainParamNames, extractParamNames, generateConventionRouteName, getControllerOptions, getControllerRoute, getControllerVersion, getPathSpecificityScore, getRouteDecoratedMethods, getRouteMetadata, paginatedResponseSchema, paginationQuerySchema, parseDomainPattern, route, signUrl, sortRoutesBySpecificity, successMessageSchema, toOpenAPIPath, uuidParamSchema, validationErrorResponseSchema, verifySignedUrl };
@@ -1,6 +1,6 @@
1
- import { Dr as Container, K as Application, d as ApplicationError } from "../index-D0US0X14.mjs";
1
+ import { Dr as Container, K as Application, d as ApplicationError } from "../index-ByOyTmqf.mjs";
2
2
  import { t as Constructor } from "../types-cySNS_lp.mjs";
3
- import { t as Command } from "../command-Bu-PjJrX.mjs";
3
+ import { t as Command } from "../command-Cmmf0oHX.mjs";
4
4
 
5
5
  //#region src/seeder/seeder.d.ts
6
6
  declare const SEEDER_INTERNALS: unique symbol;
@@ -1,3 +1,3 @@
1
1
  import { n as SEEDER_INTERNALS, r as Seeder, t as isSeeder } from "../is-seeder-CebjZCDn.mjs";
2
- import { a as SeederNameCollisionError, i as SeederRegistry, n as DbSeedListCommand, o as SeederNotRegisteredError, r as SEEDER_TOKENS, t as DbSeedCommand } from "../seeder-CJAOHEIo.mjs";
2
+ import { a as SeederNameCollisionError, i as SeederRegistry, n as DbSeedListCommand, o as SeederNotRegisteredError, r as SEEDER_TOKENS, t as DbSeedCommand } from "../seeder-zoEfEw9i.mjs";
3
3
  export { DbSeedCommand, DbSeedListCommand, SEEDER_INTERNALS, SEEDER_TOKENS, Seeder, SeederNameCollisionError, SeederNotRegisteredError, SeederRegistry, isSeeder };
@@ -1,5 +1,5 @@
1
- import { H as ApplicationError, k as ERROR_CODES } from "./errors-BdyV5PnY.mjs";
2
- import { a as __decorate, o as __decorateParam, s as __decorateMetadata } from "./logger-V6Ms3QnQ.mjs";
1
+ import { H as ApplicationError, k as ERROR_CODES } from "./errors-COW9-Mar.mjs";
2
+ import { a as __decorate, o as __decorateParam, s as __decorateMetadata } from "./logger-DlV7NtvD.mjs";
3
3
  import { t as Command } from "./command-BgSlsS4M.mjs";
4
4
  import { n as SEEDER_INTERNALS } from "./is-seeder-CebjZCDn.mjs";
5
5
  import { inject } from "tsyringe";
@@ -18,6 +18,7 @@ var SeederNameCollisionError = class extends ApplicationError {
18
18
  //#region src/seeder/seeder-registry.ts
19
19
  const SEEDER_TOKENS = { SeederRegistry: Symbol.for("stratal:seeders:registry") };
20
20
  var SeederRegistry = class {
21
+ app;
21
22
  seeders = /* @__PURE__ */ new Set();
22
23
  nameIndex = /* @__PURE__ */ new Map();
23
24
  constructor(app) {
@@ -61,6 +62,7 @@ var SeederRegistry = class {
61
62
  //#endregion
62
63
  //#region src/seeder/commands/db-seed-list.command.ts
63
64
  let DbSeedListCommand = class DbSeedListCommand extends Command {
65
+ seeders;
64
66
  static command = "db:seed:list";
65
67
  static description = "List available database seeders";
66
68
  constructor(seeders) {
@@ -80,6 +82,7 @@ DbSeedListCommand = __decorate([__decorateParam(0, inject(SEEDER_TOKENS.SeederRe
80
82
  //#endregion
81
83
  //#region src/seeder/commands/db-seed.command.ts
82
84
  let DbSeedCommand = class DbSeedCommand extends Command {
85
+ seeders;
83
86
  static command = "db:seed {names* : Seeder class names} {--a|all : Run all seeders} {--dry-run : Preview without executing}";
84
87
  static description = "Run database seeders";
85
88
  constructor(seeders) {
@@ -132,4 +135,4 @@ DbSeedCommand = __decorate([__decorateParam(0, inject(SEEDER_TOKENS.SeederRegist
132
135
  //#endregion
133
136
  export { SeederNameCollisionError as a, SeederRegistry as i, DbSeedListCommand as n, SeederNotRegisteredError as o, SEEDER_TOKENS as r, DbSeedCommand as t };
134
137
 
135
- //# sourceMappingURL=seeder-CJAOHEIo.mjs.map
138
+ //# sourceMappingURL=seeder-zoEfEw9i.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"seeder-CJAOHEIo.mjs","names":[],"sources":["../src/seeder/errors.ts","../src/seeder/seeder-registry.ts","../src/seeder/commands/db-seed-list.command.ts","../src/seeder/commands/db-seed.command.ts"],"sourcesContent":["import { ApplicationError, ERROR_CODES } from '../errors'\n\nexport class SeederNotRegisteredError extends ApplicationError {\n constructor(name: string) {\n super(\n 'errors.seederNotRegistered',\n ERROR_CODES.SYSTEM.SEEDER_NOT_REGISTERED,\n { name },\n )\n }\n}\n\nexport class SeederNameCollisionError extends ApplicationError {\n constructor(name: string) {\n super(\n 'errors.seederNameCollision',\n ERROR_CODES.SYSTEM.SEEDER_NAME_COLLISION,\n { name },\n )\n }\n}\n","import type { Application } from '../application'\nimport type { Container } from '../di/container'\nimport type { Constructor } from '../types'\nimport { SeederNameCollisionError, SeederNotRegisteredError } from './errors'\nimport { type Seeder, SEEDER_INTERNALS } from './seeder'\n\nexport const SEEDER_TOKENS = {\n SeederRegistry: Symbol.for('stratal:seeders:registry'),\n} as const\n\nexport class SeederRegistry {\n private seeders = new Set<Constructor<Seeder>>()\n private nameIndex = new Map<string, Constructor<Seeder>>()\n\n constructor(private app: Application) { }\n\n register(SeederClass: Constructor<Seeder>): void {\n const existing = this.nameIndex.get(SeederClass.name)\n if (existing && existing !== SeederClass) {\n throw new SeederNameCollisionError(SeederClass.name)\n }\n this.seeders.add(SeederClass)\n this.nameIndex.set(SeederClass.name, SeederClass)\n }\n\n async run(SeederClass: Constructor<Seeder>, options?: { container?: Container }): Promise<void> {\n if (!this.seeders.has(SeederClass)) {\n throw new SeederNotRegisteredError(SeederClass.name)\n }\n\n const execute = async (container: Container) => {\n const seeder = container.resolve<Seeder>(SeederClass)\n seeder[SEEDER_INTERNALS] = {\n run: (cls) => this.run(cls, { container }),\n container,\n }\n await seeder.run()\n }\n\n if (options?.container) {\n await execute(options.container)\n } else {\n const mockContext = this.app.createMockRouterContext('en')\n await this.app.container.runInRequestScope(mockContext, execute)\n }\n }\n\n async runAll(options?: { container?: Container }): Promise<void> {\n for (const SeederClass of this.seeders) {\n await this.run(SeederClass, options)\n }\n }\n\n find(name: string): Constructor<Seeder> | undefined {\n return this.nameIndex.get(name)\n }\n\n has(SeederClass: Constructor<Seeder>): boolean {\n return this.seeders.has(SeederClass)\n }\n\n list(): { className: string }[] {\n return [...this.seeders].map(cls => ({ className: cls.name }))\n }\n}\n","import { inject } from 'tsyringe'\nimport { Command } from '../../quarry/command'\nimport { type SeederRegistry, SEEDER_TOKENS } from '../seeder-registry'\n\nexport class DbSeedListCommand extends Command {\n static command = 'db:seed:list'\n static description = 'List available database seeders'\n\n constructor(@inject(SEEDER_TOKENS.SeederRegistry) private seeders: SeederRegistry) {\n super()\n }\n\n handle(): undefined | number {\n const list = this.seeders.list()\n if (list.length === 0) {\n this.info('No seeders found')\n return 0\n }\n this.table(['Class'], list.map(s => [s.className]))\n\n return undefined\n }\n}\n","import { inject } from 'tsyringe'\nimport { Command } from '../../quarry/command'\nimport { type SeederRegistry, SEEDER_TOKENS } from '../seeder-registry'\n\nexport class DbSeedCommand extends Command {\n static command = 'db:seed {names* : Seeder class names} {--a|all : Run all seeders} {--dry-run : Preview without executing}'\n static description = 'Run database seeders'\n\n constructor(@inject(SEEDER_TOKENS.SeederRegistry) private seeders: SeederRegistry) {\n super()\n }\n\n async handle(): Promise<number | undefined> {\n const names = this.array('names')\n const all = this.boolean('all')\n const dryRun = this.boolean('dry-run')\n\n if (names.length > 0 && all) {\n this.warn(`Ignoring \"${names.join(', ')}\" because --all takes precedence`)\n }\n\n if (names.length === 0 && !all) {\n this.fail('Specify one or more seeder class names or use --all')\n return 1\n }\n\n if (dryRun) {\n if (all) {\n const list = this.seeders.list()\n this.info('Dry run — would execute:')\n for (const s of list) {\n this.info(` ${s.className}`)\n }\n } else {\n this.info('Dry run — would execute:')\n for (const name of names) {\n const SeederClass = this.seeders.find(name)\n if (!SeederClass) {\n this.fail(`Seeder \"${name}\" not found`)\n return 1\n }\n this.info(` ${SeederClass.name}`)\n }\n }\n return 0\n }\n\n if (all) {\n await this.seeders.runAll()\n this.success('All seeders completed')\n } else {\n for (const name of names) {\n const SeederClass = this.seeders.find(name)\n if (!SeederClass) {\n this.fail(`Seeder \"${name}\" not found`)\n return 1\n }\n await this.seeders.run(SeederClass)\n this.success(`Seeder \"${name}\" completed`)\n }\n }\n\n return 0\n }\n}\n"],"mappings":";;;;;;AAEA,IAAa,2BAAb,cAA8C,iBAAiB;CAC7D,YAAY,MAAc;AACxB,QACE,8BACA,YAAY,OAAO,uBACnB,EAAE,MAAM,CACT;;;AAIL,IAAa,2BAAb,cAA8C,iBAAiB;CAC7D,YAAY,MAAc;AACxB,QACE,8BACA,YAAY,OAAO,uBACnB,EAAE,MAAM,CACT;;;;;ACZL,MAAa,gBAAgB,EAC3B,gBAAgB,OAAO,IAAI,2BAA2B,EACvD;AAED,IAAa,iBAAb,MAA4B;CAC1B,0BAAkB,IAAI,KAA0B;CAChD,4BAAoB,IAAI,KAAkC;CAE1D,YAAY,KAA0B;AAAlB,OAAA,MAAA;;CAEpB,SAAS,aAAwC;EAC/C,MAAM,WAAW,KAAK,UAAU,IAAI,YAAY,KAAK;AACrD,MAAI,YAAY,aAAa,YAC3B,OAAM,IAAI,yBAAyB,YAAY,KAAK;AAEtD,OAAK,QAAQ,IAAI,YAAY;AAC7B,OAAK,UAAU,IAAI,YAAY,MAAM,YAAY;;CAGnD,MAAM,IAAI,aAAkC,SAAoD;AAC9F,MAAI,CAAC,KAAK,QAAQ,IAAI,YAAY,CAChC,OAAM,IAAI,yBAAyB,YAAY,KAAK;EAGtD,MAAM,UAAU,OAAO,cAAyB;GAC9C,MAAM,SAAS,UAAU,QAAgB,YAAY;AACrD,UAAO,oBAAoB;IACzB,MAAM,QAAQ,KAAK,IAAI,KAAK,EAAE,WAAW,CAAC;IAC1C;IACD;AACD,SAAM,OAAO,KAAK;;AAGpB,MAAI,SAAS,UACX,OAAM,QAAQ,QAAQ,UAAU;OAC3B;GACL,MAAM,cAAc,KAAK,IAAI,wBAAwB,KAAK;AAC1D,SAAM,KAAK,IAAI,UAAU,kBAAkB,aAAa,QAAQ;;;CAIpE,MAAM,OAAO,SAAoD;AAC/D,OAAK,MAAM,eAAe,KAAK,QAC7B,OAAM,KAAK,IAAI,aAAa,QAAQ;;CAIxC,KAAK,MAA+C;AAClD,SAAO,KAAK,UAAU,IAAI,KAAK;;CAGjC,IAAI,aAA2C;AAC7C,SAAO,KAAK,QAAQ,IAAI,YAAY;;CAGtC,OAAgC;AAC9B,SAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,KAAI,SAAQ,EAAE,WAAW,IAAI,MAAM,EAAE;;;;;AC1D3D,IAAA,oBAAA,MAAM,0BAA0B,QAAQ;CAC7C,OAAO,UAAU;CACjB,OAAO,cAAc;CAErB,YAAY,SAAuE;AACjF,SAAO;AADiD,OAAA,UAAA;;CAI1D,SAA6B;EAC3B,MAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,MAAI,KAAK,WAAW,GAAG;AACrB,QAAK,KAAK,mBAAmB;AAC7B,UAAO;;AAET,OAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,KAAI,MAAK,CAAC,EAAE,UAAU,CAAC,CAAC;;;mDAVxC,OAAO,cAAc,eAAe,CAAA,EAAA,mBAAA,qBAAA,CAAA,OAAA,CAAA,CAAA,EAAA,kBAAA;;;ACJ5C,IAAA,gBAAA,MAAM,sBAAsB,QAAQ;CACzC,OAAO,UAAU;CACjB,OAAO,cAAc;CAErB,YAAY,SAAuE;AACjF,SAAO;AADiD,OAAA,UAAA;;CAI1D,MAAM,SAAsC;EAC1C,MAAM,QAAQ,KAAK,MAAM,QAAQ;EACjC,MAAM,MAAM,KAAK,QAAQ,MAAM;EAC/B,MAAM,SAAS,KAAK,QAAQ,UAAU;AAEtC,MAAI,MAAM,SAAS,KAAK,IACtB,MAAK,KAAK,aAAa,MAAM,KAAK,KAAK,CAAC,kCAAkC;AAG5E,MAAI,MAAM,WAAW,KAAK,CAAC,KAAK;AAC9B,QAAK,KAAK,sDAAsD;AAChE,UAAO;;AAGT,MAAI,QAAQ;AACV,OAAI,KAAK;IACP,MAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,SAAK,KAAK,2BAA2B;AACrC,SAAK,MAAM,KAAK,KACd,MAAK,KAAK,KAAK,EAAE,YAAY;UAE1B;AACL,SAAK,KAAK,2BAA2B;AACrC,SAAK,MAAM,QAAQ,OAAO;KACxB,MAAM,cAAc,KAAK,QAAQ,KAAK,KAAK;AAC3C,SAAI,CAAC,aAAa;AAChB,WAAK,KAAK,WAAW,KAAK,aAAa;AACvC,aAAO;;AAET,UAAK,KAAK,KAAK,YAAY,OAAO;;;AAGtC,UAAO;;AAGT,MAAI,KAAK;AACP,SAAM,KAAK,QAAQ,QAAQ;AAC3B,QAAK,QAAQ,wBAAwB;QAErC,MAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,cAAc,KAAK,QAAQ,KAAK,KAAK;AAC3C,OAAI,CAAC,aAAa;AAChB,SAAK,KAAK,WAAW,KAAK,aAAa;AACvC,WAAO;;AAET,SAAM,KAAK,QAAQ,IAAI,YAAY;AACnC,QAAK,QAAQ,WAAW,KAAK,aAAa;;AAI9C,SAAO;;;+CAtDI,OAAO,cAAc,eAAe,CAAA,EAAA,mBAAA,qBAAA,CAAA,OAAA,CAAA,CAAA,EAAA,cAAA"}
1
+ {"version":3,"file":"seeder-zoEfEw9i.mjs","names":[],"sources":["../src/seeder/errors.ts","../src/seeder/seeder-registry.ts","../src/seeder/commands/db-seed-list.command.ts","../src/seeder/commands/db-seed.command.ts"],"sourcesContent":["import { ApplicationError, ERROR_CODES } from '../errors'\n\nexport class SeederNotRegisteredError extends ApplicationError {\n constructor(name: string) {\n super(\n 'errors.seederNotRegistered',\n ERROR_CODES.SYSTEM.SEEDER_NOT_REGISTERED,\n { name },\n )\n }\n}\n\nexport class SeederNameCollisionError extends ApplicationError {\n constructor(name: string) {\n super(\n 'errors.seederNameCollision',\n ERROR_CODES.SYSTEM.SEEDER_NAME_COLLISION,\n { name },\n )\n }\n}\n","import type { Application } from '../application'\nimport type { Container } from '../di/container'\nimport type { Constructor } from '../types'\nimport { SeederNameCollisionError, SeederNotRegisteredError } from './errors'\nimport { type Seeder, SEEDER_INTERNALS } from './seeder'\n\nexport const SEEDER_TOKENS = {\n SeederRegistry: Symbol.for('stratal:seeders:registry'),\n} as const\n\nexport class SeederRegistry {\n private seeders = new Set<Constructor<Seeder>>()\n private nameIndex = new Map<string, Constructor<Seeder>>()\n\n constructor(private app: Application) { }\n\n register(SeederClass: Constructor<Seeder>): void {\n const existing = this.nameIndex.get(SeederClass.name)\n if (existing && existing !== SeederClass) {\n throw new SeederNameCollisionError(SeederClass.name)\n }\n this.seeders.add(SeederClass)\n this.nameIndex.set(SeederClass.name, SeederClass)\n }\n\n async run(SeederClass: Constructor<Seeder>, options?: { container?: Container }): Promise<void> {\n if (!this.seeders.has(SeederClass)) {\n throw new SeederNotRegisteredError(SeederClass.name)\n }\n\n const execute = async (container: Container) => {\n const seeder = container.resolve<Seeder>(SeederClass)\n seeder[SEEDER_INTERNALS] = {\n run: (cls) => this.run(cls, { container }),\n container,\n }\n await seeder.run()\n }\n\n if (options?.container) {\n await execute(options.container)\n } else {\n const mockContext = this.app.createMockRouterContext('en')\n await this.app.container.runInRequestScope(mockContext, execute)\n }\n }\n\n async runAll(options?: { container?: Container }): Promise<void> {\n for (const SeederClass of this.seeders) {\n await this.run(SeederClass, options)\n }\n }\n\n find(name: string): Constructor<Seeder> | undefined {\n return this.nameIndex.get(name)\n }\n\n has(SeederClass: Constructor<Seeder>): boolean {\n return this.seeders.has(SeederClass)\n }\n\n list(): { className: string }[] {\n return [...this.seeders].map(cls => ({ className: cls.name }))\n }\n}\n","import { inject } from 'tsyringe'\nimport { Command } from '../../quarry/command'\nimport { type SeederRegistry, SEEDER_TOKENS } from '../seeder-registry'\n\nexport class DbSeedListCommand extends Command {\n static command = 'db:seed:list'\n static description = 'List available database seeders'\n\n constructor(@inject(SEEDER_TOKENS.SeederRegistry) private seeders: SeederRegistry) {\n super()\n }\n\n handle(): undefined | number {\n const list = this.seeders.list()\n if (list.length === 0) {\n this.info('No seeders found')\n return 0\n }\n this.table(['Class'], list.map(s => [s.className]))\n\n return undefined\n }\n}\n","import { inject } from 'tsyringe'\nimport { Command } from '../../quarry/command'\nimport { type SeederRegistry, SEEDER_TOKENS } from '../seeder-registry'\n\nexport class DbSeedCommand extends Command {\n static command = 'db:seed {names* : Seeder class names} {--a|all : Run all seeders} {--dry-run : Preview without executing}'\n static description = 'Run database seeders'\n\n constructor(@inject(SEEDER_TOKENS.SeederRegistry) private seeders: SeederRegistry) {\n super()\n }\n\n async handle(): Promise<number | undefined> {\n const names = this.array('names')\n const all = this.boolean('all')\n const dryRun = this.boolean('dry-run')\n\n if (names.length > 0 && all) {\n this.warn(`Ignoring \"${names.join(', ')}\" because --all takes precedence`)\n }\n\n if (names.length === 0 && !all) {\n this.fail('Specify one or more seeder class names or use --all')\n return 1\n }\n\n if (dryRun) {\n if (all) {\n const list = this.seeders.list()\n this.info('Dry run — would execute:')\n for (const s of list) {\n this.info(` ${s.className}`)\n }\n } else {\n this.info('Dry run — would execute:')\n for (const name of names) {\n const SeederClass = this.seeders.find(name)\n if (!SeederClass) {\n this.fail(`Seeder \"${name}\" not found`)\n return 1\n }\n this.info(` ${SeederClass.name}`)\n }\n }\n return 0\n }\n\n if (all) {\n await this.seeders.runAll()\n this.success('All seeders completed')\n } else {\n for (const name of names) {\n const SeederClass = this.seeders.find(name)\n if (!SeederClass) {\n this.fail(`Seeder \"${name}\" not found`)\n return 1\n }\n await this.seeders.run(SeederClass)\n this.success(`Seeder \"${name}\" completed`)\n }\n }\n\n return 0\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
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;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"}
@@ -1 +1 @@
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;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"}
@@ -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-w0Ve52Xg.mjs.map
76
+ //# sourceMappingURL=smtp.provider-B6D7zuWX.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"smtp.provider-w0Ve52Xg.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,4 +1,4 @@
1
- import { V as RouterContext, d as ApplicationError, gn as AsyncModuleOptions, vn as DynamicModule } from "../index-D0US0X14.mjs";
1
+ import { V as RouterContext, d as ApplicationError, gn as AsyncModuleOptions, vn as DynamicModule } from "../index-ByOyTmqf.mjs";
2
2
  import { t as StratalEnv } from "../env-D1rcZ8_r.mjs";
3
3
  import { _ as StorageEntry, a as uploadResultSchema, c as getPresignedUrlInputSchema, d as fileExistsInputSchema, f as DownloadResult, g as StorageConfig, h as PresignedUrlConfig, i as UploadResult, l as presignedUrlResultSchema, m as deleteFileInputSchema, n as StreamingBlobPayloadInputTypes, o as GetPresignedUrlInput, p as DeleteFileInput, r as UploadOptions, s as PresignedUrlResult, t as IStorageProvider, u as FileExistsInput, v as StorageRouteConfig } from "../storage-provider.interface-Bd6vA4ak.mjs";
4
4
 
@@ -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-Da3Pz2X7.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-1zw-6Yiz.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,2 +1,2 @@
1
- import { t as R2StorageProvider } from "../../r2-storage.provider-Co6F0ZYV.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-BdyV5PnY.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 { k as Module } from "./module-Dk2qTa77.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
4
  import { i as z, s as withI18n } from "./validation-DtJwAv7O.mjs";
5
- import { t as Controller } from "./controller.decorator-DQzenvSN.mjs";
6
- import { n as Delete, o as Put, r as Get } from "./http-method.decorator-DXwxAfb_.mjs";
7
- import { c as FileNotFoundError, i as PresignedUrlInvalidExpiryError, l as DiskNotConfiguredError, o as InvalidDiskError, r as R2BindingNotFoundError } from "./errors-Da3Pz2X7.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-Co6F0ZYV.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-1zw-6Yiz.mjs.map
489
+ //# sourceMappingURL=storage-D8CBP72Z.mjs.map