stratal 0.0.16 → 0.0.17
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/README.md +4 -0
- package/dist/{application-zG8b-pol.d.mts → application-DfPtIzxF.d.mts} +65 -4
- package/dist/application-DfPtIzxF.d.mts.map +1 -0
- package/dist/{base-email.provider-Cuw4OAB0.mjs → base-email.provider-DypUAfWm.mjs} +1 -1
- package/dist/{base-email.provider-Cuw4OAB0.mjs.map → base-email.provider-DypUAfWm.mjs.map} +1 -1
- package/dist/bin/cloudflare-workers-loader.mjs +0 -0
- package/dist/bin/quarry.mjs +1 -50
- package/dist/bin/quarry.mjs.map +1 -1
- package/dist/cache/index.d.mts +1 -1
- package/dist/cache/index.mjs +10 -11
- package/dist/cache/index.mjs.map +1 -1
- package/dist/{colors-DJaRDXoS.mjs → colors-Y7WIFXs7.mjs} +1 -1
- package/dist/{colors-DJaRDXoS.mjs.map → colors-Y7WIFXs7.mjs.map} +1 -1
- package/dist/{command-BvCOD6df.mjs → command-B1CPgsrU.mjs} +6 -3
- package/dist/{command-BvCOD6df.mjs.map → command-B1CPgsrU.mjs.map} +1 -1
- package/dist/{command-B-QH-Vu3.d.mts → command-TnkPYWta.d.mts} +2 -2
- package/dist/{command-B-QH-Vu3.d.mts.map → command-TnkPYWta.d.mts.map} +1 -1
- package/dist/config/index.d.mts +2 -2
- package/dist/config/index.mjs +10 -11
- package/dist/config/index.mjs.map +1 -1
- package/dist/consumer-registry-Bymm6ff4.d.mts +142 -0
- package/dist/consumer-registry-Bymm6ff4.d.mts.map +1 -0
- package/dist/cron/index.d.mts +3 -116
- package/dist/cron/index.d.mts.map +1 -1
- package/dist/cron/index.mjs +3 -4
- package/dist/{cron-manager-DR7fiG6o.mjs → cron-manager-CFBamKKk.mjs} +3 -3
- package/dist/{cron-manager-DR7fiG6o.mjs.map → cron-manager-CFBamKKk.mjs.map} +1 -1
- package/dist/cron-manager-D7imGwUT.d.mts +117 -0
- package/dist/cron-manager-D7imGwUT.d.mts.map +1 -0
- package/dist/di/index.d.mts +1 -1
- package/dist/di/index.mjs +2 -3
- package/dist/email/index.d.mts +3 -3
- package/dist/email/index.mjs +16 -17
- package/dist/email/index.mjs.map +1 -1
- package/dist/errors/index.d.mts +1 -1
- package/dist/errors/index.mjs +2 -3
- package/dist/{errors-CtCi1wn6.mjs → errors-DSKapqD8.mjs} +4 -4
- package/dist/{errors-CtCi1wn6.mjs.map → errors-DSKapqD8.mjs.map} +1 -1
- package/dist/{errors-H3TZnVeX.mjs → errors-DuAR5Wke.mjs} +2 -2
- package/dist/{errors-H3TZnVeX.mjs.map → errors-DuAR5Wke.mjs.map} +1 -1
- package/dist/events/index.mjs +2 -3
- package/dist/{events-CXl-o1Ad.mjs → events-CvUSgEuN.mjs} +2 -3
- package/dist/{events-CXl-o1Ad.mjs.map → events-CvUSgEuN.mjs.map} +1 -1
- package/dist/{gateway-context-BkZ4UKaX.mjs → gateway-context-CNOLkLUC.mjs} +4 -4
- package/dist/{gateway-context-BkZ4UKaX.mjs.map → gateway-context-CNOLkLUC.mjs.map} +1 -1
- package/dist/guards/index.d.mts +1 -1
- package/dist/i18n/index.d.mts +3 -3
- package/dist/i18n/index.mjs +15 -15
- package/dist/i18n/validation/index.d.mts +1 -1
- package/dist/i18n/validation/index.mjs +1 -1
- package/dist/{i18n.module-W8OJxg3d.mjs → i18n.module-Dn9SrFdS.mjs} +210 -160
- package/dist/i18n.module-Dn9SrFdS.mjs.map +1 -0
- package/dist/{index-BJWm863C.d.mts → index-BFCxSp_f.d.mts} +82 -73
- package/dist/index-BFCxSp_f.d.mts.map +1 -0
- package/dist/{index-D9iYu2Yc.d.mts → index-DGRe6Yoa.d.mts} +5 -144
- package/dist/index-DGRe6Yoa.d.mts.map +1 -0
- package/dist/{index-DVhdhLvE.d.mts → index-NGxg-KP_.d.mts} +4 -4
- package/dist/{index-DVhdhLvE.d.mts.map → index-NGxg-KP_.d.mts.map} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +19 -19
- package/dist/{is-command-BfCgWAcQ.mjs → is-command-DJVI6wEJ.mjs} +2 -2
- package/dist/{is-command-BfCgWAcQ.mjs.map → is-command-DJVI6wEJ.mjs.map} +1 -1
- package/dist/{is-seeder-CebjZCDn.mjs → is-seeder-D5MIEcdz.mjs} +1 -1
- package/dist/{is-seeder-CebjZCDn.mjs.map → is-seeder-D5MIEcdz.mjs.map} +1 -1
- package/dist/logger/index.mjs +1 -2
- package/dist/{logger-BR1-s1Um.mjs → logger-CGT91VY6.mjs} +170 -4
- package/dist/logger-CGT91VY6.mjs.map +1 -0
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +4 -5
- package/dist/{middleware-C0Ebzswy.mjs → middleware-Bl-b5pkt.mjs} +3 -3
- package/dist/{middleware-C0Ebzswy.mjs.map → middleware-Bl-b5pkt.mjs.map} +1 -1
- package/dist/module/index.d.mts +2 -117
- package/dist/module/index.d.mts.map +1 -1
- package/dist/module/index.mjs +10 -11
- package/dist/module-registry-CmjBX6ol.d.mts +121 -0
- package/dist/module-registry-CmjBX6ol.d.mts.map +1 -0
- package/dist/{module-BgdxxzBe.mjs → module-tUtyVJ5E.mjs} +9 -8
- package/dist/module-tUtyVJ5E.mjs.map +1 -0
- package/dist/openapi/index.d.mts +54 -54
- package/dist/openapi/index.d.mts.map +1 -1
- package/dist/openapi/index.mjs +15 -15
- package/dist/openapi-tools.service-B3TxYKoQ.mjs +197 -0
- package/dist/openapi-tools.service-B3TxYKoQ.mjs.map +1 -0
- package/dist/openapi.service-DGnX3Fc4.d.mts +58 -0
- package/dist/openapi.service-DGnX3Fc4.d.mts.map +1 -0
- package/dist/quarry/index.d.mts +109 -28
- package/dist/quarry/index.d.mts.map +1 -1
- package/dist/quarry/index.mjs +9 -7
- package/dist/quarry-registry-B2rkO-JS.mjs +683 -0
- package/dist/quarry-registry-B2rkO-JS.mjs.map +1 -0
- package/dist/queue/index.d.mts +2 -1
- package/dist/queue/index.mjs +11 -12
- package/dist/queue/index.mjs.map +1 -1
- package/dist/{queue.module-BZvmeAMj.mjs → queue.module-BtI8f4Jo.mjs} +4 -4
- package/dist/{queue.module-BZvmeAMj.mjs.map → queue.module-BtI8f4Jo.mjs.map} +1 -1
- package/dist/{resend.provider-BCCACQAU.mjs → resend.provider-bXMEkdRJ.mjs} +4 -5
- package/dist/{resend.provider-BCCACQAU.mjs.map → resend.provider-bXMEkdRJ.mjs.map} +1 -1
- package/dist/router/index.d.mts +1 -1
- package/dist/router/index.mjs +14 -14
- package/dist/{router-context-BEJe9HEB.mjs → router-context-D9R1v2Ac.mjs} +7 -4
- package/dist/router-context-D9R1v2Ac.mjs.map +1 -0
- package/dist/{s3-storage.provider-BLlzQYiJ.mjs → s3-storage.provider-CttzNnDR.mjs} +5 -6
- package/dist/{s3-storage.provider-BLlzQYiJ.mjs.map → s3-storage.provider-CttzNnDR.mjs.map} +1 -1
- package/dist/seeder/index.d.mts +3 -3
- package/dist/seeder/index.mjs +6 -7
- package/dist/{seeder-Cupi5jl-.mjs → seeder-R7RXJC35.mjs} +20 -17
- package/dist/seeder-R7RXJC35.mjs.map +1 -0
- package/dist/{smtp.provider-B8XtOcHU.mjs → smtp.provider-DrbHQztF.mjs} +4 -5
- package/dist/{smtp.provider-B8XtOcHU.mjs.map → smtp.provider-DrbHQztF.mjs.map} +1 -1
- package/dist/storage/index.d.mts +2 -195
- package/dist/storage/index.d.mts.map +1 -1
- package/dist/storage/index.mjs +13 -14
- package/dist/storage/providers/index.d.mts +272 -0
- package/dist/storage/providers/index.d.mts.map +1 -0
- package/dist/storage/providers/index.mjs +5 -0
- package/dist/{storage-By_ow2o_.mjs → storage-CZKHOhci.mjs} +7 -7
- package/dist/{storage-By_ow2o_.mjs.map → storage-CZKHOhci.mjs.map} +1 -1
- package/dist/storage-provider.interface-0IqcdhBf.d.mts +197 -0
- package/dist/storage-provider.interface-0IqcdhBf.d.mts.map +1 -0
- package/dist/{stratal-CE0iTz4f.mjs → stratal-D5smIU1y.mjs} +22 -12
- package/dist/stratal-D5smIU1y.mjs.map +1 -0
- package/dist/{usage-generator-C9hWziY4.mjs → usage-generator-CVIsENuE.mjs} +2 -2
- package/dist/{usage-generator-C9hWziY4.mjs.map → usage-generator-CVIsENuE.mjs.map} +1 -1
- package/dist/{validation-Bh875Lyg.mjs → validation-DQTC259A.mjs} +4 -4
- package/dist/{validation-Bh875Lyg.mjs.map → validation-DQTC259A.mjs.map} +1 -1
- package/dist/websocket/index.d.mts +1 -1
- package/dist/websocket/index.mjs +4 -5
- package/dist/workers/index.d.mts +1 -1
- package/dist/workers/index.mjs +19 -19
- package/package.json +13 -8
- package/dist/application-zG8b-pol.d.mts.map +0 -1
- package/dist/decorate-D5j-d9_z.mjs +0 -171
- package/dist/decorate-D5j-d9_z.mjs.map +0 -1
- package/dist/i18n.module-W8OJxg3d.mjs.map +0 -1
- package/dist/index-BJWm863C.d.mts.map +0 -1
- package/dist/index-D9iYu2Yc.d.mts.map +0 -1
- package/dist/logger-BR1-s1Um.mjs.map +0 -1
- package/dist/module-BgdxxzBe.mjs.map +0 -1
- package/dist/quarry-registry-DCwqVcRp.mjs +0 -310
- package/dist/quarry-registry-DCwqVcRp.mjs.map +0 -1
- package/dist/router-context-BEJe9HEB.mjs.map +0 -1
- package/dist/seeder-Cupi5jl-.mjs.map +0 -1
- package/dist/stratal-CE0iTz4f.mjs.map +0 -1
- package/dist/types-CLhOhYsQ.d.mts +0 -64
- package/dist/types-CLhOhYsQ.d.mts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/config/config.tokens.ts","../../src/config/errors/config-module-not-initialized.error.ts","../../src/config/errors/config-not-initialized.error.ts","../../src/config/config.types.ts","../../src/config/services/config.service.ts","../../src/config/config.module.ts","../../src/config/register-as.ts"],"sourcesContent":["export const CONFIG_TOKENS = {\n\tConfigService: Symbol.for('stratal:config:service'),\n} as const\n","import { ApplicationError, ERROR_CODES } from '../../errors'\n\n/**\n * Error thrown when ConfigModule's onInitialize runs but forRoot() was never called.\n *\n * This means the module was imported without calling ConfigModule.forRoot({ load: [...] }).\n */\nexport class ConfigModuleNotInitializedError extends ApplicationError {\n constructor() {\n super(\n 'errors.configModuleNotInitialized',\n ERROR_CODES.SYSTEM.CONFIG_MODULE_NOT_INITIALIZED\n )\n }\n}\n","import { ERROR_CODES } from '../../errors'\nimport { ApplicationError } from '../../errors'\n\n/**\n * ConfigNotInitializedError\n *\n * Thrown when attempting to access ConfigService before it has been initialized.\n * This typically indicates that ConfigModule's onInitialize hook hasn't run yet,\n * or the module wasn't registered properly.\n */\nexport class ConfigNotInitializedError extends ApplicationError {\n constructor() {\n super(\n 'errors.configNotInitialized',\n ERROR_CODES.SYSTEM.CONFIG_NOT_INITIALIZED\n )\n }\n}\n","import type { z } from '../i18n/validation'\n\nexport class ConfigValidationError extends Error {\n constructor(\n message: string,\n public readonly errors: z.ZodError\n ) {\n super(message)\n this.name = 'ConfigValidationError'\n }\n}\n\n/**\n * Configuration that can be augmented by applications\n * Apps should augment this interface with their AppConfig type using module augmentation\n *\n * @example\n * ```typescript\n * // In your app (e.g., apps/backend/src/config/types.ts)\n * declare module 'stratal' {\n * interface ModuleConfig {\n * database: { url: string; maxConnections: number }\n * email: { provider: string; from: { name: string; email: string } }\n * }\n * }\n * ```\n */\nexport interface ModuleConfig { }\n\n/**\n * Generate all valid dot-notation paths from a config object type\n * @example ConfigPath<{ database: { url: string } }> = 'database' | 'database.url'\n */\nexport type ConfigPath<T> = {\n [K in keyof T & string]: T[K] extends Record<string, unknown>\n ? K | `${K}.${ConfigPath<T[K]>}`\n : K\n}[keyof T & string]\n\n/**\n * Get the value type at a dot-notation path\n * @example ConfigPathValue<{ database: { url: string } }, 'database.url'> = string\n */\nexport type ConfigPathValue<T, P extends string> = P extends `${infer K}.${infer Rest}`\n ? K extends keyof T\n ? T[K] extends Record<string, unknown>\n ? ConfigPathValue<T[K], Rest>\n : never\n : never\n : P extends keyof T\n ? T[P]\n : never\n\n/**\n * ConfigService interface with dot notation support\n */\nexport interface IConfigService<T extends object = ModuleConfig> {\n /**\n * Initialize the config service with validated configuration\n * Should be called once during application startup\n */\n initialize(config: T): void\n\n /**\n * Get config value using dot notation\n * @example config.get('database.url')\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P>\n\n /**\n * Set config value at runtime (for runtime overrides)\n * @example config.set('email.from.name', 'Custom Name')\n */\n set<P extends ConfigPath<T>>(path: P, value: ConfigPathValue<T, P>): void\n\n /**\n * Reset config to original value\n * @param path - Optional path to reset (resets entire config if omitted)\n */\n reset(path?: ConfigPath<T>): void\n\n /**\n * Get entire config object\n */\n all(): Readonly<T>\n\n /**\n * Check if a config path exists\n */\n has(path: ConfigPath<T>): boolean\n}\n","import { Transient } from '../../di/decorators'\nimport { CONFIG_TOKENS } from '../config.tokens'\nimport type { ConfigPath, ConfigPathValue, IConfigService, ModuleConfig } from '../config.types'\nimport { ConfigNotInitializedError } from '../errors'\n\n/**\n * ConfigService with dot notation support for get/set operations\n *\n * Supports runtime overrides via set() - useful for request-specific config overrides.\n * Use reset() to restore original values.\n *\n * @example\n * ```typescript\n * // Get with dot notation\n * const url = config.get('database.url')\n * const fromName = config.get('email.from.name')\n *\n * // Set at runtime (e.g., in middleware for runtime override)\n * config.set('email.from.name', 'Custom Name')\n *\n * // Reset to original\n * config.reset('email.from.name') // reset specific path\n * config.reset() // reset entire config\n * ```\n */\n@Transient(CONFIG_TOKENS.ConfigService)\nexport class ConfigService<T extends object = ModuleConfig> implements IConfigService<T> {\n private originalConfig: T | undefined\n private currentConfig: T | undefined\n\n /**\n * Initialize the config service with validated configuration\n * Called by ConfigModule during initialization\n */\n initialize(config: T): void {\n this.originalConfig = this.deepClone(config)\n this.currentConfig = this.deepClone(config)\n }\n\n /**\n * Get config value using dot notation\n * @example config.get('database.url')\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P> {\n this.ensureInitialized()\n return this.getByPath(this.currentConfig, path) as ConfigPathValue<T, P>\n }\n\n /**\n * Set config value at runtime (for runtime overrides)\n * @example config.set('email.from.name', 'Custom Name')\n */\n set<P extends ConfigPath<T>>(path: P, value: ConfigPathValue<T, P>): void {\n this.ensureInitialized()\n this.setByPath(this.currentConfig, path, value)\n }\n\n /**\n * Reset config to original value\n * @param path - Optional path to reset (resets entire config if omitted)\n */\n reset(path?: ConfigPath<T>): void {\n this.ensureInitialized()\n if (path) {\n const originalValue = this.getByPath(this.originalConfig, path)\n this.setByPath(this.currentConfig, path, this.deepClone(originalValue))\n } else {\n this.currentConfig = this.deepClone(this.originalConfig)\n }\n }\n\n /**\n * Get entire config object\n */\n all(): Readonly<T> {\n this.ensureInitialized()\n return this.currentConfig as Readonly<T>\n }\n\n /**\n * Check if a config path exists\n */\n has(path: ConfigPath<T>): boolean {\n this.ensureInitialized()\n return this.getByPath(this.currentConfig, path) !== undefined\n }\n\n private getByPath(obj: unknown, path: string): unknown {\n const keys = path.split('.')\n let current = obj\n for (const key of keys) {\n if (this.isDangerousKey(key)) return undefined\n if (current === null || current === undefined) return undefined\n current = (current as Record<string, unknown>)[key]\n }\n return current\n }\n\n private setByPath(obj: unknown, path: string, value: unknown): void {\n const keys = path.split('.')\n if (keys.some((key) => this.isDangerousKey(key))) return\n let current = obj as Record<string, unknown>\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i]\n if (!Object.hasOwn(current, key) || typeof current[key] !== 'object' || current[key] === null) {\n Object.defineProperty(current, key, {\n value: {},\n writable: true,\n enumerable: true,\n configurable: true,\n })\n }\n current = current[key] as Record<string, unknown>\n }\n Object.defineProperty(current, keys[keys.length - 1], {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n })\n }\n\n private isDangerousKey(key: string): boolean {\n return key === '__proto__' || key === 'constructor' || key === 'prototype'\n }\n\n private ensureInitialized(): void {\n if (!this.currentConfig) {\n throw new ConfigNotInitializedError()\n }\n }\n\n private deepClone<V>(obj: V): V {\n if (obj === null || typeof obj !== 'object') {\n return obj\n }\n return JSON.parse(JSON.stringify(obj)) as V\n }\n}\n","import type { z } from '../i18n/validation'\nimport { ConfigModuleNotInitializedError } from './errors'\nimport { DI_TOKENS } from '../di/tokens'\nimport { Scope } from '../di/types'\nimport { Module } from '../module'\nimport type { DynamicModule, ModuleContext, OnInitialize, Provider } from '../module/types'\nimport { CONFIG_TOKENS } from './config.tokens'\nimport { ConfigValidationError, ModuleConfig } from './config.types'\nimport type { ConfigNamespace } from './register-as'\nimport { ConfigService } from './services/config.service'\n\n/**\n * Any config namespace - uses structural typing for flexibility\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyConfigNamespace = ConfigNamespace<string, any, object>\n\n/**\n * Options for ConfigModule.forRoot\n */\nexport interface ConfigModuleOptions {\n /**\n * Array of config namespaces created via registerAs()\n */\n load: AnyConfigNamespace[]\n\n /**\n * Optional Zod schema for validating the merged config\n * Validates the entire config object after all namespaces are merged\n */\n validateSchema?: z.ZodType<object>\n}\n\n// Store options for use in onInitialize\nlet moduleOptions: ConfigModuleOptions | null = null\n\n/**\n * ConfigModule\n *\n * Provides configuration management with namespace support.\n * Uses registerAs() to create typed config namespaces that can be injected.\n *\n * @example\n * ```typescript\n * // Define config namespaces\n * const databaseConfig = registerAs('database', (env) => ({\n * url: env.DATABASE_URL,\n * maxConnections: 10\n * }))\n *\n * const emailConfig = registerAs('email', (env) => ({\n * provider: env.EMAIL_PROVIDER,\n * from: { name: 'App', email: 'noreply@example.com' }\n * }))\n *\n * // Register in module\n * @Module({\n * imports: [\n * ConfigModule.forRoot({\n * load: [databaseConfig, emailConfig],\n * validateSchema: AppConfigSchema\n * })\n * ]\n * })\n * export class AppModule {}\n *\n * // Inject config\n * constructor(\n * @inject(CONFIG_TOKENS.ConfigService) private config: IConfigService,\n * @inject(databaseConfig.KEY) private dbConfig: DatabaseConfig\n * ) {\n * // Use dot notation\n * const url = this.config.get('database.url')\n *\n * // Or inject namespace directly\n * const url = this.dbConfig.url\n * }\n * ```\n */\n@Module({\n providers: [\n // Register the main ConfigService as Singleton so initialization persists\n {\n provide: CONFIG_TOKENS.ConfigService,\n useClass: ConfigService,\n scope: Scope.Singleton,\n },\n ],\n})\nexport class ConfigModule implements OnInitialize {\n /**\n * Configure ConfigModule with namespace loaders\n *\n * @param options - Configuration options\n * @returns Dynamic module with config infrastructure\n */\n static forRoot(options: ConfigModuleOptions): DynamicModule {\n moduleOptions = options\n\n const providers: Provider[] = []\n\n // Register each namespace config using asProvider()\n for (const namespace of options.load) {\n providers.push(namespace.asProvider())\n }\n\n return {\n module: ConfigModule,\n providers,\n }\n }\n\n /**\n * Initialize config service with merged namespaces\n * Called after all providers are registered\n */\n onInitialize(context: ModuleContext): void {\n if (!moduleOptions) {\n throw new ConfigModuleNotInitializedError()\n }\n\n const env = context.container.resolve<unknown>(DI_TOKENS.CloudflareEnv)\n const configService = context.container.resolve<ConfigService>(CONFIG_TOKENS.ConfigService)\n\n // Build merged config from all namespaces\n const mergedConfig: Record<string, unknown> = {}\n\n for (const namespace of moduleOptions.load) {\n mergedConfig[namespace.namespace] = namespace.factory(env)\n }\n\n // Validate if schema provided\n if (moduleOptions.validateSchema) {\n const result = moduleOptions.validateSchema.safeParse(mergedConfig)\n if (!result.success) {\n throw new ConfigValidationError(\n 'Configuration validation failed',\n result.error\n )\n }\n }\n\n // Initialize ConfigService with merged config\n configService.initialize(mergedConfig as ModuleConfig)\n\n context.logger.debug('ConfigModule initialized', {\n namespaces: moduleOptions.load.map((n) => n.namespace),\n })\n }\n}\n","import type { InjectionToken } from 'tsyringe'\nimport { DI_TOKENS } from '../di/tokens'\nimport type { FactoryProvider } from '../module/types'\n\n/**\n * Configuration namespace registration result\n */\nexport interface ConfigNamespace<TKey extends string, TEnv, TConfig extends object> {\n /** Auto-derived injection token (e.g., 'database' -> Symbol('stratal:config:database')) */\n readonly KEY: InjectionToken<TConfig>\n /** The namespace key */\n readonly namespace: TKey\n /** The factory function that receives env and returns config */\n readonly factory: (env: TEnv) => TConfig\n /**\n * Returns a provider configuration for use in module registration\n * Automatically injects DI_TOKENS.CloudflareEnv\n */\n asProvider(): FactoryProvider<TConfig>\n}\n\n/**\n * Create a namespaced configuration factory\n * Similar to NestJS registerAs\n *\n * @param namespace - Configuration namespace (e.g., 'database', 'email')\n * @param factory - Factory function receiving env and returning config object\n * @returns ConfigNamespace with token, factory, and asProvider() method\n *\n * @example\n * ```typescript\n * // apps/backend/src/config/database.config.ts\n * export const databaseConfig = registerAs('database', (env: Env) => ({\n * url: env.DATABASE_URL,\n * maxConnections: parseInt(env.DATABASE_MAX_CONNECTIONS || '10'),\n * }))\n *\n * // Auto-generates: databaseConfig.KEY = Symbol('stratal:config:database')\n *\n * // Usage in module:\n * // Option 1: Manual provider\n * {\n * provide: databaseConfig.KEY,\n * useFactory: databaseConfig.factory,\n * inject: [DI_TOKENS.CloudflareEnv]\n * }\n *\n * // Option 2: asProvider() helper\n * databaseConfig.asProvider()\n * // Returns: { provide: databaseConfig.KEY, useFactory: ..., inject: [DI_TOKENS.CloudflareEnv] }\n * ```\n */\nexport function registerAs<TKey extends string, TEnv, TConfig extends object>(\n namespace: TKey,\n factory: (env: TEnv) => TConfig\n): ConfigNamespace<TKey, TEnv, TConfig> {\n const KEY = Symbol.for(`stratal:config:${namespace}`) as InjectionToken<TConfig>\n\n return {\n KEY,\n namespace,\n factory,\n asProvider(): FactoryProvider<TConfig> {\n return {\n provide: KEY,\n useFactory: factory,\n inject: [DI_TOKENS.CloudflareEnv],\n }\n },\n }\n}\n\n/**\n * Helper to derive config type from registerAs result\n *\n * @example\n * ```typescript\n * const databaseConfig = registerAs('database', (env) => ({ url: env.DATABASE_URL }))\n * type DatabaseConfig = InferConfigType<typeof databaseConfig>\n * // { url: string }\n * ```\n */\nexport type InferConfigType<T> = T extends ConfigNamespace<string, unknown, infer C> ? C : never\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/config/config.tokens.ts","../../src/config/errors/config-module-not-initialized.error.ts","../../src/config/errors/config-not-initialized.error.ts","../../src/config/config.types.ts","../../src/config/services/config.service.ts","../../src/config/config.module.ts","../../src/config/register-as.ts"],"sourcesContent":["export const CONFIG_TOKENS = {\n\tConfigService: Symbol.for('stratal:config:service'),\n} as const\n","import { ApplicationError, ERROR_CODES } from '../../errors'\n\n/**\n * Error thrown when ConfigModule's onInitialize runs but forRoot() was never called.\n *\n * This means the module was imported without calling ConfigModule.forRoot({ load: [...] }).\n */\nexport class ConfigModuleNotInitializedError extends ApplicationError {\n constructor() {\n super(\n 'errors.configModuleNotInitialized',\n ERROR_CODES.SYSTEM.CONFIG_MODULE_NOT_INITIALIZED\n )\n }\n}\n","import { ERROR_CODES } from '../../errors'\nimport { ApplicationError } from '../../errors'\n\n/**\n * ConfigNotInitializedError\n *\n * Thrown when attempting to access ConfigService before it has been initialized.\n * This typically indicates that ConfigModule's onInitialize hook hasn't run yet,\n * or the module wasn't registered properly.\n */\nexport class ConfigNotInitializedError extends ApplicationError {\n constructor() {\n super(\n 'errors.configNotInitialized',\n ERROR_CODES.SYSTEM.CONFIG_NOT_INITIALIZED\n )\n }\n}\n","import type { z } from '../i18n/validation'\n\nexport class ConfigValidationError extends Error {\n constructor(\n message: string,\n public readonly errors: z.ZodError\n ) {\n super(message)\n this.name = 'ConfigValidationError'\n }\n}\n\n/**\n * Configuration that can be augmented by applications\n * Apps should augment this interface with their AppConfig type using module augmentation\n *\n * @example\n * ```typescript\n * // In your app (e.g., apps/backend/src/config/types.ts)\n * declare module 'stratal' {\n * interface ModuleConfig {\n * database: { url: string; maxConnections: number }\n * email: { provider: string; from: { name: string; email: string } }\n * }\n * }\n * ```\n */\nexport interface ModuleConfig { }\n\n/**\n * Generate all valid dot-notation paths from a config object type\n * @example ConfigPath<{ database: { url: string } }> = 'database' | 'database.url'\n */\nexport type ConfigPath<T> = {\n [K in keyof T & string]: T[K] extends Record<string, unknown>\n ? K | `${K}.${ConfigPath<T[K]>}`\n : K\n}[keyof T & string]\n\n/**\n * Get the value type at a dot-notation path\n * @example ConfigPathValue<{ database: { url: string } }, 'database.url'> = string\n */\nexport type ConfigPathValue<T, P extends string> = P extends `${infer K}.${infer Rest}`\n ? K extends keyof T\n ? T[K] extends Record<string, unknown>\n ? ConfigPathValue<T[K], Rest>\n : never\n : never\n : P extends keyof T\n ? T[P]\n : never\n\n/**\n * ConfigService interface with dot notation support\n */\nexport interface IConfigService<T extends object = ModuleConfig> {\n /**\n * Initialize the config service with validated configuration\n * Should be called once during application startup\n */\n initialize(config: T): void\n\n /**\n * Get config value using dot notation\n * @example config.get('database.url')\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P>\n\n /**\n * Set config value at runtime (for runtime overrides)\n * @example config.set('email.from.name', 'Custom Name')\n */\n set<P extends ConfigPath<T>>(path: P, value: ConfigPathValue<T, P>): void\n\n /**\n * Reset config to original value\n * @param path - Optional path to reset (resets entire config if omitted)\n */\n reset(path?: ConfigPath<T>): void\n\n /**\n * Get entire config object\n */\n all(): Readonly<T>\n\n /**\n * Check if a config path exists\n */\n has(path: ConfigPath<T>): boolean\n}\n","import { Transient } from '../../di/decorators'\nimport { CONFIG_TOKENS } from '../config.tokens'\nimport type { ConfigPath, ConfigPathValue, IConfigService, ModuleConfig } from '../config.types'\nimport { ConfigNotInitializedError } from '../errors'\n\n/**\n * ConfigService with dot notation support for get/set operations\n *\n * Supports runtime overrides via set() - useful for request-specific config overrides.\n * Use reset() to restore original values.\n *\n * @example\n * ```typescript\n * // Get with dot notation\n * const url = config.get('database.url')\n * const fromName = config.get('email.from.name')\n *\n * // Set at runtime (e.g., in middleware for runtime override)\n * config.set('email.from.name', 'Custom Name')\n *\n * // Reset to original\n * config.reset('email.from.name') // reset specific path\n * config.reset() // reset entire config\n * ```\n */\n@Transient(CONFIG_TOKENS.ConfigService)\nexport class ConfigService<T extends object = ModuleConfig> implements IConfigService<T> {\n private originalConfig: T | undefined\n private currentConfig: T | undefined\n\n /**\n * Initialize the config service with validated configuration\n * Called by ConfigModule during initialization\n */\n initialize(config: T): void {\n this.originalConfig = this.deepClone(config)\n this.currentConfig = this.deepClone(config)\n }\n\n /**\n * Get config value using dot notation\n * @example config.get('database.url')\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P> {\n this.ensureInitialized()\n return this.getByPath(this.currentConfig, path) as ConfigPathValue<T, P>\n }\n\n /**\n * Set config value at runtime (for runtime overrides)\n * @example config.set('email.from.name', 'Custom Name')\n */\n set<P extends ConfigPath<T>>(path: P, value: ConfigPathValue<T, P>): void {\n this.ensureInitialized()\n this.setByPath(this.currentConfig, path, value)\n }\n\n /**\n * Reset config to original value\n * @param path - Optional path to reset (resets entire config if omitted)\n */\n reset(path?: ConfigPath<T>): void {\n this.ensureInitialized()\n if (path) {\n const originalValue = this.getByPath(this.originalConfig, path)\n this.setByPath(this.currentConfig, path, this.deepClone(originalValue))\n } else {\n this.currentConfig = this.deepClone(this.originalConfig)\n }\n }\n\n /**\n * Get entire config object\n */\n all(): Readonly<T> {\n this.ensureInitialized()\n return this.currentConfig as Readonly<T>\n }\n\n /**\n * Check if a config path exists\n */\n has(path: ConfigPath<T>): boolean {\n this.ensureInitialized()\n return this.getByPath(this.currentConfig, path) !== undefined\n }\n\n private getByPath(obj: unknown, path: string): unknown {\n const keys = path.split('.')\n let current = obj\n for (const key of keys) {\n if (this.isDangerousKey(key)) return undefined\n if (current === null || current === undefined) return undefined\n current = (current as Record<string, unknown>)[key]\n }\n return current\n }\n\n private setByPath(obj: unknown, path: string, value: unknown): void {\n const keys = path.split('.')\n if (keys.some((key) => this.isDangerousKey(key))) return\n let current = obj as Record<string, unknown>\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i]\n if (!Object.hasOwn(current, key) || typeof current[key] !== 'object' || current[key] === null) {\n Object.defineProperty(current, key, {\n value: {},\n writable: true,\n enumerable: true,\n configurable: true,\n })\n }\n current = current[key] as Record<string, unknown>\n }\n Object.defineProperty(current, keys[keys.length - 1], {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n })\n }\n\n private isDangerousKey(key: string): boolean {\n return key === '__proto__' || key === 'constructor' || key === 'prototype'\n }\n\n private ensureInitialized(): void {\n if (!this.currentConfig) {\n throw new ConfigNotInitializedError()\n }\n }\n\n private deepClone<V>(obj: V): V {\n if (obj === null || typeof obj !== 'object') {\n return obj\n }\n return JSON.parse(JSON.stringify(obj)) as V\n }\n}\n","import type { z } from '../i18n/validation'\nimport { ConfigModuleNotInitializedError } from './errors'\nimport { DI_TOKENS } from '../di/tokens'\nimport { Scope } from '../di/types'\nimport { Module } from '../module'\nimport type { DynamicModule, ModuleContext, OnInitialize, Provider } from '../module/types'\nimport { CONFIG_TOKENS } from './config.tokens'\nimport { ConfigValidationError, ModuleConfig } from './config.types'\nimport type { ConfigNamespace } from './register-as'\nimport { ConfigService } from './services/config.service'\n\n/**\n * Any config namespace - uses structural typing for flexibility\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyConfigNamespace = ConfigNamespace<string, any, object>\n\n/**\n * Options for ConfigModule.forRoot\n */\nexport interface ConfigModuleOptions {\n /**\n * Array of config namespaces created via registerAs()\n */\n load: AnyConfigNamespace[]\n\n /**\n * Optional Zod schema for validating the merged config\n * Validates the entire config object after all namespaces are merged\n */\n validateSchema?: z.ZodType<object>\n}\n\n// Store options for use in onInitialize\nlet moduleOptions: ConfigModuleOptions | null = null\n\n/**\n * ConfigModule\n *\n * Provides configuration management with namespace support.\n * Uses registerAs() to create typed config namespaces that can be injected.\n *\n * @example\n * ```typescript\n * // Define config namespaces\n * const databaseConfig = registerAs('database', (env) => ({\n * url: env.DATABASE_URL,\n * maxConnections: 10\n * }))\n *\n * const emailConfig = registerAs('email', (env) => ({\n * provider: env.EMAIL_PROVIDER,\n * from: { name: 'App', email: 'noreply@example.com' }\n * }))\n *\n * // Register in module\n * @Module({\n * imports: [\n * ConfigModule.forRoot({\n * load: [databaseConfig, emailConfig],\n * validateSchema: AppConfigSchema\n * })\n * ]\n * })\n * export class AppModule {}\n *\n * // Inject config\n * constructor(\n * @inject(CONFIG_TOKENS.ConfigService) private config: IConfigService,\n * @inject(databaseConfig.KEY) private dbConfig: DatabaseConfig\n * ) {\n * // Use dot notation\n * const url = this.config.get('database.url')\n *\n * // Or inject namespace directly\n * const url = this.dbConfig.url\n * }\n * ```\n */\n@Module({\n providers: [\n // Register the main ConfigService as Singleton so initialization persists\n {\n provide: CONFIG_TOKENS.ConfigService,\n useClass: ConfigService,\n scope: Scope.Singleton,\n },\n ],\n})\nexport class ConfigModule implements OnInitialize {\n /**\n * Configure ConfigModule with namespace loaders\n *\n * @param options - Configuration options\n * @returns Dynamic module with config infrastructure\n */\n static forRoot(options: ConfigModuleOptions): DynamicModule {\n moduleOptions = options\n\n const providers: Provider[] = []\n\n // Register each namespace config using asProvider()\n for (const namespace of options.load) {\n providers.push(namespace.asProvider())\n }\n\n return {\n module: ConfigModule,\n providers,\n }\n }\n\n /**\n * Initialize config service with merged namespaces\n * Called after all providers are registered\n */\n onInitialize(context: ModuleContext): void {\n if (!moduleOptions) {\n throw new ConfigModuleNotInitializedError()\n }\n\n const env = context.container.resolve<unknown>(DI_TOKENS.CloudflareEnv)\n const configService = context.container.resolve<ConfigService>(CONFIG_TOKENS.ConfigService)\n\n // Build merged config from all namespaces\n const mergedConfig: Record<string, unknown> = {}\n\n for (const namespace of moduleOptions.load) {\n mergedConfig[namespace.namespace] = namespace.factory(env)\n }\n\n // Validate if schema provided\n if (moduleOptions.validateSchema) {\n const result = moduleOptions.validateSchema.safeParse(mergedConfig)\n if (!result.success) {\n throw new ConfigValidationError(\n 'Configuration validation failed',\n result.error\n )\n }\n }\n\n // Initialize ConfigService with merged config\n configService.initialize(mergedConfig as ModuleConfig)\n\n context.logger.debug('ConfigModule initialized', {\n namespaces: moduleOptions.load.map((n) => n.namespace),\n })\n }\n}\n","import type { InjectionToken } from 'tsyringe'\nimport { DI_TOKENS } from '../di/tokens'\nimport type { FactoryProvider } from '../module/types'\n\n/**\n * Configuration namespace registration result\n */\nexport interface ConfigNamespace<TKey extends string, TEnv, TConfig extends object> {\n /** Auto-derived injection token (e.g., 'database' -> Symbol('stratal:config:database')) */\n readonly KEY: InjectionToken<TConfig>\n /** The namespace key */\n readonly namespace: TKey\n /** The factory function that receives env and returns config */\n readonly factory: (env: TEnv) => TConfig\n /**\n * Returns a provider configuration for use in module registration\n * Automatically injects DI_TOKENS.CloudflareEnv\n */\n asProvider(): FactoryProvider<TConfig>\n}\n\n/**\n * Create a namespaced configuration factory\n * Similar to NestJS registerAs\n *\n * @param namespace - Configuration namespace (e.g., 'database', 'email')\n * @param factory - Factory function receiving env and returning config object\n * @returns ConfigNamespace with token, factory, and asProvider() method\n *\n * @example\n * ```typescript\n * // apps/backend/src/config/database.config.ts\n * export const databaseConfig = registerAs('database', (env: Env) => ({\n * url: env.DATABASE_URL,\n * maxConnections: parseInt(env.DATABASE_MAX_CONNECTIONS || '10'),\n * }))\n *\n * // Auto-generates: databaseConfig.KEY = Symbol('stratal:config:database')\n *\n * // Usage in module:\n * // Option 1: Manual provider\n * {\n * provide: databaseConfig.KEY,\n * useFactory: databaseConfig.factory,\n * inject: [DI_TOKENS.CloudflareEnv]\n * }\n *\n * // Option 2: asProvider() helper\n * databaseConfig.asProvider()\n * // Returns: { provide: databaseConfig.KEY, useFactory: ..., inject: [DI_TOKENS.CloudflareEnv] }\n * ```\n */\nexport function registerAs<TKey extends string, TEnv, TConfig extends object>(\n namespace: TKey,\n factory: (env: TEnv) => TConfig\n): ConfigNamespace<TKey, TEnv, TConfig> {\n const KEY = Symbol.for(`stratal:config:${namespace}`) as InjectionToken<TConfig>\n\n return {\n KEY,\n namespace,\n factory,\n asProvider(): FactoryProvider<TConfig> {\n return {\n provide: KEY,\n useFactory: factory,\n inject: [DI_TOKENS.CloudflareEnv],\n }\n },\n }\n}\n\n/**\n * Helper to derive config type from registerAs result\n *\n * @example\n * ```typescript\n * const databaseConfig = registerAs('database', (env) => ({ url: env.DATABASE_URL }))\n * type DatabaseConfig = InferConfigType<typeof databaseConfig>\n * // { url: string }\n * ```\n */\nexport type InferConfigType<T> = T extends ConfigNamespace<string, unknown, infer C> ? C : never\n"],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
//#region src/queue/queue-consumer.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Queue message structure
|
|
4
|
+
*
|
|
5
|
+
* All messages dispatched to queues follow this structure.
|
|
6
|
+
* The `id` and `timestamp` are auto-generated by QueueSender.
|
|
7
|
+
*/
|
|
8
|
+
interface QueueMessage<T = unknown> {
|
|
9
|
+
/** Unique message identifier (UUID) */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Timestamp when message was dispatched (milliseconds since epoch) */
|
|
12
|
+
timestamp: number;
|
|
13
|
+
/** Message type for routing to consumers */
|
|
14
|
+
type: string;
|
|
15
|
+
/** Message payload */
|
|
16
|
+
payload: T;
|
|
17
|
+
/** Optional metadata including locale for i18n */
|
|
18
|
+
metadata?: {
|
|
19
|
+
locale?: string;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Queue consumer interface
|
|
25
|
+
*
|
|
26
|
+
* Consumers handle messages based on their `messageTypes` declaration.
|
|
27
|
+
* A consumer receives messages of the declared types from ANY queue.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* @Transient()
|
|
32
|
+
* export class EmailConsumer implements IQueueConsumer<SendEmailInput> {
|
|
33
|
+
* readonly messageTypes = ['email.send', 'email.batch.send']
|
|
34
|
+
*
|
|
35
|
+
* async handle(message: QueueMessage<SendEmailInput>): Promise<void> {
|
|
36
|
+
* // Process email...
|
|
37
|
+
* }
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
interface IQueueConsumer<T = unknown> {
|
|
42
|
+
/**
|
|
43
|
+
* Message types this consumer handles.
|
|
44
|
+
*
|
|
45
|
+
* The consumer receives messages matching these types from ANY queue.
|
|
46
|
+
* Use '*' to match all message types (wildcard consumer).
|
|
47
|
+
*/
|
|
48
|
+
readonly messageTypes: string[];
|
|
49
|
+
/**
|
|
50
|
+
* Handle an incoming message
|
|
51
|
+
*
|
|
52
|
+
* @param message - The queue message to process
|
|
53
|
+
*/
|
|
54
|
+
handle(message: QueueMessage<T>): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Optional error handler for failed message processing
|
|
57
|
+
*
|
|
58
|
+
* @param error - The error that occurred
|
|
59
|
+
* @param message - The message that failed to process
|
|
60
|
+
*/
|
|
61
|
+
onError?(error: Error, message: QueueMessage<T>): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/queue/consumer-registry.d.ts
|
|
65
|
+
/**
|
|
66
|
+
* Consumer Registry
|
|
67
|
+
*
|
|
68
|
+
* Singleton service that holds all registered queue consumers indexed by message type.
|
|
69
|
+
* Consumers declare the message types they handle, and this registry routes messages
|
|
70
|
+
* to the appropriate consumers based on their types.
|
|
71
|
+
*
|
|
72
|
+
* **Message-Type Routing:**
|
|
73
|
+
* - Consumers declare `messageTypes` array (e.g., `['email.send', 'email.batch.send']`)
|
|
74
|
+
* - When a message arrives, consumers matching the message type are invoked
|
|
75
|
+
* - A consumer can handle messages from ANY queue (routing is by type, not queue)
|
|
76
|
+
* - Use `'*'` as a wildcard to handle all message types
|
|
77
|
+
*
|
|
78
|
+
* @example Consumer registration
|
|
79
|
+
* ```typescript
|
|
80
|
+
* // In consumer.ts
|
|
81
|
+
* @Transient()
|
|
82
|
+
* export class EmailConsumer implements IQueueConsumer {
|
|
83
|
+
* readonly messageTypes = ['email.send', 'email.batch.send']
|
|
84
|
+
* // ...
|
|
85
|
+
* }
|
|
86
|
+
*
|
|
87
|
+
* // In module.ts
|
|
88
|
+
* @Module({
|
|
89
|
+
* consumers: [EmailConsumer]
|
|
90
|
+
* })
|
|
91
|
+
*
|
|
92
|
+
* // Application auto-registers via ConsumerRegistry
|
|
93
|
+
* this.consumerRegistry.register(consumer)
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
declare class ConsumerRegistry {
|
|
97
|
+
/** Map from message type to consumers handling that type */
|
|
98
|
+
private consumersByType;
|
|
99
|
+
/** Set of all registered consumers (for iteration) */
|
|
100
|
+
private allConsumers;
|
|
101
|
+
/**
|
|
102
|
+
* Register a queue consumer
|
|
103
|
+
*
|
|
104
|
+
* Indexes the consumer by each of its declared message types.
|
|
105
|
+
*
|
|
106
|
+
* @param consumer - Queue consumer to register
|
|
107
|
+
*/
|
|
108
|
+
register(consumer: IQueueConsumer): void;
|
|
109
|
+
/**
|
|
110
|
+
* Get all consumers that can handle a specific message type
|
|
111
|
+
*
|
|
112
|
+
* Returns consumers that either:
|
|
113
|
+
* - Explicitly declare the message type
|
|
114
|
+
* - Use '*' wildcard to handle all types
|
|
115
|
+
*
|
|
116
|
+
* @param messageType - The message type to find consumers for
|
|
117
|
+
* @returns Array of consumers that can handle this message type
|
|
118
|
+
*/
|
|
119
|
+
getConsumers(messageType: string): IQueueConsumer[];
|
|
120
|
+
/**
|
|
121
|
+
* Check if any consumers can handle a message type
|
|
122
|
+
*
|
|
123
|
+
* @param messageType - The message type to check
|
|
124
|
+
* @returns true if at least one consumer can handle this type
|
|
125
|
+
*/
|
|
126
|
+
hasConsumers(messageType: string): boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Get all registered message types
|
|
129
|
+
*
|
|
130
|
+
* @returns Array of message types with registered consumers
|
|
131
|
+
*/
|
|
132
|
+
getMessageTypes(): string[];
|
|
133
|
+
/**
|
|
134
|
+
* Get all registered consumers
|
|
135
|
+
*
|
|
136
|
+
* @returns Array of all registered consumers
|
|
137
|
+
*/
|
|
138
|
+
getAllConsumers(): IQueueConsumer[];
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
export { IQueueConsumer as n, QueueMessage as r, ConsumerRegistry as t };
|
|
142
|
+
//# sourceMappingURL=consumer-registry-Bymm6ff4.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consumer-registry-Bymm6ff4.d.mts","names":[],"sources":["../src/queue/queue-consumer.ts","../src/queue/consumer-registry.ts"],"mappings":";;AAMA;;;;;UAAiB,YAAA;EAIf;EAFA,EAAA;EAMA;EAJA,SAAA;EAMA;EAJA,IAAA;EAMG;EAJH,OAAA,EAAS,CAAA;EAIK;EAFd,QAAA;IACE,MAAA;IAAA,CACC,GAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;;;UAsBY,cAAA;EAsBQ;;;;;;EAAA,SAfd,YAAA;ECXE;;;;;EDkBX,MAAA,CAAO,OAAA,EAAS,YAAA,CAAa,CAAA,IAAK,OAAA;ECqDD;;;;;;ED7CjC,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,OAAA,EAAS,YAAA,CAAa,CAAA,IAAK,OAAA;AAAA;;;AAxDpD;;;;;;;;;;;;;;;AAkCA;;;;;;;;;;;;;;;;AAlCA,cC8Ba,gBAAA;EDkBJ;EAAA,QChBC,eAAA;EDwBR;EAAA,QCrBQ,YAAA;EDqBC;;;;;;;ECZT,QAAA,CAAS,QAAA,EAAU,cAAA;;;AAfrB;;;;;;;;EAuCE,YAAA,CAAa,WAAA,WAAsB,cAAA;EAjC3B;;;;;;EAgDR,YAAA,CAAa,WAAA;EAAb;;;;;EASA,eAAA,CAAA;EASiC;;;;;EAAjC,eAAA,CAAA,GAAmB,cAAA;AAAA"}
|
package/dist/cron/index.d.mts
CHANGED
|
@@ -1,119 +1,6 @@
|
|
|
1
|
-
import { s as ApplicationError } from "../index-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Interface for cron jobs that can be registered by modules
|
|
5
|
-
*
|
|
6
|
-
* Cron jobs are executed when Cloudflare triggers match their schedule.
|
|
7
|
-
* Jobs are registered via the module's getCronJobs() method.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```typescript
|
|
11
|
-
* @Transient()
|
|
12
|
-
* export class DataCleanupJob implements CronJob {
|
|
13
|
-
* readonly schedule = '0 2 * * *' // Daily at 2 AM UTC
|
|
14
|
-
*
|
|
15
|
-
* constructor(
|
|
16
|
-
* @inject(LOGGER_TOKENS.LoggerService) private logger: LoggerService,
|
|
17
|
-
* ) {}
|
|
18
|
-
*
|
|
19
|
-
* async execute(controller: ScheduledController): Promise<void> {
|
|
20
|
-
* this.logger.info('Running data cleanup')
|
|
21
|
-
* await this.cleanupExpiredData()
|
|
22
|
-
* }
|
|
23
|
-
*
|
|
24
|
-
* async onError(error: Error): Promise<void> {
|
|
25
|
-
* this.logger.error('Data cleanup failed', { error: error.message })
|
|
26
|
-
* }
|
|
27
|
-
* }
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
interface CronJob {
|
|
31
|
-
/**
|
|
32
|
-
* Cron expression that triggers this job
|
|
33
|
-
*
|
|
34
|
-
* Must match a cron trigger defined in wrangler.jsonc
|
|
35
|
-
* @example '0 2 * * *' // Daily at 2 AM UTC
|
|
36
|
-
* @example '* /15 * * * *' // Every 15 minutes
|
|
37
|
-
*/
|
|
38
|
-
readonly schedule: string;
|
|
39
|
-
/**
|
|
40
|
-
* Execute the cron job
|
|
41
|
-
*
|
|
42
|
-
* @param controller - Cloudflare ScheduledController with scheduledTime and cron
|
|
43
|
-
* @throws ApplicationError for expected errors
|
|
44
|
-
*/
|
|
45
|
-
execute(controller: ScheduledController): Promise<void>;
|
|
46
|
-
/**
|
|
47
|
-
* Optional error handler for job execution failures
|
|
48
|
-
*
|
|
49
|
-
* If not provided, errors are logged via GlobalErrorHandler
|
|
50
|
-
*
|
|
51
|
-
* @param error - Error that occurred during execution
|
|
52
|
-
* @param controller - Cloudflare ScheduledController
|
|
53
|
-
*/
|
|
54
|
-
onError?(error: Error, controller: ScheduledController): Promise<void>;
|
|
55
|
-
}
|
|
56
|
-
//#endregion
|
|
57
|
-
//#region src/cron/cron-manager.d.ts
|
|
58
|
-
/**
|
|
59
|
-
* Manages cron job registration and execution
|
|
60
|
-
*
|
|
61
|
-
* CronManager is a singleton service that:
|
|
62
|
-
* - Registers cron jobs from modules
|
|
63
|
-
* - Routes scheduled events to matching jobs
|
|
64
|
-
* - Handles errors during job execution
|
|
65
|
-
*
|
|
66
|
-
* Jobs are grouped by their cron expression, allowing multiple jobs
|
|
67
|
-
* to run on the same schedule.
|
|
68
|
-
*/
|
|
69
|
-
declare class CronManager {
|
|
70
|
-
/**
|
|
71
|
-
* Map of cron expressions to jobs
|
|
72
|
-
* Key: Cron expression (e.g., '0 2 * * *')
|
|
73
|
-
* Value: Array of jobs matching that expression
|
|
74
|
-
*/
|
|
75
|
-
private jobs;
|
|
76
|
-
/**
|
|
77
|
-
* Register a cron job
|
|
78
|
-
*
|
|
79
|
-
* Jobs with the same schedule are grouped together and executed
|
|
80
|
-
* sequentially when the trigger fires.
|
|
81
|
-
*
|
|
82
|
-
* @param job - CronJob instance to register
|
|
83
|
-
*/
|
|
84
|
-
registerJob(job: CronJob): void;
|
|
85
|
-
/**
|
|
86
|
-
* Execute all jobs matching the triggered cron expression
|
|
87
|
-
*
|
|
88
|
-
* Jobs are executed sequentially. If a job fails:
|
|
89
|
-
* - Its onError() hook is called (if defined)
|
|
90
|
-
* - Execution continues with the next job
|
|
91
|
-
* - Errors are collected and logged
|
|
92
|
-
*
|
|
93
|
-
* @param controller - Cloudflare ScheduledController
|
|
94
|
-
*/
|
|
95
|
-
executeScheduled(controller: ScheduledController): Promise<void>;
|
|
96
|
-
/**
|
|
97
|
-
* Get all registered jobs for a specific cron expression
|
|
98
|
-
*
|
|
99
|
-
* @param schedule - Cron expression
|
|
100
|
-
* @returns Array of jobs for that schedule, or empty array if none
|
|
101
|
-
*/
|
|
102
|
-
getJobsForSchedule(schedule: string): CronJob[];
|
|
103
|
-
/**
|
|
104
|
-
* Get all registered cron expressions
|
|
105
|
-
*
|
|
106
|
-
* @returns Array of unique cron expressions
|
|
107
|
-
*/
|
|
108
|
-
getAllSchedules(): string[];
|
|
109
|
-
/**
|
|
110
|
-
* Get total number of registered jobs across all schedules
|
|
111
|
-
*
|
|
112
|
-
* @returns Total job count
|
|
113
|
-
*/
|
|
114
|
-
getTotalJobCount(): number;
|
|
115
|
-
}
|
|
116
|
-
//#endregion
|
|
1
|
+
import { s as ApplicationError } from "../index-BFCxSp_f.mjs";
|
|
2
|
+
import { n as CronJob, t as CronManager } from "../cron-manager-D7imGwUT.mjs";
|
|
3
|
+
|
|
117
4
|
//#region src/cron/errors/cron-execution.error.d.ts
|
|
118
5
|
/**
|
|
119
6
|
* Error thrown when one or more cron jobs fail execution
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/cron/
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/cron/errors/cron-execution.error.ts"],"mappings":";;;;;;;;AAQA;cAAa,kBAAA,SAA2B,gBAAA;cAEtC,QAAA,UACA,eAAA,UACA,QAAA;AAAA"}
|
package/dist/cron/index.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import "../errors-
|
|
2
|
-
import "../
|
|
3
|
-
import "../
|
|
4
|
-
import { n as CronExecutionError, t as CronManager } from "../cron-manager-DR7fiG6o.mjs";
|
|
1
|
+
import "../errors-DSKapqD8.mjs";
|
|
2
|
+
import "../logger-CGT91VY6.mjs";
|
|
3
|
+
import { n as CronExecutionError, t as CronManager } from "../cron-manager-CFBamKKk.mjs";
|
|
5
4
|
export { CronExecutionError, CronManager };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as ApplicationError, b as ERROR_CODES } from "./errors-
|
|
2
|
-
import {
|
|
1
|
+
import { S as ApplicationError, b as ERROR_CODES } from "./errors-DSKapqD8.mjs";
|
|
2
|
+
import { a as __decorate, d as Transient } from "./logger-CGT91VY6.mjs";
|
|
3
3
|
//#region src/cron/errors/cron-execution.error.ts
|
|
4
4
|
/**
|
|
5
5
|
* Error thrown when one or more cron jobs fail execution
|
|
@@ -104,4 +104,4 @@ CronManager = __decorate([Transient()], CronManager);
|
|
|
104
104
|
//#endregion
|
|
105
105
|
export { CronExecutionError as n, CronManager as t };
|
|
106
106
|
|
|
107
|
-
//# sourceMappingURL=cron-manager-
|
|
107
|
+
//# sourceMappingURL=cron-manager-CFBamKKk.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cron-manager-
|
|
1
|
+
{"version":3,"file":"cron-manager-CFBamKKk.mjs","names":[],"sources":["../src/cron/errors/cron-execution.error.ts","../src/cron/cron-manager.ts"],"sourcesContent":["import { ApplicationError } from '../../errors'\nimport { ERROR_CODES } from '../../errors'\n\n/**\n * Error thrown when one or more cron jobs fail execution\n *\n * This error aggregates failures from multiple jobs that share the same schedule.\n */\nexport class CronExecutionError extends ApplicationError {\n\tconstructor(\n\t\tschedule: string,\n\t\tfailedJobsCount: number,\n\t\tjobNames: string\n\t) {\n\t\tsuper(\n\t\t\t'errors.cronExecutionFailed',\n\t\t\tERROR_CODES.SYSTEM.CRON_EXECUTION_FAILED,\n\t\t\t{\n\t\t\t\tschedule,\n\t\t\t\tcount: failedJobsCount,\n\t\t\t\tjobs: jobNames\n\t\t\t}\n\t\t)\n\t}\n}\n","import { Transient } from '../di/decorators'\nimport type { CronJob } from './cron-job'\nimport { CronExecutionError } from './errors/cron-execution.error'\n\n/**\n * Manages cron job registration and execution\n *\n * CronManager is a singleton service that:\n * - Registers cron jobs from modules\n * - Routes scheduled events to matching jobs\n * - Handles errors during job execution\n *\n * Jobs are grouped by their cron expression, allowing multiple jobs\n * to run on the same schedule.\n */\n@Transient()\nexport class CronManager {\n\t/**\n\t * Map of cron expressions to jobs\n\t * Key: Cron expression (e.g., '0 2 * * *')\n\t * Value: Array of jobs matching that expression\n\t */\n\tprivate jobs = new Map<string, CronJob[]>()\n\n\t/**\n\t * Register a cron job\n\t *\n\t * Jobs with the same schedule are grouped together and executed\n\t * sequentially when the trigger fires.\n\t *\n\t * @param job - CronJob instance to register\n\t */\n\tregisterJob(job: CronJob): void {\n\t\tconst existing = this.jobs.get(job.schedule) ?? []\n\t\texisting.push(job)\n\t\tthis.jobs.set(job.schedule, existing)\n\t}\n\n\t/**\n\t * Execute all jobs matching the triggered cron expression\n\t *\n\t * Jobs are executed sequentially. If a job fails:\n\t * - Its onError() hook is called (if defined)\n\t * - Execution continues with the next job\n\t * - Errors are collected and logged\n\t *\n\t * @param controller - Cloudflare ScheduledController\n\t */\n\tasync executeScheduled(controller: ScheduledController): Promise<void> {\n\t\tconst { cron } = controller\n\t\tconst matchingJobs = this.jobs.get(cron) ?? []\n\n\t\tif (matchingJobs.length === 0) {\n\t\t\treturn\n\t\t}\n\n\t\tconst errors: { job: string; error: Error }[] = []\n\n\t\tfor (const job of matchingJobs) {\n\t\t\tconst jobName = job.constructor.name\n\n\t\t\ttry {\n\t\t\t\tawait job.execute(controller)\n\t\t\t} catch (error) {\n\t\t\t\tconst err = error as Error\n\t\t\t\terrors.push({ job: jobName, error: err })\n\n\t\t\t\t// Call job's error handler if defined\n\t\t\t\tif (job.onError) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait job.onError(err, controller)\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// If onError() itself fails, we just continue\n\t\t\t\t\t\t// The error will be logged by GlobalErrorHandler\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If any jobs failed, throw an aggregate error\n\t\t// This ensures the error is logged by GlobalErrorHandler\n\t\tif (errors.length > 0) {\n\t\t\tconst jobNames = errors\n\t\t\t\t.map(({ job, error }) => `${job}: ${error.message}`)\n\t\t\t\t.join('; ')\n\n\t\t\tthrow new CronExecutionError(cron, errors.length, jobNames)\n\t\t}\n\t}\n\n\t/**\n\t * Get all registered jobs for a specific cron expression\n\t *\n\t * @param schedule - Cron expression\n\t * @returns Array of jobs for that schedule, or empty array if none\n\t */\n\tgetJobsForSchedule(schedule: string): CronJob[] {\n\t\treturn this.jobs.get(schedule) ?? []\n\t}\n\n\t/**\n\t * Get all registered cron expressions\n\t *\n\t * @returns Array of unique cron expressions\n\t */\n\tgetAllSchedules(): string[] {\n\t\treturn Array.from(this.jobs.keys())\n\t}\n\n\t/**\n\t * Get total number of registered jobs across all schedules\n\t *\n\t * @returns Total job count\n\t */\n\tgetTotalJobCount(): number {\n\t\tlet count = 0\n\t\tfor (const jobs of this.jobs.values()) {\n\t\t\tcount += jobs.length\n\t\t}\n\t\treturn count\n\t}\n}\n"],"mappings":";;;;;;;;AAQA,IAAa,qBAAb,cAAwC,iBAAiB;CACxD,YACC,UACA,iBACA,UACC;AACD,QACC,8BACA,YAAY,OAAO,uBACnB;GACC;GACA,OAAO;GACP,MAAM;GACN,CACD;;;;;ACNI,IAAA,cAAA,MAAM,YAAY;;;;;;CAMxB,uBAAe,IAAI,KAAwB;;;;;;;;;CAU3C,YAAY,KAAoB;EAC/B,MAAM,WAAW,KAAK,KAAK,IAAI,IAAI,SAAS,IAAI,EAAE;AAClD,WAAS,KAAK,IAAI;AAClB,OAAK,KAAK,IAAI,IAAI,UAAU,SAAS;;;;;;;;;;;;CAatC,MAAM,iBAAiB,YAAgD;EACtE,MAAM,EAAE,SAAS;EACjB,MAAM,eAAe,KAAK,KAAK,IAAI,KAAK,IAAI,EAAE;AAE9C,MAAI,aAAa,WAAW,EAC3B;EAGD,MAAM,SAA0C,EAAE;AAElD,OAAK,MAAM,OAAO,cAAc;GAC/B,MAAM,UAAU,IAAI,YAAY;AAEhC,OAAI;AACH,UAAM,IAAI,QAAQ,WAAW;YACrB,OAAO;IACf,MAAM,MAAM;AACZ,WAAO,KAAK;KAAE,KAAK;KAAS,OAAO;KAAK,CAAC;AAGzC,QAAI,IAAI,QACP,KAAI;AACH,WAAM,IAAI,QAAQ,KAAK,WAAW;YAC3B;;;AAUX,MAAI,OAAO,SAAS,GAAG;GACtB,MAAM,WAAW,OACf,KAAK,EAAE,KAAK,YAAY,GAAG,IAAI,IAAI,MAAM,UAAU,CACnD,KAAK,KAAK;AAEZ,SAAM,IAAI,mBAAmB,MAAM,OAAO,QAAQ,SAAS;;;;;;;;;CAU7D,mBAAmB,UAA6B;AAC/C,SAAO,KAAK,KAAK,IAAI,SAAS,IAAI,EAAE;;;;;;;CAQrC,kBAA4B;AAC3B,SAAO,MAAM,KAAK,KAAK,KAAK,MAAM,CAAC;;;;;;;CAQpC,mBAA2B;EAC1B,IAAI,QAAQ;AACZ,OAAK,MAAM,QAAQ,KAAK,KAAK,QAAQ,CACpC,UAAS,KAAK;AAEf,SAAO;;;0BAxGR,WAAW,CAAA,EAAA,YAAA"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
//#region src/cron/cron-job.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Interface for cron jobs that can be registered by modules
|
|
4
|
+
*
|
|
5
|
+
* Cron jobs are executed when Cloudflare triggers match their schedule.
|
|
6
|
+
* Jobs are registered via the module's getCronJobs() method.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* @Transient()
|
|
11
|
+
* export class DataCleanupJob implements CronJob {
|
|
12
|
+
* readonly schedule = '0 2 * * *' // Daily at 2 AM UTC
|
|
13
|
+
*
|
|
14
|
+
* constructor(
|
|
15
|
+
* @inject(LOGGER_TOKENS.LoggerService) private logger: LoggerService,
|
|
16
|
+
* ) {}
|
|
17
|
+
*
|
|
18
|
+
* async execute(controller: ScheduledController): Promise<void> {
|
|
19
|
+
* this.logger.info('Running data cleanup')
|
|
20
|
+
* await this.cleanupExpiredData()
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* async onError(error: Error): Promise<void> {
|
|
24
|
+
* this.logger.error('Data cleanup failed', { error: error.message })
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
interface CronJob {
|
|
30
|
+
/**
|
|
31
|
+
* Cron expression that triggers this job
|
|
32
|
+
*
|
|
33
|
+
* Must match a cron trigger defined in wrangler.jsonc
|
|
34
|
+
* @example '0 2 * * *' // Daily at 2 AM UTC
|
|
35
|
+
* @example '* /15 * * * *' // Every 15 minutes
|
|
36
|
+
*/
|
|
37
|
+
readonly schedule: string;
|
|
38
|
+
/**
|
|
39
|
+
* Execute the cron job
|
|
40
|
+
*
|
|
41
|
+
* @param controller - Cloudflare ScheduledController with scheduledTime and cron
|
|
42
|
+
* @throws ApplicationError for expected errors
|
|
43
|
+
*/
|
|
44
|
+
execute(controller: ScheduledController): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Optional error handler for job execution failures
|
|
47
|
+
*
|
|
48
|
+
* If not provided, errors are logged via GlobalErrorHandler
|
|
49
|
+
*
|
|
50
|
+
* @param error - Error that occurred during execution
|
|
51
|
+
* @param controller - Cloudflare ScheduledController
|
|
52
|
+
*/
|
|
53
|
+
onError?(error: Error, controller: ScheduledController): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/cron/cron-manager.d.ts
|
|
57
|
+
/**
|
|
58
|
+
* Manages cron job registration and execution
|
|
59
|
+
*
|
|
60
|
+
* CronManager is a singleton service that:
|
|
61
|
+
* - Registers cron jobs from modules
|
|
62
|
+
* - Routes scheduled events to matching jobs
|
|
63
|
+
* - Handles errors during job execution
|
|
64
|
+
*
|
|
65
|
+
* Jobs are grouped by their cron expression, allowing multiple jobs
|
|
66
|
+
* to run on the same schedule.
|
|
67
|
+
*/
|
|
68
|
+
declare class CronManager {
|
|
69
|
+
/**
|
|
70
|
+
* Map of cron expressions to jobs
|
|
71
|
+
* Key: Cron expression (e.g., '0 2 * * *')
|
|
72
|
+
* Value: Array of jobs matching that expression
|
|
73
|
+
*/
|
|
74
|
+
private jobs;
|
|
75
|
+
/**
|
|
76
|
+
* Register a cron job
|
|
77
|
+
*
|
|
78
|
+
* Jobs with the same schedule are grouped together and executed
|
|
79
|
+
* sequentially when the trigger fires.
|
|
80
|
+
*
|
|
81
|
+
* @param job - CronJob instance to register
|
|
82
|
+
*/
|
|
83
|
+
registerJob(job: CronJob): void;
|
|
84
|
+
/**
|
|
85
|
+
* Execute all jobs matching the triggered cron expression
|
|
86
|
+
*
|
|
87
|
+
* Jobs are executed sequentially. If a job fails:
|
|
88
|
+
* - Its onError() hook is called (if defined)
|
|
89
|
+
* - Execution continues with the next job
|
|
90
|
+
* - Errors are collected and logged
|
|
91
|
+
*
|
|
92
|
+
* @param controller - Cloudflare ScheduledController
|
|
93
|
+
*/
|
|
94
|
+
executeScheduled(controller: ScheduledController): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Get all registered jobs for a specific cron expression
|
|
97
|
+
*
|
|
98
|
+
* @param schedule - Cron expression
|
|
99
|
+
* @returns Array of jobs for that schedule, or empty array if none
|
|
100
|
+
*/
|
|
101
|
+
getJobsForSchedule(schedule: string): CronJob[];
|
|
102
|
+
/**
|
|
103
|
+
* Get all registered cron expressions
|
|
104
|
+
*
|
|
105
|
+
* @returns Array of unique cron expressions
|
|
106
|
+
*/
|
|
107
|
+
getAllSchedules(): string[];
|
|
108
|
+
/**
|
|
109
|
+
* Get total number of registered jobs across all schedules
|
|
110
|
+
*
|
|
111
|
+
* @returns Total job count
|
|
112
|
+
*/
|
|
113
|
+
getTotalJobCount(): number;
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
export { CronJob as n, CronManager as t };
|
|
117
|
+
//# sourceMappingURL=cron-manager-D7imGwUT.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron-manager-D7imGwUT.d.mts","names":[],"sources":["../src/cron/cron-job.ts","../src/cron/cron-manager.ts"],"mappings":";;AA2BA;;;;;;;;;;;;;;;;;;;;;;;;;;UAAiB,OAAA;ECXO;;;;;;;EAAA,SDmBd,QAAA;ECbD;;;;;;EDqBR,OAAA,CAAQ,UAAA,EAAY,mBAAA,GAAsB,OAAA;ECKe;;;;;;;;EDKzD,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,UAAA,EAAY,mBAAA,GAAsB,OAAA;AAAA;;;AA1B1D;;;;;;;;;;;AAAA,cCXa,WAAA;ED2BQ;;;;;EAAA,QCrBZ,IAAA;ED+B2B;;;;;;;;ECrBnC,WAAA,CAAY,GAAA,EAAK,OAAA;EAhBM;;;;;;;;;;EAgCjB,gBAAA,CAAiB,UAAA,EAAY,mBAAA,GAAsB,OAAA;EAhB7C;;;;;;EAgEZ,kBAAA,CAAmB,QAAA,WAAmB,OAAA;EAAA;;;;;EAStC,eAAA,CAAA;;;;;;EASA,gBAAA,CAAA;AAAA"}
|
package/dist/di/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $t as
|
|
1
|
+
import { $t as DI_TOKENS, Gt as ConditionalBindingFallbackError, Jt as InjectParam, Kt as Transient, Qt as DIToken, Wt as RequestScopeOperationNotAllowedError, Xt as getMethodInjections, Yt as ParamInjection, Zt as CONTAINER_TOKEN, _n as WhenOptions, an as inject, cn as singleton, dn as ConditionalBindingGive, en as Container, fn as ConditionalBindingUse, gn as Scope, hn as ExtensionDecorator, in as delay, ln as ConditionalBindingBuilder, mn as ContainerLike, nn as DependencyContainer, on as injectable, pn as PredicateContainer, qt as INJECT_PARAM_METADATA_KEY, rn as container, sn as instancePerContainerCachingFactory, tn as ContainerOptions, un as ConditionalBindingBuilderImpl } from "../index-BFCxSp_f.mjs";
|
|
2
2
|
export { CONTAINER_TOKEN, ConditionalBindingBuilder, ConditionalBindingBuilderImpl, ConditionalBindingFallbackError, ConditionalBindingGive, ConditionalBindingUse, Container, ContainerLike, ContainerOptions, DIToken, DI_TOKENS, DependencyContainer, ExtensionDecorator, INJECT_PARAM_METADATA_KEY, InjectParam, ParamInjection, PredicateContainer, RequestScopeOperationNotAllowedError, Scope, Transient, WhenOptions, container, delay, getMethodInjections, inject, injectable, instancePerContainerCachingFactory, singleton };
|
package/dist/di/index.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { _ as ConditionalBindingFallbackError, c as Container, d as inject, f as injectable, g as RequestScopeOperationNotAllowedError, h as ConditionalBindingBuilderImpl, l as container, m as singleton, p as instancePerContainerCachingFactory, s as Scope, u as delay } from "../errors-
|
|
2
|
-
import {
|
|
3
|
-
import "../logger-BR1-s1Um.mjs";
|
|
1
|
+
import { _ as ConditionalBindingFallbackError, c as Container, d as inject, f as injectable, g as RequestScopeOperationNotAllowedError, h as ConditionalBindingBuilderImpl, l as container, m as singleton, p as instancePerContainerCachingFactory, s as Scope, u as delay } from "../errors-DSKapqD8.mjs";
|
|
2
|
+
import { d as Transient, f as INJECT_PARAM_METADATA_KEY, g as DI_TOKENS, h as CONTAINER_TOKEN, m as getMethodInjections, p as InjectParam } from "../logger-CGT91VY6.mjs";
|
|
4
3
|
export { CONTAINER_TOKEN, ConditionalBindingBuilderImpl, ConditionalBindingFallbackError, Container, DI_TOKENS, INJECT_PARAM_METADATA_KEY, InjectParam, RequestScopeOperationNotAllowedError, Scope, Transient, container, delay, getMethodInjections, inject, injectable, instancePerContainerCachingFactory, singleton };
|
package/dist/email/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { o as z } from "../index-
|
|
3
|
-
import { g as QueueName, h as IQueueSender } from "../index-
|
|
1
|
+
import { at as DynamicModule, rt as AsyncModuleOptions, s as ApplicationError } from "../index-BFCxSp_f.mjs";
|
|
2
|
+
import { o as z } from "../index-NGxg-KP_.mjs";
|
|
3
|
+
import { g as QueueName, h as IQueueSender } from "../index-DGRe6Yoa.mjs";
|
|
4
4
|
import { ReactElement } from "react";
|
|
5
5
|
|
|
6
6
|
//#region src/email/email.module.d.ts
|
package/dist/email/index.mjs
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import { S as ApplicationError, b as ERROR_CODES } from "../errors-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import "../
|
|
6
|
-
import "../
|
|
7
|
-
import "../
|
|
8
|
-
import "../
|
|
9
|
-
import "../is-
|
|
10
|
-
import "../
|
|
11
|
-
import "../
|
|
12
|
-
import {
|
|
13
|
-
import { c as QUEUE_TOKENS } from "../queue.module-BZvmeAMj.mjs";
|
|
1
|
+
import { S as ApplicationError, b as ERROR_CODES } from "../errors-DSKapqD8.mjs";
|
|
2
|
+
import { a as __decorate, d as Transient, o as __decorateParam, s as __decorateMetadata, u as LOGGER_TOKENS } from "../logger-CGT91VY6.mjs";
|
|
3
|
+
import { r as Module } from "../module-tUtyVJ5E.mjs";
|
|
4
|
+
import "../events-CvUSgEuN.mjs";
|
|
5
|
+
import "../middleware-Bl-b5pkt.mjs";
|
|
6
|
+
import "../router-context-D9R1v2Ac.mjs";
|
|
7
|
+
import "../colors-Y7WIFXs7.mjs";
|
|
8
|
+
import "../command-B1CPgsrU.mjs";
|
|
9
|
+
import "../is-command-DJVI6wEJ.mjs";
|
|
10
|
+
import "../is-seeder-D5MIEcdz.mjs";
|
|
11
|
+
import { a as withI18n, i as z } from "../validation-DQTC259A.mjs";
|
|
12
|
+
import { c as QUEUE_TOKENS } from "../queue.module-BtI8f4Jo.mjs";
|
|
14
13
|
import "../queue/index.mjs";
|
|
15
|
-
import "../errors-
|
|
16
|
-
import { l as STORAGE_TOKENS } from "../storage-
|
|
14
|
+
import "../errors-DuAR5Wke.mjs";
|
|
15
|
+
import { l as STORAGE_TOKENS } from "../storage-CZKHOhci.mjs";
|
|
17
16
|
import { inject } from "tsyringe";
|
|
18
17
|
import { render } from "@react-email/render";
|
|
19
18
|
//#region src/email/email.tokens.ts
|
|
@@ -264,11 +263,11 @@ let EmailProviderFactory = class EmailProviderFactory {
|
|
|
264
263
|
async create() {
|
|
265
264
|
switch (this.options.provider) {
|
|
266
265
|
case "resend": {
|
|
267
|
-
const { ResendProvider } = await import("../resend.provider-
|
|
266
|
+
const { ResendProvider } = await import("../resend.provider-bXMEkdRJ.mjs");
|
|
268
267
|
return new ResendProvider(this.options);
|
|
269
268
|
}
|
|
270
269
|
case "smtp": {
|
|
271
|
-
const { SmtpProvider } = await import("../smtp.provider-
|
|
270
|
+
const { SmtpProvider } = await import("../smtp.provider-DrbHQztF.mjs");
|
|
272
271
|
return new SmtpProvider(this.options);
|
|
273
272
|
}
|
|
274
273
|
default: throw new EmailProviderNotSupportedError(this.options.provider);
|