@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/job-queue.drizzle-backend.ts","../../../../runtime/constants/tokens.ts","../../../../runtime/subsystems/jobs/job-queue.schema.ts"],"sourcesContent":["/**\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"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ZodType } from 'zod';
|
|
2
|
+
import { IJobQueue, JobOptions } from './job-queue.protocol.js';
|
|
3
|
+
|
|
4
|
+
declare class MemoryJobQueue implements IJobQueue {
|
|
5
|
+
private readonly handlers;
|
|
6
|
+
enqueue<T = unknown>(type: string, payload: T, _options?: JobOptions): Promise<string>;
|
|
7
|
+
process<T = unknown>(type: string, handler: (payload: T) => Promise<void>, payloadSchema?: ZodType<T>): void;
|
|
8
|
+
schedule(_type: string, _cron: string, _payload?: unknown): Promise<string>;
|
|
9
|
+
cancel(_jobId: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { MemoryJobQueue };
|
|
@@ -0,0 +1,44 @@
|
|
|
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/jobs/job-queue.memory-backend.ts
|
|
13
|
+
import { Injectable } from "@nestjs/common";
|
|
14
|
+
import { randomUUID } from "crypto";
|
|
15
|
+
var MemoryJobQueue = class {
|
|
16
|
+
handlers = /* @__PURE__ */ new Map();
|
|
17
|
+
async enqueue(type, payload, _options) {
|
|
18
|
+
const id = randomUUID();
|
|
19
|
+
const entry = this.handlers.get(type);
|
|
20
|
+
if (entry) {
|
|
21
|
+
const validated = entry.schema ? entry.schema.parse(payload) : payload;
|
|
22
|
+
await entry.handler(validated);
|
|
23
|
+
}
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
26
|
+
process(type, handler, payloadSchema) {
|
|
27
|
+
this.handlers.set(type, {
|
|
28
|
+
handler,
|
|
29
|
+
schema: payloadSchema
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async schedule(_type, _cron, _payload) {
|
|
33
|
+
return randomUUID();
|
|
34
|
+
}
|
|
35
|
+
async cancel(_jobId) {
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
MemoryJobQueue = __decorateClass([
|
|
39
|
+
Injectable()
|
|
40
|
+
], MemoryJobQueue);
|
|
41
|
+
export {
|
|
42
|
+
MemoryJobQueue
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=job-queue.memory-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/jobs/job-queue.memory-backend.ts"],"sourcesContent":["/**\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"],"mappings":";;;;;;;;;;;;AAUA,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAKpB,IAAM,iBAAN,MAA0C;AAAA,EAC9B,WAAW,oBAAI,IAG9B;AAAA,EAEF,MAAM,QAAqB,MAAc,SAAY,UAAwC;AAC3F,UAAM,KAAK,WAAW;AACtB,UAAM,QAAQ,KAAK,SAAS,IAAI,IAAI;AACpC,QAAI,OAAO;AACT,YAAM,YAAY,MAAM,SAAS,MAAM,OAAO,MAAM,OAAO,IAAI;AAC/D,YAAM,MAAM,QAAQ,SAAoB;AAAA,IAC1C;AACA,WAAO;AAAA,EACT;AAAA,EAEA,QACE,MACA,SACA,eACM;AACN,SAAK,SAAS,IAAI,MAAM;AAAA,MACtB;AAAA,MACA,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,SAAS,OAAe,OAAe,UAAqC;AAChF,WAAO,WAAW;AAAA,EACpB;AAAA,EAEA,MAAM,OAAO,QAA+B;AAAA,EAE5C;AACF;AAlCa,iBAAN;AAAA,EADN,WAAW;AAAA,GACC;","names":[]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ZodType } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* IJobQueue — Job queue protocol (port).
|
|
5
|
+
*
|
|
6
|
+
* The protocol is the port in hexagonal architecture. Use cases inject this
|
|
7
|
+
* interface via JOB_QUEUE token; they never know which backend is active.
|
|
8
|
+
*
|
|
9
|
+
* Backends:
|
|
10
|
+
* - DrizzleJobQueue: pg-boss pattern, persists jobs in job_queue table
|
|
11
|
+
* - MemoryJobQueue: Map-based, synchronous dispatch (for tests)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
interface JobOptions {
|
|
15
|
+
/** Milliseconds to wait before processing the job. */
|
|
16
|
+
delay?: number;
|
|
17
|
+
/** Maximum retry count. Default: 3. */
|
|
18
|
+
retries?: number;
|
|
19
|
+
/** Milliseconds between retries (exponential backoff base). Default: 1000. */
|
|
20
|
+
backoff?: number;
|
|
21
|
+
/** Higher priority jobs are claimed first. */
|
|
22
|
+
priority?: number;
|
|
23
|
+
}
|
|
24
|
+
interface IJobQueue {
|
|
25
|
+
/**
|
|
26
|
+
* Enqueue a job for background processing.
|
|
27
|
+
* Returns the job ID.
|
|
28
|
+
*/
|
|
29
|
+
enqueue<T = unknown>(type: string, payload: T, options?: JobOptions): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Register a handler for a job type.
|
|
32
|
+
* The optional payloadSchema validates the stored payload at runtime
|
|
33
|
+
* before passing it to the handler — closing the type safety gap.
|
|
34
|
+
*/
|
|
35
|
+
process<T = unknown>(type: string, handler: (payload: T) => Promise<void>, payloadSchema?: ZodType<T>): void;
|
|
36
|
+
/**
|
|
37
|
+
* Schedule a recurring job using a cron expression.
|
|
38
|
+
* Returns a schedule ID (stored as a job with cron metadata).
|
|
39
|
+
*/
|
|
40
|
+
schedule(type: string, cron: string, payload?: unknown): Promise<string>;
|
|
41
|
+
/**
|
|
42
|
+
* Cancel a pending job by ID.
|
|
43
|
+
* Has no effect if the job is already active, completed, or failed.
|
|
44
|
+
*/
|
|
45
|
+
cancel(jobId: string): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type { IJobQueue, JobOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=job-queue.protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import { ZodType } from 'zod';
|
|
3
|
+
import { IJobQueue, JobOptions } from './job-queue.protocol.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* RedisJobQueue — lightweight Redis job queue backend.
|
|
7
|
+
*
|
|
8
|
+
* Uses Redis Lists for simple, reliable job processing:
|
|
9
|
+
* - enqueue() → RPUSH to jobs:{type} list (JSON-serialized job)
|
|
10
|
+
* - process() → BLPOP loop per registered type (blocking pop, atomic)
|
|
11
|
+
* - schedule() → stores cron metadata (future: external scheduler picks it up)
|
|
12
|
+
* - cancel() → LREM from the pending list
|
|
13
|
+
*
|
|
14
|
+
* No external dependencies beyond ioredis (already an optional peer dep).
|
|
15
|
+
* For advanced features (rate limiting, job dependencies, dashboard), use
|
|
16
|
+
* the BullMQ backend instead.
|
|
17
|
+
*
|
|
18
|
+
* Connection model:
|
|
19
|
+
* One shared ioredis client for all operations. BLPOP consumers each get
|
|
20
|
+
* their own connection (a client blocked on BLPOP can't issue other commands).
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* JobsModule.forRoot({ backend: 'redis', redisUrl: 'redis://localhost:6379' })
|
|
24
|
+
*
|
|
25
|
+
* Requires `ioredis`:
|
|
26
|
+
* bun add ioredis
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
declare class RedisJobQueue implements IJobQueue, OnModuleInit, OnModuleDestroy {
|
|
30
|
+
private readonly redisUrl;
|
|
31
|
+
private readonly logger;
|
|
32
|
+
private client;
|
|
33
|
+
private running;
|
|
34
|
+
private readonly handlers;
|
|
35
|
+
private readonly consumers;
|
|
36
|
+
constructor(redisUrl: string);
|
|
37
|
+
onModuleInit(): Promise<void>;
|
|
38
|
+
onModuleDestroy(): Promise<void>;
|
|
39
|
+
enqueue<T = unknown>(type: string, payload: T, options?: JobOptions): Promise<string>;
|
|
40
|
+
process<T = unknown>(type: string, handler: (payload: T) => Promise<void>, payloadSchema?: ZodType<T>): void;
|
|
41
|
+
schedule(type: string, cron: string, payload?: unknown): Promise<string>;
|
|
42
|
+
cancel(jobId: string): Promise<void>;
|
|
43
|
+
private startConsumer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { RedisJobQueue };
|
|
@@ -0,0 +1,187 @@
|
|
|
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
|
+
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
12
|
+
|
|
13
|
+
// runtime/subsystems/jobs/job-queue.redis-backend.ts
|
|
14
|
+
import { Injectable, Inject, Logger } from "@nestjs/common";
|
|
15
|
+
import { randomUUID } from "crypto";
|
|
16
|
+
|
|
17
|
+
// runtime/subsystems/jobs/jobs.tokens.ts
|
|
18
|
+
var REDIS_URL = /* @__PURE__ */ Symbol("REDIS_URL");
|
|
19
|
+
|
|
20
|
+
// runtime/subsystems/jobs/job-queue.redis-backend.ts
|
|
21
|
+
var KEY_PREFIX = "jobs:";
|
|
22
|
+
var SCHEDULE_KEY = "jobs:__schedules";
|
|
23
|
+
async function createRedisClient(url) {
|
|
24
|
+
let Redis;
|
|
25
|
+
try {
|
|
26
|
+
const mod = await import("ioredis");
|
|
27
|
+
Redis = mod.default ?? mod;
|
|
28
|
+
} catch {
|
|
29
|
+
throw new Error(
|
|
30
|
+
"RedisJobQueue requires ioredis. Install it: bun add ioredis"
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return new Redis(url);
|
|
34
|
+
}
|
|
35
|
+
var RedisJobQueue = class {
|
|
36
|
+
constructor(redisUrl) {
|
|
37
|
+
this.redisUrl = redisUrl;
|
|
38
|
+
}
|
|
39
|
+
redisUrl;
|
|
40
|
+
logger = new Logger(RedisJobQueue.name);
|
|
41
|
+
client;
|
|
42
|
+
running = false;
|
|
43
|
+
handlers = /* @__PURE__ */ new Map();
|
|
44
|
+
consumers = [];
|
|
45
|
+
async onModuleInit() {
|
|
46
|
+
this.client = await createRedisClient(this.redisUrl);
|
|
47
|
+
this.running = true;
|
|
48
|
+
for (const [type] of this.handlers) {
|
|
49
|
+
await this.startConsumer(type);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async onModuleDestroy() {
|
|
53
|
+
this.running = false;
|
|
54
|
+
for (const consumer of this.consumers) {
|
|
55
|
+
try {
|
|
56
|
+
await consumer.client.disconnect();
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
this.consumers.length = 0;
|
|
61
|
+
if (this.client) {
|
|
62
|
+
await this.client.quit();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Protocol implementation
|
|
67
|
+
// ============================================================================
|
|
68
|
+
async enqueue(type, payload, options) {
|
|
69
|
+
const id = randomUUID();
|
|
70
|
+
const job = {
|
|
71
|
+
id,
|
|
72
|
+
type,
|
|
73
|
+
payload,
|
|
74
|
+
options: options ?? {},
|
|
75
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
76
|
+
attempts: 0
|
|
77
|
+
};
|
|
78
|
+
const key = KEY_PREFIX + type;
|
|
79
|
+
if (options?.delay && options.delay > 0) {
|
|
80
|
+
setTimeout(async () => {
|
|
81
|
+
try {
|
|
82
|
+
await this.client.rpush(key, JSON.stringify(job));
|
|
83
|
+
} catch (err) {
|
|
84
|
+
this.logger.error(`Failed to enqueue delayed job ${id}: ${err}`);
|
|
85
|
+
}
|
|
86
|
+
}, options.delay);
|
|
87
|
+
} else {
|
|
88
|
+
await this.client.rpush(key, JSON.stringify(job));
|
|
89
|
+
}
|
|
90
|
+
return id;
|
|
91
|
+
}
|
|
92
|
+
process(type, handler, payloadSchema) {
|
|
93
|
+
this.handlers.set(type, {
|
|
94
|
+
handler,
|
|
95
|
+
schema: payloadSchema
|
|
96
|
+
});
|
|
97
|
+
if (this.running) {
|
|
98
|
+
this.startConsumer(type).catch((err) => {
|
|
99
|
+
this.logger.error(`Failed to start consumer for ${type}: ${err}`);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async schedule(type, cron, payload) {
|
|
104
|
+
const id = randomUUID();
|
|
105
|
+
const schedule = { id, type, cron, payload, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
106
|
+
await this.client.hset(SCHEDULE_KEY, id, JSON.stringify(schedule));
|
|
107
|
+
return id;
|
|
108
|
+
}
|
|
109
|
+
async cancel(jobId) {
|
|
110
|
+
const keys = await this.client.keys(KEY_PREFIX + "*");
|
|
111
|
+
for (const key of keys) {
|
|
112
|
+
if (key === SCHEDULE_KEY) continue;
|
|
113
|
+
const items = await this.client.lrange(key, 0, -1);
|
|
114
|
+
for (const item of items) {
|
|
115
|
+
try {
|
|
116
|
+
const job = JSON.parse(item);
|
|
117
|
+
if (job.id === jobId) {
|
|
118
|
+
await this.client.lrem(key, 1, item);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
await this.client.hdel(SCHEDULE_KEY, jobId);
|
|
126
|
+
}
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// Consumer loop
|
|
129
|
+
// ============================================================================
|
|
130
|
+
async startConsumer(type) {
|
|
131
|
+
const consumerClient = await createRedisClient(this.redisUrl);
|
|
132
|
+
this.consumers.push({ type, client: consumerClient });
|
|
133
|
+
const key = KEY_PREFIX + type;
|
|
134
|
+
const entry = this.handlers.get(type);
|
|
135
|
+
if (!entry) return;
|
|
136
|
+
const loop = async () => {
|
|
137
|
+
while (this.running) {
|
|
138
|
+
try {
|
|
139
|
+
const result = await consumerClient.blpop(key, 5);
|
|
140
|
+
if (!result) continue;
|
|
141
|
+
const [, raw] = result;
|
|
142
|
+
const job = JSON.parse(raw);
|
|
143
|
+
const payload = entry.schema ? entry.schema.parse(job.payload) : job.payload;
|
|
144
|
+
try {
|
|
145
|
+
await entry.handler(payload);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
const maxRetries = job.options.retries ?? 3;
|
|
148
|
+
job.attempts += 1;
|
|
149
|
+
if (job.attempts < maxRetries) {
|
|
150
|
+
const backoff = (job.options.backoff ?? 1e3) * Math.pow(2, job.attempts - 1);
|
|
151
|
+
this.logger.warn(
|
|
152
|
+
`Job ${job.id} failed (attempt ${job.attempts}/${maxRetries}), retrying in ${backoff}ms`
|
|
153
|
+
);
|
|
154
|
+
setTimeout(async () => {
|
|
155
|
+
try {
|
|
156
|
+
await this.client.rpush(key, JSON.stringify(job));
|
|
157
|
+
} catch (e) {
|
|
158
|
+
this.logger.error(`Failed to re-enqueue job ${job.id}: ${e}`);
|
|
159
|
+
}
|
|
160
|
+
}, backoff);
|
|
161
|
+
} else {
|
|
162
|
+
this.logger.error(
|
|
163
|
+
`Job ${job.id} exhausted ${maxRetries} retries: ${err instanceof Error ? err.message : err}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
if (this.running) {
|
|
169
|
+
this.logger.error(`Consumer error for ${type}: ${err}`);
|
|
170
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
loop().catch((err) => {
|
|
176
|
+
this.logger.error(`Consumer loop for ${type} terminated: ${err}`);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
RedisJobQueue = __decorateClass([
|
|
181
|
+
Injectable(),
|
|
182
|
+
__decorateParam(0, Inject(REDIS_URL))
|
|
183
|
+
], RedisJobQueue);
|
|
184
|
+
export {
|
|
185
|
+
RedisJobQueue
|
|
186
|
+
};
|
|
187
|
+
//# sourceMappingURL=job-queue.redis-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/jobs/job-queue.redis-backend.ts","../../../../runtime/subsystems/jobs/jobs.tokens.ts"],"sourcesContent":["/**\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"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
2
|
+
import { InferSelectModel } from 'drizzle-orm';
|
|
3
|
+
|
|
4
|
+
type JobStatus = 'pending' | 'active' | 'completed' | 'failed' | 'expired';
|
|
5
|
+
declare const jobQueue: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
6
|
+
name: "job_queue";
|
|
7
|
+
schema: undefined;
|
|
8
|
+
columns: {
|
|
9
|
+
id: drizzle_orm_pg_core.PgColumn<{
|
|
10
|
+
name: "id";
|
|
11
|
+
tableName: "job_queue";
|
|
12
|
+
dataType: "string";
|
|
13
|
+
columnType: "PgUUID";
|
|
14
|
+
data: string;
|
|
15
|
+
driverParam: string;
|
|
16
|
+
notNull: true;
|
|
17
|
+
hasDefault: true;
|
|
18
|
+
isPrimaryKey: true;
|
|
19
|
+
isAutoincrement: false;
|
|
20
|
+
hasRuntimeDefault: false;
|
|
21
|
+
enumValues: undefined;
|
|
22
|
+
baseColumn: never;
|
|
23
|
+
identity: undefined;
|
|
24
|
+
generated: undefined;
|
|
25
|
+
}, {}, {}>;
|
|
26
|
+
type: drizzle_orm_pg_core.PgColumn<{
|
|
27
|
+
name: "type";
|
|
28
|
+
tableName: "job_queue";
|
|
29
|
+
dataType: "string";
|
|
30
|
+
columnType: "PgText";
|
|
31
|
+
data: string;
|
|
32
|
+
driverParam: string;
|
|
33
|
+
notNull: true;
|
|
34
|
+
hasDefault: false;
|
|
35
|
+
isPrimaryKey: false;
|
|
36
|
+
isAutoincrement: false;
|
|
37
|
+
hasRuntimeDefault: false;
|
|
38
|
+
enumValues: [string, ...string[]];
|
|
39
|
+
baseColumn: never;
|
|
40
|
+
identity: undefined;
|
|
41
|
+
generated: undefined;
|
|
42
|
+
}, {}, {}>;
|
|
43
|
+
payload: drizzle_orm_pg_core.PgColumn<{
|
|
44
|
+
name: "payload";
|
|
45
|
+
tableName: "job_queue";
|
|
46
|
+
dataType: "json";
|
|
47
|
+
columnType: "PgJsonb";
|
|
48
|
+
data: unknown;
|
|
49
|
+
driverParam: unknown;
|
|
50
|
+
notNull: true;
|
|
51
|
+
hasDefault: true;
|
|
52
|
+
isPrimaryKey: false;
|
|
53
|
+
isAutoincrement: false;
|
|
54
|
+
hasRuntimeDefault: false;
|
|
55
|
+
enumValues: undefined;
|
|
56
|
+
baseColumn: never;
|
|
57
|
+
identity: undefined;
|
|
58
|
+
generated: undefined;
|
|
59
|
+
}, {}, {}>;
|
|
60
|
+
status: drizzle_orm_pg_core.PgColumn<{
|
|
61
|
+
name: "status";
|
|
62
|
+
tableName: "job_queue";
|
|
63
|
+
dataType: "string";
|
|
64
|
+
columnType: "PgText";
|
|
65
|
+
data: JobStatus;
|
|
66
|
+
driverParam: string;
|
|
67
|
+
notNull: true;
|
|
68
|
+
hasDefault: true;
|
|
69
|
+
isPrimaryKey: false;
|
|
70
|
+
isAutoincrement: false;
|
|
71
|
+
hasRuntimeDefault: false;
|
|
72
|
+
enumValues: [string, ...string[]];
|
|
73
|
+
baseColumn: never;
|
|
74
|
+
identity: undefined;
|
|
75
|
+
generated: undefined;
|
|
76
|
+
}, {}, {
|
|
77
|
+
$type: JobStatus;
|
|
78
|
+
}>;
|
|
79
|
+
runAt: drizzle_orm_pg_core.PgColumn<{
|
|
80
|
+
name: "run_at";
|
|
81
|
+
tableName: "job_queue";
|
|
82
|
+
dataType: "date";
|
|
83
|
+
columnType: "PgTimestamp";
|
|
84
|
+
data: Date;
|
|
85
|
+
driverParam: string;
|
|
86
|
+
notNull: true;
|
|
87
|
+
hasDefault: true;
|
|
88
|
+
isPrimaryKey: false;
|
|
89
|
+
isAutoincrement: false;
|
|
90
|
+
hasRuntimeDefault: false;
|
|
91
|
+
enumValues: undefined;
|
|
92
|
+
baseColumn: never;
|
|
93
|
+
identity: undefined;
|
|
94
|
+
generated: undefined;
|
|
95
|
+
}, {}, {}>;
|
|
96
|
+
priority: drizzle_orm_pg_core.PgColumn<{
|
|
97
|
+
name: "priority";
|
|
98
|
+
tableName: "job_queue";
|
|
99
|
+
dataType: "number";
|
|
100
|
+
columnType: "PgInteger";
|
|
101
|
+
data: number;
|
|
102
|
+
driverParam: string | number;
|
|
103
|
+
notNull: true;
|
|
104
|
+
hasDefault: true;
|
|
105
|
+
isPrimaryKey: false;
|
|
106
|
+
isAutoincrement: false;
|
|
107
|
+
hasRuntimeDefault: false;
|
|
108
|
+
enumValues: undefined;
|
|
109
|
+
baseColumn: never;
|
|
110
|
+
identity: undefined;
|
|
111
|
+
generated: undefined;
|
|
112
|
+
}, {}, {}>;
|
|
113
|
+
attempts: drizzle_orm_pg_core.PgColumn<{
|
|
114
|
+
name: "attempts";
|
|
115
|
+
tableName: "job_queue";
|
|
116
|
+
dataType: "number";
|
|
117
|
+
columnType: "PgInteger";
|
|
118
|
+
data: number;
|
|
119
|
+
driverParam: string | number;
|
|
120
|
+
notNull: true;
|
|
121
|
+
hasDefault: true;
|
|
122
|
+
isPrimaryKey: false;
|
|
123
|
+
isAutoincrement: false;
|
|
124
|
+
hasRuntimeDefault: false;
|
|
125
|
+
enumValues: undefined;
|
|
126
|
+
baseColumn: never;
|
|
127
|
+
identity: undefined;
|
|
128
|
+
generated: undefined;
|
|
129
|
+
}, {}, {}>;
|
|
130
|
+
maxRetries: drizzle_orm_pg_core.PgColumn<{
|
|
131
|
+
name: "max_retries";
|
|
132
|
+
tableName: "job_queue";
|
|
133
|
+
dataType: "number";
|
|
134
|
+
columnType: "PgInteger";
|
|
135
|
+
data: number;
|
|
136
|
+
driverParam: string | number;
|
|
137
|
+
notNull: true;
|
|
138
|
+
hasDefault: true;
|
|
139
|
+
isPrimaryKey: false;
|
|
140
|
+
isAutoincrement: false;
|
|
141
|
+
hasRuntimeDefault: false;
|
|
142
|
+
enumValues: undefined;
|
|
143
|
+
baseColumn: never;
|
|
144
|
+
identity: undefined;
|
|
145
|
+
generated: undefined;
|
|
146
|
+
}, {}, {}>;
|
|
147
|
+
backoffMs: drizzle_orm_pg_core.PgColumn<{
|
|
148
|
+
name: "backoff_ms";
|
|
149
|
+
tableName: "job_queue";
|
|
150
|
+
dataType: "number";
|
|
151
|
+
columnType: "PgInteger";
|
|
152
|
+
data: number;
|
|
153
|
+
driverParam: string | number;
|
|
154
|
+
notNull: true;
|
|
155
|
+
hasDefault: true;
|
|
156
|
+
isPrimaryKey: false;
|
|
157
|
+
isAutoincrement: false;
|
|
158
|
+
hasRuntimeDefault: false;
|
|
159
|
+
enumValues: undefined;
|
|
160
|
+
baseColumn: never;
|
|
161
|
+
identity: undefined;
|
|
162
|
+
generated: undefined;
|
|
163
|
+
}, {}, {}>;
|
|
164
|
+
lastError: drizzle_orm_pg_core.PgColumn<{
|
|
165
|
+
name: "last_error";
|
|
166
|
+
tableName: "job_queue";
|
|
167
|
+
dataType: "string";
|
|
168
|
+
columnType: "PgText";
|
|
169
|
+
data: string;
|
|
170
|
+
driverParam: string;
|
|
171
|
+
notNull: false;
|
|
172
|
+
hasDefault: false;
|
|
173
|
+
isPrimaryKey: false;
|
|
174
|
+
isAutoincrement: false;
|
|
175
|
+
hasRuntimeDefault: false;
|
|
176
|
+
enumValues: [string, ...string[]];
|
|
177
|
+
baseColumn: never;
|
|
178
|
+
identity: undefined;
|
|
179
|
+
generated: undefined;
|
|
180
|
+
}, {}, {}>;
|
|
181
|
+
createdAt: drizzle_orm_pg_core.PgColumn<{
|
|
182
|
+
name: "created_at";
|
|
183
|
+
tableName: "job_queue";
|
|
184
|
+
dataType: "date";
|
|
185
|
+
columnType: "PgTimestamp";
|
|
186
|
+
data: Date;
|
|
187
|
+
driverParam: string;
|
|
188
|
+
notNull: true;
|
|
189
|
+
hasDefault: true;
|
|
190
|
+
isPrimaryKey: false;
|
|
191
|
+
isAutoincrement: false;
|
|
192
|
+
hasRuntimeDefault: false;
|
|
193
|
+
enumValues: undefined;
|
|
194
|
+
baseColumn: never;
|
|
195
|
+
identity: undefined;
|
|
196
|
+
generated: undefined;
|
|
197
|
+
}, {}, {}>;
|
|
198
|
+
completedAt: drizzle_orm_pg_core.PgColumn<{
|
|
199
|
+
name: "completed_at";
|
|
200
|
+
tableName: "job_queue";
|
|
201
|
+
dataType: "date";
|
|
202
|
+
columnType: "PgTimestamp";
|
|
203
|
+
data: Date;
|
|
204
|
+
driverParam: string;
|
|
205
|
+
notNull: false;
|
|
206
|
+
hasDefault: false;
|
|
207
|
+
isPrimaryKey: false;
|
|
208
|
+
isAutoincrement: false;
|
|
209
|
+
hasRuntimeDefault: false;
|
|
210
|
+
enumValues: undefined;
|
|
211
|
+
baseColumn: never;
|
|
212
|
+
identity: undefined;
|
|
213
|
+
generated: undefined;
|
|
214
|
+
}, {}, {}>;
|
|
215
|
+
claimedAt: drizzle_orm_pg_core.PgColumn<{
|
|
216
|
+
name: "claimed_at";
|
|
217
|
+
tableName: "job_queue";
|
|
218
|
+
dataType: "date";
|
|
219
|
+
columnType: "PgTimestamp";
|
|
220
|
+
data: Date;
|
|
221
|
+
driverParam: string;
|
|
222
|
+
notNull: false;
|
|
223
|
+
hasDefault: false;
|
|
224
|
+
isPrimaryKey: false;
|
|
225
|
+
isAutoincrement: false;
|
|
226
|
+
hasRuntimeDefault: false;
|
|
227
|
+
enumValues: undefined;
|
|
228
|
+
baseColumn: never;
|
|
229
|
+
identity: undefined;
|
|
230
|
+
generated: undefined;
|
|
231
|
+
}, {}, {}>;
|
|
232
|
+
};
|
|
233
|
+
dialect: "pg";
|
|
234
|
+
}>;
|
|
235
|
+
type JobRow = InferSelectModel<typeof jobQueue>;
|
|
236
|
+
|
|
237
|
+
export { type JobRow, type JobStatus, jobQueue };
|