@pattern-stack/codegen 0.8.1 → 0.9.1
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 +29 -0
- package/dist/{job-orchestrator.protocol-BwsBd37o.d.ts → job-orchestrator.protocol-CHOEqBDk.d.ts} +36 -1
- package/dist/runtime/subsystems/bridge/bridge-delivery-handler.d.ts +2 -2
- package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge.module.d.ts +5 -1
- package/dist/runtime/subsystems/bridge/bridge.module.js +930 -275
- package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
- package/dist/runtime/subsystems/bridge/event-flow.service.d.ts +1 -1
- package/dist/runtime/subsystems/bridge/event-flow.service.js.map +1 -1
- package/dist/runtime/subsystems/bridge/index.d.ts +4 -1
- package/dist/runtime/subsystems/bridge/index.js +837 -182
- package/dist/runtime/subsystems/bridge/index.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.d.ts +3 -1
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +92 -1
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.memory-backend.d.ts +3 -1
- package/dist/runtime/subsystems/events/event-bus.memory-backend.js +99 -0
- package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/event-keyset-cursor.d.ts +32 -0
- package/dist/runtime/subsystems/events/event-keyset-cursor.js +38 -0
- package/dist/runtime/subsystems/events/event-keyset-cursor.js.map +1 -0
- package/dist/runtime/subsystems/events/event-read.protocol.d.ts +94 -0
- package/dist/runtime/subsystems/events/event-read.protocol.js +9 -0
- package/dist/runtime/subsystems/events/event-read.protocol.js.map +1 -0
- package/dist/runtime/subsystems/events/events.module.js +177 -3
- package/dist/runtime/subsystems/events/events.module.js.map +1 -1
- package/dist/runtime/subsystems/events/events.tokens.d.ts +16 -1
- package/dist/runtime/subsystems/events/events.tokens.js +2 -0
- package/dist/runtime/subsystems/events/events.tokens.js.map +1 -1
- package/dist/runtime/subsystems/events/generated/bus.js.map +1 -1
- package/dist/runtime/subsystems/events/generated/index.js.map +1 -1
- package/dist/runtime/subsystems/events/index.d.ts +2 -1
- package/dist/runtime/subsystems/events/index.js +178 -3
- package/dist/runtime/subsystems/events/index.js.map +1 -1
- package/dist/runtime/subsystems/index.d.ts +3 -2
- package/dist/runtime/subsystems/index.js +1194 -264
- package/dist/runtime/subsystems/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/bullmq.config.d.ts +98 -0
- package/dist/runtime/subsystems/jobs/bullmq.config.js +143 -0
- package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -0
- package/dist/runtime/subsystems/jobs/index.d.ts +8 -3
- package/dist/runtime/subsystems/jobs/index.js +861 -201
- package/dist/runtime/subsystems/jobs/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-handler.base.d.ts +2 -1
- package/dist/runtime/subsystems/jobs/job-handler.base.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.d.ts +108 -0
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +922 -0
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.d.ts +2 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.d.ts +2 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.protocol.d.ts +2 -1
- package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.d.ts +53 -0
- package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js +57 -0
- package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.d.ts +4 -2
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +81 -1
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.d.ts +4 -2
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +81 -0
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-run-service.protocol.d.ts +76 -2
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.d.ts +49 -0
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +374 -0
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-worker.d.ts +2 -1
- package/dist/runtime/subsystems/jobs/job-worker.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +44 -5
- package/dist/runtime/subsystems/jobs/job-worker.module.js +832 -178
- package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.module.d.ts +10 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js +519 -20
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
- package/dist/runtime/subsystems/jobs/jobs-errors.d.ts +2 -1
- package/dist/runtime/subsystems/jobs/pool-config.loader.d.ts +9 -1
- package/dist/runtime/subsystems/jobs/pool-config.loader.js +4 -0
- package/dist/runtime/subsystems/jobs/pool-config.loader.js.map +1 -1
- package/dist/runtime/subsystems/observability/index.d.ts +4 -3
- package/dist/runtime/subsystems/observability/index.js +109 -2
- package/dist/runtime/subsystems/observability/index.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability.module.js +109 -2
- package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability.protocol.d.ts +64 -3
- package/dist/runtime/subsystems/observability/observability.service.d.ts +22 -4
- package/dist/runtime/subsystems/observability/observability.service.js +109 -2
- package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
- package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.d.ts +3 -2
- package/dist/runtime/subsystems/observability/reporters/index.d.ts +3 -2
- package/dist/src/cli/index.js +30 -6
- package/dist/src/cli/index.js.map +1 -1
- package/package.json +5 -1
- package/runtime/subsystems/bridge/bridge.module.ts +5 -0
- package/runtime/subsystems/events/event-bus.drizzle-backend.ts +109 -3
- package/runtime/subsystems/events/event-bus.memory-backend.ts +103 -1
- package/runtime/subsystems/events/event-keyset-cursor.ts +59 -0
- package/runtime/subsystems/events/event-read.protocol.ts +97 -0
- package/runtime/subsystems/events/events.module.ts +18 -2
- package/runtime/subsystems/events/events.tokens.ts +16 -0
- package/runtime/subsystems/events/index.ts +7 -0
- package/runtime/subsystems/jobs/bullmq.config.ts +125 -0
- package/runtime/subsystems/jobs/index.ts +22 -0
- package/runtime/subsystems/jobs/job-handler.base.ts +36 -0
- package/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts +381 -0
- package/runtime/subsystems/jobs/job-run-keyset-cursor.ts +88 -0
- package/runtime/subsystems/jobs/job-run-service.drizzle-backend.ts +59 -1
- package/runtime/subsystems/jobs/job-run-service.memory-backend.ts +53 -0
- package/runtime/subsystems/jobs/job-run-service.protocol.ts +77 -0
- package/runtime/subsystems/jobs/job-worker.bullmq-backend.ts +311 -0
- package/runtime/subsystems/jobs/job-worker.module.ts +124 -10
- package/runtime/subsystems/jobs/jobs-domain.module.ts +40 -21
- package/runtime/subsystems/jobs/pool-config.loader.ts +11 -0
- package/runtime/subsystems/observability/index.ts +8 -0
- package/runtime/subsystems/observability/observability.protocol.ts +76 -0
- package/runtime/subsystems/observability/observability.service.ts +148 -1
- package/templates/entity/new/clean-lite-ps/prompt-extension.js +14 -12
- package/templates/relationship/new/prompt.js +8 -5
- package/templates/subsystem/jobs/worker.ejs.t +30 -7
- package/templates/subsystem/sync/sync-audit.schema.ejs.t +12 -16
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import '@nestjs/common';
|
|
2
|
-
|
|
2
|
+
import '../events/generated/types.js';
|
|
3
|
+
export { a as ConcurrencyPolicy, D as DedupePolicy, H as HandlerRegistry, b as HandlerRegistryEntry, J as JOB_HANDLER_METADATA_KEY, c as JOB_HANDLER_REGISTRY, d as JobContext, e as JobHandler, f as JobHandlerBase, g as JobHandlerMeta, j as JobTrigger, P as ParentClosePolicy, R as RetryPolicy, S as ScopeRef, l as SpawnChildOptions, n as StepOptions } from '../../../job-orchestrator.protocol-CHOEqBDk.js';
|
|
3
4
|
import '../events/event-bus.protocol.js';
|
|
4
5
|
import '../../types/drizzle.js';
|
|
5
6
|
import 'drizzle-orm/node-postgres';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../runtime/subsystems/jobs/job-handler.base.ts"],"sourcesContent":["/**\n * Handler base class, JobContext, @JobHandler decorator, and policy types\n * for the job orchestration domain (ADR-022, JOB-2).\n *\n * User-authored jobs subclass `JobHandlerBase<TInput, TOutput>` and decorate\n * the class with `@JobHandler<TInput>('job_type', meta)`. The decorator\n * 1. stores metadata via `Reflect.defineMetadata` so Nest's reflector can\n * pick it up at module boot, and\n * 2. populates `JOB_HANDLER_REGISTRY` — a module-singleton map consumed by\n * `JobWorkerModule` (JOB-5) to materialise `job` rows and resolve\n * handler classes during claim/execute.\n *\n * No runtime orchestration lives here; this file is a pure type + decorator\n * surface so downstream PRs (JOB-3..JOB-5) can implement against a stable\n * shape.\n */\n// TODO(logging-subsystem): swap to ILogger once ADR-028 lands\nimport type { Logger } from '@nestjs/common';\nimport type { JobRun } from './job-orchestrator.protocol';\n\n// ─── ParentClosePolicy ──────────────────────────────────────────────────────\n\n/**\n * What happens to running child runs when a parent enters a terminal state.\n * Stored on the child at spawn; changes to the parent after spawn do NOT\n * retroactively rewrite children.\n */\nexport enum ParentClosePolicy {\n Terminate = 'terminate',\n Cancel = 'cancel',\n Abandon = 'abandon',\n}\n\n// ─── Policy types ───────────────────────────────────────────────────────────\n\nexport interface RetryPolicy {\n attempts: number;\n backoff: 'fixed' | 'exponential';\n baseMs: number;\n nonRetryableErrors?: string[];\n}\n\nexport interface ConcurrencyPolicy<TInput> {\n key: (input: TInput) => string;\n collisionMode: 'queue' | 'reject' | 'replace';\n}\n\nexport interface DedupePolicy<TInput> {\n key: (input: TInput) => string;\n windowMs: number;\n}\n\n/**\n * Declarative scope reference. `TScope` is parameterised so JOB-7 can narrow\n * `entity` to the generated `ScopeEntityType` union at the call site without\n * modifying this file (OQ-1 resolution, 2026-04-20).\n */\nexport interface ScopeRef<TInput, TScope extends string = string> {\n entity: TScope;\n from: (input: TInput) => string;\n}\n\nexport interface JobHandlerMeta<TInput> {\n pool?: string;\n scope?: ScopeRef<TInput>;\n retry?: RetryPolicy;\n concurrency?: ConcurrencyPolicy<TInput>;\n dedupe?: DedupePolicy<TInput>;\n timeoutMs?: number;\n replayFrom?: 'scratch' | 'last_step' | 'last_checkpoint';\n}\n\n// ─── Runtime option shapes ──────────────────────────────────────────────────\n\nexport interface StepOptions {\n retry?: RetryPolicy;\n timeoutMs?: number;\n}\n\nexport interface SpawnChildOptions {\n closePolicy?: ParentClosePolicy;\n runAt?: Date;\n priority?: number;\n tags?: Record<string, string>;\n}\n\n// ─── JobContext ─────────────────────────────────────────────────────────────\n\nexport interface JobContext<TInput> {\n readonly input: TInput;\n readonly run: JobRun;\n step<TOutput>(\n stepId: string,\n fn: () => Promise<TOutput>,\n opts?: StepOptions,\n ): Promise<TOutput>;\n spawnChild(type: string, input: unknown, opts?: SpawnChildOptions): Promise<JobRun>;\n readonly logger: Logger;\n // NOT in Phase 1 — deferred to ADR-025:\n // waitFor(kind, token, opts)\n // signal(token, payload)\n // sleep(ms)\n}\n\n// ─── JobHandlerBase ─────────────────────────────────────────────────────────\n\nexport abstract class JobHandlerBase<TInput, TOutput = unknown> {\n abstract run(ctx: JobContext<TInput>): Promise<TOutput>;\n}\n\n// ─── Registry + decorator ───────────────────────────────────────────────────\n\n/**\n * Module-singleton map keyed by job type. Populated by the `@JobHandler`\n * decorator at class definition time; consumed by `JobWorkerModule` (JOB-5)\n * to upsert `job` rows and resolve handler classes during claim/execute.\n */\nexport const JOB_HANDLER_REGISTRY = new Map<\n string,\n {\n type: string;\n meta: JobHandlerMeta<unknown>;\n handlerClass: new (...args: unknown[]) => JobHandlerBase<unknown>;\n }\n>();\n\nexport const JOB_HANDLER_METADATA_KEY = Symbol('JobHandlerMeta');\n\n/**\n * Class decorator that registers a handler with the job type, the full\n * metadata shape, and the target class constructor.\n *\n * Duplicate-type behaviour (OQ-3, resolved 2026-04-18):\n * - `NODE_ENV === 'production'` → throw; silent overwrite in prod is a\n * correctness bug.\n * - `NODE_ENV === 'test'` → silent overwrite (tests intentionally\n * re-register handlers).\n * - otherwise (dev) → `console.warn` + overwrite. `console`\n * is used intentionally instead of the Nest `Logger` — decorators run\n * at module-load time before any Nest container exists.\n */\nexport function JobHandler<TInput>(\n type: string,\n meta: JobHandlerMeta<TInput>,\n): ClassDecorator {\n return (target) => {\n if (JOB_HANDLER_REGISTRY.has(type)) {\n const env = process.env.NODE_ENV;\n if (env === 'production') {\n throw new Error(\n `[JobHandler] Duplicate registration for job type '${type}'. ` +\n `Each @JobHandler must declare a unique type.`,\n );\n }\n if (env !== 'test') {\n // eslint-disable-next-line no-console\n console.warn(\n `[JobHandler] Duplicate registration for job type '${type}'. ` +\n `Overwriting previous handler — this is almost certainly a bug.`,\n );\n }\n }\n\n Reflect.defineMetadata(JOB_HANDLER_METADATA_KEY, { type, meta }, target);\n JOB_HANDLER_REGISTRY.set(type, {\n type,\n meta: meta as JobHandlerMeta<unknown>,\n handlerClass: target as unknown as new (\n ...args: unknown[]\n ) => JobHandlerBase<unknown>,\n });\n };\n}\n\n// ─── HandlerRegistry — read helpers consumed by JobWorkerModule (JOB-5) ─────\n\n/**\n * Single entry shape returned by `HandlerRegistry.getAll()` / `.get()` and\n * exposed to `JobWorkerModule.onModuleInit` for boot-time upserts.\n *\n * Structurally compatible with `IJobOrchestrator.upsertJobRows`'s\n * `JobUpsertEntry` so the worker module can pass entries through verbatim\n * without re-mapping.\n */\nexport interface HandlerRegistryEntry {\n type: string;\n meta: JobHandlerMeta<unknown>;\n handlerClass: new (...args: unknown[]) => JobHandlerBase<unknown>;\n}\n\n/**\n * Read facade over `JOB_HANDLER_REGISTRY`. The decorator's write path is\n * unchanged; this namespace exists so consumers (the worker module, tests)\n * don't import the raw `Map` and accidentally mutate it.\n */\nexport namespace HandlerRegistry {\n /** All registered entries in insertion order. */\n export function getAll(): HandlerRegistryEntry[] {\n return Array.from(JOB_HANDLER_REGISTRY.values());\n }\n\n /** Lookup by job type, or `undefined` if no `@JobHandler` is registered. */\n export function get(type: string): HandlerRegistryEntry | undefined {\n return JOB_HANDLER_REGISTRY.get(type);\n }\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/jobs/job-handler.base.ts"],"sourcesContent":["/**\n * Handler base class, JobContext, @JobHandler decorator, and policy types\n * for the job orchestration domain (ADR-022, JOB-2).\n *\n * User-authored jobs subclass `JobHandlerBase<TInput, TOutput>` and decorate\n * the class with `@JobHandler<TInput>('job_type', meta)`. The decorator\n * 1. stores metadata via `Reflect.defineMetadata` so Nest's reflector can\n * pick it up at module boot, and\n * 2. populates `JOB_HANDLER_REGISTRY` — a module-singleton map consumed by\n * `JobWorkerModule` (JOB-5) to materialise `job` rows and resolve\n * handler classes during claim/execute.\n *\n * No runtime orchestration lives here; this file is a pure type + decorator\n * surface so downstream PRs (JOB-3..JOB-5) can implement against a stable\n * shape.\n */\n// TODO(logging-subsystem): swap to ILogger once ADR-028 lands\nimport type { Logger } from '@nestjs/common';\nimport type { EventOfType, EventTypeName } from '../events/generated/types';\nimport type { JobRun } from './job-orchestrator.protocol';\n\n// ─── ParentClosePolicy ──────────────────────────────────────────────────────\n\n/**\n * What happens to running child runs when a parent enters a terminal state.\n * Stored on the child at spawn; changes to the parent after spawn do NOT\n * retroactively rewrite children.\n */\nexport enum ParentClosePolicy {\n Terminate = 'terminate',\n Cancel = 'cancel',\n Abandon = 'abandon',\n}\n\n// ─── Policy types ───────────────────────────────────────────────────────────\n\nexport interface RetryPolicy {\n attempts: number;\n backoff: 'fixed' | 'exponential';\n baseMs: number;\n nonRetryableErrors?: string[];\n}\n\nexport interface ConcurrencyPolicy<TInput> {\n key: (input: TInput) => string;\n collisionMode: 'queue' | 'reject' | 'replace';\n}\n\nexport interface DedupePolicy<TInput> {\n key: (input: TInput) => string;\n windowMs: number;\n}\n\n/**\n * Declarative scope reference. `TScope` is parameterised so JOB-7 can narrow\n * `entity` to the generated `ScopeEntityType` union at the call site without\n * modifying this file (OQ-1 resolution, 2026-04-20).\n */\nexport interface ScopeRef<TInput, TScope extends string = string> {\n entity: TScope;\n from: (input: TInput) => string;\n}\n\n/**\n * Bridge trigger authoring shape (BRIDGE-6 follow-up — BRIDGE-6 shipped the\n * generator + runtime for `@JobHandler({ triggers })` but never added the\n * authoring field to this type; the generator's tests scan source as strings,\n * so a real decorator was never compiled and the gap went uncaught).\n *\n * Declared on `@JobHandler({ triggers })`; the codegen bridge-registry\n * generator (`src/cli/shared/bridge-registry-generator.ts`) scans these from\n * source and emits `bridge/generated/registry.ts`, validating each `event`\n * against the generated `eventRegistry` at `gen-all`. The distributed union\n * narrows `map`/`when` per `event`, so callbacks are typed against the event\n * payload (ADR-023, \"typed against PayloadOfType<T>\").\n *\n * Typed against events' generated types — the same `import type` coupling the\n * bridge already has (erased at runtime). `jobs` must NOT import `bridge`, so\n * the post-gen `BridgeTriggerEntry` is deliberately not referenced here;\n * `triggerId`/`jobType` are computed by the generator, not authored.\n */\nexport type JobTrigger<TInput> = {\n [T in EventTypeName]: {\n /** Event type that fires this trigger. Validated against `eventRegistry`. */\n event: T;\n /** Maps the event to the job input. Inlined verbatim into the registry. */\n map: (event: EventOfType<T>) => TInput;\n /** Optional guard; `false` → wrapper records `status='skipped'`. */\n when?: (event: EventOfType<T>) => boolean;\n };\n}[EventTypeName];\n\nexport interface JobHandlerMeta<TInput> {\n pool?: string;\n scope?: ScopeRef<TInput>;\n retry?: RetryPolicy;\n concurrency?: ConcurrencyPolicy<TInput>;\n dedupe?: DedupePolicy<TInput>;\n timeoutMs?: number;\n replayFrom?: 'scratch' | 'last_step' | 'last_checkpoint';\n /**\n * Bridge triggers (ADR-023 Tier 3). Codegen scans these into `bridgeRegistry`;\n * the framework `BridgeDeliveryHandler` starts this job per matched event.\n * Absent for jobs started directly or via `IEventFlow.publishAndStart`.\n */\n triggers?: readonly JobTrigger<TInput>[];\n}\n\n// ─── Runtime option shapes ──────────────────────────────────────────────────\n\nexport interface StepOptions {\n retry?: RetryPolicy;\n timeoutMs?: number;\n}\n\nexport interface SpawnChildOptions {\n closePolicy?: ParentClosePolicy;\n runAt?: Date;\n priority?: number;\n tags?: Record<string, string>;\n}\n\n// ─── JobContext ─────────────────────────────────────────────────────────────\n\nexport interface JobContext<TInput> {\n readonly input: TInput;\n readonly run: JobRun;\n step<TOutput>(\n stepId: string,\n fn: () => Promise<TOutput>,\n opts?: StepOptions,\n ): Promise<TOutput>;\n spawnChild(type: string, input: unknown, opts?: SpawnChildOptions): Promise<JobRun>;\n readonly logger: Logger;\n // NOT in Phase 1 — deferred to ADR-025:\n // waitFor(kind, token, opts)\n // signal(token, payload)\n // sleep(ms)\n}\n\n// ─── JobHandlerBase ─────────────────────────────────────────────────────────\n\nexport abstract class JobHandlerBase<TInput, TOutput = unknown> {\n abstract run(ctx: JobContext<TInput>): Promise<TOutput>;\n}\n\n// ─── Registry + decorator ───────────────────────────────────────────────────\n\n/**\n * Module-singleton map keyed by job type. Populated by the `@JobHandler`\n * decorator at class definition time; consumed by `JobWorkerModule` (JOB-5)\n * to upsert `job` rows and resolve handler classes during claim/execute.\n */\nexport const JOB_HANDLER_REGISTRY = new Map<\n string,\n {\n type: string;\n meta: JobHandlerMeta<unknown>;\n handlerClass: new (...args: unknown[]) => JobHandlerBase<unknown>;\n }\n>();\n\nexport const JOB_HANDLER_METADATA_KEY = Symbol('JobHandlerMeta');\n\n/**\n * Class decorator that registers a handler with the job type, the full\n * metadata shape, and the target class constructor.\n *\n * Duplicate-type behaviour (OQ-3, resolved 2026-04-18):\n * - `NODE_ENV === 'production'` → throw; silent overwrite in prod is a\n * correctness bug.\n * - `NODE_ENV === 'test'` → silent overwrite (tests intentionally\n * re-register handlers).\n * - otherwise (dev) → `console.warn` + overwrite. `console`\n * is used intentionally instead of the Nest `Logger` — decorators run\n * at module-load time before any Nest container exists.\n */\nexport function JobHandler<TInput>(\n type: string,\n meta: JobHandlerMeta<TInput>,\n): ClassDecorator {\n return (target) => {\n if (JOB_HANDLER_REGISTRY.has(type)) {\n const env = process.env.NODE_ENV;\n if (env === 'production') {\n throw new Error(\n `[JobHandler] Duplicate registration for job type '${type}'. ` +\n `Each @JobHandler must declare a unique type.`,\n );\n }\n if (env !== 'test') {\n // eslint-disable-next-line no-console\n console.warn(\n `[JobHandler] Duplicate registration for job type '${type}'. ` +\n `Overwriting previous handler — this is almost certainly a bug.`,\n );\n }\n }\n\n Reflect.defineMetadata(JOB_HANDLER_METADATA_KEY, { type, meta }, target);\n JOB_HANDLER_REGISTRY.set(type, {\n type,\n meta: meta as JobHandlerMeta<unknown>,\n handlerClass: target as unknown as new (\n ...args: unknown[]\n ) => JobHandlerBase<unknown>,\n });\n };\n}\n\n// ─── HandlerRegistry — read helpers consumed by JobWorkerModule (JOB-5) ─────\n\n/**\n * Single entry shape returned by `HandlerRegistry.getAll()` / `.get()` and\n * exposed to `JobWorkerModule.onModuleInit` for boot-time upserts.\n *\n * Structurally compatible with `IJobOrchestrator.upsertJobRows`'s\n * `JobUpsertEntry` so the worker module can pass entries through verbatim\n * without re-mapping.\n */\nexport interface HandlerRegistryEntry {\n type: string;\n meta: JobHandlerMeta<unknown>;\n handlerClass: new (...args: unknown[]) => JobHandlerBase<unknown>;\n}\n\n/**\n * Read facade over `JOB_HANDLER_REGISTRY`. The decorator's write path is\n * unchanged; this namespace exists so consumers (the worker module, tests)\n * don't import the raw `Map` and accidentally mutate it.\n */\nexport namespace HandlerRegistry {\n /** All registered entries in insertion order. */\n export function getAll(): HandlerRegistryEntry[] {\n return Array.from(JOB_HANDLER_REGISTRY.values());\n }\n\n /** Lookup by job type, or `undefined` if no `@JobHandler` is registered. */\n export function get(type: string): HandlerRegistryEntry | undefined {\n return JOB_HANDLER_REGISTRY.get(type);\n }\n}\n"],"mappings":";AA4BO,IAAK,oBAAL,kBAAKA,uBAAL;AACL,EAAAA,mBAAA,eAAY;AACZ,EAAAA,mBAAA,YAAS;AACT,EAAAA,mBAAA,aAAU;AAHA,SAAAA;AAAA,GAAA;AAkHL,IAAe,iBAAf,MAAyD;AAEhE;AASO,IAAM,uBAAuB,oBAAI,IAOtC;AAEK,IAAM,2BAA2B,uBAAO,gBAAgB;AAexD,SAAS,WACd,MACA,MACgB;AAChB,SAAO,CAAC,WAAW;AACjB,QAAI,qBAAqB,IAAI,IAAI,GAAG;AAClC,YAAM,MAAM,QAAQ,IAAI;AACxB,UAAI,QAAQ,cAAc;AACxB,cAAM,IAAI;AAAA,UACR,qDAAqD,IAAI;AAAA,QAE3D;AAAA,MACF;AACA,UAAI,QAAQ,QAAQ;AAElB,gBAAQ;AAAA,UACN,qDAAqD,IAAI;AAAA,QAE3D;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,eAAe,0BAA0B,EAAE,MAAM,KAAK,GAAG,MAAM;AACvE,yBAAqB,IAAI,MAAM;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,cAAc;AAAA,IAGhB,CAAC;AAAA,EACH;AACF;AAuBO,IAAU;AAAA,CAAV,CAAUC,qBAAV;AAEE,WAAS,SAAiC;AAC/C,WAAO,MAAM,KAAK,qBAAqB,OAAO,CAAC;AAAA,EACjD;AAFO,EAAAA,iBAAS;AAKT,WAAS,IAAI,MAAgD;AAClE,WAAO,qBAAqB,IAAI,IAAI;AAAA,EACtC;AAFO,EAAAA,iBAAS;AAAA,GAPD;","names":["ParentClosePolicy","HandlerRegistry"]}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ConnectionOptions, FlowProducer } from 'bullmq';
|
|
2
|
+
import { DrizzleClient } from '../../types/drizzle.js';
|
|
3
|
+
import { DrizzleTransaction } from '../events/event-bus.protocol.js';
|
|
4
|
+
import { DrizzleJobOrchestrator } from './job-orchestrator.drizzle-backend.js';
|
|
5
|
+
import { m as StartOptions, i as JobRun, C as CancelOptions } from '../../../job-orchestrator.protocol-CHOEqBDk.js';
|
|
6
|
+
import { BullMqResolvedConfig } from './bullmq.config.js';
|
|
7
|
+
import 'drizzle-orm/node-postgres';
|
|
8
|
+
import './job-orchestration.schema.js';
|
|
9
|
+
import 'drizzle-orm/pg-core';
|
|
10
|
+
import 'drizzle-orm';
|
|
11
|
+
import '@nestjs/common';
|
|
12
|
+
import '../events/generated/types.js';
|
|
13
|
+
import './pool-config.loader.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Derive a colon-safe, stable BullMQ `jobId` from a logical idempotency key.
|
|
17
|
+
*
|
|
18
|
+
* SHA-1 over the raw key. Collision analysis (spec §Gotcha 1, resolved during
|
|
19
|
+
* implementation): SHA-1's 160-bit space makes an accidental collision between
|
|
20
|
+
* two *distinct* logical keys astronomically unlikely at any realistic job
|
|
21
|
+
* volume (the birthday bound is ~2^80 keys before a 50% collision chance —
|
|
22
|
+
* orders of magnitude beyond any job throughput). SHA-1's cryptographic
|
|
23
|
+
* weakness is irrelevant here: there is no adversary forging idempotency keys,
|
|
24
|
+
* and even a forged collision only deduplicates two jobs that the caller chose
|
|
25
|
+
* to key identically. We therefore accept SHA-1 with no mitigation. The *same*
|
|
26
|
+
* logical key intentionally maps to the *same* jobId — that is the dedup
|
|
27
|
+
* mechanism, not a collision.
|
|
28
|
+
*/
|
|
29
|
+
declare function sha1JobId(idempotencyKey: string): string;
|
|
30
|
+
declare class BullMQJobOrchestrator extends DrizzleJobOrchestrator {
|
|
31
|
+
private readonly connection;
|
|
32
|
+
private readonly bullConfig;
|
|
33
|
+
private readonly bullLogger;
|
|
34
|
+
/** Lazily-opened `Queue` handles, one per pool. */
|
|
35
|
+
private readonly queues;
|
|
36
|
+
/** Single FlowProducer for parent/child hierarchies. Lazily opened. */
|
|
37
|
+
private _flow;
|
|
38
|
+
/**
|
|
39
|
+
* Cached `bullmq` value constructors, populated by `loadBullMq()` on first
|
|
40
|
+
* use (the `start`/`cancel`/`replay` entrypoints `await` it before touching
|
|
41
|
+
* a queue). Kept off the import graph so a `drizzle`-only consumer never
|
|
42
|
+
* resolves the optional `'bullmq'` package.
|
|
43
|
+
*/
|
|
44
|
+
private QueueCtor;
|
|
45
|
+
private FlowProducerCtor;
|
|
46
|
+
private bullMqLoad;
|
|
47
|
+
/**
|
|
48
|
+
* Own reference to the Drizzle client. `DrizzleJobOrchestrator.db` is
|
|
49
|
+
* `private` (can't be redeclared even privately in a subclass), and the
|
|
50
|
+
* spec forbids touching that file — so the subclass keeps its own handle
|
|
51
|
+
* under a distinct name (same instance, passed through to `super`) for the
|
|
52
|
+
* cancel-cascade snapshot + definition/run loads below.
|
|
53
|
+
*/
|
|
54
|
+
private readonly bullDb;
|
|
55
|
+
constructor(db: DrizzleClient, multiTenant: boolean, connection: ConnectionOptions, bullConfig?: BullMqResolvedConfig | null);
|
|
56
|
+
/**
|
|
57
|
+
* Lazily load the optional `bullmq` package and cache its value
|
|
58
|
+
* constructors. Idempotent (single in-flight promise). Throws a friendly,
|
|
59
|
+
* actionable error when the consumer selected `backend: 'bullmq'` but did
|
|
60
|
+
* not install the package — mirrors `createRedisClient` in the redis event
|
|
61
|
+
* backend. Must be `await`ed before any `queueFor`/`flow` access.
|
|
62
|
+
*/
|
|
63
|
+
private loadBullMq;
|
|
64
|
+
/**
|
|
65
|
+
* Open (or reuse) the `Queue` for a pool. Synchronous — callers `await
|
|
66
|
+
* loadBullMq()` first so `QueueCtor` is populated.
|
|
67
|
+
*/
|
|
68
|
+
private queueFor;
|
|
69
|
+
private flow;
|
|
70
|
+
start(type: string, input: unknown, opts?: StartOptions, tx?: DrizzleTransaction): Promise<JobRun>;
|
|
71
|
+
/**
|
|
72
|
+
* Map a `job_run` row onto a BullMQ job via `queue.add`. When the run has a
|
|
73
|
+
* `parentRunId` we attach it to the parent's existing BullMQ job through the
|
|
74
|
+
* `parent: { id, queue }` opt — BullMQ then tracks the parent/child link in
|
|
75
|
+
* its own graph. (The FlowProducer is reserved for whole-tree atomic
|
|
76
|
+
* submits, exposed as an opt-in extension via `flowProducer()`; runtime
|
|
77
|
+
* `ctx.spawnChild` is incremental, so `queue.add` with a parent ref is the
|
|
78
|
+
* correct primitive here.)
|
|
79
|
+
*
|
|
80
|
+
* The `jobId` is colon-safe + stable: `sha1(dedupeKey)` when a dedupe key is
|
|
81
|
+
* present (so the same logical key dedups), else the `job_run.id` UUID
|
|
82
|
+
* (already colon-free).
|
|
83
|
+
*
|
|
84
|
+
* The domain `parentClosePolicy` cascade is still enforced in Postgres by
|
|
85
|
+
* the shared `cancel` path — BullMQ's parent link is dispatch bookkeeping,
|
|
86
|
+
* not the authority.
|
|
87
|
+
*/
|
|
88
|
+
private dispatch;
|
|
89
|
+
/**
|
|
90
|
+
* Opt-in extension (spec §Extensions): expose the FlowProducer for
|
|
91
|
+
* consumers that want to submit a whole parent/child DAG atomically up
|
|
92
|
+
* front, rather than incrementally via `ctx.spawnChild`. Backend-specific —
|
|
93
|
+
* code using it is not portable to the Drizzle backend. Async because it
|
|
94
|
+
* lazily loads the optional `bullmq` package on first use.
|
|
95
|
+
*/
|
|
96
|
+
flowProducer(): Promise<FlowProducer>;
|
|
97
|
+
private retryOpts;
|
|
98
|
+
private dedupeOpts;
|
|
99
|
+
cancel(runId: string, opts?: CancelOptions): Promise<void>;
|
|
100
|
+
private removeFromQueue;
|
|
101
|
+
replay(runId: string): Promise<JobRun>;
|
|
102
|
+
private loadDefinition;
|
|
103
|
+
private loadRun;
|
|
104
|
+
/** Close all open queue + flow connections. Called on module destroy. */
|
|
105
|
+
closeConnections(): Promise<void>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { BullMQJobOrchestrator, sha1JobId };
|