@pattern-stack/codegen 0.9.2 → 0.10.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +5 -0
  3. package/consumer-skills/bridge/SKILL.md +265 -0
  4. package/consumer-skills/codegen/SKILL.md +115 -0
  5. package/consumer-skills/entities/SKILL.md +111 -0
  6. package/consumer-skills/entities/families-and-queries.md +82 -0
  7. package/consumer-skills/entities/yaml-reference.md +118 -0
  8. package/consumer-skills/events/SKILL.md +71 -0
  9. package/consumer-skills/events/authoring-events.md +164 -0
  10. package/consumer-skills/events/typed-bus-and-outbox.md +163 -0
  11. package/consumer-skills/jobs/SKILL.md +66 -0
  12. package/consumer-skills/jobs/handler-authoring.md +236 -0
  13. package/consumer-skills/jobs/pools-and-ordering.md +161 -0
  14. package/consumer-skills/subsystems/SKILL.md +161 -0
  15. package/consumer-skills/subsystems/wiring-and-order.md +120 -0
  16. package/consumer-skills/sync/SKILL.md +134 -0
  17. package/consumer-skills/sync/audit-and-detection.md +302 -0
  18. package/consumer-skills/sync/change-sources-and-sinks.md +442 -0
  19. package/dist/runtime/subsystems/bridge/bridge.module.d.ts +0 -1
  20. package/dist/runtime/subsystems/bridge/bridge.module.js +294 -710
  21. package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
  22. package/dist/runtime/subsystems/bridge/index.d.ts +0 -1
  23. package/dist/runtime/subsystems/bridge/index.js +248 -664
  24. package/dist/runtime/subsystems/bridge/index.js.map +1 -1
  25. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +18 -10
  26. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
  27. package/dist/runtime/subsystems/events/events.module.js +43 -244
  28. package/dist/runtime/subsystems/events/events.module.js.map +1 -1
  29. package/dist/runtime/subsystems/events/index.d.ts +0 -1
  30. package/dist/runtime/subsystems/events/index.js +39 -241
  31. package/dist/runtime/subsystems/events/index.js.map +1 -1
  32. package/dist/runtime/subsystems/index.js +174 -791
  33. package/dist/runtime/subsystems/index.js.map +1 -1
  34. package/dist/runtime/subsystems/jobs/bullmq.config.d.ts +22 -3
  35. package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -1
  36. package/dist/runtime/subsystems/jobs/index.d.ts +1 -4
  37. package/dist/runtime/subsystems/jobs/index.js +87 -506
  38. package/dist/runtime/subsystems/jobs/index.js.map +1 -1
  39. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
  40. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +3 -0
  41. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -1
  42. package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +11 -4
  43. package/dist/runtime/subsystems/jobs/job-worker.module.js +248 -664
  44. package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
  45. package/dist/runtime/subsystems/jobs/jobs-domain.module.d.ts +0 -1
  46. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +89 -391
  47. package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
  48. package/dist/src/cli/index.js +1065 -440
  49. package/dist/src/cli/index.js.map +1 -1
  50. package/dist/src/index.js +26 -4
  51. package/dist/src/index.js.map +1 -1
  52. package/package.json +2 -1
  53. package/runtime/subsystems/events/event-bus.drizzle-backend.ts +32 -10
  54. package/runtime/subsystems/events/events.module.ts +38 -6
  55. package/runtime/subsystems/events/index.ts +7 -1
  56. package/runtime/subsystems/jobs/bullmq.config.ts +23 -3
  57. package/runtime/subsystems/jobs/index.ts +13 -8
  58. package/runtime/subsystems/jobs/job-worker.bullmq-backend.ts +5 -2
  59. package/runtime/subsystems/jobs/job-worker.module.ts +27 -7
  60. package/runtime/subsystems/jobs/jobs-domain.module.ts +27 -2
  61. package/templates/subsystem/events/domain-events.schema.ejs.t +43 -2
@@ -1,4 +1,3 @@
1
- import { ConnectionOptions } from 'bullmq';
2
1
  import { PoolConfig } from './pool-config.loader.js';
3
2
 
4
3
  /**
@@ -10,6 +9,26 @@ import { PoolConfig } from './pool-config.loader.js';
10
9
  * protocol). The Drizzle backend never reads any of it.
11
10
  */
12
11
 
