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.
- package/dist/base-email.provider-CfQCA08m.mjs.map +1 -1
- package/dist/bin/cloudflare-workers-loader.mjs.map +1 -1
- package/dist/bin/quarry.mjs.map +1 -1
- package/dist/cache/index.d.mts +1 -1
- package/dist/cache/index.mjs +5 -3
- package/dist/cache/index.mjs.map +1 -1
- package/dist/command-BgSlsS4M.mjs.map +1 -1
- package/dist/{command-Bu-PjJrX.d.mts → command-Cmmf0oHX.d.mts} +2 -2
- package/dist/{command-Bu-PjJrX.d.mts.map → command-Cmmf0oHX.d.mts.map} +1 -1
- package/dist/config/index.d.mts +1 -1
- package/dist/config/index.mjs +5 -3
- package/dist/config/index.mjs.map +1 -1
- package/dist/{controller.decorator-DQzenvSN.mjs → controller.decorator-B9vwn0zK.mjs} +3 -3
- package/dist/{controller.decorator-DQzenvSN.mjs.map → controller.decorator-B9vwn0zK.mjs.map} +1 -1
- package/dist/cron/index.d.mts +2 -2
- package/dist/cron/index.mjs +1 -1
- package/dist/{cron-manager-BEsH1mjW.d.mts → cron-manager-CmTimEjf.d.mts} +2 -2
- package/dist/cron-manager-CmTimEjf.d.mts.map +1 -0
- package/dist/{cron-manager-7Symz_TE.mjs → cron-manager-DQSK8uoV.mjs} +10 -4
- package/dist/cron-manager-DQSK8uoV.mjs.map +1 -0
- package/dist/di/index.d.mts +1 -1
- package/dist/di/index.mjs +2 -2
- package/dist/email/index.d.mts +2 -2
- package/dist/email/index.mjs +12 -7
- package/dist/email/index.mjs.map +1 -1
- package/dist/errors/index.d.mts +1 -1
- package/dist/errors/index.mjs +1 -1
- package/dist/{errors-BdyV5PnY.mjs → errors-COW9-Mar.mjs} +16 -2
- package/dist/{errors-BdyV5PnY.mjs.map → errors-COW9-Mar.mjs.map} +1 -1
- package/dist/{errors-Da3Pz2X7.mjs → errors-ORxu1-Bb.mjs} +2 -2
- package/dist/{errors-Da3Pz2X7.mjs.map → errors-ORxu1-Bb.mjs.map} +1 -1
- package/dist/events/index.mjs +1 -1
- package/dist/{events-COKixqnG.mjs → events-CzCV8jI8.mjs} +4 -2
- package/dist/{events-COKixqnG.mjs.map → events-CzCV8jI8.mjs.map} +1 -1
- package/dist/{gateway-context-CdJjpUCW.mjs → gateway-context-CXmXtaUP.mjs} +4 -3
- package/dist/{gateway-context-CdJjpUCW.mjs.map → gateway-context-CXmXtaUP.mjs.map} +1 -1
- package/dist/guards/index.d.mts +1 -1
- package/dist/guards/index.mjs +1 -1
- package/dist/{guards-DUk_Kzst.mjs → guards-DU1_J9YA.mjs} +2 -1
- package/dist/{guards-DUk_Kzst.mjs.map → guards-DU1_J9YA.mjs.map} +1 -1
- package/dist/{http-method.decorator-DXwxAfb_.mjs → http-method.decorator-BrgHMdLQ.mjs} +2 -2
- package/dist/{http-method.decorator-DXwxAfb_.mjs.map → http-method.decorator-BrgHMdLQ.mjs.map} +1 -1
- package/dist/i18n/index.d.mts +1 -1
- package/dist/i18n/index.mjs +2 -2
- package/dist/{i18n.module-BBlNNlcG.mjs → i18n.module-CzXLW9Hy.mjs} +59 -19
- package/dist/i18n.module-CzXLW9Hy.mjs.map +1 -0
- package/dist/{index-D0US0X14.d.mts → index-ByOyTmqf.d.mts} +6 -3
- package/dist/{index-D0US0X14.d.mts.map → index-ByOyTmqf.d.mts.map} +1 -1
- package/dist/{index-CjaQ6_tZ.d.mts → index-DUzWs0z7.d.mts} +2 -2
- package/dist/{index-CjaQ6_tZ.d.mts.map → index-DUzWs0z7.d.mts.map} +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/is-command-C6a7WTPw.mjs.map +1 -1
- package/dist/is-seeder-CebjZCDn.mjs.map +1 -1
- package/dist/logger/index.mjs +1 -1
- package/dist/{logger-V6Ms3QnQ.mjs → logger-DlV7NtvD.mjs} +8 -4
- package/dist/{logger-V6Ms3QnQ.mjs.map → logger-DlV7NtvD.mjs.map} +1 -1
- package/dist/macroable-BmufBshB.mjs.map +1 -1
- package/dist/module/index.d.mts +1 -1
- package/dist/module/index.mjs +1 -1
- package/dist/{module-Dk2qTa77.mjs → module-BzLg57FK.mjs} +10 -4
- package/dist/{module-Dk2qTa77.mjs.map → module-BzLg57FK.mjs.map} +1 -1
- package/dist/openapi/index.d.mts +2 -2
- package/dist/openapi/index.mjs +1 -1
- package/dist/openapi-tools.service-Zs-Ewv7F.mjs.map +1 -1
- package/dist/{openapi.service-BLgvn3hJ.d.mts → openapi.service-Bt9bCIrd.d.mts} +2 -2
- package/dist/{openapi.service-BLgvn3hJ.d.mts.map → openapi.service-Bt9bCIrd.d.mts.map} +1 -1
- package/dist/quarry/index.d.mts +4 -4
- package/dist/quarry/index.mjs +1 -1
- package/dist/{quarry-registry-DNEej-Db.mjs → quarry-registry-BwY2hOxm.mjs} +15 -4
- package/dist/{quarry-registry-DNEej-Db.mjs.map → quarry-registry-BwY2hOxm.mjs.map} +1 -1
- package/dist/queue/index.d.mts +1 -1
- package/dist/queue/index.mjs +3 -2
- package/dist/queue/index.mjs.map +1 -1
- package/dist/{queue.module-BCdCiySt.mjs → queue.module-BhCjZp6H.mjs} +13 -4
- package/dist/{queue.module-BCdCiySt.mjs.map → queue.module-BhCjZp6H.mjs.map} +1 -1
- package/dist/{r2-storage.provider-Co6F0ZYV.mjs → r2-storage.provider-DuonKeYm.mjs} +5 -2
- package/dist/{r2-storage.provider-Co6F0ZYV.mjs.map → r2-storage.provider-DuonKeYm.mjs.map} +1 -1
- package/dist/{rate-limit.decorator--o6Q6p9w.mjs → rate-limit.decorator-6qzNcSOt.mjs} +2 -2
- package/dist/{rate-limit.decorator--o6Q6p9w.mjs.map → rate-limit.decorator-6qzNcSOt.mjs.map} +1 -1
- package/dist/rate-limiter/index.d.mts +1 -1
- package/dist/rate-limiter/index.mjs +13 -4
- package/dist/rate-limiter/index.mjs.map +1 -1
- package/dist/{resend.provider-M6qRLrcy.mjs → resend.provider-DB4IlFjG.mjs} +2 -1
- package/dist/{resend.provider-M6qRLrcy.mjs.map → resend.provider-DB4IlFjG.mjs.map} +1 -1
- package/dist/router/index.d.mts +1 -1
- package/dist/router/index.mjs +5 -5
- package/dist/seeder/index.d.mts +2 -2
- package/dist/seeder/index.mjs +1 -1
- package/dist/{seeder-CJAOHEIo.mjs → seeder-zoEfEw9i.mjs} +6 -3
- package/dist/{seeder-CJAOHEIo.mjs.map → seeder-zoEfEw9i.mjs.map} +1 -1
- package/dist/setup-CefZKV_e.mjs.map +1 -1
- package/dist/signed-url-BQPbv2In.mjs.map +1 -1
- package/dist/{smtp.provider-w0Ve52Xg.mjs → smtp.provider-B6D7zuWX.mjs} +2 -1
- package/dist/{smtp.provider-w0Ve52Xg.mjs.map → smtp.provider-B6D7zuWX.mjs.map} +1 -1
- package/dist/storage/index.d.mts +1 -1
- package/dist/storage/index.mjs +2 -2
- package/dist/storage/providers/index.mjs +1 -1
- package/dist/{storage-1zw-6Yiz.mjs → storage-D8CBP72Z.mjs} +13 -8
- package/dist/{storage-1zw-6Yiz.mjs.map → storage-D8CBP72Z.mjs.map} +1 -1
- package/dist/{stratal-DeEcGgdq.mjs → stratal-CNwpbSZl.mjs} +12 -10
- package/dist/{stratal-DeEcGgdq.mjs.map → stratal-CNwpbSZl.mjs.map} +1 -1
- package/dist/usage-generator-BUdlhnCK.mjs.map +1 -1
- package/dist/validation-DtJwAv7O.mjs.map +1 -1
- package/dist/websocket/index.d.mts +1 -1
- package/dist/websocket/index.mjs +1 -1
- package/dist/workers/index.d.mts +1 -1
- package/dist/workers/index.mjs +2 -2
- package/dist/workers/index.mjs.map +1 -1
- package/package.json +13 -13
- package/dist/cron-manager-7Symz_TE.mjs.map +0 -1
- package/dist/cron-manager-BEsH1mjW.d.mts.map +0 -1
- package/dist/i18n.module-BBlNNlcG.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events-COKixqnG.mjs","names":[],"sources":["../src/events/constants.ts","../src/events/decorators/listener.decorator.ts","../src/events/decorators/on.decorator.ts","../src/events/event-registry.ts"],"sourcesContent":["/**\n * Metadata keys for event listener decorators.\n *\n * Uses `Symbol.for()` (global symbol registry) so that both core and\n * framework packages can reference the same symbols without cross-imports.\n */\nexport const LISTENER_METADATA_KEYS = {\n IS_LISTENER: Symbol.for('stratal:listener'),\n EVENT_HANDLERS: Symbol.for('stratal:listener:handlers'),\n} as const\n","import { Transient } from '../../di/decorators'\nimport type { Constructor } from '../../types'\nimport { LISTENER_METADATA_KEYS } from '../constants'\n\n/**\n * Mark a class as an event listener.\n *\n * Applies `@Transient()` for DI and sets metadata so the module system\n * can auto-discover and wire listener handlers at bootstrap time.\n *\n * @example\n * ```typescript\n * @Listener()\n * export class UserCreatedListener {\n * @On('after.User.create')\n * async sendWelcomeEmail(context: EventContext<'after.User.create'>) {\n * // ...\n * }\n * }\n * ```\n */\nexport function Listener() {\n return function <T extends Constructor>(target: T) {\n Transient()(target)\n Reflect.defineMetadata(LISTENER_METADATA_KEYS.IS_LISTENER, true, target)\n return target\n }\n}\n\n/**\n * Check if a class is decorated with `@Listener()`\n */\nexport function isListener(target: Constructor): boolean {\n return Reflect.getMetadata(LISTENER_METADATA_KEYS.IS_LISTENER, target) === true\n}\n","import { LISTENER_METADATA_KEYS } from '../constants'\nimport type { EventName, EventOptions, ListenerHandlerMetadata } from '../types'\n\n/**\n * Register a method as an event handler within a `@Listener()` class.\n *\n * Accumulates handler metadata on the class so the framework can\n * auto-wire handlers with the EventRegistry at bootstrap time.\n *\n * @param event - Event name to listen for (fully typed with autocomplete)\n * @param options - Optional handler options (priority, blocking)\n *\n * @example\n * ```typescript\n * @Listener()\n * export class AuditListener {\n * @On('after.User.create')\n * async logCreate(context: EventContext<'after.User.create'>) { ... }\n *\n * @On('after.User.delete', { priority: 10 })\n * async logDelete(context: EventContext<'after.User.delete'>) { ... }\n * }\n * ```\n */\nexport function On<E extends EventName>(event: E, options?: EventOptions) {\n return function (\n target: object,\n propertyKey: string,\n _descriptor: PropertyDescriptor\n ) {\n const existingHandlers: ListenerHandlerMetadata[] =\n (Reflect.getMetadata(LISTENER_METADATA_KEYS.EVENT_HANDLERS, target.constructor) as ListenerHandlerMetadata[] | undefined) ?? []\n\n existingHandlers.push({\n methodName: propertyKey,\n event: event as string,\n options,\n })\n\n Reflect.defineMetadata(\n LISTENER_METADATA_KEYS.EVENT_HANDLERS,\n existingHandlers,\n target.constructor\n )\n }\n}\n\n/**\n * Get all `@On()` handler metadata from a listener class\n */\nexport function getListenerHandlers(target: object): ListenerHandlerMetadata[] {\n const metadataTarget = typeof target === 'function' ? target : target.constructor\n return (Reflect.getMetadata(LISTENER_METADATA_KEYS.EVENT_HANDLERS, metadataTarget) as ListenerHandlerMetadata[] | undefined) ?? []\n}\n","import { inject } from 'tsyringe'\nimport { Transient } from '../di/decorators'\nimport { DI_TOKENS } from '../di/tokens'\nimport { LOGGER_TOKENS, type LoggerService } from '../logger'\nimport type {\n EventContext,\n EventHandler,\n EventName,\n EventOptions,\n IEventRegistry,\n RegisteredHandler\n} from './types'\n\n@Transient()\nexport class EventRegistry implements IEventRegistry {\n private handlers = new Map<string, RegisteredHandler[]>()\n\n constructor(\n @inject(DI_TOKENS.ExecutionContext) private readonly ctx: ExecutionContext,\n @inject(LOGGER_TOKENS.LoggerService) private readonly logger: LoggerService\n ) { }\n\n on<E extends EventName>(event: E, handler: EventHandler<E>, options?: EventOptions): void {\n const registered: RegisteredHandler = {\n handler: handler as EventHandler,\n priority: options?.priority ?? 0,\n blocking: options?.blocking\n }\n\n const existingHandlers = this.handlers.get(event) ?? []\n existingHandlers.push(registered)\n this.handlers.set(event, existingHandlers)\n\n this.logger.debug('Event handler registered', {\n event,\n priority: registered.priority,\n blocking: registered.blocking\n })\n }\n\n async emit<E extends EventName>(\n event: E,\n context?: Partial<EventContext<E>>\n ): Promise<void> {\n // Build full context with caller-provided fields\n const fullContext = {\n ...context\n } as EventContext<E>\n\n // Find matching handlers using pattern matching\n const matchingHandlers = this.findMatchingHandlers(event)\n\n if (matchingHandlers.length === 0) {\n return\n }\n\n // Sort by priority (higher first)\n const sortedHandlers = [...matchingHandlers].sort(\n (a, b) => b.priority - a.priority\n )\n\n // Determine if we should use waitUntil\n const shouldUseWaitUntil = this.shouldUseWaitUntil(event, sortedHandlers)\n\n // Execute handlers\n const promises = sortedHandlers.map((registered) =>\n this.executeHandler(registered.handler, fullContext, event)\n )\n\n if (shouldUseWaitUntil) {\n // Non-blocking: use ctx.waitUntil\n this.ctx.waitUntil(Promise.all(promises))\n } else {\n // Blocking: await all handlers\n await Promise.all(promises)\n }\n }\n\n off<E extends EventName>(event: E, handler: EventHandler<E>): void {\n const existingHandlers = this.handlers.get(event)\n if (!existingHandlers) return\n\n const filtered = existingHandlers.filter((h) => h.handler !== handler)\n if (filtered.length > 0) {\n this.handlers.set(event, filtered)\n } else {\n this.handlers.delete(event)\n }\n\n this.logger.debug('Event handler unregistered', { event })\n }\n\n once<E extends EventName>(event: E, handler: EventHandler<E>, options?: EventOptions): void {\n const wrappedHandler = (async (context: EventContext<E>) => {\n await handler(context)\n this.off(event, wrappedHandler)\n }) as EventHandler<E>\n\n this.on(event, wrappedHandler, options)\n }\n\n /**\n * Find all handlers matching the event using pattern matching.\n * Order: exact match -> model wildcard -> operation wildcard -> global wildcard\n */\n private findMatchingHandlers(event: string): RegisteredHandler[] {\n const handlers: RegisteredHandler[] = []\n\n const parts = event.split('.')\n\n if (parts.length === 3) {\n // Database event: \"phase.model.operation\"\n const [phase, model, operation] = parts\n\n // 1. Exact match: \"after.user.create\"\n handlers.push(...(this.handlers.get(event) ?? []))\n\n // 2. Model wildcard: \"after.user\"\n handlers.push(...(this.handlers.get(`${phase}.${model}`) ?? []))\n\n // 3. Operation wildcard: \"after.create\"\n handlers.push(...(this.handlers.get(`${phase}.${operation}`) ?? []))\n\n // 4. Global wildcard: \"after\"\n handlers.push(...(this.handlers.get(phase) ?? []))\n } else if (parts.length === 2) {\n // Could be wildcard like \"after.user\" or custom event like \"auth.verified\"\n handlers.push(...(this.handlers.get(event) ?? []))\n\n if (parts[0] === 'before' || parts[0] === 'after') {\n handlers.push(...(this.handlers.get(parts[0]) ?? []))\n }\n } else {\n handlers.push(...(this.handlers.get(event) ?? []))\n }\n\n return handlers\n }\n\n /**\n * Determine if we should use ctx.waitUntil (non-blocking) or await (blocking)\n */\n private shouldUseWaitUntil(\n event: string,\n handlers: RegisteredHandler[]\n ): boolean {\n const hasBlockingHandler = handlers.some((h) => h.blocking === true)\n if (hasBlockingHandler) return false\n\n const hasNonBlockingHandler = handlers.some((h) => h.blocking === false)\n if (hasNonBlockingHandler) return true\n\n const phase = event.split('.')[0]\n if (phase === 'before') return false\n if (phase === 'after') return true\n return false // Custom events block by default\n }\n\n /**\n * Execute a single handler with error isolation\n */\n private async executeHandler<E extends EventName>(\n handler: EventHandler,\n context: EventContext<E>,\n event: string\n ): Promise<void> {\n try {\n await handler(context as EventContext)\n } catch (error) {\n this.logger.error('Event handler error', {\n event,\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n })\n }\n }\n}\n"],"mappings":";;;;;;;;;AAMA,MAAa,yBAAyB;CACpC,aAAa,OAAO,IAAI,mBAAmB;CAC3C,gBAAgB,OAAO,IAAI,4BAA4B;CACxD;;;;;;;;;;;;;;;;;;;;ACYD,SAAgB,WAAW;AACzB,QAAO,SAAiC,QAAW;AACjD,aAAW,CAAC,OAAO;AACnB,UAAQ,eAAe,uBAAuB,aAAa,MAAM,OAAO;AACxE,SAAO;;;;;;AAOX,SAAgB,WAAW,QAA8B;AACvD,QAAO,QAAQ,YAAY,uBAAuB,aAAa,OAAO,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;ACT7E,SAAgB,GAAwB,OAAU,SAAwB;AACxE,QAAO,SACL,QACA,aACA,aACA;EACA,MAAM,mBACH,QAAQ,YAAY,uBAAuB,gBAAgB,OAAO,YAAY,IAA8C,EAAE;AAEjI,mBAAiB,KAAK;GACpB,YAAY;GACL;GACP;GACD,CAAC;AAEF,UAAQ,eACN,uBAAuB,gBACvB,kBACA,OAAO,YACR;;;;;;AAOL,SAAgB,oBAAoB,QAA2C;CAC7E,MAAM,iBAAiB,OAAO,WAAW,aAAa,SAAS,OAAO;AACtE,QAAQ,QAAQ,YAAY,uBAAuB,gBAAgB,eAAe,IAA8C,EAAE;;;;;ACtC7H,IAAA,gBAAA,MAAM,cAAwC;CACnD,2BAAmB,IAAI,KAAkC;CAEzD,YACE,KACA,QACA;AAFqD,OAAA,MAAA;AACC,OAAA,SAAA;;CAGxD,GAAwB,OAAU,SAA0B,SAA8B;EACxF,MAAM,aAAgC;GAC3B;GACT,UAAU,SAAS,YAAY;GAC/B,UAAU,SAAS;GACpB;EAED,MAAM,mBAAmB,KAAK,SAAS,IAAI,MAAM,IAAI,EAAE;AACvD,mBAAiB,KAAK,WAAW;AACjC,OAAK,SAAS,IAAI,OAAO,iBAAiB;AAE1C,OAAK,OAAO,MAAM,4BAA4B;GAC5C;GACA,UAAU,WAAW;GACrB,UAAU,WAAW;GACtB,CAAC;;CAGJ,MAAM,KACJ,OACA,SACe;EAEf,MAAM,cAAc,EAClB,GAAG,SACJ;EAGD,MAAM,mBAAmB,KAAK,qBAAqB,MAAM;AAEzD,MAAI,iBAAiB,WAAW,EAC9B;EAIF,MAAM,iBAAiB,CAAC,GAAG,iBAAiB,CAAC,MAC1C,GAAG,MAAM,EAAE,WAAW,EAAE,SAC1B;EAGD,MAAM,qBAAqB,KAAK,mBAAmB,OAAO,eAAe;EAGzE,MAAM,WAAW,eAAe,KAAK,eACnC,KAAK,eAAe,WAAW,SAAS,aAAa,MAAM,CAC5D;AAED,MAAI,mBAEF,MAAK,IAAI,UAAU,QAAQ,IAAI,SAAS,CAAC;MAGzC,OAAM,QAAQ,IAAI,SAAS;;CAI/B,IAAyB,OAAU,SAAgC;EACjE,MAAM,mBAAmB,KAAK,SAAS,IAAI,MAAM;AACjD,MAAI,CAAC,iBAAkB;EAEvB,MAAM,WAAW,iBAAiB,QAAQ,MAAM,EAAE,YAAY,QAAQ;AACtE,MAAI,SAAS,SAAS,EACpB,MAAK,SAAS,IAAI,OAAO,SAAS;MAElC,MAAK,SAAS,OAAO,MAAM;AAG7B,OAAK,OAAO,MAAM,8BAA8B,EAAE,OAAO,CAAC;;CAG5D,KAA0B,OAAU,SAA0B,SAA8B;EAC1F,MAAM,kBAAkB,OAAO,YAA6B;AAC1D,SAAM,QAAQ,QAAQ;AACtB,QAAK,IAAI,OAAO,eAAe;;AAGjC,OAAK,GAAG,OAAO,gBAAgB,QAAQ;;;;;;CAOzC,qBAA6B,OAAoC;EAC/D,MAAM,WAAgC,EAAE;EAExC,MAAM,QAAQ,MAAM,MAAM,IAAI;AAE9B,MAAI,MAAM,WAAW,GAAG;GAEtB,MAAM,CAAC,OAAO,OAAO,aAAa;AAGlC,YAAS,KAAK,GAAI,KAAK,SAAS,IAAI,MAAM,IAAI,EAAE,CAAE;AAGlD,YAAS,KAAK,GAAI,KAAK,SAAS,IAAI,GAAG,MAAM,GAAG,QAAQ,IAAI,EAAE,CAAE;AAGhE,YAAS,KAAK,GAAI,KAAK,SAAS,IAAI,GAAG,MAAM,GAAG,YAAY,IAAI,EAAE,CAAE;AAGpE,YAAS,KAAK,GAAI,KAAK,SAAS,IAAI,MAAM,IAAI,EAAE,CAAE;aACzC,MAAM,WAAW,GAAG;AAE7B,YAAS,KAAK,GAAI,KAAK,SAAS,IAAI,MAAM,IAAI,EAAE,CAAE;AAElD,OAAI,MAAM,OAAO,YAAY,MAAM,OAAO,QACxC,UAAS,KAAK,GAAI,KAAK,SAAS,IAAI,MAAM,GAAG,IAAI,EAAE,CAAE;QAGvD,UAAS,KAAK,GAAI,KAAK,SAAS,IAAI,MAAM,IAAI,EAAE,CAAE;AAGpD,SAAO;;;;;CAMT,mBACE,OACA,UACS;AAET,MAD2B,SAAS,MAAM,MAAM,EAAE,aAAa,KACzC,CAAE,QAAO;AAG/B,MAD8B,SAAS,MAAM,MAAM,EAAE,aAAa,MACzC,CAAE,QAAO;EAElC,MAAM,QAAQ,MAAM,MAAM,IAAI,CAAC;AAC/B,MAAI,UAAU,SAAU,QAAO;AAC/B,MAAI,UAAU,QAAS,QAAO;AAC9B,SAAO;;;;;CAMT,MAAc,eACZ,SACA,SACA,OACe;AACf,MAAI;AACF,SAAM,QAAQ,QAAwB;WAC/B,OAAO;AACd,QAAK,OAAO,MAAM,uBAAuB;IACvC;IACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC7D,OAAO,iBAAiB,QAAQ,MAAM,QAAQ,KAAA;IAC/C,CAAC;;;;;CAhKP,WAAW;oBAKP,OAAO,UAAU,iBAAiB,CAAA;oBAClC,OAAO,cAAc,cAAc,CAAA"}
|
|
1
|
+
{"version":3,"file":"events-CzCV8jI8.mjs","names":[],"sources":["../src/events/constants.ts","../src/events/decorators/listener.decorator.ts","../src/events/decorators/on.decorator.ts","../src/events/event-registry.ts"],"sourcesContent":["/**\n * Metadata keys for event listener decorators.\n *\n * Uses `Symbol.for()` (global symbol registry) so that both core and\n * framework packages can reference the same symbols without cross-imports.\n */\nexport const LISTENER_METADATA_KEYS = {\n IS_LISTENER: Symbol.for('stratal:listener'),\n EVENT_HANDLERS: Symbol.for('stratal:listener:handlers'),\n} as const\n","import { Transient } from '../../di/decorators'\nimport type { Constructor } from '../../types'\nimport { LISTENER_METADATA_KEYS } from '../constants'\n\n/**\n * Mark a class as an event listener.\n *\n * Applies `@Transient()` for DI and sets metadata so the module system\n * can auto-discover and wire listener handlers at bootstrap time.\n *\n * @example\n * ```typescript\n * @Listener()\n * export class UserCreatedListener {\n * @On('after.User.create')\n * async sendWelcomeEmail(context: EventContext<'after.User.create'>) {\n * // ...\n * }\n * }\n * ```\n */\nexport function Listener() {\n return function <T extends Constructor>(target: T) {\n Transient()(target)\n Reflect.defineMetadata(LISTENER_METADATA_KEYS.IS_LISTENER, true, target)\n return target\n }\n}\n\n/**\n * Check if a class is decorated with `@Listener()`\n */\nexport function isListener(target: Constructor): boolean {\n return Reflect.getMetadata(LISTENER_METADATA_KEYS.IS_LISTENER, target) === true\n}\n","import { LISTENER_METADATA_KEYS } from '../constants'\nimport type { EventName, EventOptions, ListenerHandlerMetadata } from '../types'\n\n/**\n * Register a method as an event handler within a `@Listener()` class.\n *\n * Accumulates handler metadata on the class so the framework can\n * auto-wire handlers with the EventRegistry at bootstrap time.\n *\n * @param event - Event name to listen for (fully typed with autocomplete)\n * @param options - Optional handler options (priority, blocking)\n *\n * @example\n * ```typescript\n * @Listener()\n * export class AuditListener {\n * @On('after.User.create')\n * async logCreate(context: EventContext<'after.User.create'>) { ... }\n *\n * @On('after.User.delete', { priority: 10 })\n * async logDelete(context: EventContext<'after.User.delete'>) { ... }\n * }\n * ```\n */\nexport function On<E extends EventName>(event: E, options?: EventOptions) {\n return function (\n target: object,\n propertyKey: string,\n _descriptor: PropertyDescriptor\n ) {\n const existingHandlers: ListenerHandlerMetadata[] =\n (Reflect.getMetadata(LISTENER_METADATA_KEYS.EVENT_HANDLERS, target.constructor) as ListenerHandlerMetadata[] | undefined) ?? []\n\n existingHandlers.push({\n methodName: propertyKey,\n event: event as string,\n options,\n })\n\n Reflect.defineMetadata(\n LISTENER_METADATA_KEYS.EVENT_HANDLERS,\n existingHandlers,\n target.constructor\n )\n }\n}\n\n/**\n * Get all `@On()` handler metadata from a listener class\n */\nexport function getListenerHandlers(target: object): ListenerHandlerMetadata[] {\n const metadataTarget = typeof target === 'function' ? target : target.constructor\n return (Reflect.getMetadata(LISTENER_METADATA_KEYS.EVENT_HANDLERS, metadataTarget) as ListenerHandlerMetadata[] | undefined) ?? []\n}\n","import { inject } from 'tsyringe'\nimport { Transient } from '../di/decorators'\nimport { DI_TOKENS } from '../di/tokens'\nimport { LOGGER_TOKENS, type LoggerService } from '../logger'\nimport type {\n EventContext,\n EventHandler,\n EventName,\n EventOptions,\n IEventRegistry,\n RegisteredHandler\n} from './types'\n\n@Transient()\nexport class EventRegistry implements IEventRegistry {\n private handlers = new Map<string, RegisteredHandler[]>()\n\n constructor(\n @inject(DI_TOKENS.ExecutionContext) private readonly ctx: ExecutionContext,\n @inject(LOGGER_TOKENS.LoggerService) private readonly logger: LoggerService\n ) { }\n\n on<E extends EventName>(event: E, handler: EventHandler<E>, options?: EventOptions): void {\n const registered: RegisteredHandler = {\n handler: handler as EventHandler,\n priority: options?.priority ?? 0,\n blocking: options?.blocking\n }\n\n const existingHandlers = this.handlers.get(event) ?? []\n existingHandlers.push(registered)\n this.handlers.set(event, existingHandlers)\n\n this.logger.debug('Event handler registered', {\n event,\n priority: registered.priority,\n blocking: registered.blocking\n })\n }\n\n async emit<E extends EventName>(\n event: E,\n context?: Partial<EventContext<E>>\n ): Promise<void> {\n // Build full context with caller-provided fields\n const fullContext = {\n ...context\n } as EventContext<E>\n\n // Find matching handlers using pattern matching\n const matchingHandlers = this.findMatchingHandlers(event)\n\n if (matchingHandlers.length === 0) {\n return\n }\n\n // Sort by priority (higher first)\n const sortedHandlers = [...matchingHandlers].sort(\n (a, b) => b.priority - a.priority\n )\n\n // Determine if we should use waitUntil\n const shouldUseWaitUntil = this.shouldUseWaitUntil(event, sortedHandlers)\n\n // Execute handlers\n const promises = sortedHandlers.map((registered) =>\n this.executeHandler(registered.handler, fullContext, event)\n )\n\n if (shouldUseWaitUntil) {\n // Non-blocking: use ctx.waitUntil\n this.ctx.waitUntil(Promise.all(promises))\n } else {\n // Blocking: await all handlers\n await Promise.all(promises)\n }\n }\n\n off<E extends EventName>(event: E, handler: EventHandler<E>): void {\n const existingHandlers = this.handlers.get(event)\n if (!existingHandlers) return\n\n const filtered = existingHandlers.filter((h) => h.handler !== handler)\n if (filtered.length > 0) {\n this.handlers.set(event, filtered)\n } else {\n this.handlers.delete(event)\n }\n\n this.logger.debug('Event handler unregistered', { event })\n }\n\n once<E extends EventName>(event: E, handler: EventHandler<E>, options?: EventOptions): void {\n const wrappedHandler = (async (context: EventContext<E>) => {\n await handler(context)\n this.off(event, wrappedHandler)\n }) as EventHandler<E>\n\n this.on(event, wrappedHandler, options)\n }\n\n /**\n * Find all handlers matching the event using pattern matching.\n * Order: exact match -> model wildcard -> operation wildcard -> global wildcard\n */\n private findMatchingHandlers(event: string): RegisteredHandler[] {\n const handlers: RegisteredHandler[] = []\n\n const parts = event.split('.')\n\n if (parts.length === 3) {\n // Database event: \"phase.model.operation\"\n const [phase, model, operation] = parts\n\n // 1. Exact match: \"after.user.create\"\n handlers.push(...(this.handlers.get(event) ?? []))\n\n // 2. Model wildcard: \"after.user\"\n handlers.push(...(this.handlers.get(`${phase}.${model}`) ?? []))\n\n // 3. Operation wildcard: \"after.create\"\n handlers.push(...(this.handlers.get(`${phase}.${operation}`) ?? []))\n\n // 4. Global wildcard: \"after\"\n handlers.push(...(this.handlers.get(phase) ?? []))\n } else if (parts.length === 2) {\n // Could be wildcard like \"after.user\" or custom event like \"auth.verified\"\n handlers.push(...(this.handlers.get(event) ?? []))\n\n if (parts[0] === 'before' || parts[0] === 'after') {\n handlers.push(...(this.handlers.get(parts[0]) ?? []))\n }\n } else {\n handlers.push(...(this.handlers.get(event) ?? []))\n }\n\n return handlers\n }\n\n /**\n * Determine if we should use ctx.waitUntil (non-blocking) or await (blocking)\n */\n private shouldUseWaitUntil(\n event: string,\n handlers: RegisteredHandler[]\n ): boolean {\n const hasBlockingHandler = handlers.some((h) => h.blocking === true)\n if (hasBlockingHandler) return false\n\n const hasNonBlockingHandler = handlers.some((h) => h.blocking === false)\n if (hasNonBlockingHandler) return true\n\n const phase = event.split('.')[0]\n if (phase === 'before') return false\n if (phase === 'after') return true\n return false // Custom events block by default\n }\n\n /**\n * Execute a single handler with error isolation\n */\n private async executeHandler<E extends EventName>(\n handler: EventHandler,\n context: EventContext<E>,\n event: string\n ): Promise<void> {\n try {\n await handler(context as EventContext)\n } catch (error) {\n this.logger.error('Event handler error', {\n event,\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n })\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as ApplicationError, f as ROUTE_METADATA_KEYS, k as ERROR_CODES, s as RouterContext } from "./errors-
|
|
2
|
-
import { t as Controller } from "./controller.decorator-
|
|
1
|
+
import { H as ApplicationError, f as ROUTE_METADATA_KEYS, k as ERROR_CODES, s as RouterContext } from "./errors-COW9-Mar.mjs";
|
|
2
|
+
import { t as Controller } from "./controller.decorator-B9vwn0zK.mjs";
|
|
3
3
|
//#region src/websocket/decorators/gateway.decorator.ts
|
|
4
4
|
const GATEWAY_MARKER_KEY = ROUTE_METADATA_KEYS.GATEWAY_MARKER;
|
|
5
5
|
/**
|
|
@@ -173,6 +173,7 @@ var WebSocketBodyNotAvailableError = class extends ApplicationError {
|
|
|
173
173
|
* ```
|
|
174
174
|
*/
|
|
175
175
|
var GatewayContext = class extends RouterContext {
|
|
176
|
+
ws;
|
|
176
177
|
constructor(c, ws) {
|
|
177
178
|
super(c);
|
|
178
179
|
this.ws = ws;
|
|
@@ -214,4 +215,4 @@ var GatewayContext = class extends RouterContext {
|
|
|
214
215
|
//#endregion
|
|
215
216
|
export { OnMessage as a, getWsOnMessageMethod as c, isGateway as d, OnError as i, WebSocketDuplicateEventHandlerError as l, WebSocketBodyNotAvailableError as n, getWsOnCloseMethod as o, OnClose as r, getWsOnErrorMethod as s, GatewayContext as t, Gateway as u };
|
|
216
217
|
|
|
217
|
-
//# sourceMappingURL=gateway-context-
|
|
218
|
+
//# sourceMappingURL=gateway-context-CXmXtaUP.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway-context-CdJjpUCW.mjs","names":[],"sources":["../src/websocket/decorators/gateway.decorator.ts","../src/websocket/errors/websocket-duplicate-event-handler.error.ts","../src/websocket/decorators/ws-event.decorator.ts","../src/websocket/errors/websocket-body-not-available.error.ts","../src/websocket/gateway-context.ts"],"sourcesContent":["import { ROUTE_METADATA_KEYS } from '../../router/constants'\nimport { Controller } from '../../router/decorators/controller.decorator'\nimport { type Constructor } from '../../types'\nimport type { GatewayOptions } from '../../websocket/types'\n\nconst GATEWAY_MARKER_KEY = ROUTE_METADATA_KEYS.GATEWAY_MARKER\n\n/**\n * Gateway decorator for WebSocket route registration\n *\n * Marks a class as a WebSocket gateway and stores route metadata.\n * Reuses the same metadata key as @Controller for middleware compatibility —\n * `getControllerRoute()`, `forRoutes()`, and the entire middleware system work\n * with zero changes.\n *\n * @param route - WebSocket route path (e.g., '/ws/chat')\n *\n * @example\n * ```typescript\n * import { type GatewayContext, Gateway, OnMessage, OnClose } from 'stratal/websocket'\n *\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnMessage()\n * handleMessage(evt: MessageEvent, ctx: GatewayContext) {\n * ctx.send('ack')\n * }\n *\n * @OnClose()\n * handleClose(evt: CloseEvent, ctx: GatewayContext) {\n * console.log('closed')\n * }\n * }\n * ```\n */\nexport function Gateway(route: string, options?: GatewayOptions) {\n return function <T extends Constructor>(target: T) {\n Controller(route, options)(target)\n Reflect.defineMetadata(GATEWAY_MARKER_KEY, true, target)\n return target\n }\n}\n\n/**\n * Check if a class is a WebSocket gateway\n *\n * @param target - Class constructor or instance\n * @returns true if the class is decorated with @Gateway\n */\nexport function isGateway(target: object): boolean {\n const metadataTarget = typeof target === 'function' ? target : (target as { constructor: object }).constructor\n return Reflect.getMetadata(GATEWAY_MARKER_KEY, metadataTarget) === true\n}\n","import { ApplicationError, ERROR_CODES } from '../../errors'\n\nexport class WebSocketDuplicateEventHandlerError extends ApplicationError {\n constructor(decorator: string, existingMethod: string) {\n super(\n 'errors.websocketDuplicateEventHandler',\n ERROR_CODES.SYSTEM.WEBSOCKET_DUPLICATE_EVENT_HANDLER,\n { decorator, existingMethod }\n )\n }\n}\n","import { ROUTE_METADATA_KEYS } from '../../router/constants'\nimport type { Constructor } from '../../types'\nimport { WebSocketDuplicateEventHandlerError } from '../errors/websocket-duplicate-event-handler.error'\n\nconst WS_ON_MESSAGE_KEY = ROUTE_METADATA_KEYS.WS_ON_MESSAGE\nconst WS_ON_CLOSE_KEY = ROUTE_METADATA_KEYS.WS_ON_CLOSE\nconst WS_ON_ERROR_KEY = ROUTE_METADATA_KEYS.WS_ON_ERROR\n\n/**\n * Define a single-handler metadata key on the prototype.\n * Throws if a different method already owns this key (prevents silent override).\n */\nfunction defineSingleHandlerMetadata(key: string | symbol, propertyKey: string | symbol, target: object, decoratorName: string): void {\n const existing = Reflect.getMetadata(key, target) as string | symbol | undefined\n if (existing !== undefined && existing !== propertyKey) {\n throw new WebSocketDuplicateEventHandlerError(decoratorName, String(existing))\n }\n Reflect.defineMetadata(key, propertyKey, target)\n}\n\n/**\n * Marks a method as the WebSocket message handler\n *\n * @example\n * ```typescript\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnMessage()\n * handleMessage(evt: MessageEvent, ctx: GatewayContext) {\n * ctx.send(evt.data)\n * }\n * }\n * ```\n */\nexport function OnMessage(): MethodDecorator {\n // `_target` is the class prototype (method decorator convention).\n // The getter functions below read from `target.prototype` symmetrically.\n return (_target: object, propertyKey: string | symbol) => {\n defineSingleHandlerMetadata(WS_ON_MESSAGE_KEY, propertyKey, _target, 'OnMessage')\n }\n}\n\n/**\n * Marks a method as the WebSocket close handler\n *\n * @example\n * ```typescript\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnClose()\n * handleClose(evt: CloseEvent, ctx: GatewayContext) {\n * console.log('closed')\n * }\n * }\n * ```\n */\nexport function OnClose(): MethodDecorator {\n return (_target: object, propertyKey: string | symbol) => {\n defineSingleHandlerMetadata(WS_ON_CLOSE_KEY, propertyKey, _target, 'OnClose')\n }\n}\n\n/**\n * Marks a method as the WebSocket error handler\n *\n * @example\n * ```typescript\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnError()\n * handleError(evt: Event, ctx: GatewayContext) {\n * console.error('WebSocket error', evt)\n * }\n * }\n * ```\n */\nexport function OnError(): MethodDecorator {\n return (_target: object, propertyKey: string | symbol) => {\n defineSingleHandlerMetadata(WS_ON_ERROR_KEY, propertyKey, _target, 'OnError')\n }\n}\n\n/**\n * Get the method name decorated with @OnMessage\n */\nexport function getWsOnMessageMethod(target: Constructor): string | undefined {\n return Reflect.getMetadata(WS_ON_MESSAGE_KEY, target.prototype as object) as string | undefined\n}\n\n/**\n * Get the method name decorated with @OnClose\n */\nexport function getWsOnCloseMethod(target: Constructor): string | undefined {\n return Reflect.getMetadata(WS_ON_CLOSE_KEY, target.prototype as object) as string | undefined\n}\n\n/**\n * Get the method name decorated with @OnError\n */\nexport function getWsOnErrorMethod(target: Constructor): string | undefined {\n return Reflect.getMetadata(WS_ON_ERROR_KEY, target.prototype as object) as string | undefined\n}\n","import { ApplicationError, ERROR_CODES } from '../../errors'\n\nexport class WebSocketBodyNotAvailableError extends ApplicationError {\n constructor() {\n super(\n 'errors.websocketBodyNotAvailable',\n ERROR_CODES.SYSTEM.WEBSOCKET_BODY_NOT_AVAILABLE\n )\n }\n}\n","import type { Context } from 'hono'\nimport type { WSContext, WSReadyState } from 'hono/ws'\nimport type { ContextQueryResult } from '../router/router-context'\nimport { RouterContext } from '../router/router-context'\nimport type { RouterEnv } from '../router/types'\nimport { WebSocketBodyNotAvailableError } from './errors/websocket-body-not-available.error'\n\n/**\n * WebSocket gateway context\n *\n * Extends RouterContext with WebSocket-specific methods.\n * Inherits `getContainer()`, `param()`, `query()`, `header()`, `getLocale()`\n * from RouterContext. HTTP response methods (`json()`, `redirect()`, etc.) are\n * inherited but harmless post-upgrade.\n *\n * @example\n * ```typescript\n * @OnMessage()\n * handleMessage(evt: MessageEvent, ctx: GatewayContext) {\n * ctx.send('ack') // convenience method\n * ctx.header('Authorization') // upgrade request headers\n * }\n * ```\n */\nexport class GatewayContext extends RouterContext {\n constructor(c: Context<RouterEnv>, public readonly ws: WSContext) {\n super(c)\n }\n\n /** Send data through the WebSocket connection */\n send(data: string | ArrayBuffer | Uint8Array<ArrayBuffer>): void {\n this.ws.send(data)\n }\n\n /** Close the WebSocket connection */\n close(code?: number, reason?: string): void {\n this.ws.close(code, reason)\n }\n\n /** Current WebSocket ready state */\n get readyState(): WSReadyState {\n return this.ws.readyState\n }\n\n /**\n * Get route parameter value(s) from the raw request — WebSocket gateways are\n * not OpenAPI-registered, so reads come straight from Hono's matcher.\n *\n * - With a key → single string value.\n * - With no args → full `Record<string, string>` (or `{}` when none).\n *\n * @param key - Parameter name (e.g., 'id' for /ws/chat/:id)\n */\n override param(): Record<string, string>\n override param(key: string): string\n override param(key?: string): string | Record<string, string> {\n if (key === undefined) return this.c.req.param() ?? {}\n return this.c.req.param(key)!\n }\n\n /**\n * Get query parameter value from the raw request (no OpenAPI validation)\n *\n * @param key - Query parameter name\n */\n override query<R extends Record<string, unknown> | undefined = undefined, K extends string | undefined = undefined>(key?: K): ContextQueryResult<R, K> {\n if (key) {\n return this.c.req.query(key) as ContextQueryResult<R, K>\n }\n return this.c.req.query() as ContextQueryResult<R, K>\n }\n\n /**\n * Request body is not available in WebSocket gateways\n *\n * @throws WebSocketBodyNotAvailableError always — WebSocket upgrade requests do not have a body\n */\n override body<T>(): Promise<T> {\n throw new WebSocketBodyNotAvailableError()\n }\n}\n"],"mappings":";;;AAKA,MAAM,qBAAqB,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8B/C,SAAgB,QAAQ,OAAe,SAA0B;AAC/D,QAAO,SAAiC,QAAW;AACjD,aAAW,OAAO,QAAQ,CAAC,OAAO;AAClC,UAAQ,eAAe,oBAAoB,MAAM,OAAO;AACxD,SAAO;;;;;;;;;AAUX,SAAgB,UAAU,QAAyB;CACjD,MAAM,iBAAiB,OAAO,WAAW,aAAa,SAAU,OAAmC;AACnG,QAAO,QAAQ,YAAY,oBAAoB,eAAe,KAAK;;;;ACjDrE,IAAa,sCAAb,cAAyD,iBAAiB;CACxE,YAAY,WAAmB,gBAAwB;AACrD,QACE,yCACA,YAAY,OAAO,mCACnB;GAAE;GAAW;GAAgB,CAC9B;;;;;ACJL,MAAM,oBAAoB,oBAAoB;AAC9C,MAAM,kBAAkB,oBAAoB;AAC5C,MAAM,kBAAkB,oBAAoB;;;;;AAM5C,SAAS,4BAA4B,KAAsB,aAA8B,QAAgB,eAA6B;CACpI,MAAM,WAAW,QAAQ,YAAY,KAAK,OAAO;AACjD,KAAI,aAAa,KAAA,KAAa,aAAa,YACzC,OAAM,IAAI,oCAAoC,eAAe,OAAO,SAAS,CAAC;AAEhF,SAAQ,eAAe,KAAK,aAAa,OAAO;;;;;;;;;;;;;;;;AAiBlD,SAAgB,YAA6B;AAG3C,SAAQ,SAAiB,gBAAiC;AACxD,8BAA4B,mBAAmB,aAAa,SAAS,YAAY;;;;;;;;;;;;;;;;;AAkBrF,SAAgB,UAA2B;AACzC,SAAQ,SAAiB,gBAAiC;AACxD,8BAA4B,iBAAiB,aAAa,SAAS,UAAU;;;;;;;;;;;;;;;;;AAkBjF,SAAgB,UAA2B;AACzC,SAAQ,SAAiB,gBAAiC;AACxD,8BAA4B,iBAAiB,aAAa,SAAS,UAAU;;;;;;AAOjF,SAAgB,qBAAqB,QAAyC;AAC5E,QAAO,QAAQ,YAAY,mBAAmB,OAAO,UAAoB;;;;;AAM3E,SAAgB,mBAAmB,QAAyC;AAC1E,QAAO,QAAQ,YAAY,iBAAiB,OAAO,UAAoB;;;;;AAMzE,SAAgB,mBAAmB,QAAyC;AAC1E,QAAO,QAAQ,YAAY,iBAAiB,OAAO,UAAoB;;;;AClGzE,IAAa,iCAAb,cAAoD,iBAAiB;CACnE,cAAc;AACZ,QACE,oCACA,YAAY,OAAO,6BACpB;;;;;;;;;;;;;;;;;;;;;;ACiBL,IAAa,iBAAb,cAAoC,cAAc;CAChD,YAAY,GAAuB,IAA+B;AAChE,QAAM,EAAE;AADyC,OAAA,KAAA;;;CAKnD,KAAK,MAA4D;AAC/D,OAAK,GAAG,KAAK,KAAK;;;CAIpB,MAAM,MAAe,QAAuB;AAC1C,OAAK,GAAG,MAAM,MAAM,OAAO;;;CAI7B,IAAI,aAA2B;AAC7B,SAAO,KAAK,GAAG;;CAcjB,MAAe,KAA+C;AAC5D,MAAI,QAAQ,KAAA,EAAW,QAAO,KAAK,EAAE,IAAI,OAAO,IAAI,EAAE;AACtD,SAAO,KAAK,EAAE,IAAI,MAAM,IAAI;;;;;;;CAQ9B,MAAoH,KAAmC;AACrJ,MAAI,IACF,QAAO,KAAK,EAAE,IAAI,MAAM,IAAI;AAE9B,SAAO,KAAK,EAAE,IAAI,OAAO;;;;;;;CAQ3B,OAA+B;AAC7B,QAAM,IAAI,gCAAgC"}
|
|
1
|
+
{"version":3,"file":"gateway-context-CXmXtaUP.mjs","names":[],"sources":["../src/websocket/decorators/gateway.decorator.ts","../src/websocket/errors/websocket-duplicate-event-handler.error.ts","../src/websocket/decorators/ws-event.decorator.ts","../src/websocket/errors/websocket-body-not-available.error.ts","../src/websocket/gateway-context.ts"],"sourcesContent":["import { ROUTE_METADATA_KEYS } from '../../router/constants'\nimport { Controller } from '../../router/decorators/controller.decorator'\nimport { type Constructor } from '../../types'\nimport type { GatewayOptions } from '../../websocket/types'\n\nconst GATEWAY_MARKER_KEY = ROUTE_METADATA_KEYS.GATEWAY_MARKER\n\n/**\n * Gateway decorator for WebSocket route registration\n *\n * Marks a class as a WebSocket gateway and stores route metadata.\n * Reuses the same metadata key as @Controller for middleware compatibility —\n * `getControllerRoute()`, `forRoutes()`, and the entire middleware system work\n * with zero changes.\n *\n * @param route - WebSocket route path (e.g., '/ws/chat')\n *\n * @example\n * ```typescript\n * import { type GatewayContext, Gateway, OnMessage, OnClose } from 'stratal/websocket'\n *\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnMessage()\n * handleMessage(evt: MessageEvent, ctx: GatewayContext) {\n * ctx.send('ack')\n * }\n *\n * @OnClose()\n * handleClose(evt: CloseEvent, ctx: GatewayContext) {\n * console.log('closed')\n * }\n * }\n * ```\n */\nexport function Gateway(route: string, options?: GatewayOptions) {\n return function <T extends Constructor>(target: T) {\n Controller(route, options)(target)\n Reflect.defineMetadata(GATEWAY_MARKER_KEY, true, target)\n return target\n }\n}\n\n/**\n * Check if a class is a WebSocket gateway\n *\n * @param target - Class constructor or instance\n * @returns true if the class is decorated with @Gateway\n */\nexport function isGateway(target: object): boolean {\n const metadataTarget = typeof target === 'function' ? target : (target as { constructor: object }).constructor\n return Reflect.getMetadata(GATEWAY_MARKER_KEY, metadataTarget) === true\n}\n","import { ApplicationError, ERROR_CODES } from '../../errors'\n\nexport class WebSocketDuplicateEventHandlerError extends ApplicationError {\n constructor(decorator: string, existingMethod: string) {\n super(\n 'errors.websocketDuplicateEventHandler',\n ERROR_CODES.SYSTEM.WEBSOCKET_DUPLICATE_EVENT_HANDLER,\n { decorator, existingMethod }\n )\n }\n}\n","import { ROUTE_METADATA_KEYS } from '../../router/constants'\nimport type { Constructor } from '../../types'\nimport { WebSocketDuplicateEventHandlerError } from '../errors/websocket-duplicate-event-handler.error'\n\nconst WS_ON_MESSAGE_KEY = ROUTE_METADATA_KEYS.WS_ON_MESSAGE\nconst WS_ON_CLOSE_KEY = ROUTE_METADATA_KEYS.WS_ON_CLOSE\nconst WS_ON_ERROR_KEY = ROUTE_METADATA_KEYS.WS_ON_ERROR\n\n/**\n * Define a single-handler metadata key on the prototype.\n * Throws if a different method already owns this key (prevents silent override).\n */\nfunction defineSingleHandlerMetadata(key: string | symbol, propertyKey: string | symbol, target: object, decoratorName: string): void {\n const existing = Reflect.getMetadata(key, target) as string | symbol | undefined\n if (existing !== undefined && existing !== propertyKey) {\n throw new WebSocketDuplicateEventHandlerError(decoratorName, String(existing))\n }\n Reflect.defineMetadata(key, propertyKey, target)\n}\n\n/**\n * Marks a method as the WebSocket message handler\n *\n * @example\n * ```typescript\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnMessage()\n * handleMessage(evt: MessageEvent, ctx: GatewayContext) {\n * ctx.send(evt.data)\n * }\n * }\n * ```\n */\nexport function OnMessage(): MethodDecorator {\n // `_target` is the class prototype (method decorator convention).\n // The getter functions below read from `target.prototype` symmetrically.\n return (_target: object, propertyKey: string | symbol) => {\n defineSingleHandlerMetadata(WS_ON_MESSAGE_KEY, propertyKey, _target, 'OnMessage')\n }\n}\n\n/**\n * Marks a method as the WebSocket close handler\n *\n * @example\n * ```typescript\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnClose()\n * handleClose(evt: CloseEvent, ctx: GatewayContext) {\n * console.log('closed')\n * }\n * }\n * ```\n */\nexport function OnClose(): MethodDecorator {\n return (_target: object, propertyKey: string | symbol) => {\n defineSingleHandlerMetadata(WS_ON_CLOSE_KEY, propertyKey, _target, 'OnClose')\n }\n}\n\n/**\n * Marks a method as the WebSocket error handler\n *\n * @example\n * ```typescript\n * @Gateway('/ws/chat')\n * class ChatGateway {\n * @OnError()\n * handleError(evt: Event, ctx: GatewayContext) {\n * console.error('WebSocket error', evt)\n * }\n * }\n * ```\n */\nexport function OnError(): MethodDecorator {\n return (_target: object, propertyKey: string | symbol) => {\n defineSingleHandlerMetadata(WS_ON_ERROR_KEY, propertyKey, _target, 'OnError')\n }\n}\n\n/**\n * Get the method name decorated with @OnMessage\n */\nexport function getWsOnMessageMethod(target: Constructor): string | undefined {\n return Reflect.getMetadata(WS_ON_MESSAGE_KEY, target.prototype as object) as string | undefined\n}\n\n/**\n * Get the method name decorated with @OnClose\n */\nexport function getWsOnCloseMethod(target: Constructor): string | undefined {\n return Reflect.getMetadata(WS_ON_CLOSE_KEY, target.prototype as object) as string | undefined\n}\n\n/**\n * Get the method name decorated with @OnError\n */\nexport function getWsOnErrorMethod(target: Constructor): string | undefined {\n return Reflect.getMetadata(WS_ON_ERROR_KEY, target.prototype as object) as string | undefined\n}\n","import { ApplicationError, ERROR_CODES } from '../../errors'\n\nexport class WebSocketBodyNotAvailableError extends ApplicationError {\n constructor() {\n super(\n 'errors.websocketBodyNotAvailable',\n ERROR_CODES.SYSTEM.WEBSOCKET_BODY_NOT_AVAILABLE\n )\n }\n}\n","import type { Context } from 'hono'\nimport type { WSContext, WSReadyState } from 'hono/ws'\nimport type { ContextQueryResult } from '../router/router-context'\nimport { RouterContext } from '../router/router-context'\nimport type { RouterEnv } from '../router/types'\nimport { WebSocketBodyNotAvailableError } from './errors/websocket-body-not-available.error'\n\n/**\n * WebSocket gateway context\n *\n * Extends RouterContext with WebSocket-specific methods.\n * Inherits `getContainer()`, `param()`, `query()`, `header()`, `getLocale()`\n * from RouterContext. HTTP response methods (`json()`, `redirect()`, etc.) are\n * inherited but harmless post-upgrade.\n *\n * @example\n * ```typescript\n * @OnMessage()\n * handleMessage(evt: MessageEvent, ctx: GatewayContext) {\n * ctx.send('ack') // convenience method\n * ctx.header('Authorization') // upgrade request headers\n * }\n * ```\n */\nexport class GatewayContext extends RouterContext {\n constructor(c: Context<RouterEnv>, public readonly ws: WSContext) {\n super(c)\n }\n\n /** Send data through the WebSocket connection */\n send(data: string | ArrayBuffer | Uint8Array<ArrayBuffer>): void {\n this.ws.send(data)\n }\n\n /** Close the WebSocket connection */\n close(code?: number, reason?: string): void {\n this.ws.close(code, reason)\n }\n\n /** Current WebSocket ready state */\n get readyState(): WSReadyState {\n return this.ws.readyState\n }\n\n /**\n * Get route parameter value(s) from the raw request — WebSocket gateways are\n * not OpenAPI-registered, so reads come straight from Hono's matcher.\n *\n * - With a key → single string value.\n * - With no args → full `Record<string, string>` (or `{}` when none).\n *\n * @param key - Parameter name (e.g., 'id' for /ws/chat/:id)\n */\n override param(): Record<string, string>\n override param(key: string): string\n override param(key?: string): string | Record<string, string> {\n if (key === undefined) return this.c.req.param() ?? {}\n return this.c.req.param(key)!\n }\n\n /**\n * Get query parameter value from the raw request (no OpenAPI validation)\n *\n * @param key - Query parameter name\n */\n override query<R extends Record<string, unknown> | undefined = undefined, K extends string | undefined = undefined>(key?: K): ContextQueryResult<R, K> {\n if (key) {\n return this.c.req.query(key) as ContextQueryResult<R, K>\n }\n return this.c.req.query() as ContextQueryResult<R, K>\n }\n\n /**\n * Request body is not available in WebSocket gateways\n *\n * @throws WebSocketBodyNotAvailableError always — WebSocket upgrade requests do not have a body\n */\n override body<T>(): Promise<T> {\n throw new WebSocketBodyNotAvailableError()\n }\n}\n"],"mappings":";;;AAKA,MAAM,qBAAqB,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8B/C,SAAgB,QAAQ,OAAe,SAA0B;CAC/D,OAAO,SAAiC,QAAW;EACjD,WAAW,OAAO,QAAQ,CAAC,OAAO;EAClC,QAAQ,eAAe,oBAAoB,MAAM,OAAO;EACxD,OAAO;;;;;;;;;AAUX,SAAgB,UAAU,QAAyB;CACjD,MAAM,iBAAiB,OAAO,WAAW,aAAa,SAAU,OAAmC;CACnG,OAAO,QAAQ,YAAY,oBAAoB,eAAe,KAAK;;;;ACjDrE,IAAa,sCAAb,cAAyD,iBAAiB;CACxE,YAAY,WAAmB,gBAAwB;EACrD,MACE,yCACA,YAAY,OAAO,mCACnB;GAAE;GAAW;GAAgB,CAC9B;;;;;ACJL,MAAM,oBAAoB,oBAAoB;AAC9C,MAAM,kBAAkB,oBAAoB;AAC5C,MAAM,kBAAkB,oBAAoB;;;;;AAM5C,SAAS,4BAA4B,KAAsB,aAA8B,QAAgB,eAA6B;CACpI,MAAM,WAAW,QAAQ,YAAY,KAAK,OAAO;CACjD,IAAI,aAAa,KAAA,KAAa,aAAa,aACzC,MAAM,IAAI,oCAAoC,eAAe,OAAO,SAAS,CAAC;CAEhF,QAAQ,eAAe,KAAK,aAAa,OAAO;;;;;;;;;;;;;;;;AAiBlD,SAAgB,YAA6B;CAG3C,QAAQ,SAAiB,gBAAiC;EACxD,4BAA4B,mBAAmB,aAAa,SAAS,YAAY;;;;;;;;;;;;;;;;;AAkBrF,SAAgB,UAA2B;CACzC,QAAQ,SAAiB,gBAAiC;EACxD,4BAA4B,iBAAiB,aAAa,SAAS,UAAU;;;;;;;;;;;;;;;;;AAkBjF,SAAgB,UAA2B;CACzC,QAAQ,SAAiB,gBAAiC;EACxD,4BAA4B,iBAAiB,aAAa,SAAS,UAAU;;;;;;AAOjF,SAAgB,qBAAqB,QAAyC;CAC5E,OAAO,QAAQ,YAAY,mBAAmB,OAAO,UAAoB;;;;;AAM3E,SAAgB,mBAAmB,QAAyC;CAC1E,OAAO,QAAQ,YAAY,iBAAiB,OAAO,UAAoB;;;;;AAMzE,SAAgB,mBAAmB,QAAyC;CAC1E,OAAO,QAAQ,YAAY,iBAAiB,OAAO,UAAoB;;;;AClGzE,IAAa,iCAAb,cAAoD,iBAAiB;CACnE,cAAc;EACZ,MACE,oCACA,YAAY,OAAO,6BACpB;;;;;;;;;;;;;;;;;;;;;;ACiBL,IAAa,iBAAb,cAAoC,cAAc;CACG;CAAnD,YAAY,GAAuB,IAA+B;EAChE,MAAM,EAAE;EADyC,KAAA,KAAA;;;CAKnD,KAAK,MAA4D;EAC/D,KAAK,GAAG,KAAK,KAAK;;;CAIpB,MAAM,MAAe,QAAuB;EAC1C,KAAK,GAAG,MAAM,MAAM,OAAO;;;CAI7B,IAAI,aAA2B;EAC7B,OAAO,KAAK,GAAG;;CAcjB,MAAe,KAA+C;EAC5D,IAAI,QAAQ,KAAA,GAAW,OAAO,KAAK,EAAE,IAAI,OAAO,IAAI,EAAE;EACtD,OAAO,KAAK,EAAE,IAAI,MAAM,IAAI;;;;;;;CAQ9B,MAAoH,KAAmC;EACrJ,IAAI,KACF,OAAO,KAAK,EAAE,IAAI,MAAM,IAAI;EAE9B,OAAO,KAAK,EAAE,IAAI,OAAO;;;;;;;CAQ3B,OAA+B;EAC7B,MAAM,IAAI,gCAAgC"}
|
package/dist/guards/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Dr as Container, V as RouterContext } from "../index-
|
|
1
|
+
import { Dr as Container, V as RouterContext } from "../index-ByOyTmqf.mjs";
|
|
2
2
|
import { t as Constructor } from "../types-cySNS_lp.mjs";
|
|
3
3
|
import { i as LoggerService } from "../index-DBd_2wv8.mjs";
|
|
4
4
|
|
package/dist/guards/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as GUARD_METADATA_KEY, i as getMethodGuards, n as UseGuards, r as getControllerGuards, t as GuardExecutionService } from "../guards-
|
|
1
|
+
import { a as GUARD_METADATA_KEY, i as getMethodGuards, n as UseGuards, r as getControllerGuards, t as GuardExecutionService } from "../guards-DU1_J9YA.mjs";
|
|
2
2
|
export { GUARD_METADATA_KEY, GuardExecutionService, UseGuards, getControllerGuards, getMethodGuards };
|
|
@@ -95,6 +95,7 @@ function getMethodGuards(target, propertyKey) {
|
|
|
95
95
|
* Guards are executed in order; all must pass for the request to proceed.
|
|
96
96
|
*/
|
|
97
97
|
var GuardExecutionService = class {
|
|
98
|
+
logger;
|
|
98
99
|
constructor(logger) {
|
|
99
100
|
this.logger = logger;
|
|
100
101
|
}
|
|
@@ -148,4 +149,4 @@ var GuardExecutionService = class {
|
|
|
148
149
|
//#endregion
|
|
149
150
|
export { GUARD_METADATA_KEY as a, getMethodGuards as i, UseGuards as n, getControllerGuards as r, GuardExecutionService as t };
|
|
150
151
|
|
|
151
|
-
//# sourceMappingURL=guards-
|
|
152
|
+
//# sourceMappingURL=guards-DU1_J9YA.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guards-
|
|
1
|
+
{"version":3,"file":"guards-DU1_J9YA.mjs","names":[],"sources":["../src/guards/types.ts","../src/guards/use-guards.decorator.ts","../src/guards/guard-execution.service.ts"],"sourcesContent":["import type { RouterContext } from '../router'\nimport type { Constructor } from '../types'\n\n/**\n * Interface for guards that control access to routes\n *\n * Guards are executed after middlewares but before route handlers.\n * They determine if a request should be allowed to proceed.\n *\n * @example\n * ```typescript\n * class RoleGuard implements CanActivate {\n * constructor(private readonly role: string) {}\n *\n * async canActivate(context: RouterContext): Promise<boolean> {\n * const user = context.getUser()\n * return user?.roles.includes(this.role) ?? false\n * }\n * }\n * ```\n */\nexport interface CanActivate {\n /**\n * Determine if the request should be allowed\n *\n * @param context - Router context with request/response helpers\n * @returns true to allow, false to deny (throws 403)\n */\n canActivate(context: RouterContext): boolean | Promise<boolean>\n}\n\n/**\n * Type for guard class constructors\n */\nexport type GuardClass = Constructor<CanActivate>\n\n/**\n * Guard can be a class constructor or an instance\n * Instances are used for factory-created guards with configuration\n */\nexport type Guard = GuardClass | CanActivate\n\n/**\n * Options for AuthGuard factory\n */\nexport interface AuthGuardOptions {\n /**\n * Required permissions for authorization as `\"resource:action\"` strings.\n * If provided, permission check is performed after authentication.\n * If omitted, only authentication is required.\n *\n * Multiple permissions are combined with AND logic (all must be satisfied).\n *\n * @example\n * ```typescript\n * @UseGuards(AuthGuard({ permissions: 'posts:update' }))\n * @UseGuards(AuthGuard({ permissions: ['posts:update', 'posts:delete'] }))\n * @UseGuards(AuthGuard({ permissions: 'admin:access' }))\n * ```\n */\n permissions?: string | string[]\n}\n\n/**\n * Metadata stored by `@UseGuards` decorator\n */\nexport interface GuardMetadata {\n guards: Guard[]\n}\n\n/**\n * Metadata key for guard storage\n */\nexport const GUARD_METADATA_KEY = Symbol.for('stratal:guards')\n","import { GUARD_METADATA_KEY, type Guard, type GuardMetadata } from './types'\n\n/**\n * UseGuards Decorator\n *\n * Applies one or more guards to a controller or method.\n * Guards are executed in order and all must pass for the request to proceed.\n *\n * **Execution Order:**\n * 1. Request → Global Middlewares → Route Middlewares\n * 2. **Guards (controller-level, then method-level)**\n * 3. Route Handler\n *\n * **Guard Resolution:**\n * - Guard classes are resolved from the request-scoped DI container\n * - Guard instances (from factory functions) are used directly\n *\n * @param guards - Guard classes or instances to apply\n *\n * @example Authentication only\n * ```typescript\n * @Controller('/api/v1/profile')\n * @UseGuards(AuthGuard())\n * export class ProfileController {\n * show() { } // Requires authentication\n * }\n * ```\n *\n * @example Authentication with permissions\n * ```typescript\n * @Controller('/api/v1/students')\n * @UseGuards(AuthGuard({ scopes: ['students:read'] }))\n * export class StudentsController {\n * index() { } // Requires 'students:read' permission\n * }\n * ```\n *\n * @example Method-level guards\n * ```typescript\n * @Controller('/api/v1/students')\n * @UseGuards(AuthGuard()) // Controller-level: auth only\n * export class StudentsController {\n * index() { } // Auth only (inherited)\n *\n * @UseGuards(AuthGuard({ scopes: ['students:create'] }))\n * create() { } // Auth + 'students:create' permission\n * }\n * ```\n *\n * @example Multiple guards\n * ```typescript\n * @UseGuards(AuthGuard(), RateLimitGuard(), CustomGuard())\n * export class SecureController {\n * // All guards must pass\n * }\n * ```\n */\nexport function UseGuards(...guards: Guard[]): ClassDecorator & MethodDecorator {\n return (target: object, propertyKey?: string | symbol) => {\n const metadata: GuardMetadata = { guards }\n\n if (propertyKey !== undefined) {\n // Method decorator - store on method\n Reflect.defineMetadata(GUARD_METADATA_KEY, metadata, target, propertyKey)\n } else {\n // Class decorator - store on class\n Reflect.defineMetadata(GUARD_METADATA_KEY, metadata, target)\n }\n }\n}\n\n/**\n * Get controller-level guard metadata\n *\n * @param target - Controller class\n * @returns Guard metadata or undefined if not decorated\n */\nexport function getControllerGuards(target: object): GuardMetadata | undefined {\n return Reflect.getMetadata(GUARD_METADATA_KEY, target) as GuardMetadata | undefined\n}\n\n/**\n * Get method-level guard metadata\n *\n * @param target - Controller prototype\n * @param propertyKey - Method name\n * @returns Guard metadata or undefined if not decorated\n */\nexport function getMethodGuards(target: object, propertyKey: string | symbol): GuardMetadata | undefined {\n return Reflect.getMetadata(GUARD_METADATA_KEY, target, propertyKey) as GuardMetadata | undefined\n}\n","import type { Container } from '../di'\nimport type { LoggerService } from '../logger'\nimport type { RouterContext } from '../router'\nimport type { CanActivate, Guard } from './types'\n\n/**\n * Guard Execution Service\n *\n * Executes guards for a route and determines if the request should proceed.\n * Guards are executed in order; all must pass for the request to proceed.\n */\nexport class GuardExecutionService {\n constructor(private readonly logger: LoggerService) { }\n\n /**\n * Execute all guards for a route\n *\n * @param guards - Array of guards (classes or instances)\n * @param context - Router context\n * @param container - Request-scoped DI container\n * @returns true if all guards pass\n * @throws Error from first failing guard\n */\n async executeGuards(\n guards: Guard[],\n context: RouterContext,\n container: Container\n ): Promise<boolean> {\n if (guards.length === 0) {\n return true\n }\n\n this.logger.debug('Executing guards', {\n guardCount: guards.length,\n path: context.c.req.path,\n method: context.c.req.method,\n })\n\n for (const guard of guards) {\n const guardInstance = this.resolveGuard(guard, container)\n const canActivate = await guardInstance.canActivate(context)\n\n if (!canActivate) {\n this.logger.debug('Guard denied access', {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- guard.constructor may be null at runtime\n guard: guard.constructor?.name || 'AnonymousGuard',\n path: context.c.req.path,\n })\n return false\n }\n }\n\n this.logger.debug('All guards passed', {\n guardCount: guards.length,\n path: context.c.req.path,\n })\n\n return true\n }\n\n /**\n * Resolve a guard to an instance\n *\n * @param guard - Guard class or instance\n * @param container - Request-scoped DI container\n * @returns Guard instance\n */\n private resolveGuard(guard: Guard, container: Container): CanActivate {\n // If already an instance (has canActivate method), use directly\n if (this.isGuardInstance(guard)) {\n return guard\n }\n\n // Otherwise, resolve from container\n return container.resolve<CanActivate>(guard)\n }\n\n /**\n * Type guard to check if value is a guard instance\n */\n private isGuardInstance(guard: Guard): guard is CanActivate {\n return (\n typeof guard === 'object' &&\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- typeof null === 'object', null check is required\n guard !== null &&\n 'canActivate' in guard &&\n typeof guard.canActivate === 'function'\n )\n }\n}\n"],"mappings":";;;;AAyEA,MAAa,qBAAqB,OAAO,IAAI,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AChB9D,SAAgB,UAAU,GAAG,QAAmD;CAC9E,QAAQ,QAAgB,gBAAkC;EACxD,MAAM,WAA0B,EAAE,QAAQ;EAE1C,IAAI,gBAAgB,KAAA,GAElB,QAAQ,eAAe,oBAAoB,UAAU,QAAQ,YAAY;OAGzE,QAAQ,eAAe,oBAAoB,UAAU,OAAO;;;;;;;;;AAWlE,SAAgB,oBAAoB,QAA2C;CAC7E,OAAO,QAAQ,YAAY,oBAAoB,OAAO;;;;;;;;;AAUxD,SAAgB,gBAAgB,QAAgB,aAAyD;CACvG,OAAO,QAAQ,YAAY,oBAAoB,QAAQ,YAAY;;;;;;;;;;AC9ErE,IAAa,wBAAb,MAAmC;CACJ;CAA7B,YAAY,QAAwC;EAAvB,KAAA,SAAA;;;;;;;;;;;CAW7B,MAAM,cACJ,QACA,SACA,WACkB;EAClB,IAAI,OAAO,WAAW,GACpB,OAAO;EAGT,KAAK,OAAO,MAAM,oBAAoB;GACpC,YAAY,OAAO;GACnB,MAAM,QAAQ,EAAE,IAAI;GACpB,QAAQ,QAAQ,EAAE,IAAI;GACvB,CAAC;EAEF,KAAK,MAAM,SAAS,QAIlB,IAAI,CAAC,MAHiB,KAAK,aAAa,OAAO,UACR,CAAC,YAAY,QAAQ,EAE1C;GAChB,KAAK,OAAO,MAAM,uBAAuB;IAEvC,OAAO,MAAM,aAAa,QAAQ;IAClC,MAAM,QAAQ,EAAE,IAAI;IACrB,CAAC;GACF,OAAO;;EAIX,KAAK,OAAO,MAAM,qBAAqB;GACrC,YAAY,OAAO;GACnB,MAAM,QAAQ,EAAE,IAAI;GACrB,CAAC;EAEF,OAAO;;;;;;;;;CAUT,aAAqB,OAAc,WAAmC;EAEpE,IAAI,KAAK,gBAAgB,MAAM,EAC7B,OAAO;EAIT,OAAO,UAAU,QAAqB,MAAM;;;;;CAM9C,gBAAwB,OAAoC;EAC1D,OACE,OAAO,UAAU,YAEjB,UAAU,QACV,iBAAiB,SACjB,OAAO,MAAM,gBAAgB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as ROUTE_METADATA_KEYS } from "./errors-
|
|
1
|
+
import { f as ROUTE_METADATA_KEYS } from "./errors-COW9-Mar.mjs";
|
|
2
2
|
import { i as z } from "./validation-DtJwAv7O.mjs";
|
|
3
3
|
//#region src/router/decorators/http-method.decorator.ts
|
|
4
4
|
/**
|
|
@@ -93,4 +93,4 @@ const All = createHttpMethodDecorator("all");
|
|
|
93
93
|
//#endregion
|
|
94
94
|
export { Post as a, Patch as i, Delete as n, Put as o, Get as r, All as t };
|
|
95
95
|
|
|
96
|
-
//# sourceMappingURL=http-method.decorator-
|
|
96
|
+
//# sourceMappingURL=http-method.decorator-BrgHMdLQ.mjs.map
|
package/dist/{http-method.decorator-DXwxAfb_.mjs.map → http-method.decorator-BrgHMdLQ.mjs.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-method.decorator-
|
|
1
|
+
{"version":3,"file":"http-method.decorator-BrgHMdLQ.mjs","names":[],"sources":["../src/router/decorators/http-method.decorator.ts"],"sourcesContent":["import { z } from '../../i18n/validation'\nimport { ROUTE_METADATA_KEYS } from '../constants'\nimport type { ExplicitRouteMetadata, HttpMethod, RouteConfig } from '../types'\n\n/**\n * Creates an HTTP method decorator factory for the given HTTP method.\n *\n * The returned decorator stores {@link ExplicitRouteMetadata} on the method and\n * tracks the method name under {@link ROUTE_METADATA_KEYS.DECORATED_METHODS}\n * on the controller prototype so they can be discovered at registration time.\n */\nfunction createHttpMethodDecorator(method: HttpMethod) {\n return function (path: string, config?: RouteConfig) {\n return function (\n target: object,\n propertyKey: string,\n descriptor: PropertyDescriptor\n ) {\n const metadata: ExplicitRouteMetadata = {\n type: 'explicit',\n method,\n path,\n config: config ?? { response: z.any() },\n }\n\n Reflect.defineMetadata(\n ROUTE_METADATA_KEYS.ROUTE_CONFIG,\n metadata,\n target,\n propertyKey\n )\n\n // Track this method as decorated on the prototype\n const existing: string[] =\n (Reflect.getOwnMetadata(ROUTE_METADATA_KEYS.DECORATED_METHODS, target) as string[] | undefined) ?? []\n existing.push(propertyKey)\n Reflect.defineMetadata(ROUTE_METADATA_KEYS.DECORATED_METHODS, existing, target)\n\n return descriptor\n }\n }\n}\n\n/**\n * Registers a GET route on the controller method.\n *\n * @param path - Route path relative to the controller base path\n * @param config - Optional route configuration (response schema, body, params, etc.)\n *\n * @example\n * ```typescript\n * @Controller('/api/v1/users')\n * class UsersController {\n * @Get('/', { response: z.array(userSchema), summary: 'List users' })\n * async list(ctx: RouterContext) { ... }\n *\n * @Get('/:id', { params: z.object({ id: z.string().uuid() }), response: userSchema })\n * async getUser(ctx: RouterContext) { ... }\n * }\n * ```\n */\nexport const Get = createHttpMethodDecorator('get')\n\n/**\n * Registers a POST route on the controller method.\n *\n * @param path - Route path relative to the controller base path\n * @param config - Optional route configuration (response schema, body, params, etc.)\n *\n * @example\n * ```typescript\n * @Controller('/api/v1/users')\n * class UsersController {\n * @Post('/', { body: createUserSchema, response: userSchema, statusCode: 201 })\n * async createUser(ctx: RouterContext) { ... }\n * }\n * ```\n */\nexport const Post = createHttpMethodDecorator('post')\n\n/**\n * Registers a PUT route on the controller method.\n *\n * @param path - Route path relative to the controller base path\n * @param config - Optional route configuration\n */\nexport const Put = createHttpMethodDecorator('put')\n\n/**\n * Registers a PATCH route on the controller method.\n *\n * @param path - Route path relative to the controller base path\n * @param config - Optional route configuration\n */\nexport const Patch = createHttpMethodDecorator('patch')\n\n/**\n * Registers a DELETE route on the controller method.\n *\n * @param path - Route path relative to the controller base path\n * @param config - Optional route configuration\n */\nexport const Delete = createHttpMethodDecorator('delete')\n\n/**\n * Registers an ALL (any HTTP method) route on the controller method.\n * Routes using @All are registered without OpenAPI validation\n * since OpenAPI does not support a catch-all HTTP method.\n *\n * @param path - Route path relative to the controller base path\n * @param config - Optional route configuration\n */\nexport const All = createHttpMethodDecorator('all')\n"],"mappings":";;;;;;;;;;AAWA,SAAS,0BAA0B,QAAoB;CACrD,OAAO,SAAU,MAAc,QAAsB;EACnD,OAAO,SACL,QACA,aACA,YACA;GACA,MAAM,WAAkC;IACtC,MAAM;IACN;IACA;IACA,QAAQ,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE;IACxC;GAED,QAAQ,eACN,oBAAoB,cACpB,UACA,QACA,YACD;GAGD,MAAM,WACH,QAAQ,eAAe,oBAAoB,mBAAmB,OAAO,IAA6B,EAAE;GACvG,SAAS,KAAK,YAAY;GAC1B,QAAQ,eAAe,oBAAoB,mBAAmB,UAAU,OAAO;GAE/E,OAAO;;;;;;;;;;;;;;;;;;;;;;AAuBb,MAAa,MAAM,0BAA0B,MAAM;;;;;;;;;;;;;;;;AAiBnD,MAAa,OAAO,0BAA0B,OAAO;;;;;;;AAQrD,MAAa,MAAM,0BAA0B,MAAM;;;;;;;AAQnD,MAAa,QAAQ,0BAA0B,QAAQ;;;;;;;AAQvD,MAAa,SAAS,0BAA0B,SAAS;;;;;;;;;AAUzD,MAAa,MAAM,0BAA0B,MAAM"}
|
package/dist/i18n/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as LocaleNotSupportedError, C as DetectionStrategy, D as buildDetectorOptions, E as ResolvedI18nOptions, O as resolveI18nOptions, S as I18nModule, T as LanguageDetectionOptions, _ as Messages, b as messages, g as MessageRegistry, h as MessageLoaderService, k as TranslationMissingError, m as I18nContextMiddleware, v as getLocales, w as I18nModuleOptions, x as I18N_TOKENS, y as getMessages } from "../index-
|
|
1
|
+
import { A as LocaleNotSupportedError, C as DetectionStrategy, D as buildDetectorOptions, E as ResolvedI18nOptions, O as resolveI18nOptions, S as I18nModule, T as LanguageDetectionOptions, _ as Messages, b as messages, g as MessageRegistry, h as MessageLoaderService, k as TranslationMissingError, m as I18nContextMiddleware, v as getLocales, w as I18nModuleOptions, x as I18N_TOKENS, y as getMessages } from "../index-ByOyTmqf.mjs";
|
|
2
2
|
import { C as MessageParams, S as MessageKeys, T as SystemMessageKeys, _ as AppMessageNamespaces, b as II18nService, g as AppMessageKeys, v as AppMessages, w as Prefixes, x as MessageKeyPrefix, y as DeepKeys } from "../index-Bnpfq6uk.mjs";
|
|
3
3
|
export { AppMessageKeys, AppMessageNamespaces, AppMessages, DeepKeys, DetectionStrategy, I18N_TOKENS, I18nContextMiddleware, I18nModule, I18nModuleOptions, II18nService, LanguageDetectionOptions, LocaleNotSupportedError, MessageKeyPrefix, MessageKeys, MessageLoaderService, MessageParams, MessageRegistry, Messages, Prefixes, ResolvedI18nOptions, SystemMessageKeys, TranslationMissingError, buildDetectorOptions, getLocales, getMessages, messages, resolveI18nOptions };
|
package/dist/i18n/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { w as I18N_TOKENS } from "../errors-
|
|
2
|
-
import { B as LocaleNotSupportedError, F as getMessages, H as I18nContextMiddleware, I as messages, L as buildDetectorOptions, M as MessageRegistry, N as MessageLoaderService, P as getLocales, R as resolveI18nOptions, t as I18nModule, z as TranslationMissingError } from "../i18n.module-
|
|
1
|
+
import { w as I18N_TOKENS } from "../errors-COW9-Mar.mjs";
|
|
2
|
+
import { B as LocaleNotSupportedError, F as getMessages, H as I18nContextMiddleware, I as messages, L as buildDetectorOptions, M as MessageRegistry, N as MessageLoaderService, P as getLocales, R as resolveI18nOptions, t as I18nModule, z as TranslationMissingError } from "../i18n.module-CzXLW9Hy.mjs";
|
|
3
3
|
export { I18N_TOKENS, I18nContextMiddleware, I18nModule, LocaleNotSupportedError, MessageLoaderService, MessageRegistry, TranslationMissingError, buildDetectorOptions, getLocales, getMessages, messages, resolveI18nOptions };
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { A as Scope, D as runWithContainer, E as getContainer, H as ApplicationError, V as ROUTER_TOKENS, a as createHttpExceptionContext, c as DEFAULT_CONTENT_TYPE, d as ROUTER_CONTEXT_KEYS, f as ROUTE_METADATA_KEYS, k as ERROR_CODES, l as HTTP_METHODS, m as VERSION_NEUTRAL, p as SECURITY_SCHEMES, s as RouterContext, u as METHOD_STATUS_CODES, w as I18N_TOKENS } from "./errors-
|
|
2
|
-
import { a as __decorate, d as CONTAINER_TOKEN, f as DI_TOKENS, g as getMethodInjections, o as __decorateParam, p as Transient, s as __decorateMetadata, u as LOGGER_TOKENS } from "./logger-
|
|
3
|
-
import { S as createThrottleMiddleware, b as ControllerRegistrationError, c as InvalidSignatureError, d as MissingRouteParamError, f as ResponseValidationError, g as RouteNotFoundError, h as SchemaValidationError, k as Module, l as MiddlewareNextCalledMultipleTimesError, o as DomainMismatchError, p as RouteNameNotFoundError, s as DuplicateRouteNameError, u as MissingEnvironmentVariableError, v as OpenAPIRouteRegistrationError, x as ControllerMethodNotFoundError, y as HonoAppAlreadyConfiguredError } from "./module-
|
|
1
|
+
import { A as Scope, D as runWithContainer, E as getContainer, H as ApplicationError, V as ROUTER_TOKENS, a as createHttpExceptionContext, c as DEFAULT_CONTENT_TYPE, d as ROUTER_CONTEXT_KEYS, f as ROUTE_METADATA_KEYS, k as ERROR_CODES, l as HTTP_METHODS, m as VERSION_NEUTRAL, p as SECURITY_SCHEMES, s as RouterContext, u as METHOD_STATUS_CODES, w as I18N_TOKENS } from "./errors-COW9-Mar.mjs";
|
|
2
|
+
import { a as __decorate, d as CONTAINER_TOKEN, f as DI_TOKENS, g as getMethodInjections, o as __decorateParam, p as Transient, s as __decorateMetadata, u as LOGGER_TOKENS } from "./logger-DlV7NtvD.mjs";
|
|
3
|
+
import { S as createThrottleMiddleware, b as ControllerRegistrationError, c as InvalidSignatureError, d as MissingRouteParamError, f as ResponseValidationError, g as RouteNotFoundError, h as SchemaValidationError, k as Module, l as MiddlewareNextCalledMultipleTimesError, o as DomainMismatchError, p as RouteNameNotFoundError, s as DuplicateRouteNameError, u as MissingEnvironmentVariableError, v as OpenAPIRouteRegistrationError, x as ControllerMethodNotFoundError, y as HonoAppAlreadyConfiguredError } from "./module-BzLg57FK.mjs";
|
|
4
4
|
import { c as backendErrorMap, i as z, l as runWithErrorMapContext, r as validation_exports, t as OpenAPIHono } from "./validation-DtJwAv7O.mjs";
|
|
5
5
|
import { n as OPENAPI_TOKENS } from "./openapi-tools.service-Zs-Ewv7F.mjs";
|
|
6
6
|
import { t as en_exports } from "./en-DSH_bhh6.mjs";
|
|
7
|
-
import { i as getMethodGuards, r as getControllerGuards, t as GuardExecutionService } from "./guards-
|
|
8
|
-
import { n as getControllerOptions, r as getControllerRoute } from "./controller.decorator-
|
|
9
|
-
import { c as getWsOnMessageMethod, d as isGateway, o as getWsOnCloseMethod, s as getWsOnErrorMethod, t as GatewayContext } from "./gateway-context-
|
|
10
|
-
import "./http-method.decorator-
|
|
11
|
-
import { n as getRateLimits } from "./rate-limit.decorator
|
|
7
|
+
import { i as getMethodGuards, r as getControllerGuards, t as GuardExecutionService } from "./guards-DU1_J9YA.mjs";
|
|
8
|
+
import { n as getControllerOptions, r as getControllerRoute } from "./controller.decorator-B9vwn0zK.mjs";
|
|
9
|
+
import { c as getWsOnMessageMethod, d as isGateway, o as getWsOnCloseMethod, s as getWsOnErrorMethod, t as GatewayContext } from "./gateway-context-CXmXtaUP.mjs";
|
|
10
|
+
import "./http-method.decorator-BrgHMdLQ.mjs";
|
|
11
|
+
import { n as getRateLimits } from "./rate-limit.decorator-6qzNcSOt.mjs";
|
|
12
12
|
import { n as verifySignedUrl, t as signUrl } from "./signed-url-BQPbv2In.mjs";
|
|
13
13
|
import { t as setupI18nCompiler } from "./setup-CefZKV_e.mjs";
|
|
14
14
|
import { inject } from "tsyringe";
|
|
@@ -23,6 +23,7 @@ import { languageDetector } from "hono/language";
|
|
|
23
23
|
* Must run after LocaleExtractionMiddleware sets the locale.
|
|
24
24
|
*/
|
|
25
25
|
let I18nContextMiddleware = class I18nContextMiddleware {
|
|
26
|
+
i18n;
|
|
26
27
|
constructor(i18n) {
|
|
27
28
|
this.i18n = i18n;
|
|
28
29
|
}
|
|
@@ -41,6 +42,7 @@ I18nContextMiddleware = __decorate([
|
|
|
41
42
|
//#endregion
|
|
42
43
|
//#region src/openapi/services/openapi-config.service.ts
|
|
43
44
|
let OpenAPIConfigService = class OpenAPIConfigService {
|
|
45
|
+
baseOptions;
|
|
44
46
|
overrides = [];
|
|
45
47
|
constructor(baseOptions) {
|
|
46
48
|
this.baseOptions = baseOptions;
|
|
@@ -210,6 +212,8 @@ function deepMerge(target, source) {
|
|
|
210
212
|
//#endregion
|
|
211
213
|
//#region src/i18n/services/message-loader.service.ts
|
|
212
214
|
let MessageLoaderService = class MessageLoaderService {
|
|
215
|
+
registry;
|
|
216
|
+
options;
|
|
213
217
|
cache;
|
|
214
218
|
contextCache;
|
|
215
219
|
locales;
|
|
@@ -1050,10 +1054,34 @@ function toRoutingOpenAPIPath(path) {
|
|
|
1050
1054
|
* Scoring: static = 0, `:param{constraint}` = 5, `:param` = 10, wildcard `{.+}` / `{.*}` = 100.
|
|
1051
1055
|
*
|
|
1052
1056
|
* Packed as: score * 10000 - segmentCount (negative segment count so more segments = lower key = higher priority)
|
|
1057
|
+
*
|
|
1058
|
+
* Locale variants score against the path with the leading `/:locale{…}` segment
|
|
1059
|
+
* stripped — the variant's score therefore matches its primary, but its larger
|
|
1060
|
+
* segment count makes it sort just before the primary. Without this, a primary
|
|
1061
|
+
* catch-all (e.g. `/:slug{.+}`) gobbles locale-prefixed URLs because Hono picks
|
|
1062
|
+
* whichever matching route was registered first.
|
|
1053
1063
|
*/
|
|
1054
|
-
function getPathSpecificityKey(
|
|
1064
|
+
function getPathSpecificityKey(route) {
|
|
1065
|
+
const segmentCount = countSegments(route.path);
|
|
1066
|
+
const scoringPath = route.isLocaleVariant ? route.path.replace(/^\/:locale\{[^}]*\}/, "") || "/" : route.path;
|
|
1055
1067
|
let score = 0;
|
|
1056
|
-
let
|
|
1068
|
+
let i = 0;
|
|
1069
|
+
while (i < scoringPath.length) {
|
|
1070
|
+
if (scoringPath.charCodeAt(i) === 47) {
|
|
1071
|
+
i++;
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
let end = scoringPath.indexOf("/", i);
|
|
1075
|
+
if (end === -1) end = scoringPath.length;
|
|
1076
|
+
const segment = scoringPath.substring(i, end);
|
|
1077
|
+
if (segment.includes("{.+}") || segment.includes("{.*}")) score += 100;
|
|
1078
|
+
else if (segment.charCodeAt(0) === 58) score += segment.includes("{") ? 5 : 10;
|
|
1079
|
+
i = end;
|
|
1080
|
+
}
|
|
1081
|
+
return score * 1e4 - segmentCount;
|
|
1082
|
+
}
|
|
1083
|
+
function countSegments(path) {
|
|
1084
|
+
let count = 0;
|
|
1057
1085
|
let i = 0;
|
|
1058
1086
|
while (i < path.length) {
|
|
1059
1087
|
if (path.charCodeAt(i) === 47) {
|
|
@@ -1062,13 +1090,10 @@ function getPathSpecificityKey(path) {
|
|
|
1062
1090
|
}
|
|
1063
1091
|
let end = path.indexOf("/", i);
|
|
1064
1092
|
if (end === -1) end = path.length;
|
|
1065
|
-
|
|
1066
|
-
const segment = path.substring(i, end);
|
|
1067
|
-
if (segment.includes("{.+}") || segment.includes("{.*}")) score += 100;
|
|
1068
|
-
else if (segment.charCodeAt(0) === 58) score += segment.includes("{") ? 5 : 10;
|
|
1093
|
+
count++;
|
|
1069
1094
|
i = end;
|
|
1070
1095
|
}
|
|
1071
|
-
return
|
|
1096
|
+
return count;
|
|
1072
1097
|
}
|
|
1073
1098
|
/**
|
|
1074
1099
|
* Compute a specificity score for route ordering.
|
|
@@ -1089,11 +1114,13 @@ function getPathSpecificityScore(path) {
|
|
|
1089
1114
|
*
|
|
1090
1115
|
* 1. Static paths before parameterized before wildcards
|
|
1091
1116
|
* 2. More segments = more specific (tie-breaker)
|
|
1092
|
-
* 3.
|
|
1117
|
+
* 3. Locale-prefixed variants before their primary (so a locale-prefixed
|
|
1118
|
+
* request matches the variant first; a primary catch-all would otherwise
|
|
1119
|
+
* swallow the locale prefix into its param)
|
|
1093
1120
|
*/
|
|
1094
1121
|
function sortRoutesBySpecificity(routes) {
|
|
1095
1122
|
const keys = /* @__PURE__ */ new Map();
|
|
1096
|
-
for (const route of routes) keys.set(route, getPathSpecificityKey(route
|
|
1123
|
+
for (const route of routes) keys.set(route, getPathSpecificityKey(route));
|
|
1097
1124
|
const copy = routes.slice();
|
|
1098
1125
|
copy.sort((a, b) => keys.get(a) - keys.get(b));
|
|
1099
1126
|
return copy;
|
|
@@ -1161,6 +1188,12 @@ const invokeHandler = (instance, method, ...args) => {
|
|
|
1161
1188
|
}
|
|
1162
1189
|
};
|
|
1163
1190
|
let RouteRegistrationService = class RouteRegistrationService {
|
|
1191
|
+
logger;
|
|
1192
|
+
registry;
|
|
1193
|
+
routerResolver;
|
|
1194
|
+
localePathService;
|
|
1195
|
+
app;
|
|
1196
|
+
moduleRegistry;
|
|
1164
1197
|
controllerClasses = /* @__PURE__ */ new Map();
|
|
1165
1198
|
upgradeWebSocketFn = null;
|
|
1166
1199
|
constructor(logger, registry, routerResolver, localePathService, app, moduleRegistry) {
|
|
@@ -1786,6 +1819,7 @@ HonoApp = __decorate([
|
|
|
1786
1819
|
//#endregion
|
|
1787
1820
|
//#region src/router/services/locale-path.service.ts
|
|
1788
1821
|
let LocalePathService = class LocalePathService {
|
|
1822
|
+
honoApp;
|
|
1789
1823
|
_config;
|
|
1790
1824
|
_pathDetectionEnabled;
|
|
1791
1825
|
_prefixDefaultLocale;
|
|
@@ -1941,6 +1975,8 @@ const CONCRETE_HTTP_METHODS = [
|
|
|
1941
1975
|
"trace"
|
|
1942
1976
|
];
|
|
1943
1977
|
let RouteRegistry = class RouteRegistry {
|
|
1978
|
+
versioningService;
|
|
1979
|
+
localePathService;
|
|
1944
1980
|
routes = [];
|
|
1945
1981
|
namedRoutes = /* @__PURE__ */ new Map();
|
|
1946
1982
|
_sortedCache = null;
|
|
@@ -2025,7 +2061,7 @@ let RouteRegistry = class RouteRegistry {
|
|
|
2025
2061
|
}
|
|
2026
2062
|
return cache;
|
|
2027
2063
|
}
|
|
2028
|
-
/** Get all routes sorted by specificity (static > param > wildcard,
|
|
2064
|
+
/** Get all routes sorted by specificity (static > param > wildcard, locale variant before its primary) */
|
|
2029
2065
|
all() {
|
|
2030
2066
|
this._sortedCache ??= sortRoutesBySpecificity(this.routes);
|
|
2031
2067
|
return this._sortedCache;
|
|
@@ -2099,6 +2135,8 @@ function buildRouteUrl(route, name, params) {
|
|
|
2099
2135
|
return url;
|
|
2100
2136
|
}
|
|
2101
2137
|
let Uri = class Uri {
|
|
2138
|
+
registry;
|
|
2139
|
+
routerContext;
|
|
2102
2140
|
_defaults = {};
|
|
2103
2141
|
trailingSlash;
|
|
2104
2142
|
constructor(registry, routerContext, application) {
|
|
@@ -2352,6 +2390,8 @@ var VerifySignatureMiddleware = class {
|
|
|
2352
2390
|
* Uses pre-built CoreContext from MessageLoaderService (singleton) for zero-cost lookups.
|
|
2353
2391
|
*/
|
|
2354
2392
|
let I18nService = class I18nService {
|
|
2393
|
+
loader;
|
|
2394
|
+
routerContext;
|
|
2355
2395
|
constructor(loader, routerContext) {
|
|
2356
2396
|
this.loader = loader;
|
|
2357
2397
|
this.routerContext = routerContext;
|
|
@@ -2489,4 +2529,4 @@ I18nModule = _I18nModule = __decorate([Module({ providers: [
|
|
|
2489
2529
|
//#endregion
|
|
2490
2530
|
export { OpenAPIModule as A, LocaleNotSupportedError as B, validationErrorResponseSchema as C, createMiddlewareChain as D, getRouteMetadata as E, getMessages as F, I18nContextMiddleware as H, messages as I, buildDetectorOptions as L, MessageRegistry as M, MessageLoaderService as N, createDomainMiddleware as O, getLocales as P, resolveI18nOptions as R, uuidParamSchema as S, getRouteDecoratedMethods as T, OpenAPIConfigService as V, commonErrorSchemas as _, buildRouteUrl as a, paginationQuerySchema as b, LocalePathService as c, extractDomainParamNames as d, extractParamNames as f, toOpenAPIPath as g, sortRoutesBySpecificity as h, Uri as i, OpenAPIService as j, parseDomainPattern as k, HonoApp as l, getPathSpecificityScore as m, VerifySignatureMiddleware as n, RouteRegistry as o, generateConventionRouteName as p, route as r, VersioningService as s, I18nModule as t, RouteRegistrationService as u, errorResponseSchema as v, Route as w, successMessageSchema as x, paginatedResponseSchema as y, TranslationMissingError as z };
|
|
2491
2531
|
|
|
2492
|
-
//# sourceMappingURL=i18n.module-
|
|
2532
|
+
//# sourceMappingURL=i18n.module-CzXLW9Hy.mjs.map
|