@pattern-stack/codegen 0.2.0
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/CHANGELOG.md +67 -0
- package/README.md +214 -0
- package/dist/runtime/analytics/index.d.ts +6 -0
- package/dist/runtime/analytics/index.js +49 -0
- package/dist/runtime/analytics/index.js.map +1 -0
- package/dist/runtime/analytics/metrics.d.ts +75 -0
- package/dist/runtime/analytics/metrics.js +1 -0
- package/dist/runtime/analytics/metrics.js.map +1 -0
- package/dist/runtime/analytics/packs/crm-entity-measures.d.ts +21 -0
- package/dist/runtime/analytics/packs/crm-entity-measures.js +1 -0
- package/dist/runtime/analytics/packs/crm-entity-measures.js.map +1 -0
- package/dist/runtime/analytics/packs/index.d.ts +3 -0
- package/dist/runtime/analytics/packs/index.js +1 -0
- package/dist/runtime/analytics/packs/index.js.map +1 -0
- package/dist/runtime/analytics/packs/monetary-measures.d.ts +21 -0
- package/dist/runtime/analytics/packs/monetary-measures.js +1 -0
- package/dist/runtime/analytics/packs/monetary-measures.js.map +1 -0
- package/dist/runtime/analytics/specs.d.ts +49 -0
- package/dist/runtime/analytics/specs.js +1 -0
- package/dist/runtime/analytics/specs.js.map +1 -0
- package/dist/runtime/analytics/types.d.ts +85 -0
- package/dist/runtime/analytics/types.js +49 -0
- package/dist/runtime/analytics/types.js.map +1 -0
- package/dist/runtime/base-classes/activity-entity-repository.d.ts +26 -0
- package/dist/runtime/base-classes/activity-entity-repository.js +195 -0
- package/dist/runtime/base-classes/activity-entity-repository.js.map +1 -0
- package/dist/runtime/base-classes/activity-entity-service.d.ts +39 -0
- package/dist/runtime/base-classes/activity-entity-service.js +214 -0
- package/dist/runtime/base-classes/activity-entity-service.js.map +1 -0
- package/dist/runtime/base-classes/base-read-use-cases.d.ts +68 -0
- package/dist/runtime/base-classes/base-read-use-cases.js +32 -0
- package/dist/runtime/base-classes/base-read-use-cases.js.map +1 -0
- package/dist/runtime/base-classes/base-repository.d.ts +99 -0
- package/dist/runtime/base-classes/base-repository.js +160 -0
- package/dist/runtime/base-classes/base-repository.js.map +1 -0
- package/dist/runtime/base-classes/base-service.d.ts +98 -0
- package/dist/runtime/base-classes/base-service.js +186 -0
- package/dist/runtime/base-classes/base-service.js.map +1 -0
- package/dist/runtime/base-classes/index.d.ts +18 -0
- package/dist/runtime/base-classes/index.js +617 -0
- package/dist/runtime/base-classes/index.js.map +1 -0
- package/dist/runtime/base-classes/knowledge-entity-repository.d.ts +17 -0
- package/dist/runtime/base-classes/knowledge-entity-repository.js +166 -0
- package/dist/runtime/base-classes/knowledge-entity-repository.js.map +1 -0
- package/dist/runtime/base-classes/knowledge-entity-service.d.ts +15 -0
- package/dist/runtime/base-classes/knowledge-entity-service.js +192 -0
- package/dist/runtime/base-classes/knowledge-entity-service.js.map +1 -0
- package/dist/runtime/base-classes/lifecycle-events.d.ts +49 -0
- package/dist/runtime/base-classes/lifecycle-events.js +76 -0
- package/dist/runtime/base-classes/lifecycle-events.js.map +1 -0
- package/dist/runtime/base-classes/metadata-entity-repository.d.ts +27 -0
- package/dist/runtime/base-classes/metadata-entity-repository.js +212 -0
- package/dist/runtime/base-classes/metadata-entity-repository.js.map +1 -0
- package/dist/runtime/base-classes/metadata-entity-service.d.ts +39 -0
- package/dist/runtime/base-classes/metadata-entity-service.js +214 -0
- package/dist/runtime/base-classes/metadata-entity-service.js.map +1 -0
- package/dist/runtime/base-classes/synced-entity-repository.d.ts +32 -0
- package/dist/runtime/base-classes/synced-entity-repository.js +203 -0
- package/dist/runtime/base-classes/synced-entity-repository.js.map +1 -0
- package/dist/runtime/base-classes/synced-entity-service.d.ts +41 -0
- package/dist/runtime/base-classes/synced-entity-service.js +215 -0
- package/dist/runtime/base-classes/synced-entity-service.js.map +1 -0
- package/dist/runtime/base-classes/with-analytics.d.ts +18 -0
- package/dist/runtime/base-classes/with-analytics.js +11 -0
- package/dist/runtime/base-classes/with-analytics.js.map +1 -0
- package/dist/runtime/constants/tokens.d.ts +29 -0
- package/dist/runtime/constants/tokens.js +8 -0
- package/dist/runtime/constants/tokens.js.map +1 -0
- package/dist/runtime/subsystems/analytics/analytics-query.protocol.d.ts +30 -0
- package/dist/runtime/subsystems/analytics/analytics-query.protocol.js +1 -0
- package/dist/runtime/subsystems/analytics/analytics-query.protocol.js.map +1 -0
- package/dist/runtime/subsystems/analytics/analytics.module.d.ts +34 -0
- package/dist/runtime/subsystems/analytics/analytics.module.js +117 -0
- package/dist/runtime/subsystems/analytics/analytics.module.js.map +1 -0
- package/dist/runtime/subsystems/analytics/analytics.tokens.d.ts +24 -0
- package/dist/runtime/subsystems/analytics/analytics.tokens.js +10 -0
- package/dist/runtime/subsystems/analytics/analytics.tokens.js.map +1 -0
- package/dist/runtime/subsystems/analytics/cube-backend.d.ts +28 -0
- package/dist/runtime/subsystems/analytics/cube-backend.js +71 -0
- package/dist/runtime/subsystems/analytics/cube-backend.js.map +1 -0
- package/dist/runtime/subsystems/analytics/index.d.ts +6 -0
- package/dist/runtime/subsystems/analytics/index.js +122 -0
- package/dist/runtime/subsystems/analytics/index.js.map +1 -0
- package/dist/runtime/subsystems/analytics/noop-backend.d.ts +7 -0
- package/dist/runtime/subsystems/analytics/noop-backend.js +25 -0
- package/dist/runtime/subsystems/analytics/noop-backend.js.map +1 -0
- package/dist/runtime/subsystems/cache/cache.drizzle-backend.d.ts +43 -0
- package/dist/runtime/subsystems/cache/cache.drizzle-backend.js +133 -0
- package/dist/runtime/subsystems/cache/cache.drizzle-backend.js.map +1 -0
- package/dist/runtime/subsystems/cache/cache.memory-backend.d.ts +21 -0
- package/dist/runtime/subsystems/cache/cache.memory-backend.js +100 -0
- package/dist/runtime/subsystems/cache/cache.memory-backend.js.map +1 -0
- package/dist/runtime/subsystems/cache/cache.module.d.ts +37 -0
- package/dist/runtime/subsystems/cache/cache.module.js +272 -0
- package/dist/runtime/subsystems/cache/cache.module.js.map +1 -0
- package/dist/runtime/subsystems/cache/cache.protocol.d.ts +42 -0
- package/dist/runtime/subsystems/cache/cache.protocol.js +1 -0
- package/dist/runtime/subsystems/cache/cache.protocol.js.map +1 -0
- package/dist/runtime/subsystems/cache/cache.schema.d.ts +64 -0
- package/dist/runtime/subsystems/cache/cache.schema.js +18 -0
- package/dist/runtime/subsystems/cache/cache.schema.js.map +1 -0
- package/dist/runtime/subsystems/cache/cache.tokens.d.ts +18 -0
- package/dist/runtime/subsystems/cache/cache.tokens.js +8 -0
- package/dist/runtime/subsystems/cache/cache.tokens.js.map +1 -0
- package/dist/runtime/subsystems/cache/index.d.ts +11 -0
- package/dist/runtime/subsystems/cache/index.js +277 -0
- package/dist/runtime/subsystems/cache/index.js.map +1 -0
- package/dist/runtime/subsystems/events/domain-events.schema.d.ts +187 -0
- package/dist/runtime/subsystems/events/domain-events.schema.js +32 -0
- package/dist/runtime/subsystems/events/domain-events.schema.js.map +1 -0
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.d.ts +38 -0
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +199 -0
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -0
- package/dist/runtime/subsystems/events/event-bus.memory-backend.d.ts +18 -0
- package/dist/runtime/subsystems/events/event-bus.memory-backend.js +71 -0
- package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -0
- package/dist/runtime/subsystems/events/event-bus.protocol.d.ts +52 -0
- package/dist/runtime/subsystems/events/event-bus.protocol.js +1 -0
- package/dist/runtime/subsystems/events/event-bus.protocol.js.map +1 -0
- package/dist/runtime/subsystems/events/event-bus.redis-backend.d.ts +95 -0
- package/dist/runtime/subsystems/events/event-bus.redis-backend.js +229 -0
- package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -0
- package/dist/runtime/subsystems/events/events.module.d.ts +46 -0
- package/dist/runtime/subsystems/events/events.module.js +531 -0
- package/dist/runtime/subsystems/events/events.module.js.map +1 -0
- package/dist/runtime/subsystems/events/events.tokens.d.ts +19 -0
- package/dist/runtime/subsystems/events/events.tokens.js +8 -0
- package/dist/runtime/subsystems/events/events.tokens.js.map +1 -0
- package/dist/runtime/subsystems/events/index.d.ts +12 -0
- package/dist/runtime/subsystems/events/index.js +536 -0
- package/dist/runtime/subsystems/events/index.js.map +1 -0
- package/dist/runtime/subsystems/index.d.ts +24 -0
- package/dist/runtime/subsystems/index.js +1643 -0
- package/dist/runtime/subsystems/index.js.map +1 -0
- package/dist/runtime/subsystems/jobs/index.d.ts +14 -0
- package/dist/runtime/subsystems/jobs/index.js +680 -0
- package/dist/runtime/subsystems/jobs/index.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-queue.bullmq-backend.d.ts +54 -0
- package/dist/runtime/subsystems/jobs/job-queue.bullmq-backend.js +186 -0
- package/dist/runtime/subsystems/jobs/job-queue.bullmq-backend.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-queue.drizzle-backend.d.ts +38 -0
- package/dist/runtime/subsystems/jobs/job-queue.drizzle-backend.js +228 -0
- package/dist/runtime/subsystems/jobs/job-queue.drizzle-backend.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-queue.memory-backend.d.ts +12 -0
- package/dist/runtime/subsystems/jobs/job-queue.memory-backend.js +44 -0
- package/dist/runtime/subsystems/jobs/job-queue.memory-backend.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-queue.protocol.d.ts +48 -0
- package/dist/runtime/subsystems/jobs/job-queue.protocol.js +1 -0
- package/dist/runtime/subsystems/jobs/job-queue.protocol.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-queue.redis-backend.d.ts +46 -0
- package/dist/runtime/subsystems/jobs/job-queue.redis-backend.js +187 -0
- package/dist/runtime/subsystems/jobs/job-queue.redis-backend.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-queue.schema.d.ts +237 -0
- package/dist/runtime/subsystems/jobs/job-queue.schema.js +44 -0
- package/dist/runtime/subsystems/jobs/job-queue.schema.js.map +1 -0
- package/dist/runtime/subsystems/jobs/jobs.module.d.ts +18 -0
- package/dist/runtime/subsystems/jobs/jobs.module.js +676 -0
- package/dist/runtime/subsystems/jobs/jobs.module.js.map +1 -0
- package/dist/runtime/subsystems/jobs/jobs.tokens.d.ts +13 -0
- package/dist/runtime/subsystems/jobs/jobs.tokens.js +8 -0
- package/dist/runtime/subsystems/jobs/jobs.tokens.js.map +1 -0
- package/dist/runtime/subsystems/storage/index.d.ts +6 -0
- package/dist/runtime/subsystems/storage/index.js +204 -0
- package/dist/runtime/subsystems/storage/index.js.map +1 -0
- package/dist/runtime/subsystems/storage/storage.local-backend.d.ts +18 -0
- package/dist/runtime/subsystems/storage/storage.local-backend.js +108 -0
- package/dist/runtime/subsystems/storage/storage.local-backend.js.map +1 -0
- package/dist/runtime/subsystems/storage/storage.memory-backend.d.ts +28 -0
- package/dist/runtime/subsystems/storage/storage.memory-backend.js +72 -0
- package/dist/runtime/subsystems/storage/storage.memory-backend.js.map +1 -0
- package/dist/runtime/subsystems/storage/storage.module.d.ts +40 -0
- package/dist/runtime/subsystems/storage/storage.module.js +201 -0
- package/dist/runtime/subsystems/storage/storage.module.js.map +1 -0
- package/dist/runtime/subsystems/storage/storage.protocol.d.ts +69 -0
- package/dist/runtime/subsystems/storage/storage.protocol.js +1 -0
- package/dist/runtime/subsystems/storage/storage.protocol.js.map +1 -0
- package/dist/runtime/subsystems/storage/storage.tokens.d.ts +11 -0
- package/dist/runtime/subsystems/storage/storage.tokens.js +6 -0
- package/dist/runtime/subsystems/storage/storage.tokens.js.map +1 -0
- package/dist/runtime/subsystems/storage/storage.utils.d.ts +9 -0
- package/dist/runtime/subsystems/storage/storage.utils.js +18 -0
- package/dist/runtime/subsystems/storage/storage.utils.js.map +1 -0
- package/dist/runtime/types/drizzle.d.ts +17 -0
- package/dist/runtime/types/drizzle.js +1 -0
- package/dist/runtime/types/drizzle.js.map +1 -0
- package/dist/src/cli/index.d.ts +1 -0
- package/dist/src/cli/index.js +7365 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/index.d.ts +2384 -0
- package/dist/src/index.js +2198 -0
- package/dist/src/index.js.map +1 -0
- package/package.json +114 -0
- package/templates/broadcast/new/backend-interface.ejs.t +47 -0
- package/templates/broadcast/new/bridge-listener.ejs.t +67 -0
- package/templates/broadcast/new/channel.ejs.t +77 -0
- package/templates/broadcast/new/index.ejs.t +21 -0
- package/templates/broadcast/new/memory-backend.ejs.t +87 -0
- package/templates/broadcast/new/module.ejs.t +57 -0
- package/templates/broadcast/new/prompt.js +268 -0
- package/templates/broadcast/new/websocket-backend.ejs.t +259 -0
- package/templates/entity/new/backend/application/commands/create.ejs.t +55 -0
- package/templates/entity/new/backend/application/commands/delete.ejs.t +45 -0
- package/templates/entity/new/backend/application/commands/grouped-index.ejs.t +149 -0
- package/templates/entity/new/backend/application/commands/index.ejs.t +15 -0
- package/templates/entity/new/backend/application/commands/update.ejs.t +58 -0
- package/templates/entity/new/backend/application/queries/declarative-queries.ejs.t +36 -0
- package/templates/entity/new/backend/application/queries/get-by-id.ejs.t +42 -0
- package/templates/entity/new/backend/application/queries/grouped-index.ejs.t +81 -0
- package/templates/entity/new/backend/application/queries/index.ejs.t +14 -0
- package/templates/entity/new/backend/application/queries/list.ejs.t +36 -0
- package/templates/entity/new/backend/application/schemas/_inject-index.ejs.t +7 -0
- package/templates/entity/new/backend/application/schemas/dto.ejs.t +45 -0
- package/templates/entity/new/backend/database/_inject-index.ejs.t +7 -0
- package/templates/entity/new/backend/database/electric-migration.ejs.t +21 -0
- package/templates/entity/new/backend/database/repository.ejs.t +450 -0
- package/templates/entity/new/backend/database/schema.ejs.t +248 -0
- package/templates/entity/new/backend/domain/_inject-index.ejs.t +12 -0
- package/templates/entity/new/backend/domain/entity.ejs.t +108 -0
- package/templates/entity/new/backend/domain/grouped-index.ejs.t +163 -0
- package/templates/entity/new/backend/domain/index.ejs.t +15 -0
- package/templates/entity/new/backend/domain/repository-interface.ejs.t +71 -0
- package/templates/entity/new/backend/modules/core/_ensure-anchor-tokens.ejs.t +10 -0
- package/templates/entity/new/backend/modules/core/_inject-token.ejs.t +7 -0
- package/templates/entity/new/backend/modules/core/module.ejs.t +67 -0
- package/templates/entity/new/backend/modules/trpc/module.ejs.t +67 -0
- package/templates/entity/new/backend/presentation/controller.ejs.t +201 -0
- package/templates/entity/new/clean-lite-ps/controller.ejs.t +37 -0
- package/templates/entity/new/clean-lite-ps/dto/create.ejs.t +17 -0
- package/templates/entity/new/clean-lite-ps/dto/output.ejs.t +25 -0
- package/templates/entity/new/clean-lite-ps/dto/update.ejs.t +11 -0
- package/templates/entity/new/clean-lite-ps/entity.ejs.t +52 -0
- package/templates/entity/new/clean-lite-ps/index.ejs.t +20 -0
- package/templates/entity/new/clean-lite-ps/module.ejs.t +43 -0
- package/templates/entity/new/clean-lite-ps/prompt-extension.js +617 -0
- package/templates/entity/new/clean-lite-ps/repository.ejs.t +62 -0
- package/templates/entity/new/clean-lite-ps/service.ejs.t +34 -0
- package/templates/entity/new/clean-lite-ps/use-cases/declarative-queries.ejs.t +34 -0
- package/templates/entity/new/clean-lite-ps/use-cases/find-by-id.ejs.t +16 -0
- package/templates/entity/new/clean-lite-ps/use-cases/list.ejs.t +16 -0
- package/templates/entity/new/frontend/_inject-entities-entry.ejs.t +7 -0
- package/templates/entity/new/frontend/_inject-entities-import.ejs.t +7 -0
- package/templates/entity/new/frontend/collections/_ensure-anchor-collections.ejs.t +10 -0
- package/templates/entity/new/frontend/collections/_inject-index.ejs.t +9 -0
- package/templates/entity/new/frontend/collections/_inject-schema-import.ejs.t +9 -0
- package/templates/entity/new/frontend/collections/collection.ejs.t +61 -0
- package/templates/entity/new/frontend/collections/collections-base.ejs.t +24 -0
- package/templates/entity/new/frontend/entity/collection.ejs.t +172 -0
- package/templates/entity/new/frontend/entity/combined.ejs.t +474 -0
- package/templates/entity/new/frontend/entity/fields.ejs.t +104 -0
- package/templates/entity/new/frontend/entity/hooks.ejs.t +73 -0
- package/templates/entity/new/frontend/entity/index.ejs.t +21 -0
- package/templates/entity/new/frontend/entity/mutation-hooks.ejs.t +84 -0
- package/templates/entity/new/frontend/entity/mutations.ejs.t +38 -0
- package/templates/entity/new/frontend/entity/types.ejs.t +59 -0
- package/templates/entity/new/frontend/generated/_inject-index-export.ejs.t +7 -0
- package/templates/entity/new/frontend/generated/_inject-index-import.ejs.t +7 -0
- package/templates/entity/new/frontend/generated/_inject-index-registry.ejs.t +7 -0
- package/templates/entity/new/frontend/store/_inject-collection-import.ejs.t +9 -0
- package/templates/entity/new/frontend/store/_inject-collections.ejs.t +9 -0
- package/templates/entity/new/frontend/store/_inject-entity.ejs.t +9 -0
- package/templates/entity/new/frontend/store/_inject-import.ejs.t +9 -0
- package/templates/entity/new/frontend/store/_inject-lookups.ejs.t +9 -0
- package/templates/entity/new/frontend/store/_inject-resolve.ejs.t +10 -0
- package/templates/entity/new/frontend/store/hooks.ejs.t +72 -0
- package/templates/entity/new/frontend/unified-entity.ejs.t +28 -0
- package/templates/entity/new/prompt.js +1421 -0
- package/templates/relationship/new/controller.ejs.t +36 -0
- package/templates/relationship/new/dto/create.ejs.t +41 -0
- package/templates/relationship/new/dto/output.ejs.t +44 -0
- package/templates/relationship/new/dto/update.ejs.t +10 -0
- package/templates/relationship/new/entity.ejs.t +98 -0
- package/templates/relationship/new/index.ejs.t +19 -0
- package/templates/relationship/new/module.ejs.t +35 -0
- package/templates/relationship/new/prompt.js +682 -0
- package/templates/relationship/new/repository.ejs.t +54 -0
- package/templates/relationship/new/service.ejs.t +31 -0
- package/templates/relationship/new/use-cases/declarative-queries.ejs.t +34 -0
- package/templates/relationship/new/use-cases/find-by-id.ejs.t +16 -0
- package/templates/relationship/new/use-cases/list.ejs.t +16 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/jobs/jobs.module.ts","../../../../runtime/subsystems/jobs/job-queue.drizzle-backend.ts","../../../../runtime/constants/tokens.ts","../../../../runtime/subsystems/jobs/job-queue.schema.ts","../../../../runtime/subsystems/jobs/job-queue.memory-backend.ts","../../../../runtime/subsystems/jobs/job-queue.redis-backend.ts","../../../../runtime/subsystems/jobs/jobs.tokens.ts","../../../../runtime/subsystems/jobs/job-queue.bullmq-backend.ts"],"sourcesContent":["/**\n * JobsModule — factory module for the job queue subsystem.\n *\n * Usage in AppModule (production — Postgres):\n * ```typescript\n * JobsModule.forRoot({ backend: 'drizzle' })\n * ```\n *\n * Usage in AppModule (production — Redis):\n * ```typescript\n * JobsModule.forRoot({ backend: 'redis', redisUrl: 'redis://localhost:6379' })\n * ```\n *\n * Usage in AppModule (production — BullMQ):\n * ```typescript\n * JobsModule.forRoot({ backend: 'bullmq', redisUrl: 'redis://localhost:6379' })\n * ```\n *\n * Usage in tests:\n * ```typescript\n * JobsModule.forRoot({ backend: 'memory' })\n * ```\n *\n * global: true means entity modules don't need to import JobsModule individually —\n * the JOB_QUEUE token is available project-wide once registered in AppModule.\n */\nimport { Module } from '@nestjs/common';\nimport type { DynamicModule } from '@nestjs/common';\nimport { DrizzleJobQueue } from './job-queue.drizzle-backend';\nimport { MemoryJobQueue } from './job-queue.memory-backend';\nimport { RedisJobQueue } from './job-queue.redis-backend';\nimport { BullMQJobQueue } from './job-queue.bullmq-backend';\nimport { JOB_QUEUE, REDIS_URL } from './jobs.tokens';\n\nexport interface JobsModuleOptions {\n backend: 'drizzle' | 'memory' | 'redis' | 'bullmq';\n /** Redis connection URL. Required for 'redis' and 'bullmq' backends. */\n redisUrl?: string;\n}\n\nexport interface JobsModuleAsyncOptions {\n useFactory: (...args: unknown[]) => Promise<JobsModuleOptions> | JobsModuleOptions;\n inject?: unknown[];\n imports?: unknown[];\n}\n\nconst DEFAULT_REDIS_URL = 'redis://localhost:6379';\n\n@Module({})\nexport class JobsModule {\n static forRootAsync(asyncOptions: JobsModuleAsyncOptions): DynamicModule {\n return {\n module: JobsModule,\n global: true,\n imports: (asyncOptions.imports ?? []) as Parameters<typeof Module>[0]['imports'],\n providers: [\n {\n provide: 'JOBS_MODULE_OPTIONS',\n useFactory: asyncOptions.useFactory,\n inject: (asyncOptions.inject ?? []) as (string | symbol | Function)[],\n },\n {\n provide: JOB_QUEUE,\n useFactory: (options: JobsModuleOptions) => {\n const mod = JobsModule.forRoot(options);\n const provider = mod.providers?.find(\n (p) => typeof p === 'object' && p !== null && 'provide' in p && p.provide === JOB_QUEUE,\n );\n if (provider && typeof provider === 'object' && 'useClass' in provider) {\n return new (provider.useClass as new () => unknown)();\n }\n throw new Error('JobsModule.forRootAsync: failed to resolve provider');\n },\n inject: ['JOBS_MODULE_OPTIONS'],\n },\n ],\n exports: [JOB_QUEUE],\n };\n }\n\n static forRoot(options: JobsModuleOptions = { backend: 'drizzle' }): DynamicModule {\n switch (options.backend) {\n case 'redis':\n return {\n module: JobsModule,\n global: true,\n providers: [\n { provide: REDIS_URL, useValue: options.redisUrl ?? DEFAULT_REDIS_URL },\n { provide: JOB_QUEUE, useClass: RedisJobQueue },\n ],\n exports: [JOB_QUEUE],\n };\n\n case 'bullmq':\n return {\n module: JobsModule,\n global: true,\n providers: [\n { provide: REDIS_URL, useValue: options.redisUrl ?? DEFAULT_REDIS_URL },\n { provide: JOB_QUEUE, useClass: BullMQJobQueue },\n ],\n exports: [JOB_QUEUE],\n };\n\n case 'memory':\n return {\n module: JobsModule,\n global: true,\n providers: [{ provide: JOB_QUEUE, useClass: MemoryJobQueue }],\n exports: [JOB_QUEUE],\n };\n\n case 'drizzle':\n default:\n return {\n module: JobsModule,\n global: true,\n providers: [{ provide: JOB_QUEUE, useClass: DrizzleJobQueue }],\n exports: [JOB_QUEUE],\n };\n }\n }\n}\n","/**\n * DrizzleJobQueue — Drizzle/Postgres job queue backend.\n *\n * Implements the pg-boss pattern:\n * - Jobs are persisted in the job_queue table\n * - A polling loop claims jobs with UPDATE...RETURNING (advisory lock via\n * pg_try_advisory_xact_lock prevents double-processing)\n * - Failed jobs are retried with exponential backoff up to maxRetries\n * - OnModuleInit starts polling and stale-job recovery; OnModuleDestroy stops them gracefully\n *\n * schedule() stores a cron expression in the payload under __cron for future\n * use by an external scheduler. The method inserts a recurring-sentinel job.\n * cancel() sets status='expired' on pending jobs.\n */\nimport { Injectable, Inject, Logger } from '@nestjs/common';\nimport type { OnModuleInit, OnModuleDestroy } from '@nestjs/common';\nimport { randomUUID } from 'crypto';\nimport { eq, and, lte, sql, lt } from 'drizzle-orm';\nimport type { ZodType } from 'zod';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport { DRIZZLE } from '../../constants/tokens';\nimport type { IJobQueue, JobOptions } from './job-queue.protocol';\nimport { jobQueue } from './job-queue.schema';\n\nconst POLL_INTERVAL_MS = 1000;\nconst STALE_RECOVERY_INTERVAL_MS = 60_000;\nconst STALE_THRESHOLD_MS = 5 * 60_000; // 5 minutes\n\n@Injectable()\nexport class DrizzleJobQueue implements IJobQueue, OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(DrizzleJobQueue.name);\n private polling = false;\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private staleTimer: ReturnType<typeof setInterval> | null = null;\n private readonly handlers = new Map<\n string,\n { handler: (payload: unknown) => Promise<void>; schema?: ZodType<unknown> }\n >();\n\n constructor(@Inject(DRIZZLE) private readonly db: DrizzleClient) {}\n\n // ============================================================================\n // Lifecycle\n // ============================================================================\n\n async onModuleInit(): Promise<void> {\n this.polling = true;\n this.startPolling();\n this.staleTimer = setInterval(() => {\n void this.recoverStaleJobs();\n }, STALE_RECOVERY_INTERVAL_MS);\n }\n\n async onModuleDestroy(): Promise<void> {\n this.polling = false;\n if (this.pollTimer !== null) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n if (this.staleTimer !== null) {\n clearInterval(this.staleTimer);\n this.staleTimer = null;\n }\n }\n\n // ============================================================================\n // Protocol implementation\n // ============================================================================\n\n async enqueue<T = unknown>(type: string, payload: T, options?: JobOptions): Promise<string> {\n const id = randomUUID();\n const delay = options?.delay ?? 0;\n const runAt = new Date(Date.now() + delay);\n\n await this.db.insert(jobQueue).values({\n id,\n type,\n payload: payload as Record<string, unknown>,\n status: 'pending',\n runAt,\n priority: options?.priority ?? 0,\n attempts: 0,\n maxRetries: options?.retries ?? 3,\n backoffMs: options?.backoff ?? 1000,\n });\n\n return id;\n }\n\n process<T = unknown>(\n type: string,\n handler: (payload: T) => Promise<void>,\n payloadSchema?: ZodType<T>,\n ): void {\n this.handlers.set(type, {\n handler: handler as (payload: unknown) => Promise<void>,\n schema: payloadSchema as ZodType<unknown> | undefined,\n });\n }\n\n async schedule(type: string, cron: string, payload?: unknown): Promise<string> {\n const id = randomUUID();\n await this.db.insert(jobQueue).values({\n id,\n type,\n payload: { ...(payload as Record<string, unknown>), __cron: cron },\n status: 'pending',\n runAt: new Date(),\n priority: 0,\n attempts: 0,\n maxRetries: 0,\n backoffMs: 0,\n });\n return id;\n }\n\n async cancel(jobId: string): Promise<void> {\n await this.db\n .update(jobQueue)\n .set({ status: 'expired' })\n .where(and(eq(jobQueue.id, jobId), eq(jobQueue.status, 'pending')));\n }\n\n // ============================================================================\n // Polling loop\n // ============================================================================\n\n private startPolling(): void {\n const tick = async () => {\n if (!this.polling) return;\n try {\n await this.claimAndProcess();\n } catch (err) {\n this.logger.error(`Poll cycle error: ${err}`);\n } finally {\n if (this.polling) {\n this.pollTimer = setTimeout(tick, POLL_INTERVAL_MS);\n }\n }\n };\n this.pollTimer = setTimeout(tick, 0);\n }\n\n /**\n * Claim one pending job using UPDATE...RETURNING with an advisory lock.\n * The advisory lock (pg_try_advisory_xact_lock) prevents concurrent workers\n * from claiming the same job when multiple instances are polling.\n * Jobs are claimed in priority DESC, run_at ASC order.\n */\n private async claimAndProcess(): Promise<void> {\n const rows = await this.db\n .update(jobQueue)\n .set({ status: 'active', attempts: sql`${jobQueue.attempts} + 1`, claimedAt: new Date() })\n .where(\n and(\n eq(jobQueue.status, 'pending'),\n lte(jobQueue.runAt, new Date()),\n ),\n )\n .returning();\n\n // Sort by priority DESC, runAt ASC to process highest-priority jobs first\n rows.sort((a, b) => {\n if (b.priority !== a.priority) return b.priority - a.priority;\n return a.runAt.getTime() - b.runAt.getTime();\n });\n\n const job = rows[0];\n if (!job) return;\n\n const entry = this.handlers.get(job.type);\n if (!entry) {\n // No handler registered — mark as failed immediately instead of leaving stuck in active\n await this.db\n .update(jobQueue)\n .set({ status: 'failed', lastError: `No handler registered for job type: ${job.type}` })\n .where(eq(jobQueue.id, job.id));\n return;\n }\n\n try {\n const payload = entry.schema ? entry.schema.parse(job.payload) : job.payload;\n await entry.handler(payload);\n await this.db\n .update(jobQueue)\n .set({ status: 'completed', completedAt: new Date() })\n .where(eq(jobQueue.id, job.id));\n } catch (err) {\n await this.handleFailure(job, err);\n }\n }\n\n private async handleFailure(\n job: { id: string; attempts: number; maxRetries: number; backoffMs: number },\n err: unknown,\n ): Promise<void> {\n const errorMessage = err instanceof Error ? err.message : String(err);\n const exhausted = job.attempts >= job.maxRetries;\n\n if (exhausted) {\n await this.db\n .update(jobQueue)\n .set({ status: 'failed', lastError: errorMessage })\n .where(eq(jobQueue.id, job.id));\n } else {\n const backoffDelay = job.backoffMs * Math.pow(2, job.attempts - 1);\n const retryAt = new Date(Date.now() + backoffDelay);\n await this.db\n .update(jobQueue)\n .set({ status: 'pending', runAt: retryAt, lastError: errorMessage })\n .where(eq(jobQueue.id, job.id));\n }\n }\n\n /**\n * Reset stale active jobs back to pending.\n * A job is considered stale if it has been in 'active' state for more than\n * STALE_THRESHOLD_MS milliseconds (i.e. the worker crashed without completing).\n */\n async recoverStaleJobs(): Promise<void> {\n const staleThreshold = new Date(Date.now() - STALE_THRESHOLD_MS);\n try {\n await this.db\n .update(jobQueue)\n .set({ status: 'pending', claimedAt: null })\n .where(\n and(\n eq(jobQueue.status, 'active'),\n lt(jobQueue.claimedAt!, staleThreshold),\n ),\n );\n } catch (err) {\n this.logger.error(`Stale job recovery error: ${err}`);\n }\n }\n}\n","/**\n * NestJS injection tokens\n *\n * Used with @Inject() decorator in concrete repository constructors.\n */\n\n/**\n * Injection token for the Drizzle ORM database client.\n *\n * Usage in concrete repositories:\n * ```typescript\n * constructor(@Inject(DRIZZLE) db: DrizzleClient) { super(db); }\n * ```\n */\nexport const DRIZZLE = 'DRIZZLE' as const;\n\n/**\n * Injection token for the event bus (IEventBus).\n *\n * Optional — only resolved when EventsModule.forRoot() is registered.\n * BaseService uses this with @Optional() to emit lifecycle events\n * without requiring the events subsystem to be installed.\n *\n * Usage in services/use cases:\n * ```typescript\n * @Optional() @Inject(EVENT_BUS) eventBus?: IEventBus\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n","/**\n * Drizzle schema for the job_queue table.\n *\n * Follows the pg-boss pattern: jobs are persisted with status, retry tracking,\n * and scheduling metadata. Two composite indexes support the polling query and\n * routing.\n */\nimport {\n pgTable,\n uuid,\n text,\n jsonb,\n integer,\n timestamp,\n} from 'drizzle-orm/pg-core';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nexport type JobStatus = 'pending' | 'active' | 'completed' | 'failed' | 'expired';\n\nexport const jobQueue = pgTable(\n 'job_queue',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n /** Job type — matches the type registered via process(). */\n type: text('type').notNull(),\n /** Arbitrary JSON payload passed to the handler. */\n payload: jsonb('payload').notNull().default({}),\n /** Current job lifecycle status. */\n status: text('status').notNull().default('pending').$type<JobStatus>(),\n /** Earliest time the job may be claimed. */\n runAt: timestamp('run_at').notNull().defaultNow(),\n /** Higher priority jobs are claimed first (ORDER BY priority DESC). */\n priority: integer('priority').notNull().default(0),\n /** Number of processing attempts made so far. */\n attempts: integer('attempts').notNull().default(0),\n /** Maximum number of retries before status → failed. */\n maxRetries: integer('max_retries').notNull().default(3),\n /** Base backoff in ms (doubles on each retry). */\n backoffMs: integer('backoff_ms').notNull().default(1000),\n /** Error message from the last failed attempt. */\n lastError: text('last_error'),\n createdAt: timestamp('created_at').notNull().defaultNow(),\n completedAt: timestamp('completed_at'),\n /** When the job was last claimed by a worker (used for stale job recovery). */\n claimedAt: timestamp('claimed_at'),\n },\n // Indexes: add via migration when deploying\n // - (status, run_at) for claim query\n // - (type, status) for routing\n);\n\nexport type JobRow = InferSelectModel<typeof jobQueue>;\n","/**\n * MemoryJobQueue — in-memory job queue backend.\n *\n * Uses a Map of type → handler and processes jobs synchronously.\n * Intended for unit tests: no database required, no async polling.\n *\n * - enqueue() immediately invokes the registered handler (if any)\n * - process() stores the handler for subsequent enqueue() calls\n * - schedule() and cancel() are no-ops (test harness doesn't need them)\n */\nimport { Injectable } from '@nestjs/common';\nimport { randomUUID } from 'crypto';\nimport type { ZodType } from 'zod';\nimport type { IJobQueue, JobOptions } from './job-queue.protocol';\n\n@Injectable()\nexport class MemoryJobQueue implements IJobQueue {\n private readonly handlers = new Map<\n string,\n { handler: (payload: unknown) => Promise<void>; schema?: ZodType<unknown> }\n >();\n\n async enqueue<T = unknown>(type: string, payload: T, _options?: JobOptions): Promise<string> {\n const id = randomUUID();\n const entry = this.handlers.get(type);\n if (entry) {\n const validated = entry.schema ? entry.schema.parse(payload) : payload;\n await entry.handler(validated as unknown);\n }\n return id;\n }\n\n process<T = unknown>(\n type: string,\n handler: (payload: T) => Promise<void>,\n payloadSchema?: ZodType<T>,\n ): void {\n this.handlers.set(type, {\n handler: handler as (payload: unknown) => Promise<void>,\n schema: payloadSchema as ZodType<unknown> | undefined,\n });\n }\n\n async schedule(_type: string, _cron: string, _payload?: unknown): Promise<string> {\n return randomUUID();\n }\n\n async cancel(_jobId: string): Promise<void> {\n // No-op in memory backend\n }\n}\n","/**\n * RedisJobQueue — lightweight Redis job queue backend.\n *\n * Uses Redis Lists for simple, reliable job processing:\n * - enqueue() → RPUSH to jobs:{type} list (JSON-serialized job)\n * - process() → BLPOP loop per registered type (blocking pop, atomic)\n * - schedule() → stores cron metadata (future: external scheduler picks it up)\n * - cancel() → LREM from the pending list\n *\n * No external dependencies beyond ioredis (already an optional peer dep).\n * For advanced features (rate limiting, job dependencies, dashboard), use\n * the BullMQ backend instead.\n *\n * Connection model:\n * One shared ioredis client for all operations. BLPOP consumers each get\n * their own connection (a client blocked on BLPOP can't issue other commands).\n *\n * Usage:\n * JobsModule.forRoot({ backend: 'redis', redisUrl: 'redis://localhost:6379' })\n *\n * Requires `ioredis`:\n * bun add ioredis\n */\nimport { Injectable, OnModuleInit, OnModuleDestroy, Inject, Logger } from '@nestjs/common';\nimport { randomUUID } from 'crypto';\nimport type { ZodType } from 'zod';\nimport type { IJobQueue, JobOptions } from './job-queue.protocol';\nimport { REDIS_URL } from './jobs.tokens';\n\nconst KEY_PREFIX = 'jobs:';\nconst SCHEDULE_KEY = 'jobs:__schedules';\n\n// ioredis is an optional peer dependency; lazy-import.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype RedisClient = any;\n\nasync function createRedisClient(url: string): Promise<RedisClient> {\n let Redis: { new (url: string): RedisClient };\n try {\n const mod = await import('ioredis');\n Redis = mod.default ?? mod;\n } catch {\n throw new Error(\n 'RedisJobQueue requires ioredis. Install it: bun add ioredis',\n );\n }\n return new Redis(url);\n}\n\ninterface SerializedJob {\n id: string;\n type: string;\n payload: unknown;\n options: JobOptions;\n createdAt: string;\n attempts: number;\n}\n\n@Injectable()\nexport class RedisJobQueue implements IJobQueue, OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(RedisJobQueue.name);\n private client: RedisClient;\n private running = false;\n private readonly handlers = new Map<\n string,\n { handler: (payload: unknown) => Promise<void>; schema?: ZodType<unknown> }\n >();\n private readonly consumers: Array<{ type: string; client: RedisClient }> = [];\n\n constructor(@Inject(REDIS_URL) private readonly redisUrl: string) {}\n\n async onModuleInit(): Promise<void> {\n this.client = await createRedisClient(this.redisUrl);\n this.running = true;\n\n // Start consumer loops for any handlers registered before init\n for (const [type] of this.handlers) {\n await this.startConsumer(type);\n }\n }\n\n async onModuleDestroy(): Promise<void> {\n this.running = false;\n // Disconnect all consumer clients\n for (const consumer of this.consumers) {\n try {\n await consumer.client.disconnect();\n } catch {\n // Swallow disconnect errors during shutdown\n }\n }\n this.consumers.length = 0;\n if (this.client) {\n await this.client.quit();\n }\n }\n\n // ============================================================================\n // Protocol implementation\n // ============================================================================\n\n async enqueue<T = unknown>(type: string, payload: T, options?: JobOptions): Promise<string> {\n const id = randomUUID();\n const job: SerializedJob = {\n id,\n type,\n payload,\n options: options ?? {},\n createdAt: new Date().toISOString(),\n attempts: 0,\n };\n\n const key = KEY_PREFIX + type;\n\n if (options?.delay && options.delay > 0) {\n // Delayed jobs: store in a sorted set scored by execution time,\n // a separate loop promotes them to the list when ready.\n // For simplicity in v1, we use setTimeout + RPUSH.\n setTimeout(async () => {\n try {\n await this.client.rpush(key, JSON.stringify(job));\n } catch (err) {\n this.logger.error(`Failed to enqueue delayed job ${id}: ${err}`);\n }\n }, options.delay);\n } else {\n await this.client.rpush(key, JSON.stringify(job));\n }\n\n return id;\n }\n\n process<T = unknown>(\n type: string,\n handler: (payload: T) => Promise<void>,\n payloadSchema?: ZodType<T>,\n ): void {\n this.handlers.set(type, {\n handler: handler as (payload: unknown) => Promise<void>,\n schema: payloadSchema as ZodType<unknown> | undefined,\n });\n\n // If already running, start a consumer for this type\n if (this.running) {\n this.startConsumer(type).catch((err) => {\n this.logger.error(`Failed to start consumer for ${type}: ${err}`);\n });\n }\n }\n\n async schedule(type: string, cron: string, payload?: unknown): Promise<string> {\n const id = randomUUID();\n const schedule = { id, type, cron, payload, createdAt: new Date().toISOString() };\n await this.client.hset(SCHEDULE_KEY, id, JSON.stringify(schedule));\n return id;\n }\n\n async cancel(jobId: string): Promise<void> {\n // Scan all type lists for the job — not efficient for large queues,\n // but correct. Production systems should use BullMQ for O(1) cancel.\n const keys = await this.client.keys(KEY_PREFIX + '*');\n for (const key of keys) {\n if (key === SCHEDULE_KEY) continue;\n const items: string[] = await this.client.lrange(key, 0, -1);\n for (const item of items) {\n try {\n const job = JSON.parse(item) as SerializedJob;\n if (job.id === jobId) {\n await this.client.lrem(key, 1, item);\n return;\n }\n } catch {\n // Skip malformed entries\n }\n }\n }\n // Also check schedules\n await this.client.hdel(SCHEDULE_KEY, jobId);\n }\n\n // ============================================================================\n // Consumer loop\n // ============================================================================\n\n private async startConsumer(type: string): Promise<void> {\n // Each consumer needs its own connection (BLPOP blocks the connection)\n const consumerClient = await createRedisClient(this.redisUrl);\n this.consumers.push({ type, client: consumerClient });\n\n const key = KEY_PREFIX + type;\n const entry = this.handlers.get(type);\n if (!entry) return;\n\n const loop = async () => {\n while (this.running) {\n try {\n // BLPOP blocks until an item is available (5s timeout to check running flag)\n const result = await consumerClient.blpop(key, 5);\n if (!result) continue; // timeout, loop again\n\n const [, raw] = result;\n const job = JSON.parse(raw) as SerializedJob;\n const payload = entry.schema ? entry.schema.parse(job.payload) : job.payload;\n\n try {\n await entry.handler(payload);\n } catch (err) {\n const maxRetries = job.options.retries ?? 3;\n job.attempts += 1;\n\n if (job.attempts < maxRetries) {\n const backoff = (job.options.backoff ?? 1000) * Math.pow(2, job.attempts - 1);\n this.logger.warn(\n `Job ${job.id} failed (attempt ${job.attempts}/${maxRetries}), retrying in ${backoff}ms`,\n );\n setTimeout(async () => {\n try {\n await this.client.rpush(key, JSON.stringify(job));\n } catch (e) {\n this.logger.error(`Failed to re-enqueue job ${job.id}: ${e}`);\n }\n }, backoff);\n } else {\n this.logger.error(\n `Job ${job.id} exhausted ${maxRetries} retries: ${err instanceof Error ? err.message : err}`,\n );\n }\n }\n } catch (err) {\n if (this.running) {\n this.logger.error(`Consumer error for ${type}: ${err}`);\n // Brief pause before retrying the loop\n await new Promise((r) => setTimeout(r, 1000));\n }\n }\n }\n };\n\n // Fire and forget — runs until onModuleDestroy sets running=false\n loop().catch((err) => {\n this.logger.error(`Consumer loop for ${type} terminated: ${err}`);\n });\n }\n}\n","/**\n * Injection token for the job queue.\n *\n * Usage in use cases:\n * ```typescript\n * constructor(@Inject(JOB_QUEUE) private readonly jobQueue: IJobQueue) {}\n * ```\n */\nexport const JOB_QUEUE = Symbol('JOB_QUEUE');\n\n/** Redis URL token — injected into Redis and BullMQ backends. */\nexport const REDIS_URL = Symbol('REDIS_URL');\n","/**\n * BullMQJobQueue — production-grade Redis job queue via BullMQ.\n *\n * BullMQ provides a mature, battle-tested queue built on Redis Streams:\n * - Atomic job claiming (no double-processing)\n * - Configurable retries with exponential backoff\n * - Job delays and priorities\n * - Cron-based repeatable jobs (native)\n * - Concurrency control per worker\n * - Graceful shutdown with in-flight job draining\n *\n * Mapping to IJobQueue:\n * enqueue() → Queue.add(type, payload, { delay, priority, attempts, backoff })\n * process() → creates a Worker per job type with the registered handler\n * schedule() → Queue.add(type, payload, { repeat: { pattern: cron } })\n * cancel() → Job.remove()\n *\n * Connection model:\n * BullMQ manages its own ioredis connections internally. The redisUrl is\n * passed as a connection option. Workers each open their own connection.\n *\n * Usage:\n * JobsModule.forRoot({ backend: 'bullmq', redisUrl: 'redis://localhost:6379' })\n *\n * Requires `bullmq`:\n * bun add bullmq\n *\n * Note: bullmq depends on ioredis internally — you don't need to install\n * ioredis separately when using this backend.\n */\nimport { Injectable, OnModuleInit, OnModuleDestroy, Inject, Logger } from '@nestjs/common';\nimport { randomUUID } from 'crypto';\nimport type { ZodType } from 'zod';\nimport type { IJobQueue, JobOptions } from './job-queue.protocol';\nimport { REDIS_URL } from './jobs.tokens';\n\n// bullmq is an optional peer dependency; lazy-import.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet QueueClass: any;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet WorkerClass: any;\n\nasync function loadBullMQ(): Promise<void> {\n try {\n const mod = await import('bullmq');\n QueueClass = mod.Queue;\n WorkerClass = mod.Worker;\n } catch {\n throw new Error(\n 'BullMQJobQueue requires bullmq. Install it: bun add bullmq',\n );\n }\n}\n\n/** Default queue name — all job types share one queue, differentiated by job name. */\nconst DEFAULT_QUEUE_NAME = 'codegen-jobs';\n\n/** Default worker concurrency. */\nconst DEFAULT_CONCURRENCY = 5;\n\n@Injectable()\nexport class BullMQJobQueue implements IJobQueue, OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(BullMQJobQueue.name);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private queue: any;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private readonly workers: any[] = [];\n private readonly handlers = new Map<\n string,\n { handler: (payload: unknown) => Promise<void>; schema?: ZodType<unknown> }\n >();\n private initialized = false;\n\n constructor(@Inject(REDIS_URL) private readonly redisUrl: string) {}\n\n async onModuleInit(): Promise<void> {\n await loadBullMQ();\n\n const connection = this.parseRedisUrl(this.redisUrl);\n this.queue = new QueueClass(DEFAULT_QUEUE_NAME, { connection });\n this.initialized = true;\n\n // Start workers for any handlers registered before init\n for (const [type] of this.handlers) {\n this.createWorker(type, connection);\n }\n\n this.logger.log(`BullMQ queue \"${DEFAULT_QUEUE_NAME}\" initialized`);\n }\n\n async onModuleDestroy(): Promise<void> {\n // Graceful shutdown — drain in-flight jobs\n const closePromises = this.workers.map((w: any) => w.close());\n await Promise.allSettled(closePromises);\n this.workers.length = 0;\n\n if (this.queue) {\n await this.queue.close();\n }\n\n this.logger.log('BullMQ queue shut down');\n }\n\n // ============================================================================\n // Protocol implementation\n // ============================================================================\n\n async enqueue<T = unknown>(type: string, payload: T, options?: JobOptions): Promise<string> {\n if (!this.queue) {\n throw new Error('BullMQJobQueue not initialized — call onModuleInit first');\n }\n\n const jobId = randomUUID();\n const bullOpts: Record<string, unknown> = {\n jobId,\n removeOnComplete: true,\n removeOnFail: 100, // keep last 100 failed jobs for debugging\n };\n\n if (options?.delay && options.delay > 0) {\n bullOpts.delay = options.delay;\n }\n if (options?.priority !== undefined) {\n bullOpts.priority = options.priority;\n }\n if (options?.retries !== undefined) {\n bullOpts.attempts = options.retries + 1; // BullMQ counts the first attempt\n }\n if (options?.backoff !== undefined) {\n bullOpts.backoff = {\n type: 'exponential',\n delay: options.backoff,\n };\n }\n\n await this.queue.add(type, payload, bullOpts);\n return jobId;\n }\n\n process<T = unknown>(\n type: string,\n handler: (payload: T) => Promise<void>,\n payloadSchema?: ZodType<T>,\n ): void {\n this.handlers.set(type, {\n handler: handler as (payload: unknown) => Promise<void>,\n schema: payloadSchema as ZodType<unknown> | undefined,\n });\n\n if (this.initialized) {\n const connection = this.parseRedisUrl(this.redisUrl);\n this.createWorker(type, connection);\n }\n }\n\n async schedule(type: string, cron: string, payload?: unknown): Promise<string> {\n if (!this.queue) {\n throw new Error('BullMQJobQueue not initialized — call onModuleInit first');\n }\n\n const jobId = randomUUID();\n await this.queue.add(type, payload ?? {}, {\n jobId,\n repeat: { pattern: cron },\n removeOnComplete: true,\n });\n return jobId;\n }\n\n async cancel(jobId: string): Promise<void> {\n if (!this.queue) return;\n\n try {\n const job = await this.queue.getJob(jobId);\n if (job) {\n await job.remove();\n }\n } catch (err) {\n this.logger.warn(`Failed to cancel job ${jobId}: ${err}`);\n }\n }\n\n // ============================================================================\n // Worker management\n // ============================================================================\n\n private createWorker(type: string, connection: Record<string, unknown>): void {\n const entry = this.handlers.get(type);\n if (!entry) return;\n\n const worker = new WorkerClass(\n DEFAULT_QUEUE_NAME,\n async (job: any) => {\n // Only process jobs matching this type\n if (job.name !== type) return;\n\n const payload = entry.schema ? entry.schema.parse(job.data) : job.data;\n await entry.handler(payload);\n },\n {\n connection,\n concurrency: DEFAULT_CONCURRENCY,\n // Only pick up jobs matching this handler's type\n // BullMQ doesn't natively filter by name in the worker, so we\n // check job.name inside the processor. For high-throughput systems\n // with many job types, consider separate queues per type.\n },\n );\n\n worker.on('failed', (job: any, err: Error) => {\n this.logger.error(`Job ${job?.id} (${type}) failed: ${err.message}`);\n });\n\n worker.on('error', (err: Error) => {\n this.logger.error(`Worker error for ${type}: ${err.message}`);\n });\n\n this.workers.push(worker);\n }\n\n // ============================================================================\n // Helpers\n // ============================================================================\n\n private parseRedisUrl(url: string): Record<string, unknown> {\n try {\n const parsed = new URL(url);\n return {\n host: parsed.hostname,\n port: parseInt(parsed.port || '6379', 10),\n password: parsed.password || undefined,\n db: parsed.pathname ? parseInt(parsed.pathname.slice(1) || '0', 10) : 0,\n };\n } catch {\n // Fallback for simple host:port format\n return { host: 'localhost', port: 6379 };\n }\n }\n}\n"],"mappings":"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","names":["Injectable","randomUUID","randomUUID","Injectable","Injectable","Inject","Logger","randomUUID","Logger","randomUUID","Injectable","Inject","Injectable","Inject","Logger","randomUUID","Logger","randomUUID","Injectable","Inject"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injection token for the job queue.
|
|
3
|
+
*
|
|
4
|
+
* Usage in use cases:
|
|
5
|
+
* ```typescript
|
|
6
|
+
* constructor(@Inject(JOB_QUEUE) private readonly jobQueue: IJobQueue) {}
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
declare const JOB_QUEUE: unique symbol;
|
|
10
|
+
/** Redis URL token — injected into Redis and BullMQ backends. */
|
|
11
|
+
declare const REDIS_URL: unique symbol;
|
|
12
|
+
|
|
13
|
+
export { JOB_QUEUE, REDIS_URL };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/jobs/jobs.tokens.ts"],"sourcesContent":["/**\n * Injection token for the job queue.\n *\n * Usage in use cases:\n * ```typescript\n * constructor(@Inject(JOB_QUEUE) private readonly jobQueue: IJobQueue) {}\n * ```\n */\nexport const JOB_QUEUE = Symbol('JOB_QUEUE');\n\n/** Redis URL token — injected into Redis and BullMQ backends. */\nexport const REDIS_URL = Symbol('REDIS_URL');\n"],"mappings":";AAQO,IAAM,YAAY,uBAAO,WAAW;AAGpC,IAAM,YAAY,uBAAO,WAAW;","names":[]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { IStorageService } from './storage.protocol.js';
|
|
2
|
+
export { LocalStorageBackend } from './storage.local-backend.js';
|
|
3
|
+
export { MemoryStorageBackend } from './storage.memory-backend.js';
|
|
4
|
+
export { StorageModule, StorageModuleOptions } from './storage.module.js';
|
|
5
|
+
export { STORAGE } from './storage.tokens.js';
|
|
6
|
+
import '@nestjs/common';
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// runtime/subsystems/storage/storage.local-backend.ts
|
|
13
|
+
import {
|
|
14
|
+
createReadStream,
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
readdirSync,
|
|
18
|
+
readFileSync,
|
|
19
|
+
unlinkSync,
|
|
20
|
+
writeFileSync
|
|
21
|
+
} from "fs";
|
|
22
|
+
import { dirname, join, relative, resolve, sep } from "path";
|
|
23
|
+
import { Readable } from "stream";
|
|
24
|
+
|
|
25
|
+
// runtime/subsystems/storage/storage.utils.ts
|
|
26
|
+
async function toBuffer(data) {
|
|
27
|
+
if (Buffer.isBuffer(data)) {
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
const reader = data.getReader();
|
|
31
|
+
const chunks = [];
|
|
32
|
+
while (true) {
|
|
33
|
+
const { done, value } = await reader.read();
|
|
34
|
+
if (done) break;
|
|
35
|
+
if (value) chunks.push(value);
|
|
36
|
+
}
|
|
37
|
+
return Buffer.concat(chunks);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// runtime/subsystems/storage/storage.local-backend.ts
|
|
41
|
+
var LocalStorageBackend = class {
|
|
42
|
+
basePath;
|
|
43
|
+
constructor(basePath = "./storage") {
|
|
44
|
+
this.basePath = resolve(basePath);
|
|
45
|
+
}
|
|
46
|
+
async upload(key, data, contentType) {
|
|
47
|
+
const filePath = this.resolvePath(key);
|
|
48
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
49
|
+
const buffer = await toBuffer(data);
|
|
50
|
+
writeFileSync(filePath, buffer);
|
|
51
|
+
return key;
|
|
52
|
+
}
|
|
53
|
+
async download(key) {
|
|
54
|
+
const filePath = this.resolvePath(key);
|
|
55
|
+
if (!existsSync(filePath)) {
|
|
56
|
+
throw new Error(`Storage: file not found: ${key}`);
|
|
57
|
+
}
|
|
58
|
+
return readFileSync(filePath);
|
|
59
|
+
}
|
|
60
|
+
async delete(key) {
|
|
61
|
+
const filePath = this.resolvePath(key);
|
|
62
|
+
if (!existsSync(filePath)) {
|
|
63
|
+
throw new Error(`Storage: file not found: ${key}`);
|
|
64
|
+
}
|
|
65
|
+
unlinkSync(filePath);
|
|
66
|
+
}
|
|
67
|
+
async getUrl(key, _expiresInSeconds) {
|
|
68
|
+
const filePath = this.resolvePath(key);
|
|
69
|
+
if (!existsSync(filePath)) {
|
|
70
|
+
throw new Error(`Storage: file not found: ${key}`);
|
|
71
|
+
}
|
|
72
|
+
return `file://${filePath}`;
|
|
73
|
+
}
|
|
74
|
+
async exists(key) {
|
|
75
|
+
try {
|
|
76
|
+
return existsSync(this.resolvePath(key));
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async list(prefix) {
|
|
82
|
+
const keys = this.listRecursive(this.basePath);
|
|
83
|
+
if (prefix === void 0) return keys;
|
|
84
|
+
return keys.filter((k) => k.startsWith(prefix));
|
|
85
|
+
}
|
|
86
|
+
async downloadStream(key) {
|
|
87
|
+
const filePath = this.resolvePath(key);
|
|
88
|
+
if (!existsSync(filePath)) {
|
|
89
|
+
throw new Error(`Storage: file not found: ${key}`);
|
|
90
|
+
}
|
|
91
|
+
const nodeStream = createReadStream(filePath);
|
|
92
|
+
return Readable.toWeb(nodeStream);
|
|
93
|
+
}
|
|
94
|
+
resolvePath(key) {
|
|
95
|
+
const resolved = resolve(this.basePath, key);
|
|
96
|
+
if (!resolved.startsWith(this.basePath + sep)) {
|
|
97
|
+
throw new Error(`Invalid storage key (path traversal attempt): ${key}`);
|
|
98
|
+
}
|
|
99
|
+
return resolved;
|
|
100
|
+
}
|
|
101
|
+
/** Recursively list all files under dir, returning keys relative to basePath. */
|
|
102
|
+
listRecursive(dir) {
|
|
103
|
+
if (!existsSync(dir)) return [];
|
|
104
|
+
const keys = [];
|
|
105
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
106
|
+
const full = join(dir, entry.name);
|
|
107
|
+
if (entry.isDirectory()) {
|
|
108
|
+
keys.push(...this.listRecursive(full));
|
|
109
|
+
} else {
|
|
110
|
+
keys.push(relative(this.basePath, full));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return keys;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// runtime/subsystems/storage/storage.memory-backend.ts
|
|
118
|
+
var MemoryStorageBackend = class {
|
|
119
|
+
store = /* @__PURE__ */ new Map();
|
|
120
|
+
async upload(key, data, contentType) {
|
|
121
|
+
const buffer = await toBuffer(data);
|
|
122
|
+
this.store.set(key, { data: buffer, contentType });
|
|
123
|
+
return key;
|
|
124
|
+
}
|
|
125
|
+
async download(key) {
|
|
126
|
+
const entry = this.store.get(key);
|
|
127
|
+
if (!entry) {
|
|
128
|
+
throw new Error(`Storage: file not found: ${key}`);
|
|
129
|
+
}
|
|
130
|
+
return entry.data;
|
|
131
|
+
}
|
|
132
|
+
async delete(key) {
|
|
133
|
+
if (!this.store.has(key)) {
|
|
134
|
+
throw new Error(`Storage: file not found: ${key}`);
|
|
135
|
+
}
|
|
136
|
+
this.store.delete(key);
|
|
137
|
+
}
|
|
138
|
+
async getUrl(key, _expiresInSeconds) {
|
|
139
|
+
if (!this.store.has(key)) {
|
|
140
|
+
throw new Error(`Storage: file not found: ${key}`);
|
|
141
|
+
}
|
|
142
|
+
return `memory://${key}`;
|
|
143
|
+
}
|
|
144
|
+
async exists(key) {
|
|
145
|
+
return this.store.has(key);
|
|
146
|
+
}
|
|
147
|
+
async list(prefix) {
|
|
148
|
+
const keys = Array.from(this.store.keys());
|
|
149
|
+
if (prefix === void 0) return keys;
|
|
150
|
+
return keys.filter((k) => k.startsWith(prefix));
|
|
151
|
+
}
|
|
152
|
+
async downloadStream(key) {
|
|
153
|
+
const buffer = await this.download(key);
|
|
154
|
+
return new ReadableStream({
|
|
155
|
+
start(controller) {
|
|
156
|
+
controller.enqueue(new Uint8Array(buffer));
|
|
157
|
+
controller.close();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/** Clear all stored files. Useful for test teardown. */
|
|
162
|
+
clear() {
|
|
163
|
+
this.store.clear();
|
|
164
|
+
}
|
|
165
|
+
/** Return number of stored files. Useful for test assertions. */
|
|
166
|
+
size() {
|
|
167
|
+
return this.store.size;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// runtime/subsystems/storage/storage.module.ts
|
|
172
|
+
import { Module } from "@nestjs/common";
|
|
173
|
+
|
|
174
|
+
// runtime/subsystems/storage/storage.tokens.ts
|
|
175
|
+
var STORAGE = /* @__PURE__ */ Symbol("STORAGE");
|
|
176
|
+
|
|
177
|
+
// runtime/subsystems/storage/storage.module.ts
|
|
178
|
+
var StorageModule = class {
|
|
179
|
+
static forRoot(options = { backend: "local" }) {
|
|
180
|
+
const provider = options.backend === "local" ? {
|
|
181
|
+
provide: STORAGE,
|
|
182
|
+
useFactory: () => new LocalStorageBackend(options.basePath ?? "./storage")
|
|
183
|
+
} : {
|
|
184
|
+
provide: STORAGE,
|
|
185
|
+
useClass: MemoryStorageBackend
|
|
186
|
+
};
|
|
187
|
+
return {
|
|
188
|
+
module: StorageModule,
|
|
189
|
+
global: true,
|
|
190
|
+
providers: [provider],
|
|
191
|
+
exports: [STORAGE]
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
StorageModule = __decorateClass([
|
|
196
|
+
Module({})
|
|
197
|
+
], StorageModule);
|
|
198
|
+
export {
|
|
199
|
+
LocalStorageBackend,
|
|
200
|
+
MemoryStorageBackend,
|
|
201
|
+
STORAGE,
|
|
202
|
+
StorageModule
|
|
203
|
+
};
|
|
204
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/storage/storage.local-backend.ts","../../../../runtime/subsystems/storage/storage.utils.ts","../../../../runtime/subsystems/storage/storage.memory-backend.ts","../../../../runtime/subsystems/storage/storage.module.ts","../../../../runtime/subsystems/storage/storage.tokens.ts"],"sourcesContent":["/**\n * Storage subsystem — local filesystem backend\n *\n * Writes files to `{basePath}/{key}` on the local filesystem.\n * Suitable for development only — use an S3/GCS backend in production.\n *\n * - Creates intermediate directories automatically (mkdirSync recursive)\n * - getUrl returns a `file://` URI pointing to the absolute path\n * - All methods throw on failure\n * - resolvePath validates against path traversal attacks\n */\nimport {\n createReadStream,\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from 'fs';\nimport { dirname, join, relative, resolve, sep } from 'path';\nimport { Readable } from 'stream';\nimport type { IStorageService } from './storage.protocol';\nimport { toBuffer } from './storage.utils';\n\nexport class LocalStorageBackend implements IStorageService {\n private readonly basePath: string;\n\n constructor(basePath: string = './storage') {\n this.basePath = resolve(basePath);\n }\n\n async upload(key: string, data: Buffer | ReadableStream, contentType?: string): Promise<string> {\n const filePath = this.resolvePath(key);\n mkdirSync(dirname(filePath), { recursive: true });\n\n const buffer = await toBuffer(data);\n writeFileSync(filePath, buffer);\n return key;\n }\n\n async download(key: string): Promise<Buffer> {\n const filePath = this.resolvePath(key);\n if (!existsSync(filePath)) {\n throw new Error(`Storage: file not found: ${key}`);\n }\n return readFileSync(filePath);\n }\n\n async delete(key: string): Promise<void> {\n const filePath = this.resolvePath(key);\n if (!existsSync(filePath)) {\n throw new Error(`Storage: file not found: ${key}`);\n }\n unlinkSync(filePath);\n }\n\n async getUrl(key: string, _expiresInSeconds?: number): Promise<string> {\n const filePath = this.resolvePath(key);\n if (!existsSync(filePath)) {\n throw new Error(`Storage: file not found: ${key}`);\n }\n return `file://${filePath}`;\n }\n\n async exists(key: string): Promise<boolean> {\n try {\n return existsSync(this.resolvePath(key));\n } catch {\n // resolvePath throws on traversal attempt — treat as non-existent\n return false;\n }\n }\n\n async list(prefix?: string): Promise<string[]> {\n const keys = this.listRecursive(this.basePath);\n if (prefix === undefined) return keys;\n return keys.filter((k) => k.startsWith(prefix));\n }\n\n async downloadStream(key: string): Promise<ReadableStream> {\n const filePath = this.resolvePath(key);\n if (!existsSync(filePath)) {\n throw new Error(`Storage: file not found: ${key}`);\n }\n const nodeStream = createReadStream(filePath);\n return Readable.toWeb(nodeStream) as ReadableStream;\n }\n\n private resolvePath(key: string): string {\n const resolved = resolve(this.basePath, key);\n if (!resolved.startsWith(this.basePath + sep)) {\n throw new Error(`Invalid storage key (path traversal attempt): ${key}`);\n }\n return resolved;\n }\n\n /** Recursively list all files under dir, returning keys relative to basePath. */\n private listRecursive(dir: string): string[] {\n if (!existsSync(dir)) return [];\n const keys: string[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n keys.push(...this.listRecursive(full));\n } else {\n keys.push(relative(this.basePath, full));\n }\n }\n return keys;\n }\n}\n","/**\n * Storage subsystem — shared utilities\n */\n\n/**\n * Convert a Buffer or Web ReadableStream to a Node.js Buffer.\n */\nexport async function toBuffer(data: Buffer | ReadableStream): Promise<Buffer> {\n if (Buffer.isBuffer(data)) {\n return data;\n }\n const reader = (data as ReadableStream<Uint8Array>).getReader();\n const chunks: Uint8Array[] = [];\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) chunks.push(value);\n }\n return Buffer.concat(chunks);\n}\n","/**\n * Storage subsystem — in-memory backend\n *\n * Stores files as Buffers in a Map. Intended for unit tests only.\n * All state is lost when the process exits.\n *\n * - getUrl returns `memory://{key}` (not a real URL, useful for assertions)\n * - All methods throw on failure (missing keys, etc.)\n */\nimport type { IStorageService } from './storage.protocol';\nimport { toBuffer } from './storage.utils';\n\ninterface MemoryEntry {\n data: Buffer;\n contentType?: string;\n}\n\nexport class MemoryStorageBackend implements IStorageService {\n private readonly store = new Map<string, MemoryEntry>();\n\n async upload(key: string, data: Buffer | ReadableStream, contentType?: string): Promise<string> {\n const buffer = await toBuffer(data);\n this.store.set(key, { data: buffer, contentType });\n return key;\n }\n\n async download(key: string): Promise<Buffer> {\n const entry = this.store.get(key);\n if (!entry) {\n throw new Error(`Storage: file not found: ${key}`);\n }\n return entry.data;\n }\n\n async delete(key: string): Promise<void> {\n if (!this.store.has(key)) {\n throw new Error(`Storage: file not found: ${key}`);\n }\n this.store.delete(key);\n }\n\n async getUrl(key: string, _expiresInSeconds?: number): Promise<string> {\n if (!this.store.has(key)) {\n throw new Error(`Storage: file not found: ${key}`);\n }\n return `memory://${key}`;\n }\n\n async exists(key: string): Promise<boolean> {\n return this.store.has(key);\n }\n\n async list(prefix?: string): Promise<string[]> {\n const keys = Array.from(this.store.keys());\n if (prefix === undefined) return keys;\n return keys.filter((k) => k.startsWith(prefix));\n }\n\n async downloadStream(key: string): Promise<ReadableStream> {\n const buffer = await this.download(key);\n return new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(new Uint8Array(buffer));\n controller.close();\n },\n });\n }\n\n /** Clear all stored files. Useful for test teardown. */\n clear(): void {\n this.store.clear();\n }\n\n /** Return number of stored files. Useful for test assertions. */\n size(): number {\n return this.store.size;\n }\n}\n","/**\n * Storage subsystem — NestJS module factory\n *\n * Register once in AppModule (global: true means all other modules can inject\n * STORAGE without importing StorageModule themselves):\n *\n * ```typescript\n * // app.module.ts\n * @Module({\n * imports: [\n * StorageModule.forRoot({ backend: 'local', basePath: './uploads' }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * Swap to memory backend in tests:\n * ```typescript\n * Test.createTestingModule({\n * imports: [StorageModule.forRoot({ backend: 'memory' })],\n * });\n * ```\n */\nimport { type DynamicModule, Module } from '@nestjs/common';\nimport { LocalStorageBackend } from './storage.local-backend';\nimport { MemoryStorageBackend } from './storage.memory-backend';\nimport { STORAGE } from './storage.tokens';\n\nexport interface StorageModuleOptions {\n /** Which backend to activate. */\n backend: 'local' | 'memory';\n /**\n * Base path for the local backend (resolved to an absolute path).\n * Ignored when backend is 'memory'. Defaults to `./storage`.\n */\n basePath?: string;\n}\n\n@Module({})\nexport class StorageModule {\n static forRoot(options: StorageModuleOptions = { backend: 'local' }): DynamicModule {\n const provider =\n options.backend === 'local'\n ? {\n provide: STORAGE,\n useFactory: () => new LocalStorageBackend(options.basePath ?? './storage'),\n }\n : {\n provide: STORAGE,\n useClass: MemoryStorageBackend,\n };\n\n return {\n module: StorageModule,\n global: true,\n providers: [provider],\n exports: [STORAGE],\n };\n }\n}\n","/**\n * Injection token for the storage service.\n *\n * Usage in use cases:\n * ```typescript\n * constructor(@Inject(STORAGE) private readonly storage: IStorageService) {}\n * ```\n */\nexport const STORAGE = Symbol('STORAGE');\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { IStorageService } from './storage.protocol.js';
|
|
2
|
+
|
|
3
|
+
declare class LocalStorageBackend implements IStorageService {
|
|
4
|
+
private readonly basePath;
|
|
5
|
+
constructor(basePath?: string);
|
|
6
|
+
upload(key: string, data: Buffer | ReadableStream, contentType?: string): Promise<string>;
|
|
7
|
+
download(key: string): Promise<Buffer>;
|
|
8
|
+
delete(key: string): Promise<void>;
|
|
9
|
+
getUrl(key: string, _expiresInSeconds?: number): Promise<string>;
|
|
10
|
+
exists(key: string): Promise<boolean>;
|
|
11
|
+
list(prefix?: string): Promise<string[]>;
|
|
12
|
+
downloadStream(key: string): Promise<ReadableStream>;
|
|
13
|
+
private resolvePath;
|
|
14
|
+
/** Recursively list all files under dir, returning keys relative to basePath. */
|
|
15
|
+
private listRecursive;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { LocalStorageBackend };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// runtime/subsystems/storage/storage.local-backend.ts
|
|
2
|
+
import {
|
|
3
|
+
createReadStream,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
unlinkSync,
|
|
9
|
+
writeFileSync
|
|
10
|
+
} from "fs";
|
|
11
|
+
import { dirname, join, relative, resolve, sep } from "path";
|
|
12
|
+
import { Readable } from "stream";
|
|
13
|
+
|
|
14
|
+
// runtime/subsystems/storage/storage.utils.ts
|
|
15
|
+
async function toBuffer(data) {
|
|
16
|
+
if (Buffer.isBuffer(data)) {
|
|
17
|
+
return data;
|
|
18
|
+
}
|
|
19
|
+
const reader = data.getReader();
|
|
20
|
+
const chunks = [];
|
|
21
|
+
while (true) {
|
|
22
|
+
const { done, value } = await reader.read();
|
|
23
|
+
if (done) break;
|
|
24
|
+
if (value) chunks.push(value);
|
|
25
|
+
}
|
|
26
|
+
return Buffer.concat(chunks);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// runtime/subsystems/storage/storage.local-backend.ts
|
|
30
|
+
var LocalStorageBackend = class {
|
|
31
|
+
basePath;
|
|
32
|
+
constructor(basePath = "./storage") {
|
|
33
|
+
this.basePath = resolve(basePath);
|
|
34
|
+
}
|
|
35
|
+
async upload(key, data, contentType) {
|
|
36
|
+
const filePath = this.resolvePath(key);
|
|
37
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
38
|
+
const buffer = await toBuffer(data);
|
|
39
|
+
writeFileSync(filePath, buffer);
|
|
40
|
+
return key;
|
|
41
|
+
}
|
|
42
|
+
async download(key) {
|
|
43
|
+
const filePath = this.resolvePath(key);
|
|
44
|
+
if (!existsSync(filePath)) {
|
|
45
|
+
throw new Error(`Storage: file not found: ${key}`);
|
|
46
|
+
}
|
|
47
|
+
return readFileSync(filePath);
|
|
48
|
+
}
|
|
49
|
+
async delete(key) {
|
|
50
|
+
const filePath = this.resolvePath(key);
|
|
51
|
+
if (!existsSync(filePath)) {
|
|
52
|
+
throw new Error(`Storage: file not found: ${key}`);
|
|
53
|
+
}
|
|
54
|
+
unlinkSync(filePath);
|
|
55
|
+
}
|
|
56
|
+
async getUrl(key, _expiresInSeconds) {
|
|
57
|
+
const filePath = this.resolvePath(key);
|
|
58
|
+
if (!existsSync(filePath)) {
|
|
59
|
+
throw new Error(`Storage: file not found: ${key}`);
|
|
60
|
+
}
|
|
61
|
+
return `file://${filePath}`;
|
|
62
|
+
}
|
|
63
|
+
async exists(key) {
|
|
64
|
+
try {
|
|
65
|
+
return existsSync(this.resolvePath(key));
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async list(prefix) {
|
|
71
|
+
const keys = this.listRecursive(this.basePath);
|
|
72
|
+
if (prefix === void 0) return keys;
|
|
73
|
+
return keys.filter((k) => k.startsWith(prefix));
|
|
74
|
+
}
|
|
75
|
+
async downloadStream(key) {
|
|
76
|
+
const filePath = this.resolvePath(key);
|
|
77
|
+
if (!existsSync(filePath)) {
|
|
78
|
+
throw new Error(`Storage: file not found: ${key}`);
|
|
79
|
+
}
|
|
80
|
+
const nodeStream = createReadStream(filePath);
|
|
81
|
+
return Readable.toWeb(nodeStream);
|
|
82
|
+
}
|
|
83
|
+
resolvePath(key) {
|
|
84
|
+
const resolved = resolve(this.basePath, key);
|
|
85
|
+
if (!resolved.startsWith(this.basePath + sep)) {
|
|
86
|
+
throw new Error(`Invalid storage key (path traversal attempt): ${key}`);
|
|
87
|
+
}
|
|
88
|
+
return resolved;
|
|
89
|
+
}
|
|
90
|
+
/** Recursively list all files under dir, returning keys relative to basePath. */
|
|
91
|
+
listRecursive(dir) {
|
|
92
|
+
if (!existsSync(dir)) return [];
|
|
93
|
+
const keys = [];
|
|
94
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
95
|
+
const full = join(dir, entry.name);
|
|
96
|
+
if (entry.isDirectory()) {
|
|
97
|
+
keys.push(...this.listRecursive(full));
|
|
98
|
+
} else {
|
|
99
|
+
keys.push(relative(this.basePath, full));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return keys;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
export {
|
|
106
|
+
LocalStorageBackend
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=storage.local-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/storage/storage.local-backend.ts","../../../../runtime/subsystems/storage/storage.utils.ts"],"sourcesContent":["/**\n * Storage subsystem — local filesystem backend\n *\n * Writes files to `{basePath}/{key}` on the local filesystem.\n * Suitable for development only — use an S3/GCS backend in production.\n *\n * - Creates intermediate directories automatically (mkdirSync recursive)\n * - getUrl returns a `file://` URI pointing to the absolute path\n * - All methods throw on failure\n * - resolvePath validates against path traversal attacks\n */\nimport {\n createReadStream,\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from 'fs';\nimport { dirname, join, relative, resolve, sep } from 'path';\nimport { Readable } from 'stream';\nimport type { IStorageService } from './storage.protocol';\nimport { toBuffer } from './storage.utils';\n\nexport class LocalStorageBackend implements IStorageService {\n private readonly basePath: string;\n\n constructor(basePath: string = './storage') {\n this.basePath = resolve(basePath);\n }\n\n async upload(key: string, data: Buffer | ReadableStream, contentType?: string): Promise<string> {\n const filePath = this.resolvePath(key);\n mkdirSync(dirname(filePath), { recursive: true });\n\n const buffer = await toBuffer(data);\n writeFileSync(filePath, buffer);\n return key;\n }\n\n async download(key: string): Promise<Buffer> {\n const filePath = this.resolvePath(key);\n if (!existsSync(filePath)) {\n throw new Error(`Storage: file not found: ${key}`);\n }\n return readFileSync(filePath);\n }\n\n async delete(key: string): Promise<void> {\n const filePath = this.resolvePath(key);\n if (!existsSync(filePath)) {\n throw new Error(`Storage: file not found: ${key}`);\n }\n unlinkSync(filePath);\n }\n\n async getUrl(key: string, _expiresInSeconds?: number): Promise<string> {\n const filePath = this.resolvePath(key);\n if (!existsSync(filePath)) {\n throw new Error(`Storage: file not found: ${key}`);\n }\n return `file://${filePath}`;\n }\n\n async exists(key: string): Promise<boolean> {\n try {\n return existsSync(this.resolvePath(key));\n } catch {\n // resolvePath throws on traversal attempt — treat as non-existent\n return false;\n }\n }\n\n async list(prefix?: string): Promise<string[]> {\n const keys = this.listRecursive(this.basePath);\n if (prefix === undefined) return keys;\n return keys.filter((k) => k.startsWith(prefix));\n }\n\n async downloadStream(key: string): Promise<ReadableStream> {\n const filePath = this.resolvePath(key);\n if (!existsSync(filePath)) {\n throw new Error(`Storage: file not found: ${key}`);\n }\n const nodeStream = createReadStream(filePath);\n return Readable.toWeb(nodeStream) as ReadableStream;\n }\n\n private resolvePath(key: string): string {\n const resolved = resolve(this.basePath, key);\n if (!resolved.startsWith(this.basePath + sep)) {\n throw new Error(`Invalid storage key (path traversal attempt): ${key}`);\n }\n return resolved;\n }\n\n /** Recursively list all files under dir, returning keys relative to basePath. */\n private listRecursive(dir: string): string[] {\n if (!existsSync(dir)) return [];\n const keys: string[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n keys.push(...this.listRecursive(full));\n } else {\n keys.push(relative(this.basePath, full));\n }\n }\n return keys;\n }\n}\n","/**\n * Storage subsystem — shared utilities\n */\n\n/**\n * Convert a Buffer or Web ReadableStream to a Node.js Buffer.\n */\nexport async function toBuffer(data: Buffer | ReadableStream): Promise<Buffer> {\n if (Buffer.isBuffer(data)) {\n return data;\n }\n const reader = (data as ReadableStream<Uint8Array>).getReader();\n const chunks: Uint8Array[] = [];\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) chunks.push(value);\n }\n return Buffer.concat(chunks);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { IStorageService } from './storage.protocol.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Storage subsystem — in-memory backend
|
|
5
|
+
*
|
|
6
|
+
* Stores files as Buffers in a Map. Intended for unit tests only.
|
|
7
|
+
* All state is lost when the process exits.
|
|
8
|
+
*
|
|
9
|
+
* - getUrl returns `memory://{key}` (not a real URL, useful for assertions)
|
|
10
|
+
* - All methods throw on failure (missing keys, etc.)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
declare class MemoryStorageBackend implements IStorageService {
|
|
14
|
+
private readonly store;
|
|
15
|
+
upload(key: string, data: Buffer | ReadableStream, contentType?: string): Promise<string>;
|
|
16
|
+
download(key: string): Promise<Buffer>;
|
|
17
|
+
delete(key: string): Promise<void>;
|
|
18
|
+
getUrl(key: string, _expiresInSeconds?: number): Promise<string>;
|
|
19
|
+
exists(key: string): Promise<boolean>;
|
|
20
|
+
list(prefix?: string): Promise<string[]>;
|
|
21
|
+
downloadStream(key: string): Promise<ReadableStream>;
|
|
22
|
+
/** Clear all stored files. Useful for test teardown. */
|
|
23
|
+
clear(): void;
|
|
24
|
+
/** Return number of stored files. Useful for test assertions. */
|
|
25
|
+
size(): number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { MemoryStorageBackend };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// runtime/subsystems/storage/storage.utils.ts
|
|
2
|
+
async function toBuffer(data) {
|
|
3
|
+
if (Buffer.isBuffer(data)) {
|
|
4
|
+
return data;
|
|
5
|
+
}
|
|
6
|
+
const reader = data.getReader();
|
|
7
|
+
const chunks = [];
|
|
8
|
+
while (true) {
|
|
9
|
+
const { done, value } = await reader.read();
|
|
10
|
+
if (done) break;
|
|
11
|
+
if (value) chunks.push(value);
|
|
12
|
+
}
|
|
13
|
+
return Buffer.concat(chunks);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// runtime/subsystems/storage/storage.memory-backend.ts
|
|
17
|
+
var MemoryStorageBackend = class {
|
|
18
|
+
store = /* @__PURE__ */ new Map();
|
|
19
|
+
async upload(key, data, contentType) {
|
|
20
|
+
const buffer = await toBuffer(data);
|
|
21
|
+
this.store.set(key, { data: buffer, contentType });
|
|
22
|
+
return key;
|
|
23
|
+
}
|
|
24
|
+
async download(key) {
|
|
25
|
+
const entry = this.store.get(key);
|
|
26
|
+
if (!entry) {
|
|
27
|
+
throw new Error(`Storage: file not found: ${key}`);
|
|
28
|
+
}
|
|
29
|
+
return entry.data;
|
|
30
|
+
}
|
|
31
|
+
async delete(key) {
|
|
32
|
+
if (!this.store.has(key)) {
|
|
33
|
+
throw new Error(`Storage: file not found: ${key}`);
|
|
34
|
+
}
|
|
35
|
+
this.store.delete(key);
|
|
36
|
+
}
|
|
37
|
+
async getUrl(key, _expiresInSeconds) {
|
|
38
|
+
if (!this.store.has(key)) {
|
|
39
|
+
throw new Error(`Storage: file not found: ${key}`);
|
|
40
|
+
}
|
|
41
|
+
return `memory://${key}`;
|
|
42
|
+
}
|
|
43
|
+
async exists(key) {
|
|
44
|
+
return this.store.has(key);
|
|
45
|
+
}
|
|
46
|
+
async list(prefix) {
|
|
47
|
+
const keys = Array.from(this.store.keys());
|
|
48
|
+
if (prefix === void 0) return keys;
|
|
49
|
+
return keys.filter((k) => k.startsWith(prefix));
|
|
50
|
+
}
|
|
51
|
+
async downloadStream(key) {
|
|
52
|
+
const buffer = await this.download(key);
|
|
53
|
+
return new ReadableStream({
|
|
54
|
+
start(controller) {
|
|
55
|
+
controller.enqueue(new Uint8Array(buffer));
|
|
56
|
+
controller.close();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** Clear all stored files. Useful for test teardown. */
|
|
61
|
+
clear() {
|
|
62
|
+
this.store.clear();
|
|
63
|
+
}
|
|
64
|
+
/** Return number of stored files. Useful for test assertions. */
|
|
65
|
+
size() {
|
|
66
|
+
return this.store.size;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
export {
|
|
70
|
+
MemoryStorageBackend
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=storage.memory-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/storage/storage.utils.ts","../../../../runtime/subsystems/storage/storage.memory-backend.ts"],"sourcesContent":["/**\n * Storage subsystem — shared utilities\n */\n\n/**\n * Convert a Buffer or Web ReadableStream to a Node.js Buffer.\n */\nexport async function toBuffer(data: Buffer | ReadableStream): Promise<Buffer> {\n if (Buffer.isBuffer(data)) {\n return data;\n }\n const reader = (data as ReadableStream<Uint8Array>).getReader();\n const chunks: Uint8Array[] = [];\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) chunks.push(value);\n }\n return Buffer.concat(chunks);\n}\n","/**\n * Storage subsystem — in-memory backend\n *\n * Stores files as Buffers in a Map. Intended for unit tests only.\n * All state is lost when the process exits.\n *\n * - getUrl returns `memory://{key}` (not a real URL, useful for assertions)\n * - All methods throw on failure (missing keys, etc.)\n */\nimport type { IStorageService } from './storage.protocol';\nimport { toBuffer } from './storage.utils';\n\ninterface MemoryEntry {\n data: Buffer;\n contentType?: string;\n}\n\nexport class MemoryStorageBackend implements IStorageService {\n private readonly store = new Map<string, MemoryEntry>();\n\n async upload(key: string, data: Buffer | ReadableStream, contentType?: string): Promise<string> {\n const buffer = await toBuffer(data);\n this.store.set(key, { data: buffer, contentType });\n return key;\n }\n\n async download(key: string): Promise<Buffer> {\n const entry = this.store.get(key);\n if (!entry) {\n throw new Error(`Storage: file not found: ${key}`);\n }\n return entry.data;\n }\n\n async delete(key: string): Promise<void> {\n if (!this.store.has(key)) {\n throw new Error(`Storage: file not found: ${key}`);\n }\n this.store.delete(key);\n }\n\n async getUrl(key: string, _expiresInSeconds?: number): Promise<string> {\n if (!this.store.has(key)) {\n throw new Error(`Storage: file not found: ${key}`);\n }\n return `memory://${key}`;\n }\n\n async exists(key: string): Promise<boolean> {\n return this.store.has(key);\n }\n\n async list(prefix?: string): Promise<string[]> {\n const keys = Array.from(this.store.keys());\n if (prefix === undefined) return keys;\n return keys.filter((k) => k.startsWith(prefix));\n }\n\n async downloadStream(key: string): Promise<ReadableStream> {\n const buffer = await this.download(key);\n return new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(new Uint8Array(buffer));\n controller.close();\n },\n });\n }\n\n /** Clear all stored files. Useful for test teardown. */\n clear(): void {\n this.store.clear();\n }\n\n /** Return number of stored files. Useful for test assertions. */\n size(): number {\n return this.store.size;\n }\n}\n"],"mappings":";AAOA,eAAsB,SAAS,MAAgD;AAC7E,MAAI,OAAO,SAAS,IAAI,GAAG;AACzB,WAAO;AAAA,EACT;AACA,QAAM,SAAU,KAAoC,UAAU;AAC9D,QAAM,SAAuB,CAAC;AAC9B,SAAO,MAAM;AACX,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,QAAI,KAAM;AACV,QAAI,MAAO,QAAO,KAAK,KAAK;AAAA,EAC9B;AACA,SAAO,OAAO,OAAO,MAAM;AAC7B;;;ACFO,IAAM,uBAAN,MAAsD;AAAA,EAC1C,QAAQ,oBAAI,IAAyB;AAAA,EAEtD,MAAM,OAAO,KAAa,MAA+B,aAAuC;AAC9F,UAAM,SAAS,MAAM,SAAS,IAAI;AAClC,SAAK,MAAM,IAAI,KAAK,EAAE,MAAM,QAAQ,YAAY,CAAC;AACjD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,SAAS,KAA8B;AAC3C,UAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;AAChC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,4BAA4B,GAAG,EAAE;AAAA,IACnD;AACA,WAAO,MAAM;AAAA,EACf;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,QAAI,CAAC,KAAK,MAAM,IAAI,GAAG,GAAG;AACxB,YAAM,IAAI,MAAM,4BAA4B,GAAG,EAAE;AAAA,IACnD;AACA,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AAAA,EAEA,MAAM,OAAO,KAAa,mBAA6C;AACrE,QAAI,CAAC,KAAK,MAAM,IAAI,GAAG,GAAG;AACxB,YAAM,IAAI,MAAM,4BAA4B,GAAG,EAAE;AAAA,IACnD;AACA,WAAO,YAAY,GAAG;AAAA,EACxB;AAAA,EAEA,MAAM,OAAO,KAA+B;AAC1C,WAAO,KAAK,MAAM,IAAI,GAAG;AAAA,EAC3B;AAAA,EAEA,MAAM,KAAK,QAAoC;AAC7C,UAAM,OAAO,MAAM,KAAK,KAAK,MAAM,KAAK,CAAC;AACzC,QAAI,WAAW,OAAW,QAAO;AACjC,WAAO,KAAK,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,CAAC;AAAA,EAChD;AAAA,EAEA,MAAM,eAAe,KAAsC;AACzD,UAAM,SAAS,MAAM,KAAK,SAAS,GAAG;AACtC,WAAO,IAAI,eAA2B;AAAA,MACpC,MAAM,YAAY;AAChB,mBAAW,QAAQ,IAAI,WAAW,MAAM,CAAC;AACzC,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA;AAAA,EAGA,OAAe;AACb,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;","names":[]}
|