12
+ /**
13
+ * #6 — Structural mirror of BullMQ's `ConnectionOptions`. Declared locally
14
+ * so this config file (which ships into EVERY jobs install, drizzle or
15
+ * bullmq) does NOT need the `bullmq` peer dep resolved by the consumer's
16
+ * tsc. The bullmq backend internally casts to the real `ConnectionOptions`
17
+ * — that file is only vendored when `--backend bullmq` is selected
18
+ * (see `backendFileFilter`).
19
+ *
20
+ * Accepts the `{ url }` shape this resolver emits, plus the host/port/
21
+ * password/db form BullMQ also accepts, with an open index for any extra
22
+ * ioredis options consumers may flow through.
23
+ */
24
+ type BullMqConnectionOptions = {
25
+ url?: string;
26
+ host?: string;
27
+ port?: number;
28
+ password?: string;
29
+ db?: number;
30
+ [key: string]: unknown;
31
+ };
13
32
  /**
14
33
  * Typed shape of `codegen.config.yaml: jobs.extensions.bullmq`. Snake_case
15
34
  * because it mirrors the YAML the consumer authors.
@@ -53,7 +72,7 @@ interface BullMqExtensionsConfig {
53
72
  * orchestrator + worker actually consume.
54
73
  */
55
74
  interface BullMqResolvedConfig {
56
- connection: ConnectionOptions;
75
+ connection: BullMqConnectionOptions;
57
76
  queuePrefix?: string;
58
77
  bullBoard?: {
59
78
  enabled: boolean;
@@ -95,4 +114,4 @@ declare function resolveBullMqConfig(ext: BullMqExtensionsConfig | undefined): B
95
114
  */
96
115
  declare function resolvePoolQueueName(pool: string, config: BullMqResolvedConfig | null | undefined, poolConfig?: PoolConfig): string;
97
116
 
98
- export { BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG, type BullMqExtensionsConfig, type BullMqResolvedConfig, resolveBullMqConfig, resolvePoolQueueName };
117
+ export { BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG, type BullMqConnectionOptions, type BullMqExtensionsConfig, type BullMqResolvedConfig, resolveBullMqConfig, resolvePoolQueueName };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../runtime/subsystems/jobs/pool-config.loader.ts","../../../../runtime/subsystems/jobs/bullmq.config.ts"],"sourcesContent":["/**\n * Pool config loader for the job orchestration domain (ADR-022, JOB-5).\n *\n * Reads `codegen.config.yaml: jobs.pools` from `process.cwd()` (or an\n * explicit `configPath` for tests), merges user-defined pools onto the five\n * framework defaults, and returns the resolved `Map<string, PoolDefinition>`\n * consumed by `JobWorkerModule.onModuleInit` and `JobsDomainModule`'s\n * config-validator surface.\n *\n * Invariants:\n * - User cannot flip `reserved: true` on a framework pool — silently\n * preserved. The three `events_*` pools are reserved infrastructure\n * for the events outbox drain.\n * - User-defined pools cannot set `reserved: true` — `reserved` is\n * framework-only metadata.\n * - Missing `codegen.config.yaml` is not an error; loader returns the\n * framework defaults verbatim.\n *\n * Result is cached at module scope after first call so repeated reads (e.g.\n * a worker module + a one-off scaffold validator in the same process) hit\n * the same parse. Tests that pass `configPath` skip the cache and isolate.\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\n\nexport interface PoolDefinition {\n /** Routing identifier — reused as the per-pool worker queue name. */\n queue: string;\n /** Max parallel in-flight `processRun` calls for this pool's worker. */\n concurrency: number;\n /** `true` ⇒ user `@JobHandler` may not target it. Framework-only. */\n reserved: boolean;\n /** Free-text annotation surfaced in admin UIs / logs. */\n description?: string;\n}\n\nexport type PoolConfig = Map<string, PoolDefinition>;\n\n/**\n * Five framework defaults. Three reserved `events_*` pools drain the\n * `IEventBus` outbox (one per `DomainEvent.direction`); `interactive` and\n * `batch` are user-default pools (`batch` is the `@JobHandler` default\n * when no `pool` is specified).\n */\nexport const FRAMEWORK_POOLS: Readonly<Record<string, PoolDefinition>> = Object.freeze({\n events_inbound: Object.freeze({\n queue: 'jobs-events-inbound',\n concurrency: 20,\n reserved: true,\n description: 'Inbound events drain (events subsystem outbox).',\n }),\n events_change: Object.freeze({\n queue: 'jobs-events-change',\n concurrency: 30,\n reserved: true,\n description: 'Change events drain (events subsystem outbox).',\n }),\n events_outbound: Object.freeze({\n queue: 'jobs-events-outbound',\n concurrency: 10,\n reserved: true,\n description: 'Outbound events drain (events subsystem outbox).',\n }),\n interactive: Object.freeze({\n queue: 'jobs-interactive',\n concurrency: 20,\n reserved: false,\n description: 'User-facing latency-sensitive jobs.',\n }),\n batch: Object.freeze({\n queue: 'jobs-batch',\n concurrency: 5,\n reserved: false,\n description: 'Default pool for background jobs.',\n }),\n});\n\n/** Names of the framework reserved pools. Cheap inline lookup for the worker. */\nexport const RESERVED_POOL_NAMES: ReadonlySet<string> = new Set(\n Object.entries(FRAMEWORK_POOLS)\n .filter(([, def]) => def.reserved)\n .map(([name]) => name),\n);\n\n/**\n * Cache by absolute config path. The `cwd` default is normalised before\n * lookup so two callers passing the same path share the cache; explicit\n * test-only paths cache separately.\n */\nconst cache = new Map<string, PoolConfig>();\n\n/**\n * Reset the loader cache. Test-only — not exported from the package\n * `index.ts`. Useful for tests that mutate `process.cwd()` between cases.\n */\nexport function _resetPoolConfigCacheForTests(): void {\n cache.clear();\n}\n\n/**\n * Resolve the merged pool config.\n *\n * @param configPath optional absolute or cwd-relative path; defaults to\n * `${process.cwd()}/codegen.config.yaml`.\n */\nexport function loadPoolConfig(configPath?: string): PoolConfig {\n const resolved = resolve(configPath ?? `${process.cwd()}/codegen.config.yaml`);\n const cached = cache.get(resolved);\n if (cached) return cached;\n\n const merged = new Map<string, PoolDefinition>();\n // Seed with framework defaults first — they always take precedence on\n // `reserved` and provide defaults for `queue` / `concurrency` if user\n // overrides only some fields.\n for (const [name, def] of Object.entries(FRAMEWORK_POOLS)) {\n merged.set(name, { ...def });\n }\n\n if (!existsSync(resolved)) {\n cache.set(resolved, merged);\n return merged;\n }\n\n let raw: unknown;\n try {\n raw = parseYaml(readFileSync(resolved, 'utf8'));\n } catch (err) {\n throw new Error(\n `pool-config.loader: failed to parse YAML at ${resolved}: ${(err as Error).message}`,\n );\n }\n\n const userPools = extractUserPools(raw);\n for (const [name, userDef] of Object.entries(userPools)) {\n const existing = merged.get(name);\n if (existing) {\n // Framework pool — user may tweak concurrency + description but\n // cannot flip `reserved`. `queue` is frozen too (reserved framework\n // pools' queue identifiers are part of the cross-subsystem contract\n // with the events outbox drain).\n const next: PoolDefinition = {\n queue: existing.queue,\n concurrency:\n typeof userDef.concurrency === 'number'\n ? userDef.concurrency\n : existing.concurrency,\n reserved: existing.reserved,\n description: userDef.description ?? existing.description,\n };\n merged.set(name, next);\n continue;\n }\n // User-defined pool. Validate required fields; reject reserved.\n if (typeof userDef.queue !== 'string' || userDef.queue.length === 0) {\n throw new Error(\n `pool-config.loader: pool '${name}' must declare a non-empty 'queue'.`,\n );\n }\n if (typeof userDef.concurrency !== 'number' || userDef.concurrency <= 0) {\n throw new Error(\n `pool-config.loader: pool '${name}' must declare a positive 'concurrency'.`,\n );\n }\n if (userDef.reserved === true) {\n throw new Error(\n `pool-config.loader: user-defined pool '${name}' cannot set ` +\n `'reserved: true' — reserved is framework-only.`,\n );\n }\n merged.set(name, {\n queue: userDef.queue,\n concurrency: userDef.concurrency,\n reserved: false,\n description: userDef.description,\n });\n }\n\n cache.set(resolved, merged);\n return merged;\n}\n\n/**\n * Names of every non-reserved pool in the resolved config. The default\n * worker activation set when `JobWorkerModuleOptions.pools` is omitted —\n * the worker process never claims the reserved `events_*` pools by\n * default; those are bound by the events subsystem's outbox bridge.\n */\nexport function allNonReservedPoolNames(config: PoolConfig): string[] {\n const out: string[] = [];\n for (const [name, def] of config) {\n if (!def.reserved) out.push(name);\n }\n return out;\n}\n\n/**\n * Names of **every** pool in the resolved config, reserved `events_*` lanes\n * included. The activation set for a standalone worker booted with\n * `JobWorkerModule.forRoot({ allPools: true })` (BULLMQ-1 Phase 1) — the\n * single worker process drains both user pools and the bridge's reserved\n * pools so wrapper `job_run` rows are never stranded.\n */\nexport function allPoolNames(config: PoolConfig): string[] {\n return [...config.keys()];\n}\n\n// ─── internals ──────────────────────────────────────────────────────────────\n\ninterface UserPoolShape {\n queue?: string;\n concurrency?: number;\n reserved?: boolean;\n description?: string;\n}\n\nfunction extractUserPools(raw: unknown): Record<string, UserPoolShape> {\n if (!raw || typeof raw !== 'object') return {};\n const jobs = (raw as { jobs?: unknown }).jobs;\n if (!jobs || typeof jobs !== 'object') return {};\n const pools = (jobs as { pools?: unknown }).pools;\n if (!pools || typeof pools !== 'object') return {};\n const out: Record<string, UserPoolShape> = {};\n for (const [name, def] of Object.entries(pools as Record<string, unknown>)) {\n if (!def || typeof def !== 'object') continue;\n out[name] = def as UserPoolShape;\n }\n return out;\n}\n","/**\n * BullMQ backend configuration surface (BULLMQ-1, ADR-022 extension slot).\n *\n * The core `IJobOrchestrator` contract is backend-agnostic; everything in\n * this file is BullMQ-specific and lives behind the\n * `jobs.extensions.bullmq.*` config namespace (CLAUDE.md core/extension\n * protocol). The Drizzle backend never reads any of it.\n */\nimport type { ConnectionOptions } from 'bullmq';\nimport { loadPoolConfig, type PoolConfig } from './pool-config.loader';\n\n/**\n * Typed shape of `codegen.config.yaml: jobs.extensions.bullmq`. Snake_case\n * because it mirrors the YAML the consumer authors.\n *\n * ```yaml\n * jobs:\n * backend: bullmq\n * extensions:\n * bullmq:\n * redis_url: redis://localhost:6379 # or env REDIS_URL\n * queue_prefix: myapp # optional namespace (ADR-022 OQ)\n * bull_board:\n * enabled: true\n * mount_path: /api/admin/queues\n * ```\n */\nexport interface BullMqExtensionsConfig {\n /**\n * Redis/Valkey connection URL. When omitted, the runtime resolves\n * `process.env.REDIS_URL`, then falls back to `redis://localhost:6379`.\n */\n redis_url?: string;\n /**\n * Optional queue-name prefix to avoid collisions when several codegen apps\n * share one Redis (ADR-022 §\"BullMQ queue naming collisions\"). Applied to\n * every pool queue alias.\n */\n queue_prefix?: string;\n /**\n * Bull Board dashboard — opt-in extension (not core). Mounting is the\n * consumer's responsibility (it needs the consumer's Express/Nest adapter +\n * admin auth); we only carry the config. See README + spec §Extensions.\n */\n bull_board?: {\n enabled: boolean;\n mount_path?: string;\n };\n}\n\n/**\n * The runtime form after `redis_url`/env resolution. This is what the\n * orchestrator + worker actually consume.\n */\nexport interface BullMqResolvedConfig {\n connection: ConnectionOptions;\n queuePrefix?: string;\n bullBoard?: { enabled: boolean; mountPath: string };\n}\n\n/** DI token for the resolved BullMQ `ConnectionOptions` (ioredis-compatible). */\nexport const BULLMQ_CONNECTION = Symbol('BULLMQ_CONNECTION');\n\n/** DI token for the full resolved BullMQ config (prefix + bull board). */\nexport const BULLMQ_RESOLVED_CONFIG = Symbol('BULLMQ_RESOLVED_CONFIG');\n\nconst DEFAULT_REDIS_URL = 'redis://localhost:6379';\nconst DEFAULT_BULL_BOARD_MOUNT = '/admin/queues';\n\n/**\n * Resolve the BullMQ runtime config from the extension block.\n *\n * Precedence for the connection URL:\n * 1. explicit `extensions.bullmq.redis_url`\n * 2. `process.env.REDIS_URL`\n * 3. `redis://localhost:6379`\n *\n * Returns a `{ url }` connection shape — BullMQ/ioredis accept a URL string\n * via the `{ url }` ConnectionOptions form.\n */\nexport function resolveBullMqConfig(\n ext: BullMqExtensionsConfig | undefined,\n): BullMqResolvedConfig {\n const url =\n ext?.redis_url ?? process.env.REDIS_URL ?? DEFAULT_REDIS_URL;\n\n const resolved: BullMqResolvedConfig = {\n connection: { url } as ConnectionOptions,\n queuePrefix: ext?.queue_prefix,\n };\n if (ext?.bull_board?.enabled) {\n resolved.bullBoard = {\n enabled: true,\n mountPath: ext.bull_board.mount_path ?? DEFAULT_BULL_BOARD_MOUNT,\n };\n }\n return resolved;\n}\n\n/**\n * Resolve the BullMQ queue name for a *logical pool name*. The orchestrator\n * and worker MUST agree on this mapping or jobs are enqueued onto a queue\n * nobody consumes. Both derive it identically:\n *\n * 1. Look up the pool's `queue` alias (e.g. `jobs-batch`) in the resolved\n * pool config — the same alias `JobWorkerModule.onModuleInit` logs and\n * that the BullMQ `Worker` binds to.\n * 2. Fall back to the logical pool name when the pool is unknown (defensive;\n * still a stable, colon-free identifier).\n * 3. Apply the optional `queue_prefix` namespace for multi-app Redis\n * sharing — `:` is fine in the *queue name* (it is only forbidden in the\n * `jobId`, hence the sha1 there).\n *\n * `poolConfig` defaults to the cached `loadPoolConfig()` so callers that only\n * hold the logical pool name (the orchestrator) don't need to thread the map.\n */\nexport function resolvePoolQueueName(\n pool: string,\n config: BullMqResolvedConfig | null | undefined,\n poolConfig: PoolConfig = loadPoolConfig(),\n): string {\n const alias = poolConfig.get(pool)?.queue ?? pool;\n const prefix = config?.queuePrefix;\n return prefix ? `${prefix}:${alias}` : alias;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../runtime/subsystems/jobs/pool-config.loader.ts","../../../../runtime/subsystems/jobs/bullmq.config.ts"],"sourcesContent":["/**\n * Pool config loader for the job orchestration domain (ADR-022, JOB-5).\n *\n * Reads `codegen.config.yaml: jobs.pools` from `process.cwd()` (or an\n * explicit `configPath` for tests), merges user-defined pools onto the five\n * framework defaults, and returns the resolved `Map<string, PoolDefinition>`\n * consumed by `JobWorkerModule.onModuleInit` and `JobsDomainModule`'s\n * config-validator surface.\n *\n * Invariants:\n * - User cannot flip `reserved: true` on a framework pool — silently\n * preserved. The three `events_*` pools are reserved infrastructure\n * for the events outbox drain.\n * - User-defined pools cannot set `reserved: true` — `reserved` is\n * framework-only metadata.\n * - Missing `codegen.config.yaml` is not an error; loader returns the\n * framework defaults verbatim.\n *\n * Result is cached at module scope after first call so repeated reads (e.g.\n * a worker module + a one-off scaffold validator in the same process) hit\n * the same parse. Tests that pass `configPath` skip the cache and isolate.\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\n\nexport interface PoolDefinition {\n /** Routing identifier — reused as the per-pool worker queue name. */\n queue: string;\n /** Max parallel in-flight `processRun` calls for this pool's worker. */\n concurrency: number;\n /** `true` ⇒ user `@JobHandler` may not target it. Framework-only. */\n reserved: boolean;\n /** Free-text annotation surfaced in admin UIs / logs. */\n description?: string;\n}\n\nexport type PoolConfig = Map<string, PoolDefinition>;\n\n/**\n * Five framework defaults. Three reserved `events_*` pools drain the\n * `IEventBus` outbox (one per `DomainEvent.direction`); `interactive` and\n * `batch` are user-default pools (`batch` is the `@JobHandler` default\n * when no `pool` is specified).\n */\nexport const FRAMEWORK_POOLS: Readonly<Record<string, PoolDefinition>> = Object.freeze({\n events_inbound: Object.freeze({\n queue: 'jobs-events-inbound',\n concurrency: 20,\n reserved: true,\n description: 'Inbound events drain (events subsystem outbox).',\n }),\n events_change: Object.freeze({\n queue: 'jobs-events-change',\n concurrency: 30,\n reserved: true,\n description: 'Change events drain (events subsystem outbox).',\n }),\n events_outbound: Object.freeze({\n queue: 'jobs-events-outbound',\n concurrency: 10,\n reserved: true,\n description: 'Outbound events drain (events subsystem outbox).',\n }),\n interactive: Object.freeze({\n queue: 'jobs-interactive',\n concurrency: 20,\n reserved: false,\n description: 'User-facing latency-sensitive jobs.',\n }),\n batch: Object.freeze({\n queue: 'jobs-batch',\n concurrency: 5,\n reserved: false,\n description: 'Default pool for background jobs.',\n }),\n});\n\n/** Names of the framework reserved pools. Cheap inline lookup for the worker. */\nexport const RESERVED_POOL_NAMES: ReadonlySet<string> = new Set(\n Object.entries(FRAMEWORK_POOLS)\n .filter(([, def]) => def.reserved)\n .map(([name]) => name),\n);\n\n/**\n * Cache by absolute config path. The `cwd` default is normalised before\n * lookup so two callers passing the same path share the cache; explicit\n * test-only paths cache separately.\n */\nconst cache = new Map<string, PoolConfig>();\n\n/**\n * Reset the loader cache. Test-only — not exported from the package\n * `index.ts`. Useful for tests that mutate `process.cwd()` between cases.\n */\nexport function _resetPoolConfigCacheForTests(): void {\n cache.clear();\n}\n\n/**\n * Resolve the merged pool config.\n *\n * @param configPath optional absolute or cwd-relative path; defaults to\n * `${process.cwd()}/codegen.config.yaml`.\n */\nexport function loadPoolConfig(configPath?: string): PoolConfig {\n const resolved = resolve(configPath ?? `${process.cwd()}/codegen.config.yaml`);\n const cached = cache.get(resolved);\n if (cached) return cached;\n\n const merged = new Map<string, PoolDefinition>();\n // Seed with framework defaults first — they always take precedence on\n // `reserved` and provide defaults for `queue` / `concurrency` if user\n // overrides only some fields.\n for (const [name, def] of Object.entries(FRAMEWORK_POOLS)) {\n merged.set(name, { ...def });\n }\n\n if (!existsSync(resolved)) {\n cache.set(resolved, merged);\n return merged;\n }\n\n let raw: unknown;\n try {\n raw = parseYaml(readFileSync(resolved, 'utf8'));\n } catch (err) {\n throw new Error(\n `pool-config.loader: failed to parse YAML at ${resolved}: ${(err as Error).message}`,\n );\n }\n\n const userPools = extractUserPools(raw);\n for (const [name, userDef] of Object.entries(userPools)) {\n const existing = merged.get(name);\n if (existing) {\n // Framework pool — user may tweak concurrency + description but\n // cannot flip `reserved`. `queue` is frozen too (reserved framework\n // pools' queue identifiers are part of the cross-subsystem contract\n // with the events outbox drain).\n const next: PoolDefinition = {\n queue: existing.queue,\n concurrency:\n typeof userDef.concurrency === 'number'\n ? userDef.concurrency\n : existing.concurrency,\n reserved: existing.reserved,\n description: userDef.description ?? existing.description,\n };\n merged.set(name, next);\n continue;\n }\n // User-defined pool. Validate required fields; reject reserved.\n if (typeof userDef.queue !== 'string' || userDef.queue.length === 0) {\n throw new Error(\n `pool-config.loader: pool '${name}' must declare a non-empty 'queue'.`,\n );\n }\n if (typeof userDef.concurrency !== 'number' || userDef.concurrency <= 0) {\n throw new Error(\n `pool-config.loader: pool '${name}' must declare a positive 'concurrency'.`,\n );\n }\n if (userDef.reserved === true) {\n throw new Error(\n `pool-config.loader: user-defined pool '${name}' cannot set ` +\n `'reserved: true' — reserved is framework-only.`,\n );\n }\n merged.set(name, {\n queue: userDef.queue,\n concurrency: userDef.concurrency,\n reserved: false,\n description: userDef.description,\n });\n }\n\n cache.set(resolved, merged);\n return merged;\n}\n\n/**\n * Names of every non-reserved pool in the resolved config. The default\n * worker activation set when `JobWorkerModuleOptions.pools` is omitted —\n * the worker process never claims the reserved `events_*` pools by\n * default; those are bound by the events subsystem's outbox bridge.\n */\nexport function allNonReservedPoolNames(config: PoolConfig): string[] {\n const out: string[] = [];\n for (const [name, def] of config) {\n if (!def.reserved) out.push(name);\n }\n return out;\n}\n\n/**\n * Names of **every** pool in the resolved config, reserved `events_*` lanes\n * included. The activation set for a standalone worker booted with\n * `JobWorkerModule.forRoot({ allPools: true })` (BULLMQ-1 Phase 1) — the\n * single worker process drains both user pools and the bridge's reserved\n * pools so wrapper `job_run` rows are never stranded.\n */\nexport function allPoolNames(config: PoolConfig): string[] {\n return [...config.keys()];\n}\n\n// ─── internals ──────────────────────────────────────────────────────────────\n\ninterface UserPoolShape {\n queue?: string;\n concurrency?: number;\n reserved?: boolean;\n description?: string;\n}\n\nfunction extractUserPools(raw: unknown): Record<string, UserPoolShape> {\n if (!raw || typeof raw !== 'object') return {};\n const jobs = (raw as { jobs?: unknown }).jobs;\n if (!jobs || typeof jobs !== 'object') return {};\n const pools = (jobs as { pools?: unknown }).pools;\n if (!pools || typeof pools !== 'object') return {};\n const out: Record<string, UserPoolShape> = {};\n for (const [name, def] of Object.entries(pools as Record<string, unknown>)) {\n if (!def || typeof def !== 'object') continue;\n out[name] = def as UserPoolShape;\n }\n return out;\n}\n","/**\n * BullMQ backend configuration surface (BULLMQ-1, ADR-022 extension slot).\n *\n * The core `IJobOrchestrator` contract is backend-agnostic; everything in\n * this file is BullMQ-specific and lives behind the\n * `jobs.extensions.bullmq.*` config namespace (CLAUDE.md core/extension\n * protocol). The Drizzle backend never reads any of it.\n */\nimport { loadPoolConfig, type PoolConfig } from './pool-config.loader';\n\n/**\n * #6 — Structural mirror of BullMQ's `ConnectionOptions`. Declared locally\n * so this config file (which ships into EVERY jobs install, drizzle or\n * bullmq) does NOT need the `bullmq` peer dep resolved by the consumer's\n * tsc. The bullmq backend internally casts to the real `ConnectionOptions`\n * — that file is only vendored when `--backend bullmq` is selected\n * (see `backendFileFilter`).\n *\n * Accepts the `{ url }` shape this resolver emits, plus the host/port/\n * password/db form BullMQ also accepts, with an open index for any extra\n * ioredis options consumers may flow through.\n */\nexport type BullMqConnectionOptions = {\n url?: string;\n host?: string;\n port?: number;\n password?: string;\n db?: number;\n [key: string]: unknown;\n};\n\n/**\n * Typed shape of `codegen.config.yaml: jobs.extensions.bullmq`. Snake_case\n * because it mirrors the YAML the consumer authors.\n *\n * ```yaml\n * jobs:\n * backend: bullmq\n * extensions:\n * bullmq:\n * redis_url: redis://localhost:6379 # or env REDIS_URL\n * queue_prefix: myapp # optional namespace (ADR-022 OQ)\n * bull_board:\n * enabled: true\n * mount_path: /api/admin/queues\n * ```\n */\nexport interface BullMqExtensionsConfig {\n /**\n * Redis/Valkey connection URL. When omitted, the runtime resolves\n * `process.env.REDIS_URL`, then falls back to `redis://localhost:6379`.\n */\n redis_url?: string;\n /**\n * Optional queue-name prefix to avoid collisions when several codegen apps\n * share one Redis (ADR-022 §\"BullMQ queue naming collisions\"). Applied to\n * every pool queue alias.\n */\n queue_prefix?: string;\n /**\n * Bull Board dashboard — opt-in extension (not core). Mounting is the\n * consumer's responsibility (it needs the consumer's Express/Nest adapter +\n * admin auth); we only carry the config. See README + spec §Extensions.\n */\n bull_board?: {\n enabled: boolean;\n mount_path?: string;\n };\n}\n\n/**\n * The runtime form after `redis_url`/env resolution. This is what the\n * orchestrator + worker actually consume.\n */\nexport interface BullMqResolvedConfig {\n connection: BullMqConnectionOptions;\n queuePrefix?: string;\n bullBoard?: { enabled: boolean; mountPath: string };\n}\n\n/** DI token for the resolved BullMQ `ConnectionOptions` (ioredis-compatible). */\nexport const BULLMQ_CONNECTION = Symbol('BULLMQ_CONNECTION');\n\n/** DI token for the full resolved BullMQ config (prefix + bull board). */\nexport const BULLMQ_RESOLVED_CONFIG = Symbol('BULLMQ_RESOLVED_CONFIG');\n\nconst DEFAULT_REDIS_URL = 'redis://localhost:6379';\nconst DEFAULT_BULL_BOARD_MOUNT = '/admin/queues';\n\n/**\n * Resolve the BullMQ runtime config from the extension block.\n *\n * Precedence for the connection URL:\n * 1. explicit `extensions.bullmq.redis_url`\n * 2. `process.env.REDIS_URL`\n * 3. `redis://localhost:6379`\n *\n * Returns a `{ url }` connection shape — BullMQ/ioredis accept a URL string\n * via the `{ url }` ConnectionOptions form.\n */\nexport function resolveBullMqConfig(\n ext: BullMqExtensionsConfig | undefined,\n): BullMqResolvedConfig {\n const url =\n ext?.redis_url ?? process.env.REDIS_URL ?? DEFAULT_REDIS_URL;\n\n const resolved: BullMqResolvedConfig = {\n connection: { url },\n queuePrefix: ext?.queue_prefix,\n };\n if (ext?.bull_board?.enabled) {\n resolved.bullBoard = {\n enabled: true,\n mountPath: ext.bull_board.mount_path ?? DEFAULT_BULL_BOARD_MOUNT,\n };\n }\n return resolved;\n}\n\n/**\n * Resolve the BullMQ queue name for a *logical pool name*. The orchestrator\n * and worker MUST agree on this mapping or jobs are enqueued onto a queue\n * nobody consumes. Both derive it identically:\n *\n * 1. Look up the pool's `queue` alias (e.g. `jobs-batch`) in the resolved\n * pool config — the same alias `JobWorkerModule.onModuleInit` logs and\n * that the BullMQ `Worker` binds to.\n * 2. Fall back to the logical pool name when the pool is unknown (defensive;\n * still a stable, colon-free identifier).\n * 3. Apply the optional `queue_prefix` namespace for multi-app Redis\n * sharing — `:` is fine in the *queue name* (it is only forbidden in the\n * `jobId`, hence the sha1 there).\n *\n * `poolConfig` defaults to the cached `loadPoolConfig()` so callers that only\n * hold the logical pool name (the orchestrator) don't need to thread the map.\n */\nexport function resolvePoolQueueName(\n pool: string,\n config: BullMqResolvedConfig | null | undefined,\n poolConfig: PoolConfig = loadPoolConfig(),\n): string {\n const alias = poolConfig.get(pool)?.queue ?? pool;\n const prefix = config?.queuePrefix;\n return prefix ? `${prefix}:${alias}` : alias;\n}\n"],"mappings":"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","names":[]}
@@ -6,9 +6,7 @@ export { IJobStepService, JobStep, RecordStepInput } from './job-step-service.pr
6
6
  export { DrizzleJobOrchestrator } from './job-orchestrator.drizzle-backend.js';
7
7
  export { DrizzleJobRunService } from './job-run-service.drizzle-backend.js';
8
8
  export { DrizzleJobStepService } from './job-step-service.drizzle-backend.js';
9
- export { BullMQJobOrchestrator, sha1JobId } from './job-orchestrator.bullmq-backend.js';
10
- export { BullMQJobWorker, BullMQJobWorkerOptions } from './job-worker.bullmq-backend.js';
11
- export { BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG, BullMqExtensionsConfig, BullMqResolvedConfig, resolveBullMqConfig, resolvePoolQueueName } from './bullmq.config.js';
9
+ export { BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG, BullMqConnectionOptions, BullMqExtensionsConfig, BullMqResolvedConfig, resolveBullMqConfig, resolvePoolQueueName } from './bullmq.config.js';
12
10
  export { JOB_WORKER_OPTIONS, JobWorker, JobWorkerOptions, buildClaimQuery, buildStaleSweepQuery, classifyError, computeBackoff } from './job-worker.js';
13
11
  export { BootValidationError, JobCollisionError, JobNotReplayableError, JobTemplateFieldMissingError, JobTypeNotFoundError, MissingTenantIdError, ReservedPoolViolationError } from './jobs-errors.js';
14
12
  export { MemoryJobStore } from './memory-job-store.js';
@@ -25,5 +23,4 @@ import '../../types/drizzle.js';
25
23
  import 'drizzle-orm/node-postgres';
26
24
  import '@nestjs/common';
27
25
  import '../events/generated/types.js';
28
- import 'bullmq';
29
26
  import '@nestjs/core';