stratal 0.0.17 → 0.0.18
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-DypUAfWm.mjs → base-email.provider-Cuw4OAB0.mjs} +1 -1
- package/dist/{base-email.provider-DypUAfWm.mjs.map → base-email.provider-Cuw4OAB0.mjs.map} +1 -1
- package/dist/bin/cloudflare-workers-loader.mjs +33 -1
- package/dist/bin/cloudflare-workers-loader.mjs.map +1 -1
- package/dist/bin/quarry.mjs +182 -5
- package/dist/bin/quarry.mjs.map +1 -1
- package/dist/cache/index.d.mts +2 -2
- package/dist/cache/index.d.mts.map +1 -1
- package/dist/cache/index.mjs +3 -10
- package/dist/cache/index.mjs.map +1 -1
- package/dist/{colors-Y7WIFXs7.mjs → colors-BTAnQRGU.mjs} +1 -1
- package/dist/{colors-Y7WIFXs7.mjs.map → colors-BTAnQRGU.mjs.map} +1 -1
- package/dist/{command-TnkPYWta.d.mts → command-B1YuV-UZ.d.mts} +2 -2
- package/dist/{command-TnkPYWta.d.mts.map → command-B1YuV-UZ.d.mts.map} +1 -1
- package/dist/{command-B1CPgsrU.mjs → command-DjGqCYHv.mjs} +3 -3
- package/dist/command-DjGqCYHv.mjs.map +1 -0
- package/dist/config/index.d.mts +2 -2
- package/dist/config/index.mjs +12 -19
- package/dist/config/index.mjs.map +1 -1
- package/dist/{consumer-registry-Bymm6ff4.d.mts → consumer-registry-BkuHXR_u.d.mts} +1 -1
- package/dist/{consumer-registry-Bymm6ff4.d.mts.map → consumer-registry-BkuHXR_u.d.mts.map} +1 -1
- package/dist/cron/index.d.mts +2 -2
- package/dist/cron/index.d.mts.map +1 -1
- package/dist/cron/index.mjs +1 -3
- package/dist/{cron-manager-CFBamKKk.mjs → cron-manager-1KnZvojs.mjs} +3 -3
- package/dist/{cron-manager-CFBamKKk.mjs.map → cron-manager-1KnZvojs.mjs.map} +1 -1
- package/dist/{cron-manager-D7imGwUT.d.mts → cron-manager-BnEZquBL.d.mts} +2 -2
- package/dist/{cron-manager-D7imGwUT.d.mts.map → cron-manager-BnEZquBL.d.mts.map} +1 -1
- package/dist/di/index.d.mts +2 -2
- package/dist/di/index.mjs +3 -3
- package/dist/email/index.d.mts +3 -3
- package/dist/email/index.mjs +8 -16
- package/dist/email/index.mjs.map +1 -1
- package/dist/{en-DaewN8hc.mjs → en-3QnZwP-u.mjs} +10 -1
- package/dist/en-3QnZwP-u.mjs.map +1 -0
- package/dist/errors/index.d.mts +2 -2
- package/dist/errors/index.mjs +2 -3
- package/dist/errors--RBIvDXr.mjs +1560 -0
- package/dist/errors--RBIvDXr.mjs.map +1 -0
- package/dist/{errors-DuAR5Wke.mjs → errors-B7hCnXgB.mjs} +2 -2
- package/dist/{errors-DuAR5Wke.mjs.map → errors-B7hCnXgB.mjs.map} +1 -1
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +1 -2
- package/dist/{events-CvUSgEuN.mjs → events-UTJliZhl.mjs} +2 -2
- package/dist/{events-CvUSgEuN.mjs.map → events-UTJliZhl.mjs.map} +1 -1
- package/dist/{gateway-context-CNOLkLUC.mjs → gateway-context-BdBFoQd8.mjs} +66 -10
- package/dist/gateway-context-BdBFoQd8.mjs.map +1 -0
- package/dist/guards/index.d.mts +3 -3
- package/dist/guards/index.d.mts.map +1 -1
- package/dist/guards/index.mjs +1 -1
- package/dist/{guards-DUk_Kzst.mjs → guards-MtDgcHnF.mjs} +1 -1
- package/dist/{guards-DUk_Kzst.mjs.map → guards-MtDgcHnF.mjs.map} +1 -1
- package/dist/i18n/index.d.mts +3 -3
- package/dist/i18n/index.mjs +3 -16
- package/dist/i18n/messages/en/index.d.mts +1 -1
- package/dist/i18n/messages/en/index.mjs +1 -1
- package/dist/i18n/utils/index.d.mts +30 -0
- package/dist/i18n/utils/index.d.mts.map +1 -0
- package/dist/i18n/utils/index.mjs +2 -0
- package/dist/i18n/validation/index.d.mts +1 -1
- package/dist/i18n/validation/index.mjs +1 -1
- package/dist/i18n.module-BpLLLCTg.mjs +2462 -0
- package/dist/i18n.module-BpLLLCTg.mjs.map +1 -0
- package/dist/{index-D_w_Rmtd.d.mts → index-BDh9J2KD.d.mts} +10 -1
- package/dist/{index-D_w_Rmtd.d.mts.map → index-BDh9J2KD.d.mts.map} +1 -1
- package/dist/{index-Dp6A5ywM.d.mts → index-BR23zDMy.d.mts} +1 -1
- package/dist/{index-Dp6A5ywM.d.mts.map → index-BR23zDMy.d.mts.map} +1 -1
- package/dist/index-BrmS34sa.d.mts +4287 -0
- package/dist/index-BrmS34sa.d.mts.map +1 -0
- package/dist/{index-DGRe6Yoa.d.mts → index-DPxmo6AY.d.mts} +4 -4
- package/dist/{index-DGRe6Yoa.d.mts.map → index-DPxmo6AY.d.mts.map} +1 -1
- package/dist/{index-NGxg-KP_.d.mts → index-Dfpd_ypO.d.mts} +36 -7
- package/dist/index-Dfpd_ypO.d.mts.map +1 -0
- package/dist/index.d.mts +4 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -20
- package/dist/{is-command-DJVI6wEJ.mjs → is-command-PvULqiTa.mjs} +2 -2
- package/dist/{is-command-DJVI6wEJ.mjs.map → is-command-PvULqiTa.mjs.map} +1 -1
- package/dist/{is-seeder-D5MIEcdz.mjs → is-seeder-BN9Ej1r7.mjs} +1 -1
- package/dist/{is-seeder-D5MIEcdz.mjs.map → is-seeder-BN9Ej1r7.mjs.map} +1 -1
- package/dist/logger/index.d.mts +1 -1
- package/dist/logger/index.mjs +1 -1
- package/dist/{logger-CGT91VY6.mjs → logger-c0ftIK4G.mjs} +29 -29
- package/dist/logger-c0ftIK4G.mjs.map +1 -0
- package/dist/module/index.d.mts +3 -4
- package/dist/module/index.d.mts.map +1 -1
- package/dist/module/index.mjs +1 -10
- package/dist/module-C3YZ-kZN.mjs +719 -0
- package/dist/module-C3YZ-kZN.mjs.map +1 -0
- package/dist/openapi/index.d.mts +3 -3
- package/dist/openapi/index.mjs +2 -15
- package/dist/{openapi-tools.service-B3TxYKoQ.mjs → openapi-tools.service-B77QXD56.mjs} +1 -1
- package/dist/{openapi-tools.service-B3TxYKoQ.mjs.map → openapi-tools.service-B77QXD56.mjs.map} +1 -1
- package/dist/{openapi.service-DGnX3Fc4.d.mts → openapi.service-6yj0BUY4.d.mts} +9 -17
- package/dist/openapi.service-6yj0BUY4.d.mts.map +1 -0
- package/dist/quarry/index.d.mts +25 -11
- package/dist/quarry/index.d.mts.map +1 -1
- package/dist/quarry/index.mjs +4 -8
- package/dist/{quarry-registry-B2rkO-JS.mjs → quarry-registry-CQCIlYTO.mjs} +37 -34
- package/dist/quarry-registry-CQCIlYTO.mjs.map +1 -0
- package/dist/queue/index.d.mts +2 -2
- package/dist/queue/index.mjs +3 -13
- package/dist/queue/index.mjs.map +1 -1
- package/dist/{queue.module-BtI8f4Jo.mjs → queue.module-DIjD6nr-.mjs} +39 -42
- package/dist/queue.module-DIjD6nr-.mjs.map +1 -0
- package/dist/{resend.provider-bXMEkdRJ.mjs → resend.provider-Bvw36rQy.mjs} +2 -4
- package/dist/{resend.provider-bXMEkdRJ.mjs.map → resend.provider-Bvw36rQy.mjs.map} +1 -1
- package/dist/router/index.d.mts +2 -2
- package/dist/router/index.mjs +5 -16
- package/dist/{s3-storage.provider-CttzNnDR.mjs → s3-storage.provider-BAhHDMI3.mjs} +13 -5
- package/dist/s3-storage.provider-BAhHDMI3.mjs.map +1 -0
- package/dist/seeder/index.d.mts +3 -4
- package/dist/seeder/index.d.mts.map +1 -1
- package/dist/seeder/index.mjs +2 -6
- package/dist/{seeder-R7RXJC35.mjs → seeder-D7VXULXB.mjs} +5 -5
- package/dist/{seeder-R7RXJC35.mjs.map → seeder-D7VXULXB.mjs.map} +1 -1
- package/dist/setup-BRIN-iYT.mjs +37 -0
- package/dist/setup-BRIN-iYT.mjs.map +1 -0
- package/dist/{smtp.provider-DrbHQztF.mjs → smtp.provider-CAwpvzvD.mjs} +2 -4
- package/dist/{smtp.provider-DrbHQztF.mjs.map → smtp.provider-CAwpvzvD.mjs.map} +1 -1
- package/dist/storage/index.d.mts +2 -2
- package/dist/storage/index.d.mts.map +1 -1
- package/dist/storage/index.mjs +2 -13
- package/dist/storage/providers/index.d.mts +2 -1
- package/dist/storage/providers/index.d.mts.map +1 -1
- package/dist/storage/providers/index.mjs +1 -4
- package/dist/{storage-CZKHOhci.mjs → storage-CJ-QOwNv.mjs} +8 -9
- package/dist/storage-CJ-QOwNv.mjs.map +1 -0
- package/dist/{storage-provider.interface-0IqcdhBf.d.mts → storage-provider.interface-YRtyYBxV.d.mts} +8 -2
- package/dist/storage-provider.interface-YRtyYBxV.d.mts.map +1 -0
- package/dist/stratal-B7G4i9-N.mjs +502 -0
- package/dist/stratal-B7G4i9-N.mjs.map +1 -0
- package/dist/{types-DahElfUw.d.mts → types-CN0zONAZ.d.mts} +2 -2
- package/dist/types-CN0zONAZ.d.mts.map +1 -0
- package/dist/{usage-generator-CVIsENuE.mjs → usage-generator-Cl1HPlUp.mjs} +2 -2
- package/dist/{usage-generator-CVIsENuE.mjs.map → usage-generator-Cl1HPlUp.mjs.map} +1 -1
- package/dist/{validation-DQTC259A.mjs → validation-B4bePOa_.mjs} +2 -2
- package/dist/{validation-DQTC259A.mjs.map → validation-B4bePOa_.mjs.map} +1 -1
- package/dist/websocket/index.d.mts +2 -2
- package/dist/websocket/index.d.mts.map +1 -1
- package/dist/websocket/index.mjs +1 -4
- package/dist/workers/index.d.mts +1 -1
- package/dist/workers/index.d.mts.map +1 -1
- package/dist/workers/index.mjs +2 -20
- package/dist/workers/index.mjs.map +1 -1
- package/package.json +34 -31
- package/dist/application-DfPtIzxF.d.mts +0 -177
- package/dist/application-DfPtIzxF.d.mts.map +0 -1
- package/dist/command-B1CPgsrU.mjs.map +0 -1
- package/dist/en-DaewN8hc.mjs.map +0 -1
- package/dist/errors-DSKapqD8.mjs +0 -707
- package/dist/errors-DSKapqD8.mjs.map +0 -1
- package/dist/gateway-context-CNOLkLUC.mjs.map +0 -1
- package/dist/i18n.module-Dn9SrFdS.mjs +0 -1841
- package/dist/i18n.module-Dn9SrFdS.mjs.map +0 -1
- package/dist/index-BFCxSp_f.d.mts +0 -2625
- package/dist/index-BFCxSp_f.d.mts.map +0 -1
- package/dist/index-NGxg-KP_.d.mts.map +0 -1
- package/dist/logger-CGT91VY6.mjs.map +0 -1
- package/dist/middleware/index.d.mts +0 -2
- package/dist/middleware/index.mjs +0 -5
- package/dist/middleware-Bl-b5pkt.mjs +0 -362
- package/dist/middleware-Bl-b5pkt.mjs.map +0 -1
- package/dist/module-registry-CmjBX6ol.d.mts +0 -121
- package/dist/module-registry-CmjBX6ol.d.mts.map +0 -1
- package/dist/module-tUtyVJ5E.mjs +0 -371
- package/dist/module-tUtyVJ5E.mjs.map +0 -1
- package/dist/openapi.service-DGnX3Fc4.d.mts.map +0 -1
- package/dist/quarry-registry-B2rkO-JS.mjs.map +0 -1
- package/dist/queue.module-BtI8f4Jo.mjs.map +0 -1
- package/dist/router-context-D9R1v2Ac.mjs +0 -267
- package/dist/router-context-D9R1v2Ac.mjs.map +0 -1
- package/dist/s3-storage.provider-CttzNnDR.mjs.map +0 -1
- package/dist/storage-CZKHOhci.mjs.map +0 -1
- package/dist/storage-provider.interface-0IqcdhBf.d.mts.map +0 -1
- package/dist/stratal-D5smIU1y.mjs +0 -315
- package/dist/stratal-D5smIU1y.mjs.map +0 -1
- package/dist/types-DahElfUw.d.mts.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors--RBIvDXr.mjs","names":["honoStream","honoStreamText","honoStreamSSE"],"sources":["../src/errors/application-error.ts","../src/router/router.tokens.ts","../src/di/errors/conditional-binding-fallback.error.ts","../src/di/errors/request-scope-operation-not-allowed.error.ts","../src/di/conditional-binding-builder.ts","../src/di/container.ts","../src/di/types.ts","../src/errors/error-codes.ts","../src/errors/container-not-initialized.error.ts","../src/di/container-storage.ts","../src/i18n/i18n.tokens.ts","../src/errors/http-exception.ts","../src/errors/get-http-status.ts","../src/errors/internal-error.ts","../src/errors/is-application-error.ts","../src/errors/exception-handler.ts","../src/errors/default-exception-handler.ts","../src/errors/error-response.ts","../src/router/constants.ts","../src/router/router-context.ts","../src/errors/exception-context.ts","../src/errors/request-container-not-initialized.error.ts","../src/errors/stratal-not-initialized.error.ts"],"sourcesContent":["import type { Environment, ErrorResponse } from './error-response'\nimport type { ExceptionContext } from './exception-context'\nimport type { MessageKeys } from '../i18n'\nimport type { ErrorCode } from './error-codes'\n\n/**\n * ApplicationError\n *\n * Abstract base class for all application errors.\n *\n * @deprecated Use {@link HttpException} for new error classes. `HttpException` provides\n * a simpler constructor that takes `(httpStatus, message?)` and derives the error code\n * automatically. Existing subclasses will continue to work but should be migrated over time.\n *\n * Features:\n * - Type-safe error codes from ERROR_CODES registry\n * - Type-safe message keys from i18n module\n * - Localized message keys (translated by ExceptionHandler)\n * - Structured metadata for logging and interpolation\n * - Proper Error prototype chain\n * - Automatic timestamp generation\n * - Serialization for RPC transmission\n * - Optional self-reporting via `report()` method\n * - Optional self-rendering via `render()` method\n *\n * Message Localization:\n * - Each error class passes an i18n key (e.g., 'errors.userNotFound') to super()\n * - `Error.message` contains the i18n key for useful stack traces and fallback display\n * - Metadata provides interpolation parameters (e.g., { userId: '123' })\n * - ExceptionHandler translates the message key using I18nService before sending response\n * - This ensures errors are localized based on the user's locale\n */\nexport abstract class ApplicationError extends Error {\n /**\n * Controls whether stack traces are captured.\n * Set to false in production to skip the expensive Error.captureStackTrace() call,\n * since stack traces are stripped from responses in production anyway.\n */\n static captureStackTraces = true\n\n /**\n * Type-safe error code from ERROR_CODES registry\n * See error-codes.ts for the complete registry\n */\n public readonly code: ErrorCode\n\n /**\n * ISO timestamp when the error was created\n */\n public readonly timestamp: string\n\n /**\n * Additional structured data about the error\n * Used for:\n * 1. Logging and debugging\n * 2. Message interpolation (e.g., { userId: '123', email: 'user@example.com' })\n */\n public readonly metadata?: Record<string, unknown>\n\n /**\n * @param i18nKey - Type-safe i18n message key (e.g., 'errors.userNotFound')\n * @param code - Type-safe error code from ERROR_CODES registry\n * @param metadata - Optional data for logging and interpolation\n */\n constructor(\n i18nKey: MessageKeys,\n code: ErrorCode,\n metadata?: Record<string, unknown>\n ) {\n // Pass i18nKey to Error.message for useful stack traces (e.g., \"InternalError: errors.internalError\")\n super(i18nKey)\n\n // Maintains proper prototype chain for instanceof checks\n Object.setPrototypeOf(this, new.target.prototype)\n\n this.name = this.constructor.name\n this.code = code\n this.timestamp = new Date().toISOString()\n this.metadata = metadata\n\n // Capture stack trace, excluding constructor call from it\n // Skip in production where stack traces are stripped from responses anyway\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- captureStackTrace is V8-specific, not always present\n if (ApplicationError.captureStackTraces && Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor)\n }\n }\n\n /**\n * Filter metadata to include only user-facing properties\n *\n * User-facing properties (validation/constraint errors):\n * - issues: Validation errors from SchemaValidationError\n * - fields: Constraint violation fields\n * - field: Single field constraint/foreign key\n *\n * Internal properties (excluded from response):\n * - path, method: Route debugging\n * - controllerName, reason: Controller errors\n * - details, etc.: Internal debugging info\n *\n * @param metadata - Raw metadata object\n * @returns Filtered metadata with only whitelisted properties\n */\n private static filterMetadata(\n metadata?: Record<string, unknown>\n ): Record<string, unknown> | undefined {\n if (!metadata) return undefined\n\n // Whitelist of user-facing metadata properties\n const whitelist = ['issues', 'fields', 'field']\n\n const filtered: Record<string, unknown> = {}\n let hasUserFacingData = false\n\n for (const key of whitelist) {\n if (key in metadata && metadata[key] !== undefined) {\n filtered[key] = metadata[key]\n hasUserFacingData = true\n }\n }\n\n // Only return metadata if there's actual user-facing data\n return hasUserFacingData ? filtered : undefined\n }\n\n /**\n * Serialize error to ErrorResponse format for RPC transmission\n *\n * @param env - Environment (development | production)\n * @param translatedMessage - Optional translated message (from ExceptionHandler)\n * @returns ErrorResponse object suitable for JSON serialization\n */\n toErrorResponse(env: Environment, translatedMessage?: string): ErrorResponse {\n const message = translatedMessage ?? this.message\n\n return {\n code: this.code,\n message,\n timestamp: this.timestamp,\n // Include filtered user-facing metadata in all environments\n metadata: ApplicationError.filterMetadata(this.metadata),\n // Stack trace only in development for debugging\n // Rewrite first line with translated message for readable debugging\n stack: env === 'development'\n ? this.stack?.replace(this.message, message)\n : undefined,\n }\n }\n\n /**\n * JSON serialization (used by JSON.stringify)\n * Defaults to development mode for backward compatibility\n * Note: This will use the untranslated message key - use ExceptionHandler for proper localization\n */\n toJSON(): ErrorResponse {\n return this.toErrorResponse('development')\n }\n\n /**\n * Self-reporting hook. Override in subclasses to define custom reporting logic\n * that runs instead of the default logger.\n *\n * - Return `void` (or nothing) to **skip** default reporting after this runs.\n * - Return `false` to **also run** default reporting after this runs.\n *\n * @example\n * ```typescript\n * class PaymentError extends HttpException {\n * report(): void {\n * sentry.captureException(this)\n * // Default logging is skipped\n * }\n * }\n *\n * class SoftError extends HttpException {\n * report(): false {\n * analytics.track(this)\n * return false // Default logging also runs\n * }\n * }\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-invalid-void-type\n report?(): void | false\n\n /**\n * Self-rendering hook. Override in subclasses to define how this error\n * is rendered into a Response.\n *\n * Return `undefined` to fall through to the default renderer.\n *\n * @param ctx - The execution context (narrow via `ctx.type` for HTTP helpers)\n * @returns A Response, ErrorResponse, or undefined to use default rendering\n *\n * @example\n * ```typescript\n * class MaintenanceError extends HttpException {\n * render(ctx: ExceptionContext): Response | undefined {\n * if (ctx.type === 'http') {\n * return ctx.ctx.html('<h1>Down for maintenance</h1>', 503)\n * }\n * }\n * }\n * ```\n */\n render?(ctx: ExceptionContext): Response | ErrorResponse | undefined\n}\n","/**\n * Dependency injection tokens for the router system\n */\nexport const ROUTER_TOKENS = {\n /**\n * Token for RouterContext (request-scoped)\n * Contains Hono context wrapper with helper methods\n */\n RouterContext: Symbol.for('stratal:router:context'),\n\n /**\n * Token for RouteRegistry (singleton)\n * Central registry of all application routes — source of truth for route:list, route:types, URL generation\n */\n RouteRegistry: Symbol.for('stratal:router:route-registry'),\n\n /**\n * Token for VersioningService (singleton)\n * Resolves version prefixes for route paths\n */\n VersioningService: Symbol.for('stratal:router:versioning-service'),\n\n /**\n * Token for LocalePathService (singleton)\n * Resolves locale path variants and computes LocalePathConfig\n */\n LocalePathService: Symbol.for('stratal:router:locale-path-service'),\n\n /**\n * Token for RouterResolver (singleton, may be null)\n * Internal resolver that computes effective Router config per controller\n */\n RouterResolver: Symbol.for('stratal:router:router-resolver'),\n\n /**\n * Token for HonoApp (singleton)\n * The Hono application instance with Stratal-specific setup\n */\n HonoApp: Symbol.for('stratal:router:hono-app'),\n\n /**\n * Token for Uri (request-scoped)\n * URL generation service — route URLs, signed URLs, current/previous URL access\n */\n Uri: Symbol.for('stratal:router:uri'),\n} as const\n","import { ERROR_CODES } from '../../errors'\nimport { ApplicationError } from '../../errors'\n\n/**\n * ConditionalBindingFallbackError\n *\n * Thrown when a conditional binding predicate returns false but no fallback\n * implementation was provided and no existing registration exists for the token.\n *\n * This typically indicates a misconfiguration in the DI setup where:\n * - A `when().use().give()` chain was used without `otherwise()`\n * - AND the token wasn't previously registered\n * - AND the predicate evaluated to false at resolution time\n */\nexport class ConditionalBindingFallbackError extends ApplicationError {\n constructor(token: string) {\n super(\n 'errors.conditionalBindingFallback',\n ERROR_CODES.SYSTEM.INFRASTRUCTURE_ERROR,\n { token }\n )\n }\n}\n","import { ERROR_CODES } from '../../errors'\nimport { ApplicationError } from '../../errors'\n\n/**\n * RequestScopeOperationNotAllowedError\n *\n * Thrown when attempting to call a method that is not allowed on the current container scope.\n * - `createRequestScope()` and `runInRequestScope()` can only be called on global containers\n */\nexport class RequestScopeOperationNotAllowedError extends ApplicationError {\n constructor(methodName: string) {\n super(\n 'errors.requestScopeOperationNotAllowed',\n ERROR_CODES.SYSTEM.INFRASTRUCTURE_ERROR,\n { methodName }\n )\n }\n}\n","/**\n * Conditional Binding Builder\n *\n * Provides a fluent API for predicate-based service registration.\n * The predicate is evaluated lazily at resolution time using tsyringe's\n * predicateAwareClassFactory.\n *\n * @example With explicit fallback\n * ```typescript\n * container\n * .when((c) => c.resolve(CONFIG_TOKEN).get('env') === 'development')\n * .use(FORMATTER_TOKEN)\n * .give(PrettyFormatter)\n * .otherwise(JsonFormatter)\n * ```\n *\n * @example Without fallback (uses existing registration)\n * ```typescript\n * container\n * .when((c) => c.resolve(FEATURE_FLAG_TOKEN).isEnabled('newFeature'))\n * .use(SERVICE_TOKEN)\n * .give(NewServiceImpl) // Falls back to existing if predicate is false\n * ```\n */\n\nimport type { DependencyContainer } from 'tsyringe'\nimport { predicateAwareClassFactory } from 'tsyringe'\nimport type InjectionToken from 'tsyringe/dist/typings/providers/injection-token'\nimport type { Constructor } from '../types'\nimport { ConditionalBindingFallbackError } from './errors'\nimport type { WhenOptions } from './types'\n\n/**\n * Container interface for predicate functions\n * Using a minimal interface to avoid circular imports\n */\nexport interface PredicateContainer {\n resolve<T>(token: InjectionToken<T>): T\n isRegistered<T>(token: InjectionToken<T>): boolean\n}\n\n/**\n * Initial builder returned by container.when()\n */\nexport interface ConditionalBindingBuilder {\n /**\n * Specify the token to conditionally bind\n *\n * @param token - DI token for the service\n * @returns Builder for specifying implementations\n */\n use<T extends object>(token: InjectionToken<T>): ConditionalBindingUse<T>\n}\n\n/**\n * Builder after specifying token with use()\n */\nexport interface ConditionalBindingUse<T extends object> {\n /**\n * Specify the implementation when predicate returns true.\n * Registration is completed immediately.\n *\n * If predicate is false at resolution time:\n * - Uses `otherwise()` implementation if provided\n * - Falls back to existing registration if available\n * - Throws error if no fallback exists\n *\n * @param implementation - Service class to use when predicate is true\n * @returns Builder for optional fallback specification\n */\n give(implementation: Constructor<T>): ConditionalBindingGive<T>\n}\n\n/**\n * Builder after specifying true implementation with give()\n * Registration is already complete at this point.\n */\nexport interface ConditionalBindingGive<T extends object> {\n /**\n * Optionally specify a fallback implementation when predicate returns false.\n * This re-registers with the explicit fallback instead of existing registration.\n *\n * @param implementation - Service class to use when predicate is false\n */\n otherwise(implementation: Constructor<T>): void\n}\n\n/**\n * Implementation of ConditionalBindingBuilder\n *\n * @internal\n */\nexport class ConditionalBindingBuilderImpl implements ConditionalBindingBuilder {\n constructor(\n private readonly tsyringeContainer: DependencyContainer,\n private readonly predicateContainer: PredicateContainer,\n private readonly predicate: (container: PredicateContainer) => boolean,\n private readonly options: WhenOptions\n ) { }\n\n use<T extends object>(token: InjectionToken<T>): ConditionalBindingUse<T> {\n return new ConditionalBindingUseImpl<T>(\n this.tsyringeContainer,\n this.predicateContainer,\n this.predicate,\n this.options,\n token\n )\n }\n}\n\n/**\n * Implementation of ConditionalBindingUse\n *\n * @internal\n */\nclass ConditionalBindingUseImpl<T extends object> implements ConditionalBindingUse<T> {\n constructor(\n private readonly tsyringeContainer: DependencyContainer,\n private readonly predicateContainer: PredicateContainer,\n private readonly predicate: (container: PredicateContainer) => boolean,\n private readonly options: WhenOptions,\n private readonly token: InjectionToken<T>\n ) { }\n\n give(trueImplementation: Constructor<T>): ConditionalBindingGive<T> {\n // Get fallback: existing registration or a class that throws\n const falseImplementation = this.getFallbackImplementation()\n\n // Register using predicateAwareClassFactory\n this.registerWithPredicate(trueImplementation, falseImplementation)\n\n // Return builder for optional otherwise()\n return {\n otherwise: (implementation: Constructor<T>) => {\n this.registerWithPredicate(trueImplementation, implementation,)\n },\n }\n }\n\n /**\n * Get fallback implementation: existing registration or throw-on-resolve class\n */\n private getFallbackImplementation(): Constructor<T> {\n // Check if token is already registered\n if (this.tsyringeContainer.isRegistered(this.token)) {\n // Create a class that resolves the existing registration\n // We need to capture the current registration before we overwrite it\n const existingInstance = this.tsyringeContainer.resolve<T>(this.token)\n\n // Return a \"class\" that just returns the existing instance\n // Using a factory wrapper since predicateAwareClassFactory expects a constructor\n return class ExistingInstanceWrapper {\n static instance = existingInstance\n constructor() {\n return ExistingInstanceWrapper.instance as unknown as ExistingInstanceWrapper\n }\n } as unknown as Constructor<T>\n }\n\n // No existing registration - create a class that throws\n const tokenStr = typeof this.token === 'symbol'\n ? (this.token.description ?? 'unknown')\n : typeof this.token === 'function'\n ? this.token.name\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- token can be string or object; String() is intentional fallback\n : String(this.token)\n return class NoFallbackError {\n constructor() {\n throw new ConditionalBindingFallbackError(tokenStr)\n }\n } as unknown as Constructor<T>\n }\n\n private registerWithPredicate(\n trueImplementation: Constructor<T>,\n falseImplementation: Constructor<T>\n ): void {\n const { predicate, predicateContainer, options } = this\n\n this.tsyringeContainer.register(this.token, {\n useFactory: predicateAwareClassFactory<T>(\n () => predicate(predicateContainer),\n trueImplementation,\n falseImplementation,\n options.cache ?? false\n )\n })\n }\n}\n","/**\n * Unified DI Container\n *\n * Provides a developer-friendly wrapper around tsyringe with:\n * - Auto-token extraction from decorator metadata\n * - Auto-scope detection from decorator metadata\n * - Seamless global/request scope interoperability\n * - Request scope lifecycle management\n *\n * **Two-Tier Container Architecture:**\n * ```\n * Global Container (managed by Container)\n * ↓\n * All services registered here\n * (singletons + container-scoped)\n * ↓\n * Request Container (child, per request)\n * ↓\n * Fresh instances for ContainerScoped services\n * RouterContext registered per request\n * ```\n */\nimport { type DependencyContainer, type Lifecycle } from 'tsyringe'\nimport type InjectionToken from 'tsyringe/dist/typings/providers/injection-token'\nimport type { RouterContext } from '../router/router-context'\nimport { ROUTER_TOKENS } from '../router/router.tokens'\nimport type { Constructor } from '../types'\nimport { ConditionalBindingBuilderImpl, type ConditionalBindingBuilder, type PredicateContainer } from './conditional-binding-builder'\nimport { RequestScopeOperationNotAllowedError } from './errors/request-scope-operation-not-allowed.error'\nimport { CONTAINER_TOKEN } from './tokens'\nimport type { ExtensionDecorator, Scope, WhenOptions } from './types'\n\n/**\n * Options for creating a Container instance\n */\nexport interface ContainerOptions {\n /** Pre-created DependencyContainer */\n container: DependencyContainer\n /** Whether this is a request-scoped container */\n isRequestScoped?: boolean\n}\n\n/**\n * Unified Container for DI management\n *\n * Manages the two-tier container hierarchy:\n * - Global scope: Singletons, base instances of request-scoped services\n * - Request scope: Context-enriched instances per HTTP request\n *\n * @example Basic registration\n * ```typescript\n * import { container as tsyringeRootContainer } from 'tsyringe'\n *\n * const container = new Container({\n * container: tsyringeRootContainer.createChildContainer()\n * })\n *\n * container.register(I18nService)\n * container.register(MY_TOKEN, MyService)\n * container.registerSingleton(ConfigService)\n * container.registerValue(MY_TOKEN, myInstance)\n * ```\n *\n * @example Request scope (automatic lifecycle)\n * ```typescript\n * await container.runInRequestScope(routerContext, async (requestContainer) => {\n * const i18n = requestContainer.resolve(I18N_TOKEN)\n * })\n * ```\n */\nexport class Container {\n private readonly container: DependencyContainer\n private readonly isRequestScoped: boolean\n\n constructor(options: ContainerOptions) {\n this.isRequestScoped = options.isRequestScoped ?? false\n this.container = options.container\n\n // Only register CONTAINER_TOKEN for global container (not request-scoped)\n if (!this.isRequestScoped) {\n this.container.register(CONTAINER_TOKEN, { useValue: this })\n }\n }\n\n // ============================================================\n // Registration Methods\n // ============================================================\n\n /**\n * Register a service with optional explicit token and scope\n */\n register<T extends object>(serviceClass: Constructor<T>, scope?: Scope): void\n register<T extends object>(token: InjectionToken<T>, serviceClass: Constructor<T>, scope?: Scope): void\n register<T extends object>(\n tokenOrClass: InjectionToken<T> | Constructor<T>,\n serviceClassOrScope?: Constructor<T> | Scope,\n scope?: Scope\n ): void {\n let token: InjectionToken<T>\n let serviceClass: Constructor<T>\n let lifecycle: Lifecycle | undefined\n\n if (typeof serviceClassOrScope === 'function') {\n // Called as register(token, class, scope?)\n token = tokenOrClass as InjectionToken<T>\n serviceClass = serviceClassOrScope\n lifecycle = scope as Lifecycle | undefined\n } else {\n // Called as register(class, scope?)\n token = tokenOrClass as Constructor<T>\n serviceClass = tokenOrClass as Constructor<T>\n lifecycle = serviceClassOrScope as Lifecycle | undefined\n }\n\n if (lifecycle !== undefined) {\n this.container.register(token, { useClass: serviceClass }, { lifecycle })\n } else {\n this.container.register(token, { useClass: serviceClass })\n }\n }\n\n /**\n * Register a service as singleton\n */\n registerSingleton<T extends object>(serviceClass: Constructor<T>): void\n registerSingleton<T extends object>(token: InjectionToken<T>, serviceClass: Constructor<T>): void\n registerSingleton<T extends object>(\n tokenOrClass: InjectionToken<T> | Constructor<T>,\n serviceClass?: Constructor<T>\n ): void {\n if (serviceClass !== undefined) {\n this.container.registerSingleton(tokenOrClass as InjectionToken<T>, serviceClass)\n } else {\n const targetClass = tokenOrClass as Constructor<T>\n this.container.registerSingleton(targetClass, targetClass)\n }\n }\n\n /**\n * Register a value (instance) directly\n */\n registerValue<T>(token: InjectionToken<T>, value: T): void {\n this.container.register(token, { useValue: value })\n }\n\n /**\n * Register with factory function\n */\n registerFactory<T>(\n token: InjectionToken<T>,\n factory: (container: Container) => T\n ): void {\n this.container.register(token, { useFactory: () => factory(this) })\n }\n\n /**\n * Register an alias to an existing token\n */\n registerExisting<T>(alias: InjectionToken<T>, target: InjectionToken<T>): void {\n this.container.register(alias, { useToken: target })\n }\n\n // ============================================================\n // Resolution Methods\n // ============================================================\n\n /**\n * Resolve a service from the container\n */\n resolve<T>(token: InjectionToken<T>): T {\n return this.container.resolve<T>(token)\n }\n\n /**\n * Check if a token is registered\n */\n isRegistered<T>(token: InjectionToken<T>): boolean {\n return this.container.isRegistered(token)\n }\n\n // ============================================================\n // Conditional Registration Methods\n // ============================================================\n\n /**\n * Start a conditional binding with predicate evaluation\n */\n when(\n predicate: (container: PredicateContainer) => boolean,\n options: WhenOptions = {}\n ): ConditionalBindingBuilder {\n return new ConditionalBindingBuilderImpl(\n this.container,\n this,\n predicate,\n options\n )\n }\n\n /**\n * Replace a service registration with a decorated version\n */\n extend<T>(token: InjectionToken<T>, decorator: ExtensionDecorator<T>): void {\n const currentInstance = this.container.resolve<T>(token)\n const decoratedInstance = decorator(currentInstance, this)\n this.container.register(token, { useValue: decoratedInstance })\n }\n\n // ============================================================\n // Request Scope Management\n // ============================================================\n\n /**\n * Run callback within request scope\n *\n * Creates a child container with fresh instances for services registered with `scope: Scope.Request`.\n * Callback receives the request-scoped container as argument.\n *\n * Can only be called on global container (not request-scoped).\n */\n async runInRequestScope<T>(\n routerContext: RouterContext,\n callback: (requestContainer: Container) => T | Promise<T>\n ): Promise<T> {\n if (this.isRequestScoped) {\n throw new RequestScopeOperationNotAllowedError('runInRequestScope')\n }\n\n const requestContainer = this.createRequestScope(routerContext)\n try {\n return await callback(requestContainer)\n } finally {\n await requestContainer.dispose()\n }\n }\n\n /**\n * Create request scope container\n *\n * Can only be called on global container (not request-scoped).\n */\n createRequestScope(routerContext: RouterContext): Container {\n if (this.isRequestScoped) {\n throw new RequestScopeOperationNotAllowedError('createRequestScope')\n }\n\n const childContainer = this.container.createChildContainer()\n childContainer.register(ROUTER_TOKENS.RouterContext, { useValue: routerContext })\n\n return new Container({ container: childContainer, isRequestScoped: true })\n }\n\n // ============================================================\n // Escape Hatches\n // ============================================================\n\n /**\n * Get underlying tsyringe container\n */\n getTsyringeContainer(): DependencyContainer {\n return this.container\n }\n\n dispose() {\n return this.container.dispose()\n }\n}\n\n// Re-export tsyringe utilities for convenience\nexport { container, delay, inject, injectable, instancePerContainerCachingFactory, singleton } from 'tsyringe'\nexport type { DependencyContainer } from 'tsyringe'\n\n","/**\n * DI Type Definitions\n *\n * Core type definitions for the dependency injection system.\n * Simplified after removing LazyProxy - no more type wrappers needed.\n */\n\nimport { Lifecycle } from 'tsyringe'\nimport type InjectionToken from 'tsyringe/dist/typings/providers/injection-token'\n\n/**\n * Service scope for DI registration\n *\n * Maps directly to tsyringe's Lifecycle enum.\n * Scope is specified at registration time via provider configuration,\n * not at class decoration time.\n *\n * @example\n * ```typescript\n * // In module providers:\n * { provide: MY_TOKEN, useClass: MyService, scope: Scope.Singleton }\n *\n * // In Application.ts:\n * container.register(MY_TOKEN, MyService, Scope.Request)\n * ```\n */\n/* eslint-disable @typescript-eslint/prefer-literal-enum-member -- values must stay in sync with tsyringe Lifecycle */\nexport enum Scope {\n /** New instance per resolution (default) */\n Transient = Lifecycle.Transient,\n /** Single instance shared globally */\n Singleton = Lifecycle.Singleton,\n /** New instance per child container (per request) */\n Request = Lifecycle.ContainerScoped,\n}\n/* eslint-enable @typescript-eslint/prefer-literal-enum-member */\n\n/**\n * Options for conditional binding with `when()` method\n */\nexport interface WhenOptions {\n /**\n * Cache predicate result after first evaluation.\n * When true, the predicate is evaluated once and the result is reused.\n * When false (default), predicate is evaluated on each resolution.\n */\n cache?: boolean\n}\n\n/**\n * Decorator function type for extend() method\n *\n * @template T The service type being decorated\n */\nexport type ExtensionDecorator<T> = (service: T, container: ContainerLike) => T\n\n/**\n * Minimal container interface for decorator functions\n * Avoids circular dependency with Container class\n */\nexport interface ContainerLike {\n resolve<T>(token: InjectionToken<T>): T\n}\n\n","/**\n * Centralized Error Code Registry\n *\n * Error codes are organized by category with specific ranges:\n * - 1000-1999: Validation errors\n * - 2000-2999: Database errors (generic)\n * - 3000-3999: Authentication & Authorization\n * - 4000-4999: Resource errors\n * - 5000-5999: Domain-specific business logic (per module)\n * - 9000-9999: System/Internal errors\n * - 9000-9099: Router errors\n * - 9100-9199: Configuration errors\n * - 9200-9299: Infrastructure errors\n * - 9300-9399: I18n errors\n */\n\nexport const ERROR_CODES = {\n /**\n * Database Errors (2000-2999)\n * Generic database errors thrown by Prisma client extensions\n */\n DATABASE: {\n /** Generic database error */\n GENERIC: 2000,\n /** Record not found in database */\n RECORD_NOT_FOUND: 2001,\n /** Unique constraint violation */\n UNIQUE_CONSTRAINT: 2002,\n /** Foreign key constraint violation */\n FOREIGN_KEY_CONSTRAINT: 2003,\n /** Database connection failed */\n CONNECTION_FAILED: 2004,\n /** Database timeout */\n TIMEOUT: 2005,\n /** Null constraint violation */\n NULL_CONSTRAINT: 2006,\n /** Too many database connections */\n TOO_MANY_CONNECTIONS: 2007,\n /** Transaction conflict or deadlock */\n TRANSACTION_CONFLICT: 2008,\n },\n\n /**\n * Authentication Errors (3000-3099)\n * Authentication-related failures\n */\n AUTH: {\n /** Invalid credentials provided */\n INVALID_CREDENTIALS: 3000,\n /** Session expired or invalid */\n SESSION_EXPIRED: 3001,\n /** Account locked or disabled */\n ACCOUNT_LOCKED: 3002,\n /** Invalid or expired token */\n INVALID_TOKEN: 3003,\n /** Context not initialized */\n CONTEXT_NOT_INITIALIZED: 3004,\n /** User not authenticated */\n USER_NOT_AUTHENTICATED: 3005,\n /** Email verification required before login */\n EMAIL_NOT_VERIFIED: 3007,\n /** Password doesn't meet minimum length */\n PASSWORD_TOO_SHORT: 3008,\n /** Password exceeds maximum length */\n PASSWORD_TOO_LONG: 3009,\n /** Account with email already exists */\n ACCOUNT_ALREADY_EXISTS: 3010,\n /** User creation failed */\n FAILED_TO_CREATE_USER: 3011,\n /** Session creation failed */\n FAILED_TO_CREATE_SESSION: 3012,\n /** User update failed */\n FAILED_TO_UPDATE_USER: 3013,\n /** Social account already linked */\n SOCIAL_ACCOUNT_LINKED: 3014,\n /** Last account cannot be unlinked */\n CANNOT_UNLINK_LAST_ACCOUNT: 3015,\n },\n\n /**\n * Authorization Errors (3100-3199)\n * Permission and access control failures\n */\n AUTHZ: {\n /** Insufficient permissions */\n FORBIDDEN: 3100,\n /** Resource access denied */\n ACCESS_DENIED: 3101,\n /** User lacks required role */\n INSUFFICIENT_PERMISSIONS: 3102,\n },\n\n /**\n * Resource Errors (4000-4999)\n * Generic resource-related errors\n */\n RESOURCE: {\n /** Generic resource not found */\n NOT_FOUND: 4000,\n /** Route/endpoint not found */\n ROUTE_NOT_FOUND: 4004,\n /** Resource conflict or duplicate */\n CONFLICT: 4100,\n /** Resource already exists */\n ALREADY_EXISTS: 4101,\n },\n\n /**\n * Validation Errors (1000-1999)\n * Input validation failures\n */\n VALIDATION: {\n /** Generic validation error */\n GENERIC: 1000,\n /** Required field missing */\n REQUIRED_FIELD: 1001,\n /** Invalid format */\n INVALID_FORMAT: 1002,\n /** Schema validation failed */\n SCHEMA_VALIDATION: 1003,\n /** Request validation failed (OpenAPI, etc.) */\n REQUEST_VALIDATION: 1004,\n /** Response validation failed (response body doesn't match declared schema) */\n RESPONSE_VALIDATION: 1005,\n },\n\n /**\n * Router Errors (9000-9099)\n * Router and controller-related INTERNAL errors\n */\n ROUTER: {\n /** Controller registration error */\n CONTROLLER_REGISTRATION_ERROR: 9005,\n /** Controller method not found */\n CONTROLLER_METHOD_NOT_FOUND: 9006,\n /** OpenAPI route registration failed */\n OPENAPI_ROUTE_REGISTRATION: 9008,\n /** Duplicate route name in RouteRegistry */\n DUPLICATE_ROUTE_NAME: 9010,\n /** Named route not found in RouteRegistry */\n ROUTE_NAME_NOT_FOUND: 9011,\n /** Required route parameter missing during URL generation */\n MISSING_ROUTE_PARAM: 9012,\n /** router.use() called inside group() callback */\n USE_SCOPE_VIOLATION: 9013,\n },\n\n /**\n * I18n Errors (9300-9399)\n * Internationalization and localization errors\n */\n I18N: {\n /** Translation key missing from all locales */\n TRANSLATION_MISSING: 9300,\n /** Requested locale not supported */\n LOCALE_NOT_SUPPORTED: 9301,\n },\n\n /**\n * System Errors (9000-9999)\n * Internal system errors and unexpected failures\n */\n SYSTEM: {\n /** Internal server error */\n INTERNAL_ERROR: 9000,\n\n // Configuration Errors (9100-9199)\n /** Generic configuration error */\n CONFIGURATION_ERROR: 9100,\n /** ConfigService not initialized */\n CONFIG_NOT_INITIALIZED: 9101,\n /** Module already registered */\n MODULE_ALREADY_REGISTERED: 9102,\n /** Circular module dependency detected */\n MODULE_CIRCULAR_DEPENDENCY: 9103,\n /** Module dependency not found */\n MODULE_DEPENDENCY_NOT_FOUND: 9104,\n /** Invalid error code range */\n INVALID_ERROR_CODE_RANGE: 9105,\n /** Invalid module provider configuration */\n INVALID_MODULE_PROVIDER: 9106,\n /** ConfigModule.forRoot() was not called */\n CONFIG_MODULE_NOT_INITIALIZED: 9107,\n\n // Infrastructure Errors (9200-9299)\n /** Generic infrastructure error */\n INFRASTRUCTURE_ERROR: 9200,\n /** Execution context not initialized */\n EXECUTION_CONTEXT_NOT_INITIALIZED: 9201,\n /** Request container not initialized */\n REQUEST_CONTAINER_NOT_INITIALIZED: 9202,\n /** Queue binding not found */\n QUEUE_BINDING_NOT_FOUND: 9203,\n /** Cron job execution failed */\n CRON_EXECUTION_FAILED: 9204,\n /** Queue provider not supported */\n QUEUE_PROVIDER_NOT_SUPPORTED: 9205,\n /** body() called on WebSocket gateway context */\n WEBSOCKET_BODY_NOT_AVAILABLE: 9206,\n /** Duplicate WebSocket event decorator on a gateway */\n WEBSOCKET_DUPLICATE_EVENT_HANDLER: 9207,\n /** Seeder name collision — two seeders share the same class name */\n SEEDER_NAME_COLLISION: 9208,\n /** Seeder not registered in the SeederRegistry */\n SEEDER_NOT_REGISTERED: 9209,\n /** Application container not initialized (AsyncLocalStorage) */\n CONTAINER_NOT_INITIALIZED: 9210,\n /** Required environment variable not set */\n MISSING_ENVIRONMENT_VARIABLE: 9211,\n },\n} as const\n\n/**\n * Recursively extract all leaf values from a nested object type\n * Similar to DeepKeys but extracts values instead of keys\n *\n * Example:\n * { DATABASE: { GENERIC: 2000, NOT_FOUND: 2001 }, AUTH: { INVALID: 3000 } }\n * becomes\n * 2000 | 2001 | 3000\n */\ntype DeepValues<T> = T extends object\n ? { [K in keyof T]: DeepValues<T[K]> }[keyof T]\n : T\n\n/**\n * Type helper to extract all error code values\n * Union type of all numeric error codes defined in ERROR_CODES\n *\n * Type: 2000 | 2001 | 2002 | ... | 9203\n */\nexport type ErrorCode = DeepValues<typeof ERROR_CODES>\n","import { ApplicationError } from './application-error'\nimport { ERROR_CODES } from './error-codes'\n\n/**\n * Thrown when attempting to access the application container via AsyncLocalStorage\n * before `Application.initialize()` has been called.\n *\n * This typically means `route()` or another standalone function is being called\n * outside the application lifecycle.\n */\nexport class ContainerNotInitializedError extends ApplicationError {\n constructor() {\n super(\n 'errors.containerNotInitialized',\n ERROR_CODES.SYSTEM.CONTAINER_NOT_INITIALIZED\n )\n }\n}\n","import { AsyncLocalStorage } from 'node:async_hooks'\nimport { ContainerNotInitializedError } from '../errors/container-not-initialized.error'\nimport type { Container } from './container'\n\n/**\n * AsyncLocalStorage for the application container.\n *\n * Set by `Application.initialize()` — all code from that point onward\n * (Stratal handlers, TestingModuleBuilder, standalone functions like `route()`)\n * can access the container without DI or static singletons.\n *\n * Follows the same pattern as `errorMapContextStorage` in `i18n/validation/validation.context.ts`.\n */\nexport const containerStorage = new AsyncLocalStorage<Container>()\n\n/**\n * Get the application container from AsyncLocalStorage.\n *\n * @throws ContainerNotInitializedError if called outside `Application.initialize()` scope\n */\nexport function getContainer(): Container {\n const container = containerStorage.getStore()\n if (!container) {\n throw new ContainerNotInitializedError()\n }\n return container\n}\n\n/**\n * Run a function within a container context.\n *\n * @param container - The application container to store\n * @param fn - The function to execute with container access\n */\nexport function runWithContainer<T>(container: Container, fn: () => T): T {\n return containerStorage.run(container, fn)\n}\n","/**\n * I18n Module DI Tokens\n * Symbol-based tokens to avoid string collisions\n */\n\nexport const I18N_TOKENS = {\n /** MessageLoaderService - loads and caches locale messages */\n MessageLoader: Symbol.for('stratal:i18n:message:loader'),\n /** I18nService - request-scoped translation service */\n I18nService: Symbol.for('stratal:i18n:service'),\n /** I18nModuleOptions - configuration options from forRoot() */\n Options: Symbol.for('stratal:i18n:options'),\n /** MessageRegistry - singleton accumulator for registerMessages() contributions */\n MessageRegistry: Symbol.for('stratal:i18n:message:registry'),\n} as const\n","import type { ContentfulStatusCode } from 'hono/utils/http-status'\nimport type { MessageKeys } from '../i18n'\nimport { ApplicationError } from './application-error'\nimport { ERROR_CODES, type ErrorCode } from './error-codes'\n\n/**\n * Maps common HTTP status codes to their default error codes.\n * Used by {@link HttpException} to derive the error code automatically.\n */\nconst HTTP_STATUS_TO_ERROR_CODE: Partial<Record<number, ErrorCode>> = {\n 400: ERROR_CODES.VALIDATION.GENERIC,\n 401: ERROR_CODES.AUTH.USER_NOT_AUTHENTICATED,\n 403: ERROR_CODES.AUTHZ.FORBIDDEN,\n 404: ERROR_CODES.RESOURCE.NOT_FOUND,\n 409: ERROR_CODES.RESOURCE.CONFLICT,\n 422: ERROR_CODES.VALIDATION.GENERIC,\n 500: ERROR_CODES.SYSTEM.INTERNAL_ERROR,\n}\n\n/**\n * Default human-readable messages for common HTTP status codes.\n * Used as fallback when no message is provided to {@link HttpException}.\n */\nconst HTTP_STATUS_MESSAGES: Partial<Record<number, string>> = {\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not Found',\n 409: 'Conflict',\n 422: 'Unprocessable Entity',\n 500: 'Internal Server Error',\n}\n\n/**\n * HTTP-centric exception base class.\n *\n * Unlike {@link ApplicationError} which requires `(i18nKey, code, metadata)`,\n * `HttpException` takes just `(httpStatus, message?)` and derives the error code\n * from the HTTP status automatically.\n *\n * The message can be a plain string or an i18n key — the {@link ExceptionHandler}\n * tries to translate it via `i18n.t()`, falling back to the raw string if the\n * key is not found.\n *\n * Existing {@link ApplicationError} subclasses can be migrated to this gradually.\n *\n * @example\n * ```typescript\n * // Simple usage with plain message\n * throw new HttpException(404, 'User not found')\n *\n * // With i18n key (auto-translated if key exists)\n * throw new HttpException(422, 'errors.invalidInput')\n *\n * // Default message for status code\n * throw new HttpException(500)\n *\n * // Subclass for domain-specific errors\n * class PaymentDeclinedError extends HttpException {\n * constructor() {\n * super(402, 'errors.paymentDeclined')\n * }\n * }\n * ```\n */\nexport class HttpException extends ApplicationError {\n /**\n * The HTTP status code for this exception.\n * Used by the {@link ExceptionHandler} to set the response status.\n */\n public readonly httpStatus: ContentfulStatusCode\n\n /**\n * @param httpStatus - HTTP status code (e.g., 404, 422, 500)\n * @param message - Optional message string or i18n key. Defaults to the\n * standard HTTP status message (e.g., \"Not Found\" for 404).\n */\n constructor(httpStatus: ContentfulStatusCode, message?: string) {\n const code = HTTP_STATUS_TO_ERROR_CODE[httpStatus] ?? ERROR_CODES.SYSTEM.INTERNAL_ERROR\n const messageStr = message ?? HTTP_STATUS_MESSAGES[httpStatus] ?? 'Internal Server Error'\n // Cast to MessageKeys for ApplicationError compat — ExceptionHandler will\n // attempt i18n.t() translation and fall back to the raw string\n super(messageStr as MessageKeys, code)\n this.httpStatus = httpStatus\n }\n}\n\n/**\n * Throw an HTTP exception from anywhere in the application.\n *\n * The message can be a plain string or an i18n key — the {@link ExceptionHandler}\n * translates it automatically, falling back to the raw string if the key is not found.\n *\n * @param status - HTTP status code\n * @param message - Optional message (plain string or i18n key)\n * @throws {@link HttpException} — always throws, never returns\n *\n * @example\n * ```typescript\n * // With plain message\n * abort(404, 'User not found')\n *\n * // Default message for status\n * abort(403)\n *\n * // With i18n key\n * abort(422, 'errors.invalidInput')\n * ```\n */\nexport function abort(\n status: ContentfulStatusCode,\n message?: MessageKeys | string & {},\n): never {\n throw new HttpException(status, message)\n}\n","import type { ContentfulStatusCode } from 'hono/utils/http-status'\nimport type { ApplicationError } from './application-error'\nimport { ERROR_CODES } from './error-codes'\nimport { HttpException } from './http-exception'\n\n/**\n * Maps error codes to HTTP status codes\n *\n * This utility is used by the frontend to set appropriate HTTP status codes\n * when returning errors from API routes.\n *\n * @param code - Numeric error code from ERROR_CODES registry\n * @returns HTTP status code (200-599)\n */\nexport function getHttpStatus(code: number): ContentfulStatusCode {\n // Validation errors (1000-1999) → 400 Bad Request\n if (code >= 1000 && code < 2000) {\n return 400\n }\n\n // Database errors (2000-2999)\n if (code >= 2000 && code < 3000) {\n // Record not found → 404\n if (code === ERROR_CODES.DATABASE.RECORD_NOT_FOUND) return 404\n // Unique constraint / conflict → 409\n if (code === ERROR_CODES.DATABASE.UNIQUE_CONSTRAINT) return 409\n // Other database errors → 500\n return 500\n }\n\n // Authentication errors (3000-3099) → 401 Unauthorized\n if (code >= 3000 && code < 3100) {\n return 401\n }\n\n // Authorization errors (3100-3199) → 403 Forbidden\n if (code >= 3100 && code < 3200) {\n return 403\n }\n\n // Resource not found (4000-4099) → 404 Not Found\n if (code >= 4000 && code < 4100) {\n return 404\n }\n\n // Resource conflict (4100-4199) → 409 Conflict\n if (code >= 4100 && code < 4200) {\n return 409\n }\n\n // Business logic errors (5000-5999)\n if (code >= 5000 && code < 6000) {\n // Domain-specific NOT_FOUND errors → 404\n // 5000: NOTES.NOT_FOUND\n // 5100: USERS.NOT_FOUND\n // 5200: APPLICATIONS.NOT_FOUND\n if (code === 5000 || code === 5100 || code === 5200) {\n return 404\n }\n // Other business logic errors → 422 Unprocessable Entity\n return 422\n }\n\n // System errors (9000-9999) → 500 Internal Server Error\n if (code >= 9000) {\n return 500\n }\n\n // Default to 500 for unknown codes\n return 500\n}\n\n/**\n * Resolve the HTTP status code for an ApplicationError.\n *\n * If the error is an {@link HttpException}, its `httpStatus` property takes precedence.\n * Otherwise, falls back to the code-range-based mapping via {@link getHttpStatus}.\n *\n * @param error - The application error to resolve the status for\n * @returns HTTP status code\n */\nexport function resolveHttpStatus(error: ApplicationError): ContentfulStatusCode {\n if (error instanceof HttpException) {\n return error.httpStatus\n }\n return getHttpStatus(error.code)\n}\n","import { ERROR_CODES } from './error-codes'\nimport { ApplicationError } from './application-error'\n\n/**\n * InternalError\n *\n * Represents an unexpected internal server error.\n * Used to wrap unknown errors that don't fit into specific error categories.\n *\n * This error is thrown when:\n * - An unexpected exception occurs\n * - An error type is not recognized\n * - A system-level failure happens\n */\nexport class InternalError extends ApplicationError {\n constructor(metadata?: Record<string, unknown>) {\n super(\n 'errors.internalError',\n ERROR_CODES.SYSTEM.INTERNAL_ERROR,\n metadata\n )\n }\n}\n","import { ApplicationError } from './application-error'\n\n/**\n * Type guard to check if an error is an ApplicationError\n *\n * @param error - The error to check\n * @returns True if the error is an ApplicationError instance\n */\nexport function isApplicationError(error: unknown): error is ApplicationError {\n return error instanceof ApplicationError\n}\n","import type { ContentfulStatusCode } from 'hono/utils/http-status'\nimport { inject } from 'tsyringe'\nimport { CONTAINER_TOKEN, type Container } from '../di'\nimport { Transient } from '../di/decorators'\nimport { DI_TOKENS } from '../di/tokens'\nimport { type StratalEnv } from '../env'\nimport type { StratalExecutionContext } from '../execution-context'\nimport { I18N_TOKENS } from '../i18n/i18n.tokens'\nimport type { II18nService, MessageKeys } from '../i18n/i18n.types'\nimport { LOGGER_TOKENS, type LoggerService } from '../logger'\nimport type { ApplicationError } from './application-error'\nimport type { Environment, ErrorResponse } from './error-response'\nimport type { ExceptionContext, HttpExceptionContext } from './exception-context'\nimport type {\n ApplicationErrorConstructor,\n ContextCallback,\n LogSeverity,\n RenderableCallback,\n Reportable,\n ReportableCallback,\n RespondCallback,\n} from './exception-handler.types'\nimport { resolveHttpStatus } from './get-http-status'\nimport { InternalError } from './internal-error'\nimport { isApplicationError } from './is-application-error'\n\ninterface ReportableEntry {\n errorClass: ApplicationErrorConstructor\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n callback: ReportableCallback<any>\n shouldStop: boolean\n}\n\ninterface RenderableEntry {\n errorClass: ApplicationErrorConstructor\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n callback: RenderableCallback<any>\n}\n\n/**\n * ExceptionHandler — Laravel-inspired exception handling for Stratal.\n *\n * Provides a composable, expressive API for controlling how exceptions are\n * reported (logged / sent to external services) and rendered (turned into\n * HTTP Responses or ErrorResponse objects).\n *\n * **Lifecycle:**\n * 1. The framework resolves this from the DI container (once at init time).\n * 2. `register()` is called to let the user configure reporting / rendering.\n * 3. Module `onException()` hooks contribute additional configuration.\n * 4. On every error, `handle()` runs the pipeline: normalize → report → render → respond.\n *\n * **Usage — extend and override `register()`:**\n *\n * @example\n * ```typescript\n * export class AppExceptionHandler extends ExceptionHandler {\n * register(): void {\n * this.reportable(PaymentError, (e, ctx) => {\n * this.resolve(SentryService).captureException(e)\n * }).stop()\n *\n * this.renderable(MaintenanceError, (e, ctx) => {\n * if (ctx.type === 'http') return ctx.ctx.html('<h1>Maintenance</h1>', 503)\n * })\n *\n * this.dontReport([RouteNotFoundError])\n * this.level(RecordNotFoundError, 'warn')\n * this.context(() => ({ region: 'us-east-1' }))\n * this.respond((res, err) => {\n * res.headers.set('X-Error-Code', String(err.code))\n * return res\n * })\n * }\n * }\n * ```\n */\n@Transient()\nexport abstract class ExceptionHandler {\n private readonly reportables: ReportableEntry[] = []\n private readonly renderables: RenderableEntry[] = []\n private readonly dontReportSet = new Set<ApplicationErrorConstructor>()\n private readonly levelOverrides = new Map<ApplicationErrorConstructor, LogSeverity>()\n private readonly contextCallbacks: ContextCallback[] = []\n private readonly respondCallbacks: RespondCallback[] = []\n private readonly environment: Environment\n\n constructor(\n @inject(LOGGER_TOKENS.LoggerService) protected readonly logger: LoggerService,\n @inject(DI_TOKENS.CloudflareEnv) protected readonly env: StratalEnv,\n @inject(CONTAINER_TOKEN) private readonly container: Container,\n @inject(DI_TOKENS.ExecutionContext) private readonly executionContext: StratalExecutionContext,\n ) {\n this.environment = this.env.ENVIRONMENT as Environment\n }\n\n /**\n * Configure exception reporting and rendering.\n *\n * Override this method in your handler class to register custom\n * `reportable()`, `renderable()`, `dontReport()`, `level()`,\n * `context()`, and `respond()` callbacks.\n */\n abstract register(): void\n\n // ── Public Configuration API ──────────────────────────────────────\n\n /**\n * Register a custom reporting callback for a specific exception type.\n *\n * The callback is invoked when an error matching `errorClass` (via `instanceof`)\n * is thrown. Chain `.stop()` to prevent the default logger from also reporting.\n *\n * @typeParam T - The exception type to match\n * @param errorClass - Constructor of the exception to match\n * @param callback - Reporting function receiving the typed error and context\n * @returns A {@link Reportable} with a `stop()` method\n *\n * @example\n * ```typescript\n * this.reportable(PaymentError, (e, ctx) => {\n * sentry.captureException(e)\n * }).stop() // skip default logging\n * ```\n */\n reportable<T extends ApplicationError>(\n errorClass: ApplicationErrorConstructor<T>,\n callback: ReportableCallback<T>,\n ): Reportable {\n const entry: ReportableEntry = { errorClass, callback, shouldStop: false }\n this.reportables.push(entry)\n return {\n stop: () => { entry.shouldStop = true },\n }\n }\n\n /**\n * Register a custom rendering callback for a specific exception type.\n *\n * The callback should return a `Response` (for HTTP contexts), an `ErrorResponse`,\n * or `undefined` to fall through to the default renderer.\n *\n * @typeParam T - The exception type to match\n * @param errorClass - Constructor of the exception to match\n * @param callback - Rendering function receiving the typed error and context\n *\n * @example\n * ```typescript\n * this.renderable(MaintenanceError, (e, ctx) => {\n * if (ctx.type === 'http') {\n * return ctx.ctx.html('<h1>Down for maintenance</h1>', 503)\n * }\n * })\n * ```\n */\n renderable<T extends ApplicationError>(\n errorClass: ApplicationErrorConstructor<T>,\n callback: RenderableCallback<T>,\n ): void {\n this.renderables.push({ errorClass, callback })\n }\n\n /**\n * Suppress reporting (logging) for the given exception types.\n *\n * Errors matching these classes will still be rendered into responses\n * but will not be logged or sent to external reporters.\n *\n * @param errorClasses - Array of exception constructors to suppress\n *\n * @example\n * ```typescript\n * this.dontReport([RouteNotFoundError, SchemaValidationError])\n * ```\n */\n dontReport(errorClasses: ApplicationErrorConstructor[]): void {\n for (const cls of errorClasses) {\n this.dontReportSet.add(cls)\n }\n }\n\n /**\n * Override the log severity for a specific exception type.\n *\n * By default, severity is derived from the error code range.\n * Use this to promote or demote specific errors.\n *\n * @param errorClass - Constructor of the exception to override\n * @param severity - The log severity to use\n *\n * @example\n * ```typescript\n * this.level(RecordNotFoundError, 'warn')\n * ```\n */\n level(errorClass: ApplicationErrorConstructor, severity: LogSeverity): void {\n this.levelOverrides.set(errorClass, severity)\n }\n\n /**\n * Add global context data to all exception log entries.\n *\n * The callback is invoked on every reported error and its return value\n * is merged into the log data.\n *\n * @param callback - Function returning key-value pairs to include in logs\n *\n * @example\n * ```typescript\n * this.context(() => ({\n * appVersion: '1.2.3',\n * region: env.CF_REGION,\n * }))\n * ```\n */\n context(callback: ContextCallback): void {\n this.contextCallbacks.push(callback)\n }\n\n /**\n * Register a callback to post-process every error Response before it is returned.\n *\n * Use this to add headers, modify the body, change content type, or\n * transform the response in any way.\n *\n * @param callback - Function receiving (response, error, context) and returning a Response\n *\n * @example\n * ```typescript\n * this.respond((response, error, ctx) => {\n * response.headers.set('X-Error-Code', String(error.code))\n * return response\n * })\n * ```\n */\n respond(callback: RespondCallback): void {\n this.respondCallbacks.push(callback)\n }\n\n /**\n * Resolve a service from the DI container.\n *\n * Useful inside `register()` callbacks for accessing injected services\n * (e.g., Sentry, analytics, custom loggers).\n *\n * @typeParam T - The type of the service to resolve\n * @param token - DI token (symbol or constructor)\n * @returns The resolved service instance\n *\n * @example\n * ```typescript\n * this.reportable(CriticalError, (e) => {\n * this.resolve(SentryService).captureException(e)\n * })\n * ```\n */\n resolve<T>(token: symbol | (new (...args: unknown[]) => T)): T {\n return this.container.resolve<T>(token)\n }\n\n // ── Pipeline Entry Point ──────────────────────────────────────────\n\n /**\n * Handle an error through the full exception pipeline.\n *\n * This is the single entry point used by all contexts (HTTP, queue, cron, CLI).\n * It normalizes the error, reports it (non-blocking via `waitUntil`),\n * renders it into a Response, and applies post-processing.\n *\n * @param error - The thrown error (may or may not be an ApplicationError)\n * @param context - The execution context where the error occurred\n * @returns A Response (JSON by default, customizable via renderable/respond)\n */\n async handle(error: unknown, context: ExceptionContext): Promise<Response> {\n const appError = this.normalizeError(error)\n\n // Report via waitUntil — non-blocking in all CF Workers contexts\n this.executionContext.waitUntil(this.performReport(appError, context))\n\n // Render into a Response\n const response = await this.performRender(appError, context)\n\n // Post-process\n return this.applyRespondCallbacks(response, appError, context)\n }\n\n // ── Internals ─────────────────────────────────────────────────────\n\n /**\n * Normalize an unknown error into an ApplicationError.\n * Non-ApplicationError values are wrapped in InternalError.\n */\n private normalizeError(error: unknown): ApplicationError {\n if (isApplicationError(error)) {\n return error\n }\n\n const originalMessage = error instanceof Error ? error.message : String(error)\n const internalError = new InternalError({\n originalError: originalMessage,\n stack: error instanceof Error ? error.stack : undefined,\n })\n\n // In development, preserve the original error message and stack\n // so the dev error overlay shows what actually went wrong\n if (this.environment === 'development') {\n internalError.message = originalMessage\n if (error instanceof Error && error.stack) {\n internalError.stack = error.stack\n }\n }\n\n return internalError\n }\n\n /**\n * Run the reporting pipeline for an error.\n */\n private async performReport(error: ApplicationError, context: ExceptionContext): Promise<void> {\n // 1. Self-report\n if (typeof error.report === 'function') {\n const result = error.report()\n // void (undefined) = skip default; false = also run default\n if (result !== false) return\n }\n\n // 2. Check dontReport\n if (this.shouldNotReport(error)) return\n\n // 3. Registered reportable callbacks (most-specific wins)\n const entry = this.findReportable(error)\n if (entry) {\n await entry.callback(error, context)\n if (entry.shouldStop) return\n }\n\n // 4. Default reporting\n this.defaultReport(error, context)\n }\n\n /**\n * Run the rendering pipeline for an error, producing a Response.\n */\n private async performRender(error: ApplicationError, context: ExceptionContext): Promise<Response> {\n // 1. Self-render\n if (typeof error.render === 'function') {\n const result = error.render(context)\n if (result !== undefined) {\n return this.toResponse(result, error)\n }\n }\n\n // 2. Registered renderable callbacks (most-specific wins)\n const entry = this.findRenderable(error)\n if (entry) {\n const result = entry.callback(error, context)\n if (result !== undefined) {\n return this.toResponse(await result, error)\n }\n }\n\n // 3. Default rendering (content-negotiated)\n return this.defaultRender(error, context)\n }\n\n /**\n * Apply all respond() callbacks to post-process a Response.\n */\n private applyRespondCallbacks(\n response: Response,\n error: ApplicationError,\n context: ExceptionContext,\n ): Response {\n let result = response\n for (const callback of this.respondCallbacks) {\n result = callback(result, error, context)\n }\n return result\n }\n\n /**\n * Check if an error is in the dontReport set.\n */\n private shouldNotReport(error: ApplicationError): boolean {\n for (const cls of this.dontReportSet) {\n if (error instanceof cls) return true\n }\n return false\n }\n\n /**\n * Find the most-specific reportable entry for an error.\n * Walks entries in registration order; picks the most-specific `instanceof` match.\n */\n private findReportable(error: ApplicationError): ReportableEntry | undefined {\n let best: ReportableEntry | undefined\n for (const entry of this.reportables) {\n if (error instanceof entry.errorClass) {\n // More specific class wins (subclass check)\n if (!best || !(error instanceof best.errorClass) || entry.errorClass.prototype instanceof best.errorClass) {\n best = entry\n }\n }\n }\n return best\n }\n\n /**\n * Find the most-specific renderable entry for an error.\n */\n private findRenderable(error: ApplicationError): RenderableEntry | undefined {\n let best: RenderableEntry | undefined\n for (const entry of this.renderables) {\n if (error instanceof entry.errorClass) {\n if (!best || !(error instanceof best.errorClass) || entry.errorClass.prototype instanceof best.errorClass) {\n best = entry\n }\n }\n }\n return best\n }\n\n /**\n * Default reporting — log with appropriate severity and i18n translation.\n */\n private defaultReport(error: ApplicationError, context: ExceptionContext): void {\n const translatedMessage = this.translateError(error, context)\n const severity = this.resolveSeverity(error)\n\n const globalContext = this.gatherContext()\n\n const logData = {\n code: error.code,\n message: translatedMessage,\n timestamp: error.timestamp,\n metadata: error.metadata,\n name: error.name,\n ...globalContext,\n }\n\n switch (severity) {\n case 'debug':\n this.logger.debug('[ApplicationError]', logData)\n break\n case 'info':\n this.logger.info('[ApplicationError]', logData)\n break\n case 'warn':\n this.logger.warn('[ApplicationError]', logData)\n break\n case 'error':\n this.logger.error('[ApplicationError]', logData)\n break\n }\n }\n\n /**\n * Default rendering — content-negotiated.\n *\n * For HTTP requests that accept HTML: renders a minimal branded HTML page.\n * For everything else (API, queue, cron, CLI): returns JSON.\n *\n * Errors are always logged via `performReport` (non-blocking waitUntil),\n * so they appear in the console regardless of the rendered response format.\n */\n private defaultRender(error: ApplicationError, context: ExceptionContext): Response {\n const translatedMessage = this.translateError(error, context)\n const errorResponse = error.toErrorResponse(this.environment, translatedMessage)\n const status = resolveHttpStatus(error)\n\n if (context.type === 'http' && this.wantsHtml(context)) {\n return this.renderDefaultHtml(errorResponse, status)\n }\n\n return Response.json(errorResponse, { status })\n }\n\n // ── Content Negotiation ──────────────────────────────────────────\n\n /**\n * Check if the HTTP request prefers an HTML response.\n *\n * Uses the `Accept` header to determine format. Inertia v3 XHR requests\n * send `Accept: text/html, application/xhtml+xml`, so they naturally\n * receive HTML error pages (displayed in Inertia's error modal in dev).\n *\n * Override in a subclass to customize content negotiation logic.\n */\n protected wantsHtml(context: HttpExceptionContext): boolean {\n const accept = context.ctx.c.req.header('accept') ?? ''\n return accept.includes('text/html')\n }\n\n /**\n * Minimal production HTML error page with inline styles.\n */\n private renderDefaultHtml(\n errorResponse: ErrorResponse,\n status: ContentfulStatusCode,\n ): Response {\n const title = this.escapeHtml(errorResponse.message)\n const html = `<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>${status} - ${title}</title>\n<style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui,-apple-system,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f8fafc;color:#334155}.container{text-align:center;padding:2rem}.status{font-size:6rem;font-weight:800;color:#13c397;line-height:1}.message{font-size:1.25rem;color:#64748b;margin-top:1rem}</style>\n</head><body><div class=\"container\"><div class=\"status\">${status}</div><div class=\"message\">${title}</div></div></body></html>`\n return new Response(html, {\n status,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n })\n }\n\n private escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n }\n\n /**\n * Convert a render result (Response or ErrorResponse) into a Response.\n */\n private toResponse(result: Response | ErrorResponse, error: ApplicationError): Response {\n if (result instanceof Response) return result\n const status = resolveHttpStatus(error)\n return Response.json(result, { status })\n }\n\n /**\n * Translate an error's message key via i18n.\n * Uses the request container (from HTTP context) for correct locale,\n * falling back to the global container or raw message string.\n */\n private translateError(error: ApplicationError, context: ExceptionContext): string {\n try {\n const resolveContainer = context.type === 'http'\n ? context.ctx.getContainer()\n : this.container\n const i18n = resolveContainer.resolve<II18nService>(I18N_TOKENS.I18nService)\n const params = error.metadata as Record<string, string | number> | undefined\n return i18n.t(error.message as MessageKeys, params)\n } catch {\n // I18n unavailable (startup/RPC context) — return raw message key\n return error.message\n }\n }\n\n /**\n * Resolve the log severity for an error.\n * Checks level overrides first, then falls back to code-range-based severity.\n */\n private resolveSeverity(error: ApplicationError): LogSeverity {\n // Check registered overrides (most-specific class wins)\n let bestClass: ApplicationErrorConstructor | undefined\n let bestSeverity: LogSeverity | undefined\n\n for (const [cls, severity] of this.levelOverrides) {\n if (error instanceof cls) {\n if (!bestClass || cls.prototype instanceof bestClass) {\n bestClass = cls\n bestSeverity = severity\n }\n }\n }\n\n return bestSeverity ?? this.getDefaultSeverity(error.code)\n }\n\n /**\n * Determine default log severity based on error code range.\n */\n private getDefaultSeverity(code: number): LogSeverity {\n if (code >= 9000) return 'error'\n if (code >= 2000 && code < 3000) return 'error'\n if (code >= 5000 && code < 6000) return 'warn'\n if (code >= 1000 && code < 2000) return 'info'\n if (code >= 3000 && code < 5000) return 'warn'\n return 'error'\n }\n\n /**\n * Gather all global context data from registered callbacks.\n */\n private gatherContext(): Record<string, unknown> {\n if (this.contextCallbacks.length === 0) return {}\n const merged: Record<string, unknown> = {}\n for (const callback of this.contextCallbacks) {\n Object.assign(merged, callback())\n }\n return merged\n }\n}\n","import { Transient } from '../di/decorators';\nimport { ExceptionHandler } from './exception-handler';\n\n/**\n * DefaultExceptionHandler — the built-in exception handler used when no\n * custom handler is provided via `ApplicationConfig.exceptionHandler`.\n *\n * Has an empty `register()` method, so all exceptions flow through the\n * default pipeline: severity-based logging, i18n translation, and JSON\n * error response serialization.\n *\n * To customize exception handling, extend {@link ExceptionHandler} and\n * override `register()`, then pass your class to the Stratal config:\n *\n * @example\n * ```typescript\n * new Stratal({\n * module: AppModule,\n * exceptionHandler: AppExceptionHandler,\n * })\n * ```\n */\n@Transient()\nexport class DefaultExceptionHandler extends ExceptionHandler {\n register(): void {\n // No custom configuration — uses all defaults\n }\n}\n","export type Environment = 'development' | 'staging' | 'production'\n\nexport interface ErrorResponse {\n /**\n * Numeric error code for identification and escalation\n * See error-codes.ts for the complete registry\n */\n code: number\n\n /**\n * Human-readable error message\n * Fixed per error type, not customizable\n */\n message: string\n\n /**\n * ISO timestamp when the error occurred\n */\n timestamp: string\n\n /**\n * Additional structured data about the error\n * Only included in development environment\n */\n metadata?: Record<string, unknown>\n\n /**\n * Stack trace for debugging\n * Only included in development environment\n */\n stack?: string\n}\n\n/**\n * Type guard to check if an object is an ErrorResponse\n */\nexport function isErrorResponse(obj: unknown): obj is ErrorResponse {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'code' in obj &&\n typeof (obj as ErrorResponse).code === 'number' &&\n 'message' in obj &&\n typeof (obj as ErrorResponse).message === 'string' &&\n 'timestamp' in obj &&\n typeof (obj as ErrorResponse).timestamp === 'string'\n )\n}\n","/**\n * Type-safe context keys for Hono router variables\n * Using symbols to avoid string collisions\n */\nexport const ROUTER_CONTEXT_KEYS = {\n REQUEST_CONTAINER: 'requestContainer',\n LOCALE: 'locale'\n} as const satisfies Record<string, string>\n\n/**\n * Metadata keys for storing route and controller configuration\n * Using symbols to avoid collisions with other decorators\n */\nexport const ROUTE_METADATA_KEYS = {\n CONTROLLER_ROUTE: Symbol.for('stratal:controller:route'),\n CONTROLLER_OPTIONS: Symbol.for('stratal:controller:options'),\n CONTROLLER_MIDDLEWARES: Symbol.for('stratal:controller:middlewares'),\n ROUTE_CONFIG: Symbol.for('stratal:route:config'),\n DECORATED_METHODS: Symbol.for('stratal:decorated:methods'),\n AUTH_GUARD: Symbol.for('stratal:auth:guard'),\n GATEWAY_MARKER: Symbol.for('stratal:gateway:marker'),\n WS_ON_MESSAGE: Symbol.for('stratal:ws:on-message'),\n WS_ON_CLOSE: Symbol.for('stratal:ws:on-close'),\n WS_ON_ERROR: Symbol.for('stratal:ws:on-error'),\n} as const\n\n/**\n * Security scheme identifiers for OpenAPI\n * These reference the security scheme definitions in security.schemas.ts\n */\nexport const SECURITY_SCHEMES = {\n BEARER_AUTH: 'bearerAuth',\n API_KEY: 'apiKey',\n SESSION_COOKIE: 'sessionCookie'\n} as const\n\n/**\n * HTTP method mapping for RESTful controller methods\n * Maps controller method names to HTTP verbs and path patterns\n */\nexport const HTTP_METHODS = {\n index: { method: 'get', path: '' } as const,\n show: { method: 'get', path: '/:id' } as const,\n create: { method: 'post', path: '' } as const,\n update: { method: 'put', path: '/:id' } as const,\n patch: { method: 'patch', path: '/:id' } as const,\n destroy: { method: 'delete', path: '/:id' } as const\n} as const\n\n/**\n * Default success status codes for RESTful controller methods\n * Used by @Route() decorator to auto-derive response status\n */\nexport const METHOD_STATUS_CODES = {\n index: 200,\n show: 200,\n create: 201,\n update: 200,\n patch: 200,\n destroy: 200\n} as const\n\n/**\n * Sentinel symbol to opt a controller out of versioning.\n * When used as the version, no prefix is applied even when defaultVersion is set.\n */\nexport const VERSION_NEUTRAL = Symbol.for('stratal:version:neutral')\n\n/**\n * Default content type for request bodies and responses\n */\nexport const DEFAULT_CONTENT_TYPE = 'application/json'\n","import type { Context } from 'hono'\nimport type { SSEStreamingApi } from 'hono/streaming'\nimport { stream as honoStream, streamSSE as honoStreamSSE, streamText as honoStreamText } from 'hono/streaming'\nimport type { ContentfulStatusCode, RedirectStatusCode } from 'hono/utils/http-status'\nimport type { StreamingApi } from 'hono/utils/stream'\nimport type { Container } from '../di/container'\nimport { RequestContainerNotInitializedError } from '../errors'\nimport { ROUTER_CONTEXT_KEYS } from './constants'\nimport type { RouteName, RouteParams } from './route-map'\nimport { ROUTER_TOKENS } from './router.tokens'\nimport type { RouterEnv } from './types'\nimport type { SignedUriOptions, Uri, UriOptions } from './uri'\n\nexport type ContextQueryResult<R extends Record<string, unknown> | undefined, K extends string | undefined> = K extends string ? string : R extends undefined ? Record<string, unknown> : R\n\n/**\n * Router context wrapper with helper methods\n *\n * Provides convenient access to Hono's context and common request/response operations.\n * The native Hono context is available via the `c` property for advanced use cases.\n *\n * @example\n * ```typescript\n * async index(ctx: RouterContext): Promise<Response> {\n * // Use helper methods\n * const users = await this.service.findAll()\n * return ctx.json(users)\n * }\n *\n * async show(ctx: RouterContext): Promise<Response> {\n * // Access route params\n * const id = ctx.param('id')\n * const user = await this.service.findById(id)\n * return ctx.json(user)\n * }\n *\n * async create(ctx: RouterContext): Promise<Response> {\n * // Parse request body\n * const body = await ctx.body<CreateUserInput>()\n * const user = await this.service.create(body)\n * return ctx.json(user, 201)\n * }\n * ```\n */\nexport class RouterContext<T extends RouterEnv = RouterEnv> {\n /**\n * Native Hono context\n * Access for advanced use cases not covered by helper methods\n */\n constructor(\n public readonly c: Context<T>\n ) { }\n\n /**\n * Get request-scoped DI container\n * Contains request-specific services and context (AuthContext)\n *\n * @throws Error if container not initialized\n */\n getContainer(): Container {\n const container = this.c.get(ROUTER_CONTEXT_KEYS.REQUEST_CONTAINER)\n if (!container) {\n throw new RequestContainerNotInitializedError()\n }\n return container as Container\n }\n\n /**\n * Set locale for the current request\n *\n * @param locale - Locale code (e.g., 'en', 'fr')\n */\n setLocale(locale: string): void {\n this.c.set(ROUTER_CONTEXT_KEYS.LOCALE, locale)\n }\n\n /**\n * Get locale for the current request\n *\n * @returns Current locale code\n */\n getLocale(): string {\n const locale = this.c.get(ROUTER_CONTEXT_KEYS.LOCALE)\n return (locale as string) || 'en'\n }\n\n /**\n * Return JSON response\n *\n * When data is null, automatically returns 204 No Content (configurable via status param).\n *\n * @param data - Data to serialize as JSON, or null for 204\n * @param status - HTTP status code (default: 200, or 204 when data is null)\n */\n json(data: object | null, status?: ContentfulStatusCode): Response {\n if (data === null) {\n return this.c.body(null, status ?? 204)\n }\n return this.c.json(data, status)\n }\n\n /**\n * Get route parameter value\n *\n * @param key - Parameter name (e.g., 'id' for /users/:id)\n */\n param(key: string): string {\n return (this.c.req as unknown as { valid(target: 'param'): Record<string, string> }).valid('param')[key]\n }\n\n /**\n * Get query parameter value\n *\n * @param key - Query parameter name\n */\n query<R extends Record<string, unknown> | undefined = undefined, K extends string | undefined = undefined>(key?: K): ContextQueryResult<R, K> {\n const validated = (this.c.req as unknown as { valid(target: 'query'): Record<string, unknown> }).valid('query')\n return key ? validated[key] as ContextQueryResult<R, K> : validated as ContextQueryResult<R, K>\n }\n\n /**\n * Get request header value\n *\n * @param name - Header name (case-insensitive)\n */\n header(name: string): string | undefined {\n return this.c.req.header(name)\n }\n\n /**\n * Get validated request body from OpenAPI route\n * Returns pre-validated data that has passed schema validation\n *\n * @returns Validated JSON body\n */\n body<T>(): Promise<T> {\n // Type assertion needed because req.valid() is type-safe per route\n // but this is a generic helper method that works across all routes\n return (this.c.req as unknown as { valid(target: 'json'): Promise<T> }).valid('json')\n }\n\n /**\n * Return text response\n *\n * @param text - Text content\n * @param status - HTTP status code (default: 200)\n */\n text(text: string, status?: ContentfulStatusCode): Response {\n return this.c.text(text, status)\n }\n\n /**\n * Return HTML response\n *\n * @param html - HTML content\n * @param status - HTTP status code (default: 200)\n */\n html(html: string, status?: ContentfulStatusCode): Response {\n return this.c.html(html, status)\n }\n\n /**\n * Generate a URL from a named route.\n *\n * Keys matching `:param` placeholders fill the path.\n * Domain params are consumed from the same object.\n * Extra keys become query string parameters.\n *\n * @param name - Named route identifier\n * @param params - Route params + domain params + extra query params\n * @param options - URL generation options (e.g., `{ absolute: true }`)\n *\n * @example\n * ```typescript\n * ctx.route('users.show', { id: '1' }) // '/v1/users/1'\n * ctx.route('users.show', { id: '1', q: 'test' }) // '/v1/users/1?q=test'\n * ```\n */\n route<N extends RouteName>(name: N, params?: RouteParams<N>, options?: UriOptions): string {\n return this.resolveUri().route(name, params, options)\n }\n\n /**\n * Get a domain parameter value from the current request.\n * Domain params are set by the domain matching middleware.\n *\n * @param key - Domain parameter name (e.g., 'tenant' from '{tenant}.myapp.com')\n *\n * @example\n * ```typescript\n * const tenant = ctx.domain('tenant')\n * ```\n */\n domain(key: string): string {\n return this.c.get(`domain:${key}`) as string\n }\n\n /**\n * Generate a signed URL from a named route.\n *\n * @param name - Named route identifier\n * @param params - Route params (same as route())\n * @param options - Signing options (e.g., expiresIn) and URL options\n * @returns Signed URL string with signature query param\n */\n async signedUrl<N extends RouteName>(name: N, params?: RouteParams<N>, options?: SignedUriOptions): Promise<string> {\n return this.resolveUri().signedRoute(name, params, options)\n }\n\n /**\n * Check if the current request has a valid signature.\n *\n * @returns true if the URL signature is valid and not expired\n */\n async hasValidSignature(): Promise<boolean> {\n return this.resolveUri().hasValidSignature()\n }\n\n /**\n * Redirect to another URL\n *\n * @param url - Target URL\n * @param status - HTTP status code (default: 302)\n */\n redirect(url: string, status?: RedirectStatusCode): Response {\n return this.c.redirect(url, status)\n }\n\n /**\n * Return a streaming response (binary/generic)\n *\n * @param callback - Async function that writes to the stream\n * @param onError - Optional error handler called if an error occurs during streaming\n */\n stream(callback: (stream: StreamingApi) => Promise<void>, onError?: (err: Error, stream: StreamingApi) => Promise<void>): Response {\n return honoStream(this.c, callback, onError)\n }\n\n /**\n * Return a streaming text response\n *\n * Automatically sets `Content-Encoding: Identity` for Cloudflare Workers compatibility.\n *\n * @param callback - Async function that writes text to the stream\n * @param onError - Optional error handler called if an error occurs during streaming\n */\n streamText(callback: (stream: StreamingApi) => Promise<void>, onError?: (err: Error, stream: StreamingApi) => Promise<void>): Response {\n this.c.header('Content-Encoding', 'Identity')\n return honoStreamText(this.c, callback, onError)\n }\n\n /**\n * Return a Server-Sent Events (SSE) streaming response\n *\n * Automatically sets `Content-Encoding: Identity` for Cloudflare Workers compatibility.\n *\n * @param callback - Async function that writes SSE events to the stream\n * @param onError - Optional error handler called if an error occurs during streaming\n */\n streamSSE(callback: (stream: SSEStreamingApi) => Promise<void>, onError?: (err: Error, stream: SSEStreamingApi) => Promise<void>): Response {\n this.c.header('Content-Encoding', 'Identity')\n return honoStreamSSE(this.c, callback, onError)\n }\n\n private resolveUri(): Uri {\n return this.getContainer().resolve<Uri>(ROUTER_TOKENS.Uri)\n }\n}\n","import type { Context } from 'hono'\nimport { RouterContext } from '../router/router-context'\nimport type { RouterEnv } from '../router/types'\n\n/**\n * Exception context for errors occurring during HTTP request handling.\n *\n * Provides access to the full {@link RouterContext} for building responses\n * with `ctx.json()`, `ctx.text()`, `ctx.html()`, etc.\n */\nexport interface HttpExceptionContext {\n readonly type: 'http'\n /** Stratal RouterContext — use for building HTTP responses */\n readonly ctx: RouterContext\n}\n\n/**\n * Exception context for errors occurring during queue message processing.\n */\nexport interface QueueExceptionContext {\n readonly type: 'queue'\n /** Name of the queue being processed */\n readonly queueName: string\n}\n\n/**\n * Exception context for errors occurring during scheduled cron execution.\n */\nexport interface CronExceptionContext {\n readonly type: 'cron'\n}\n\n/**\n * Exception context for errors occurring during CLI command execution.\n */\nexport interface CliExceptionContext {\n readonly type: 'cli'\n /** Name of the command that threw */\n readonly commandName: string\n}\n\n/**\n * Discriminated union of all exception context types.\n *\n * Narrow via `ctx.type` to access context-specific properties:\n *\n * @example\n * ```typescript\n * handler.renderable(MyError, (error, ctx) => {\n * if (ctx.type === 'http') {\n * return ctx.ctx.json({ message: 'Something went wrong' }, 500)\n * }\n * // Non-HTTP contexts: return undefined to use default rendering\n * })\n * ```\n */\nexport type ExceptionContext =\n | HttpExceptionContext\n | QueueExceptionContext\n | CronExceptionContext\n | CliExceptionContext\n\n/**\n * Create an HTTP exception context from a Hono context.\n *\n * @param c - The raw Hono context from the request\n * @returns An {@link HttpExceptionContext} wrapping a RouterContext\n */\nexport function createHttpExceptionContext(c: Context<RouterEnv>): HttpExceptionContext {\n return { type: 'http', ctx: new RouterContext(c) }\n}\n\n/**\n * Create a queue exception context.\n *\n * @param queueName - The name of the queue being processed\n * @returns A {@link QueueExceptionContext}\n */\nexport function createQueueExceptionContext(queueName: string): QueueExceptionContext {\n return { type: 'queue', queueName }\n}\n\n/**\n * Create a cron exception context.\n *\n * @returns A {@link CronExceptionContext}\n */\nexport function createCronExceptionContext(): CronExceptionContext {\n return { type: 'cron' }\n}\n\n/**\n * Create a CLI command exception context.\n *\n * @param commandName - The name of the command that threw\n * @returns A {@link CliExceptionContext}\n */\nexport function createCliExceptionContext(commandName: string): CliExceptionContext {\n return { type: 'cli', commandName }\n}\n","import { ApplicationError } from './application-error'\nimport { ERROR_CODES } from './error-codes'\n\n/**\n * RequestContainerNotInitializedError\n *\n * Thrown when attempting to access the request-scoped container before it has been initialized.\n * This typically indicates that the RouterService middleware hasn't run yet,\n * or the router context is being accessed outside of a request lifecycle.\n */\nexport class RequestContainerNotInitializedError extends ApplicationError {\n constructor() {\n super(\n 'errors.requestContainerNotInitialized',\n ERROR_CODES.SYSTEM.REQUEST_CONTAINER_NOT_INITIALIZED\n )\n }\n}\n","import { ApplicationError } from './application-error'\nimport { ERROR_CODES } from './error-codes'\n\n/**\n * StratalNotInitializedError\n *\n * Thrown when attempting to resolve the Application instance before Stratal has been instantiated.\n * This typically indicates that the Stratal instance is not exported as the default export.\n */\nexport class StratalNotInitializedError extends ApplicationError {\n constructor() {\n super(\n 'errors.stratalNotInitialized',\n ERROR_CODES.SYSTEM.INFRASTRUCTURE_ERROR\n )\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { H as ApplicationError, k as ERROR_CODES } from "./errors--RBIvDXr.mjs";
|
|
2
2
|
//#region src/storage/errors/disk-not-configured.error.ts
|
|
3
3
|
var DiskNotConfiguredError = class extends ApplicationError {
|
|
4
4
|
constructor(disk) {
|
|
@@ -64,4 +64,4 @@ var StorageResponseBodyMissingError = class extends ApplicationError {
|
|
|
64
64
|
//#endregion
|
|
65
65
|
export { InvalidDiskError as a, DiskNotConfiguredError as c, InvalidFileTypeError as i, StorageProviderNotSupportedError as n, FileTooLargeError as o, PresignedUrlInvalidExpiryError as r, FileNotFoundError as s, StorageResponseBodyMissingError as t };
|
|
66
66
|
|
|
67
|
-
//# sourceMappingURL=errors-
|
|
67
|
+
//# sourceMappingURL=errors-B7hCnXgB.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors-
|
|
1
|
+
{"version":3,"file":"errors-B7hCnXgB.mjs","names":[],"sources":["../src/storage/errors/disk-not-configured.error.ts","../src/storage/errors/file-not-found.error.ts","../src/storage/errors/file-too-large.error.ts","../src/storage/errors/invalid-disk.error.ts","../src/storage/errors/invalid-file-type.error.ts","../src/storage/errors/presigned-url-invalid-expiry.error.ts","../src/storage/errors/storage-provider-not-supported.error.ts","../src/storage/errors/storage-response-body-missing.error.ts"],"sourcesContent":["import { ApplicationError, ERROR_CODES } from '../../errors'\n\nexport class DiskNotConfiguredError extends ApplicationError {\n constructor(disk: string) {\n super('errors.storage.diskNotConfigured', ERROR_CODES.SYSTEM.CONFIGURATION_ERROR, { disk })\n }\n}\n","import { ERROR_CODES } from '../../errors'\nimport { ApplicationError } from '../../errors'\n\nexport class FileNotFoundError extends ApplicationError {\n constructor(path: string) {\n super('errors.storage.fileNotFound', ERROR_CODES.RESOURCE.NOT_FOUND, { path })\n }\n}\n","import { ERROR_CODES } from '../../errors'\nimport { ApplicationError } from '../../errors'\n\nexport class FileTooLargeError extends ApplicationError {\n constructor(size: number, maxSize: number) {\n super('errors.storage.fileTooLarge', ERROR_CODES.VALIDATION.INVALID_FORMAT, {\n size,\n maxSize,\n })\n }\n}\n","import { ERROR_CODES } from '../../errors'\nimport { ApplicationError } from '../../errors'\n\nexport class InvalidDiskError extends ApplicationError {\n constructor(disk: string) {\n super('errors.storage.invalidDisk', ERROR_CODES.SYSTEM.CONFIGURATION_ERROR, { disk })\n }\n}\n","import { ERROR_CODES } from '../../errors'\nimport { ApplicationError } from '../../errors'\n\nexport class InvalidFileTypeError extends ApplicationError {\n constructor(mimeType: string) {\n super('errors.storage.invalidFileType', ERROR_CODES.VALIDATION.INVALID_FORMAT, {\n mimeType,\n })\n }\n}\n","import { ERROR_CODES } from '../../errors'\nimport { ApplicationError } from '../../errors'\n\nexport class PresignedUrlInvalidExpiryError extends ApplicationError {\n constructor(expiresIn: number, min: number, max: number) {\n super('errors.storage.presignedUrlInvalidExpiry', ERROR_CODES.VALIDATION.INVALID_FORMAT, {\n expiresIn,\n min,\n max,\n })\n }\n}\n","import { ERROR_CODES } from '../../errors'\nimport { ApplicationError } from '../../errors'\n\nexport class StorageProviderNotSupportedError extends ApplicationError {\n constructor(provider: string) {\n super('errors.storage.providerNotSupported', ERROR_CODES.SYSTEM.CONFIGURATION_ERROR, { provider })\n }\n}\n","import { ERROR_CODES } from '../../errors'\nimport { ApplicationError } from '../../errors'\n\nexport class StorageResponseBodyMissingError extends ApplicationError {\n constructor(path: string) {\n super(\n 'errors.storage.responseBodyMissing',\n ERROR_CODES.SYSTEM.INFRASTRUCTURE_ERROR,\n { path }\n )\n }\n}\n"],"mappings":";;AAEA,IAAa,yBAAb,cAA4C,iBAAiB;CAC3D,YAAY,MAAc;AACxB,QAAM,oCAAoC,YAAY,OAAO,qBAAqB,EAAE,MAAM,CAAC;;;;;ACD/F,IAAa,oBAAb,cAAuC,iBAAiB;CACtD,YAAY,MAAc;AACxB,QAAM,+BAA+B,YAAY,SAAS,WAAW,EAAE,MAAM,CAAC;;;;;ACFlF,IAAa,oBAAb,cAAuC,iBAAiB;CACtD,YAAY,MAAc,SAAiB;AACzC,QAAM,+BAA+B,YAAY,WAAW,gBAAgB;GAC1E;GACA;GACD,CAAC;;;;;ACLN,IAAa,mBAAb,cAAsC,iBAAiB;CACrD,YAAY,MAAc;AACxB,QAAM,8BAA8B,YAAY,OAAO,qBAAqB,EAAE,MAAM,CAAC;;;;;ACFzF,IAAa,uBAAb,cAA0C,iBAAiB;CACzD,YAAY,UAAkB;AAC5B,QAAM,kCAAkC,YAAY,WAAW,gBAAgB,EAC7E,UACD,CAAC;;;;;ACJN,IAAa,iCAAb,cAAoD,iBAAiB;CACnE,YAAY,WAAmB,KAAa,KAAa;AACvD,QAAM,4CAA4C,YAAY,WAAW,gBAAgB;GACvF;GACA;GACA;GACD,CAAC;;;;;ACNN,IAAa,mCAAb,cAAsD,iBAAiB;CACrE,YAAY,UAAkB;AAC5B,QAAM,uCAAuC,YAAY,OAAO,qBAAqB,EAAE,UAAU,CAAC;;;;;ACFtG,IAAa,kCAAb,cAAqD,iBAAiB;CACpE,YAAY,MAAc;AACxB,QACE,sCACA,YAAY,OAAO,sBACnB,EAAE,MAAM,CACT"}
|
package/dist/events/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as Constructor } from "../types-
|
|
2
|
-
import { i as LoggerService } from "../index-
|
|
1
|
+
import { t as Constructor } from "../types-CN0zONAZ.mjs";
|
|
2
|
+
import { i as LoggerService } from "../index-BR23zDMy.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/events/constants.d.ts
|
|
5
5
|
/**
|
package/dist/events/index.mjs
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import { a as isListener, i as Listener, n as On, o as LISTENER_METADATA_KEYS, r as getListenerHandlers, t as EventRegistry } from "../events-CvUSgEuN.mjs";
|
|
1
|
+
import { a as isListener, i as Listener, n as On, o as LISTENER_METADATA_KEYS, r as getListenerHandlers, t as EventRegistry } from "../events-UTJliZhl.mjs";
|
|
3
2
|
export { EventRegistry, LISTENER_METADATA_KEYS, Listener, On, getListenerHandlers, isListener };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as __decorate,
|
|
1
|
+
import { a as __decorate, f as DI_TOKENS, o as __decorateParam, p as Transient, s as __decorateMetadata, u as LOGGER_TOKENS } from "./logger-c0ftIK4G.mjs";
|
|
2
2
|
import { inject } from "tsyringe";
|
|
3
3
|
//#region src/events/constants.ts
|
|
4
4
|
/**
|
|
@@ -187,4 +187,4 @@ EventRegistry = __decorate([
|
|
|
187
187
|
//#endregion
|
|
188
188
|
export { isListener as a, Listener as i, On as n, LISTENER_METADATA_KEYS as o, getListenerHandlers as r, EventRegistry as t };
|
|
189
189
|
|
|
190
|
-
//# sourceMappingURL=events-
|
|
190
|
+
//# sourceMappingURL=events-UTJliZhl.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events-CvUSgEuN.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
|
+
{"version":3,"file":"events-UTJliZhl.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,KAAK,CAC5C,QAAO;AAG/B,MAD8B,SAAS,MAAM,MAAM,EAAE,aAAa,MAAM,CAC7C,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,9 +1,67 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
//#region src/websocket/decorators/gateway.decorator.ts
|
|
1
|
+
import { H as ApplicationError, f as ROUTE_METADATA_KEYS, k as ERROR_CODES, s as RouterContext } from "./errors--RBIvDXr.mjs";
|
|
2
|
+
import { p as Transient } from "./logger-c0ftIK4G.mjs";
|
|
3
|
+
//#region src/router/decorators/controller.decorator.ts
|
|
5
4
|
const CONTROLLER_ROUTE_KEY = ROUTE_METADATA_KEYS.CONTROLLER_ROUTE;
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Base controller decorator for route registration
|
|
7
|
+
*
|
|
8
|
+
* This is the core controller decorator that handles:
|
|
9
|
+
* - Transient scope registration (request-scoped)
|
|
10
|
+
* - Route metadata storage
|
|
11
|
+
* - Controller options (tags, security schemes, hideFromDocs)
|
|
12
|
+
*
|
|
13
|
+
* @param route - Base route for this controller (e.g., '/api/v1/users')
|
|
14
|
+
* @param options - Optional configuration (tags, security schemes, hideFromDocs)
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { Controller } from 'stratal/router'
|
|
19
|
+
*
|
|
20
|
+
* @Controller('/api/v1/users', { tags: ['Users'] })
|
|
21
|
+
* export class UsersController implements IController {
|
|
22
|
+
* // All routes accessible
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function Controller(route, options) {
|
|
27
|
+
return function(target) {
|
|
28
|
+
Transient()(target);
|
|
29
|
+
Reflect.defineMetadata(CONTROLLER_ROUTE_KEY, route, target);
|
|
30
|
+
if (options) Reflect.defineMetadata(ROUTE_METADATA_KEYS.CONTROLLER_OPTIONS, options, target);
|
|
31
|
+
return target;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get the route from controller class metadata
|
|
36
|
+
*
|
|
37
|
+
* @param target - Controller class or instance
|
|
38
|
+
* @returns Route string or undefined if not set
|
|
39
|
+
*/
|
|
40
|
+
function getControllerRoute(target) {
|
|
41
|
+
const metadataTarget = typeof target === "function" ? target : target.constructor;
|
|
42
|
+
return Reflect.getMetadata(CONTROLLER_ROUTE_KEY, metadataTarget);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get the options from controller class metadata
|
|
46
|
+
*
|
|
47
|
+
* @param target - Controller class or instance
|
|
48
|
+
* @returns Controller options or undefined if not set
|
|
49
|
+
*/
|
|
50
|
+
function getControllerOptions(target) {
|
|
51
|
+
const metadataTarget = typeof target === "function" ? target : target.constructor;
|
|
52
|
+
return Reflect.getMetadata(ROUTE_METADATA_KEYS.CONTROLLER_OPTIONS, metadataTarget);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get the version from controller class metadata
|
|
56
|
+
*
|
|
57
|
+
* @param target - Controller class or instance
|
|
58
|
+
* @returns Version string, array, VERSION_NEUTRAL symbol, or undefined if not set
|
|
59
|
+
*/
|
|
60
|
+
function getControllerVersion(target) {
|
|
61
|
+
return getControllerOptions(target)?.version;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/websocket/decorators/gateway.decorator.ts
|
|
7
65
|
const GATEWAY_MARKER_KEY = ROUTE_METADATA_KEYS.GATEWAY_MARKER;
|
|
8
66
|
/**
|
|
9
67
|
* Gateway decorator for WebSocket route registration
|
|
@@ -35,10 +93,8 @@ const GATEWAY_MARKER_KEY = ROUTE_METADATA_KEYS.GATEWAY_MARKER;
|
|
|
35
93
|
*/
|
|
36
94
|
function Gateway(route, options) {
|
|
37
95
|
return function(target) {
|
|
38
|
-
|
|
39
|
-
Reflect.defineMetadata(CONTROLLER_ROUTE_KEY, route, target);
|
|
96
|
+
Controller(route, options)(target);
|
|
40
97
|
Reflect.defineMetadata(GATEWAY_MARKER_KEY, true, target);
|
|
41
|
-
if (options) Reflect.defineMetadata(CONTROLLER_OPTIONS_KEY, options, target);
|
|
42
98
|
return target;
|
|
43
99
|
};
|
|
44
100
|
}
|
|
@@ -221,6 +277,6 @@ var GatewayContext = class extends RouterContext {
|
|
|
221
277
|
}
|
|
222
278
|
};
|
|
223
279
|
//#endregion
|
|
224
|
-
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 };
|
|
280
|
+
export { OnMessage as a, getWsOnMessageMethod as c, isGateway as d, Controller as f, getControllerVersion as h, OnError as i, WebSocketDuplicateEventHandlerError as l, getControllerRoute as m, WebSocketBodyNotAvailableError as n, getWsOnCloseMethod as o, getControllerOptions as p, OnClose as r, getWsOnErrorMethod as s, GatewayContext as t, Gateway as u };
|
|
225
281
|
|
|
226
|
-
//# sourceMappingURL=gateway-context-
|
|
282
|
+
//# sourceMappingURL=gateway-context-BdBFoQd8.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-context-BdBFoQd8.mjs","names":[],"sources":["../src/router/decorators/controller.decorator.ts","../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 { Transient } from '../../di/decorators'\nimport { type Constructor } from '../../types'\nimport { ROUTE_METADATA_KEYS } from '../constants'\nimport { type ControllerOptions } from '../types'\n\nconst CONTROLLER_ROUTE_KEY = ROUTE_METADATA_KEYS.CONTROLLER_ROUTE\n\n/**\n * Base controller decorator for route registration\n *\n * This is the core controller decorator that handles:\n * - Transient scope registration (request-scoped)\n * - Route metadata storage\n * - Controller options (tags, security schemes, hideFromDocs)\n *\n * @param route - Base route for this controller (e.g., '/api/v1/users')\n * @param options - Optional configuration (tags, security schemes, hideFromDocs)\n *\n * @example\n * ```typescript\n * import { Controller } from 'stratal/router'\n *\n * @Controller('/api/v1/users', { tags: ['Users'] })\n * export class UsersController implements IController {\n * // All routes accessible\n * }\n * ```\n */\nexport function Controller(route: string, options?: ControllerOptions) {\n return function <T extends Constructor>(target: T) {\n // Wrap @Transient (handles @injectable and scope metadata)\n Transient()(target)\n\n // Store route metadata on the class\n Reflect.defineMetadata(CONTROLLER_ROUTE_KEY, route, target)\n\n // Store options metadata if provided\n if (options) {\n Reflect.defineMetadata(ROUTE_METADATA_KEYS.CONTROLLER_OPTIONS, options, target)\n }\n\n return target\n }\n}\n\n/**\n * Get the route from controller class metadata\n *\n * @param target - Controller class or instance\n * @returns Route string or undefined if not set\n */\nexport function getControllerRoute(target: object): string | undefined {\n // Check if target is a class constructor (function) or an instance\n // If class, get metadata from it directly; if instance, get from constructor\n const metadataTarget = typeof target === 'function' ? target : (target as { constructor: object }).constructor\n return Reflect.getMetadata(CONTROLLER_ROUTE_KEY, metadataTarget) as string | undefined\n}\n\n/**\n * Get the options from controller class metadata\n *\n * @param target - Controller class or instance\n * @returns Controller options or undefined if not set\n */\nexport function getControllerOptions(target: object): ControllerOptions | undefined {\n const metadataTarget = typeof target === 'function' ? target : (target as { constructor: object }).constructor\n return Reflect.getMetadata(ROUTE_METADATA_KEYS.CONTROLLER_OPTIONS, metadataTarget) as ControllerOptions | undefined\n}\n\n/**\n * Get the version from controller class metadata\n *\n * @param target - Controller class or instance\n * @returns Version string, array, VERSION_NEUTRAL symbol, or undefined if not set\n */\nexport function getControllerVersion(target: object): ControllerOptions['version'] {\n const options = getControllerOptions(target)\n return options?.version\n}\n","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 from the raw request (no OpenAPI validation)\n *\n * @param key - Parameter name (e.g., 'id' for /ws/chat/:id)\n */\n override param(key: string): string {\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":"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"}
|
package/dist/guards/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as Constructor } from "../types-
|
|
3
|
-
import { i as LoggerService } from "../index-
|
|
1
|
+
import { Cr as Container, V as RouterContext } from "../index-BrmS34sa.mjs";
|
|
2
|
+
import { t as Constructor } from "../types-CN0zONAZ.mjs";
|
|
3
|
+
import { i as LoggerService } from "../index-BR23zDMy.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/guards/types.d.ts
|
|
6
6
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/guards/types.ts","../../src/guards/use-guards.decorator.ts","../../src/guards/guard-execution.service.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/guards/types.ts","../../src/guards/use-guards.decorator.ts","../../src/guards/guard-execution.service.ts"],"mappings":";;;;;;;;AAqBA;;;;;;;;;;AAaA;;;;;UAbiB,WAAA;EAmBA;;;;AAKjB;;EAjBE,WAAA,CAAY,OAAA,EAAS,aAAA,aAA0B,OAAA;AAAA;;AA6BjD;;KAvBY,UAAA,GAAa,WAAA,CAAY,WAAA;;;AA8BrC;;KAxBY,KAAA,GAAQ,UAAA,GAAa,WAAA;;;;UAKhB,gBAAA;;ACYjB;;;;EDNE,MAAA;AAAA;;;;UAMe,aAAA;EACf,MAAA,EAAQ,KAAA;AAAA;;;ACmBV;cDba,kBAAA;;;;;;;AA3Cb;;;;;;;;;;AAaA;;;;;AAMA;;;;;AAKA;;;;;AAYA;;;;;AAOA;;;;;;;;ACPA;;;;;;;;;;;;;iBAAgB,SAAA,CAAA,GAAa,MAAA,EAAQ,KAAA,KAAU,cAAA,GAAiB,eAAA;AAoBhE;;;;;AAWA;AAXA,iBAAgB,mBAAA,CAAoB,MAAA,WAAiB,aAAA;;;;;;;;iBAWrC,eAAA,CAAgB,MAAA,UAAgB,WAAA,oBAA+B,aAAA;;;;ADnE/E;;;;;cEVa,qBAAA;EAAA,iBACkB,MAAA;cAAA,MAAA,EAAQ,aAAA;EFgBiB;;AAMxD;;;;;AAMA;;EEjBQ,aAAA,CACJ,MAAA,EAAQ,KAAA,IACR,OAAA,EAAS,aAAA,EACT,SAAA,EAAW,SAAA,GACV,OAAA;EFae;;AAKpB;;;;;EALoB,QE2BV,YAAA;EFVoB;;;EAAA,QEuBpB,eAAA;AAAA"}
|
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-MtDgcHnF.mjs";
|
|
2
2
|
export { GUARD_METADATA_KEY, GuardExecutionService, UseGuards, getControllerGuards, getMethodGuards };
|
|
@@ -148,4 +148,4 @@ var GuardExecutionService = class {
|
|
|
148
148
|
//#endregion
|
|
149
149
|
export { GUARD_METADATA_KEY as a, getMethodGuards as i, UseGuards as n, getControllerGuards as r, GuardExecutionService as t };
|
|
150
150
|
|
|
151
|
-
//# sourceMappingURL=guards-
|
|
151
|
+
//# sourceMappingURL=guards-MtDgcHnF.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guards-
|
|
1
|
+
{"version":3,"file":"guards-MtDgcHnF.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 (scopes) for authorization\n * If provided, permission check is performed after authentication.\n * If omitted, only authentication is required.\n */\n scopes?: 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":";;;;AAgEA,MAAa,qBAAqB,OAAO,IAAI,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACP9D,SAAgB,UAAU,GAAG,QAAmD;AAC9E,SAAQ,QAAgB,gBAAkC;EACxD,MAAM,WAA0B,EAAE,QAAQ;AAE1C,MAAI,gBAAgB,KAAA,EAElB,SAAQ,eAAe,oBAAoB,UAAU,QAAQ,YAAY;MAGzE,SAAQ,eAAe,oBAAoB,UAAU,OAAO;;;;;;;;;AAWlE,SAAgB,oBAAoB,QAA2C;AAC7E,QAAO,QAAQ,YAAY,oBAAoB,OAAO;;;;;;;;;AAUxD,SAAgB,gBAAgB,QAAgB,aAAyD;AACvG,QAAO,QAAQ,YAAY,oBAAoB,QAAQ,YAAY;;;;;;;;;;AC9ErE,IAAa,wBAAb,MAAmC;CACjC,YAAY,QAAwC;AAAvB,OAAA,SAAA;;;;;;;;;;;CAW7B,MAAM,cACJ,QACA,SACA,WACkB;AAClB,MAAI,OAAO,WAAW,EACpB,QAAO;AAGT,OAAK,OAAO,MAAM,oBAAoB;GACpC,YAAY,OAAO;GACnB,MAAM,QAAQ,EAAE,IAAI;GACpB,QAAQ,QAAQ,EAAE,IAAI;GACvB,CAAC;AAEF,OAAK,MAAM,SAAS,OAIlB,KAAI,CAFgB,MADE,KAAK,aAAa,OAAO,UAAU,CACjB,YAAY,QAAQ,EAE1C;AAChB,QAAK,OAAO,MAAM,uBAAuB;IAEvC,OAAO,MAAM,aAAa,QAAQ;IAClC,MAAM,QAAQ,EAAE,IAAI;IACrB,CAAC;AACF,UAAO;;AAIX,OAAK,OAAO,MAAM,qBAAqB;GACrC,YAAY,OAAO;GACnB,MAAM,QAAQ,EAAE,IAAI;GACrB,CAAC;AAEF,SAAO;;;;;;;;;CAUT,aAAqB,OAAc,WAAmC;AAEpE,MAAI,KAAK,gBAAgB,MAAM,CAC7B,QAAO;AAIT,SAAO,UAAU,QAAqB,MAAM;;;;;CAM9C,gBAAwB,OAAoC;AAC1D,SACE,OAAO,UAAU,YAEjB,UAAU,QACV,iBAAiB,SACjB,OAAO,MAAM,gBAAgB"}
|