@pattern-stack/codegen 0.14.0 → 0.14.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/dist/runtime/subsystems/bridge/bridge-delivery.memory-backend.d.ts +1 -1
- package/dist/runtime/subsystems/bridge/bridge-delivery.memory-backend.js +2 -2
- package/dist/runtime/subsystems/bridge/bridge-delivery.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge.module.js +172 -159
- package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
- package/dist/runtime/subsystems/bridge/index.js +154 -141
- package/dist/runtime/subsystems/bridge/index.js.map +1 -1
- package/dist/runtime/subsystems/index.js +161 -148
- package/dist/runtime/subsystems/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/index.js +128 -115
- package/dist/runtime/subsystems/jobs/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js +128 -6
- package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +17 -0
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-step-service.memory-backend.js +25 -2
- package/dist/runtime/subsystems/jobs/job-step-service.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +26 -1
- package/dist/runtime/subsystems/jobs/job-worker.module.js +150 -137
- package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js +133 -124
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
- package/dist/src/cli/index.js +804 -454
- package/dist/src/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/runtime/subsystems/bridge/bridge-delivery.memory-backend.ts +8 -1
- package/runtime/subsystems/jobs/job-orchestrator.memory-backend.ts +8 -3
- package/runtime/subsystems/jobs/job-run-service.memory-backend.ts +4 -1
- package/runtime/subsystems/jobs/job-step-service.memory-backend.ts +7 -2
- package/runtime/subsystems/jobs/job-worker.module.ts +13 -1
|
@@ -22,7 +22,7 @@ declare class MemoryBridgeDeliveryRepo implements IJobBridge {
|
|
|
22
22
|
* Unlike `insertDelivery`, this read does NOT call `assertTenantId`:
|
|
23
23
|
* `tenantId === undefined` is the supported cross-tenant admin view.
|
|
24
24
|
*/
|
|
25
|
-
getStatusHistogram(windowHours: number, tenantId?: string | null): Promise<StatusHistogram>;
|
|
25
|
+
getStatusHistogram(windowHours: number, tenantId?: string | null, nowMs?: number): Promise<StatusHistogram>;
|
|
26
26
|
/** All deliveries for a given event (any status, declaration order). */
|
|
27
27
|
getDeliveriesForEvent(eventId: string): BridgeDeliveryRecord[];
|
|
28
28
|
/** All deliveries currently in the given status. */
|
|
@@ -80,11 +80,11 @@ var MemoryBridgeDeliveryRepo = class {
|
|
|
80
80
|
* Unlike `insertDelivery`, this read does NOT call `assertTenantId`:
|
|
81
81
|
* `tenantId === undefined` is the supported cross-tenant admin view.
|
|
82
82
|
*/
|
|
83
|
-
async getStatusHistogram(windowHours, tenantId) {
|
|
83
|
+
async getStatusHistogram(windowHours, tenantId, nowMs = Date.now()) {
|
|
84
84
|
if (!Number.isFinite(windowHours) || windowHours <= 0) {
|
|
85
85
|
throw new RangeError("windowHours must be positive");
|
|
86
86
|
}
|
|
87
|
-
const cutoffMs =
|
|
87
|
+
const cutoffMs = nowMs - windowHours * 36e5;
|
|
88
88
|
const histogram = {
|
|
89
89
|
pending: 0,
|
|
90
90
|
delivered: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../runtime/subsystems/bridge/bridge-delivery.memory-backend.ts","../../../../runtime/subsystems/bridge/bridge-errors.ts"],"sourcesContent":["/**\n * MemoryBridgeDeliveryRepo — in-memory `IJobBridge` (BRIDGE-3, ADR-023 Phase 2).\n *\n * Behavioral twin of the Drizzle backend (BRIDGE-4) for use in\n * `just test-unit`. No Docker, no Postgres, fully synchronous. Backs a\n * `Map<\"${eventId}::${triggerId}\", BridgeDeliveryRecord>` so the UNIQUE\n * `(event_id, trigger_id)` constraint can be simulated cheaply.\n *\n * Precedent: `MemoryEventBus` (EVT-5), `MemoryJobOrchestrator`\n * (jobs subsystem). Same shape — a class implementing the protocol plus\n * test-only helpers (`getDeliveriesForEvent`, `getByStatus`, `clear`) that\n * BRIDGE-5's framework-handler tests and BRIDGE-7's facade tests will\n * exercise.\n *\n * The synthetic `UniqueConstraintError` carries a `constraint` field equal\n * to the Drizzle constraint name (`uq_bridge_delivery_event_trigger`, set\n * in BRIDGE-1's schema) so consumers — including BRIDGE-4's\n * `INSERT … ON CONFLICT (event_id, trigger_id) DO NOTHING` path and\n * BRIDGE-7's Case B dedup tests — can branch on the same discriminator\n * regardless of which backend is wired up. ADR-023 explicitly relies on\n * this constraint as the dedup mechanism in two places (replay; facade-\n * vs-drain Case B); a typed error makes both call sites checkable.\n */\nimport { randomUUID } from 'node:crypto';\n\nimport type {\n BridgeDeliveryInsert,\n IJobBridge,\n StatusHistogram,\n} from './bridge.protocol';\nimport type { BridgeDeliveryRecord } from './bridge-delivery.schema';\nimport { UniqueConstraintError } from './bridge-errors';\n\nconst BRIDGE_DELIVERY_UNIQUE_CONSTRAINT =\n 'uq_bridge_delivery_event_trigger' as const;\n\nfunction key(eventId: string, triggerId: string): string {\n return `${eventId}::${triggerId}`;\n}\n\nexport class MemoryBridgeDeliveryRepo implements IJobBridge {\n private readonly deliveries = new Map<string, BridgeDeliveryRecord>();\n\n async insertDelivery(row: BridgeDeliveryInsert): Promise<void> {\n const k = key(row.eventId, row.triggerId);\n if (this.deliveries.has(k)) {\n throw new UniqueConstraintError(\n BRIDGE_DELIVERY_UNIQUE_CONSTRAINT,\n row.eventId,\n row.triggerId,\n );\n }\n // Materialize a full BridgeDeliveryRecord — fill in DB defaults that\n // the insert payload allowed to be omitted.\n const record: BridgeDeliveryRecord = {\n id: row.id ?? randomUUID(),\n eventId: row.eventId,\n triggerId: row.triggerId,\n wrapperRunId: row.wrapperRunId ?? null,\n userRunId: row.userRunId ?? null,\n status: row.status ?? 'pending',\n skipReason: row.skipReason ?? null,\n error: (row.error as Record<string, unknown> | null | undefined) ?? null,\n tenantId: row.tenantId ?? null,\n attemptedAt:\n row.attemptedAt instanceof Date ? row.attemptedAt : new Date(),\n deliveredAt:\n row.deliveredAt instanceof Date ? row.deliveredAt : null,\n };\n this.deliveries.set(k, record);\n }\n\n async findDelivery(\n eventId: string,\n triggerId: string,\n ): Promise<BridgeDeliveryRecord | null> {\n return this.deliveries.get(key(eventId, triggerId)) ?? null;\n }\n\n async findDeliveryById(id: string): Promise<BridgeDeliveryRecord | null> {\n for (const record of this.deliveries.values()) {\n if (record.id === id) return record;\n }\n return null;\n }\n\n async markDelivered(id: string, userRunId: string): Promise<void> {\n const record = this.findById(id);\n record.status = 'delivered';\n record.userRunId = userRunId;\n record.deliveredAt = new Date();\n }\n\n async markSkipped(id: string, reason: string): Promise<void> {\n const record = this.findById(id);\n record.status = 'skipped';\n record.skipReason = reason;\n }\n\n async markFailed(\n id: string,\n error: Record<string, unknown>,\n ): Promise<void> {\n const record = this.findById(id);\n record.status = 'failed';\n record.error = error;\n }\n\n /**\n * Observability read — see `IJobBridge.getStatusHistogram` JSDoc for the\n * tenant-filter and windowHours contract.\n *\n * Unlike `insertDelivery`, this read does NOT call `assertTenantId`:\n * `tenantId === undefined` is the supported cross-tenant admin view.\n */\n async getStatusHistogram(\n windowHours: number,\n tenantId?: string | null,\n ): Promise<StatusHistogram> {\n if (!Number.isFinite(windowHours) || windowHours <= 0) {\n throw new RangeError('windowHours must be positive');\n }\n\n const cutoffMs = Date.now() - windowHours * 3_600_000;\n const histogram: StatusHistogram = {\n pending: 0,\n delivered: 0,\n skipped: 0,\n failed: 0,\n };\n\n for (const record of this.deliveries.values()) {\n if (record.attemptedAt.getTime() < cutoffMs) continue;\n if (tenantId === null && record.tenantId !== null) continue;\n if (typeof tenantId === 'string' && record.tenantId !== tenantId) {\n continue;\n }\n // tenantId === undefined → no tenant filter.\n histogram[record.status] += 1;\n }\n\n return histogram;\n }\n\n // ─── Test helpers ────────────────────────────────────────────────────────\n\n /** All deliveries for a given event (any status, declaration order). */\n getDeliveriesForEvent(eventId: string): BridgeDeliveryRecord[] {\n return [...this.deliveries.values()].filter((r) => r.eventId === eventId);\n }\n\n /** All deliveries currently in the given status. */\n getByStatus(\n status: BridgeDeliveryRecord['status'],\n ): BridgeDeliveryRecord[] {\n return [...this.deliveries.values()].filter((r) => r.status === status);\n }\n\n /** Reset the store. Tests use this in `beforeEach`. */\n clear(): void {\n this.deliveries.clear();\n }\n\n // ─── Internals ───────────────────────────────────────────────────────────\n\n private findById(id: string): BridgeDeliveryRecord {\n for (const record of this.deliveries.values()) {\n if (record.id === id) return record;\n }\n throw new Error(\n `MemoryBridgeDeliveryRepo: no delivery with id '${id}' (transition ` +\n `methods may not be called for unknown rows; the framework handler ` +\n `should always findDelivery first or operate on a row it just ` +\n `inserted).`,\n );\n }\n}\n","/**\n * Typed errors for the bridge subsystem (ADR-023 Phase 2, BRIDGE-2).\n *\n * All thrown by the three enforcement sites named in ADR-023 §Multi-tenancy:\n * - `EventFlowService.publishAndStart` entry (BRIDGE-7)\n * - `BridgeDeliveryHandler.handle` entry (BRIDGE-5)\n * - `DrizzleBridgeDeliveryRepo.insertDelivery` pre-write (BRIDGE-4)\n *\n * Same shape as `runtime/subsystems/jobs/jobs-errors.ts` and\n * `runtime/subsystems/events/events-errors.ts` so consumers can catch them\n * with the same exception-filter pattern across all three subsystems.\n */\n\n/**\n * Thrown when `BridgeModule` was configured with `multiTenant: true` but\n * the caller did not pass a `tenantId` at one of the three enforcement\n * sites listed above.\n *\n * **Strict enforcement rationale (mirrors JOB-8 / SYNC-6 stance, locked\n * 2026-04-18 for jobs; same rationale applies here).** Cross-tenant data\n * leakage is the worst class of bug a multi-tenant system can ship;\n * surfacing the misuse loudly at the call site (rather than silently\n * defaulting to `null` or to \"the last tenant seen\") prevents both\n * accidental global writes and sneaky reads that return a union of tenants.\n *\n * - `undefined` `tenantId` → throw this error.\n * - Explicit `null` `tenantId` → passes; opts the call into cross-tenant\n * work (e.g. a system housekeeping event with no owning tenant). The\n * `bridge_delivery` row is persisted with `tenant_id = NULL`.\n *\n * The `callSite` constructor argument names which of the three enforcement\n * sites threw — review reports and ops dashboards rely on a stable site\n * name, so use the canonical strings: `'EventFlowService.publishAndStart'`,\n * `'BridgeDeliveryHandler.handle'`,\n * `'DrizzleBridgeDeliveryRepo.insertDelivery'`.\n */\nexport class MissingTenantIdError extends Error {\n override readonly name = 'MissingTenantIdError';\n constructor(public readonly callSite: string) {\n super(\n `MissingTenantIdError: BridgeModule was configured with ` +\n `multiTenant=true but ${callSite} was called without tenantId ` +\n `(undefined). Pass an explicit tenantId, or pass null for ` +\n `cross-tenant work.`,\n );\n }\n}\n\n/**\n * Synthetic error thrown by `MemoryBridgeDeliveryRepo.insertDelivery` when\n * a duplicate `(event_id, trigger_id)` insert hits the simulated UNIQUE\n * constraint (BRIDGE-3).\n *\n * Carries a `constraint` field equal to the Drizzle constraint name\n * declared in BRIDGE-1's schema (`uq_bridge_delivery_event_trigger`) so\n * call sites can branch on the same discriminator regardless of which\n * backend is wired up. This matters because ADR-023 explicitly leans on\n * the constraint as the dedup mechanism in two places — outbox replay\n * and `publishAndStart` Case B — and BRIDGE-4 / BRIDGE-7 will share a\n * type-check path with BRIDGE-3-driven tests.\n *\n * The Drizzle backend (BRIDGE-4) does NOT throw this error: it uses\n * `INSERT … ON CONFLICT (event_id, trigger_id) DO NOTHING RETURNING id`\n * per the BRIDGE-4 spec recommendation, so collisions surface as an empty\n * result set rather than an exception. The error exists so the memory\n * backend can faithfully model the \"duplicate raises\" behaviour for tests\n * that want to assert the constraint actually fires.\n */\n/**\n * Thrown by `BridgeModule.onModuleInit` when `JobWorkerModule` is wired\n * alongside the bridge but its active `pools` list does not include one\n * or more of the three reserved bridge pools (`events_inbound`,\n * `events_change`, `events_outbound`).\n *\n * Without a worker polling those pools, the wrapper `job_run` rows the\n * outbox drain inserts (BRIDGE-4) sit `pending` forever — a silent\n * footgun where `eventFlow.publish(...)` returns success but no user\n * job ever spawns. The boot-time check converts that into a fail-fast.\n *\n * Operators can either (a) add `...BRIDGE_RESERVED_POOLS` to their\n * `JobWorkerModule.forRoot({ pools })` configuration so the same\n * process polls the reserved pools, or (b) run a separate worker\n * process per reserved pool for lane isolation (ADR-022 §Pool\n * isolation).\n */\nexport class BridgeReservedPoolsNotPolledError extends Error {\n override readonly name = 'BridgeReservedPoolsNotPolledError';\n constructor(public readonly missingPools: readonly string[]) {\n super(\n `BridgeModule loaded but JobWorkerModule is not polling reserved ` +\n `pool '${missingPools[0]}'. Add ...BRIDGE_RESERVED_POOLS to your ` +\n `JobWorkerModule.forRoot({ pools }) configuration. Missing pools: ` +\n `${missingPools.join(', ')}. (Bridge-fanout wrappers will sit ` +\n `pending forever without these pollers.)`,\n );\n }\n}\n\nexport class UniqueConstraintError extends Error {\n override readonly name = 'UniqueConstraintError';\n constructor(\n public readonly constraint: string,\n public readonly eventId: string,\n public readonly triggerId: string,\n ) {\n super(\n `UniqueConstraintError: duplicate insert into bridge_delivery for ` +\n `(event_id='${eventId}', trigger_id='${triggerId}') — violates ` +\n `constraint '${constraint}'.`,\n );\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/bridge/bridge-delivery.memory-backend.ts","../../../../runtime/subsystems/bridge/bridge-errors.ts"],"sourcesContent":["/**\n * MemoryBridgeDeliveryRepo — in-memory `IJobBridge` (BRIDGE-3, ADR-023 Phase 2).\n *\n * Behavioral twin of the Drizzle backend (BRIDGE-4) for use in\n * `just test-unit`. No Docker, no Postgres, fully synchronous. Backs a\n * `Map<\"${eventId}::${triggerId}\", BridgeDeliveryRecord>` so the UNIQUE\n * `(event_id, trigger_id)` constraint can be simulated cheaply.\n *\n * Precedent: `MemoryEventBus` (EVT-5), `MemoryJobOrchestrator`\n * (jobs subsystem). Same shape — a class implementing the protocol plus\n * test-only helpers (`getDeliveriesForEvent`, `getByStatus`, `clear`) that\n * BRIDGE-5's framework-handler tests and BRIDGE-7's facade tests will\n * exercise.\n *\n * The synthetic `UniqueConstraintError` carries a `constraint` field equal\n * to the Drizzle constraint name (`uq_bridge_delivery_event_trigger`, set\n * in BRIDGE-1's schema) so consumers — including BRIDGE-4's\n * `INSERT … ON CONFLICT (event_id, trigger_id) DO NOTHING` path and\n * BRIDGE-7's Case B dedup tests — can branch on the same discriminator\n * regardless of which backend is wired up. ADR-023 explicitly relies on\n * this constraint as the dedup mechanism in two places (replay; facade-\n * vs-drain Case B); a typed error makes both call sites checkable.\n */\nimport { randomUUID } from 'node:crypto';\n\nimport type {\n BridgeDeliveryInsert,\n IJobBridge,\n StatusHistogram,\n} from './bridge.protocol';\nimport type { BridgeDeliveryRecord } from './bridge-delivery.schema';\nimport { UniqueConstraintError } from './bridge-errors';\n\nconst BRIDGE_DELIVERY_UNIQUE_CONSTRAINT =\n 'uq_bridge_delivery_event_trigger' as const;\n\nfunction key(eventId: string, triggerId: string): string {\n return `${eventId}::${triggerId}`;\n}\n\nexport class MemoryBridgeDeliveryRepo implements IJobBridge {\n private readonly deliveries = new Map<string, BridgeDeliveryRecord>();\n\n async insertDelivery(row: BridgeDeliveryInsert): Promise<void> {\n const k = key(row.eventId, row.triggerId);\n if (this.deliveries.has(k)) {\n throw new UniqueConstraintError(\n BRIDGE_DELIVERY_UNIQUE_CONSTRAINT,\n row.eventId,\n row.triggerId,\n );\n }\n // Materialize a full BridgeDeliveryRecord — fill in DB defaults that\n // the insert payload allowed to be omitted.\n const record: BridgeDeliveryRecord = {\n id: row.id ?? randomUUID(),\n eventId: row.eventId,\n triggerId: row.triggerId,\n wrapperRunId: row.wrapperRunId ?? null,\n userRunId: row.userRunId ?? null,\n status: row.status ?? 'pending',\n skipReason: row.skipReason ?? null,\n error: (row.error as Record<string, unknown> | null | undefined) ?? null,\n tenantId: row.tenantId ?? null,\n attemptedAt:\n row.attemptedAt instanceof Date ? row.attemptedAt : new Date(),\n deliveredAt:\n row.deliveredAt instanceof Date ? row.deliveredAt : null,\n };\n this.deliveries.set(k, record);\n }\n\n async findDelivery(\n eventId: string,\n triggerId: string,\n ): Promise<BridgeDeliveryRecord | null> {\n return this.deliveries.get(key(eventId, triggerId)) ?? null;\n }\n\n async findDeliveryById(id: string): Promise<BridgeDeliveryRecord | null> {\n for (const record of this.deliveries.values()) {\n if (record.id === id) return record;\n }\n return null;\n }\n\n async markDelivered(id: string, userRunId: string): Promise<void> {\n const record = this.findById(id);\n record.status = 'delivered';\n record.userRunId = userRunId;\n record.deliveredAt = new Date();\n }\n\n async markSkipped(id: string, reason: string): Promise<void> {\n const record = this.findById(id);\n record.status = 'skipped';\n record.skipReason = reason;\n }\n\n async markFailed(\n id: string,\n error: Record<string, unknown>,\n ): Promise<void> {\n const record = this.findById(id);\n record.status = 'failed';\n record.error = error;\n }\n\n /**\n * Observability read — see `IJobBridge.getStatusHistogram` JSDoc for the\n * tenant-filter and windowHours contract.\n *\n * Unlike `insertDelivery`, this read does NOT call `assertTenantId`:\n * `tenantId === undefined` is the supported cross-tenant admin view.\n */\n async getStatusHistogram(\n windowHours: number,\n tenantId?: string | null,\n // Reference instant for the window cutoff. Defaults to the wall clock; exposed\n // as a test-injection seam so a caller can pin the cutoff to the same `now` it\n // positioned a boundary row from. Without it the cutoff is re-sampled here a few\n // ms later, pushing an \"exactly at the boundary\" row just below the window\n // (the OBS-3 boundary test flake). The drizzle backend has no analogue — it\n // evaluates the cutoff once in SQL via `now()`.\n nowMs: number = Date.now(),\n ): Promise<StatusHistogram> {\n if (!Number.isFinite(windowHours) || windowHours <= 0) {\n throw new RangeError('windowHours must be positive');\n }\n\n const cutoffMs = nowMs - windowHours * 3_600_000;\n const histogram: StatusHistogram = {\n pending: 0,\n delivered: 0,\n skipped: 0,\n failed: 0,\n };\n\n for (const record of this.deliveries.values()) {\n if (record.attemptedAt.getTime() < cutoffMs) continue;\n if (tenantId === null && record.tenantId !== null) continue;\n if (typeof tenantId === 'string' && record.tenantId !== tenantId) {\n continue;\n }\n // tenantId === undefined → no tenant filter.\n histogram[record.status] += 1;\n }\n\n return histogram;\n }\n\n // ─── Test helpers ────────────────────────────────────────────────────────\n\n /** All deliveries for a given event (any status, declaration order). */\n getDeliveriesForEvent(eventId: string): BridgeDeliveryRecord[] {\n return [...this.deliveries.values()].filter((r) => r.eventId === eventId);\n }\n\n /** All deliveries currently in the given status. */\n getByStatus(\n status: BridgeDeliveryRecord['status'],\n ): BridgeDeliveryRecord[] {\n return [...this.deliveries.values()].filter((r) => r.status === status);\n }\n\n /** Reset the store. Tests use this in `beforeEach`. */\n clear(): void {\n this.deliveries.clear();\n }\n\n // ─── Internals ───────────────────────────────────────────────────────────\n\n private findById(id: string): BridgeDeliveryRecord {\n for (const record of this.deliveries.values()) {\n if (record.id === id) return record;\n }\n throw new Error(\n `MemoryBridgeDeliveryRepo: no delivery with id '${id}' (transition ` +\n `methods may not be called for unknown rows; the framework handler ` +\n `should always findDelivery first or operate on a row it just ` +\n `inserted).`,\n );\n }\n}\n","/**\n * Typed errors for the bridge subsystem (ADR-023 Phase 2, BRIDGE-2).\n *\n * All thrown by the three enforcement sites named in ADR-023 §Multi-tenancy:\n * - `EventFlowService.publishAndStart` entry (BRIDGE-7)\n * - `BridgeDeliveryHandler.handle` entry (BRIDGE-5)\n * - `DrizzleBridgeDeliveryRepo.insertDelivery` pre-write (BRIDGE-4)\n *\n * Same shape as `runtime/subsystems/jobs/jobs-errors.ts` and\n * `runtime/subsystems/events/events-errors.ts` so consumers can catch them\n * with the same exception-filter pattern across all three subsystems.\n */\n\n/**\n * Thrown when `BridgeModule` was configured with `multiTenant: true` but\n * the caller did not pass a `tenantId` at one of the three enforcement\n * sites listed above.\n *\n * **Strict enforcement rationale (mirrors JOB-8 / SYNC-6 stance, locked\n * 2026-04-18 for jobs; same rationale applies here).** Cross-tenant data\n * leakage is the worst class of bug a multi-tenant system can ship;\n * surfacing the misuse loudly at the call site (rather than silently\n * defaulting to `null` or to \"the last tenant seen\") prevents both\n * accidental global writes and sneaky reads that return a union of tenants.\n *\n * - `undefined` `tenantId` → throw this error.\n * - Explicit `null` `tenantId` → passes; opts the call into cross-tenant\n * work (e.g. a system housekeeping event with no owning tenant). The\n * `bridge_delivery` row is persisted with `tenant_id = NULL`.\n *\n * The `callSite` constructor argument names which of the three enforcement\n * sites threw — review reports and ops dashboards rely on a stable site\n * name, so use the canonical strings: `'EventFlowService.publishAndStart'`,\n * `'BridgeDeliveryHandler.handle'`,\n * `'DrizzleBridgeDeliveryRepo.insertDelivery'`.\n */\nexport class MissingTenantIdError extends Error {\n override readonly name = 'MissingTenantIdError';\n constructor(public readonly callSite: string) {\n super(\n `MissingTenantIdError: BridgeModule was configured with ` +\n `multiTenant=true but ${callSite} was called without tenantId ` +\n `(undefined). Pass an explicit tenantId, or pass null for ` +\n `cross-tenant work.`,\n );\n }\n}\n\n/**\n * Synthetic error thrown by `MemoryBridgeDeliveryRepo.insertDelivery` when\n * a duplicate `(event_id, trigger_id)` insert hits the simulated UNIQUE\n * constraint (BRIDGE-3).\n *\n * Carries a `constraint` field equal to the Drizzle constraint name\n * declared in BRIDGE-1's schema (`uq_bridge_delivery_event_trigger`) so\n * call sites can branch on the same discriminator regardless of which\n * backend is wired up. This matters because ADR-023 explicitly leans on\n * the constraint as the dedup mechanism in two places — outbox replay\n * and `publishAndStart` Case B — and BRIDGE-4 / BRIDGE-7 will share a\n * type-check path with BRIDGE-3-driven tests.\n *\n * The Drizzle backend (BRIDGE-4) does NOT throw this error: it uses\n * `INSERT … ON CONFLICT (event_id, trigger_id) DO NOTHING RETURNING id`\n * per the BRIDGE-4 spec recommendation, so collisions surface as an empty\n * result set rather than an exception. The error exists so the memory\n * backend can faithfully model the \"duplicate raises\" behaviour for tests\n * that want to assert the constraint actually fires.\n */\n/**\n * Thrown by `BridgeModule.onModuleInit` when `JobWorkerModule` is wired\n * alongside the bridge but its active `pools` list does not include one\n * or more of the three reserved bridge pools (`events_inbound`,\n * `events_change`, `events_outbound`).\n *\n * Without a worker polling those pools, the wrapper `job_run` rows the\n * outbox drain inserts (BRIDGE-4) sit `pending` forever — a silent\n * footgun where `eventFlow.publish(...)` returns success but no user\n * job ever spawns. The boot-time check converts that into a fail-fast.\n *\n * Operators can either (a) add `...BRIDGE_RESERVED_POOLS` to their\n * `JobWorkerModule.forRoot({ pools })` configuration so the same\n * process polls the reserved pools, or (b) run a separate worker\n * process per reserved pool for lane isolation (ADR-022 §Pool\n * isolation).\n */\nexport class BridgeReservedPoolsNotPolledError extends Error {\n override readonly name = 'BridgeReservedPoolsNotPolledError';\n constructor(public readonly missingPools: readonly string[]) {\n super(\n `BridgeModule loaded but JobWorkerModule is not polling reserved ` +\n `pool '${missingPools[0]}'. Add ...BRIDGE_RESERVED_POOLS to your ` +\n `JobWorkerModule.forRoot({ pools }) configuration. Missing pools: ` +\n `${missingPools.join(', ')}. (Bridge-fanout wrappers will sit ` +\n `pending forever without these pollers.)`,\n );\n }\n}\n\nexport class UniqueConstraintError extends Error {\n override readonly name = 'UniqueConstraintError';\n constructor(\n public readonly constraint: string,\n public readonly eventId: string,\n public readonly triggerId: string,\n ) {\n super(\n `UniqueConstraintError: duplicate insert into bridge_delivery for ` +\n `(event_id='${eventId}', trigger_id='${triggerId}') — violates ` +\n `constraint '${constraint}'.`,\n );\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -12,19 +12,20 @@ var __decorateParam = (index4, decorator) => (target, key2) => decorator(target,
|
|
|
12
12
|
|
|
13
13
|
// runtime/subsystems/bridge/bridge.module.ts
|
|
14
14
|
import {
|
|
15
|
-
Inject as
|
|
15
|
+
Inject as Inject13,
|
|
16
16
|
Module as Module3,
|
|
17
17
|
Optional as Optional7
|
|
18
18
|
} from "@nestjs/common";
|
|
19
19
|
|
|
20
20
|
// runtime/subsystems/jobs/job-worker.module.ts
|
|
21
21
|
import {
|
|
22
|
-
Inject as
|
|
22
|
+
Inject as Inject8,
|
|
23
23
|
Injectable as Injectable8,
|
|
24
24
|
Logger as Logger4,
|
|
25
25
|
Module as Module2,
|
|
26
26
|
Optional as Optional2
|
|
27
27
|
} from "@nestjs/common";
|
|
28
|
+
import { ModuleRef as ModuleRef2 } from "@nestjs/core";
|
|
28
29
|
|
|
29
30
|
// runtime/subsystems/token-key.ts
|
|
30
31
|
var PKG = "@pattern-stack/codegen";
|
|
@@ -916,8 +917,129 @@ DrizzleJobStepService = __decorateClass([
|
|
|
916
917
|
], DrizzleJobStepService);
|
|
917
918
|
|
|
918
919
|
// runtime/subsystems/jobs/job-orchestrator.memory-backend.ts
|
|
920
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
921
|
+
import { Inject as Inject5, Injectable as Injectable5, Logger as Logger2, Optional } from "@nestjs/common";
|
|
922
|
+
import { ModuleRef } from "@nestjs/core";
|
|
923
|
+
|
|
924
|
+
// runtime/subsystems/jobs/memory-job-store.ts
|
|
925
|
+
var MemoryJobStore = class {
|
|
926
|
+
/** Runs keyed by `id` (single source of truth for status/scope/lineage). */
|
|
927
|
+
runs = /* @__PURE__ */ new Map();
|
|
928
|
+
/** Steps keyed by `job_run_id`; array order matches insertion order. */
|
|
929
|
+
steps = /* @__PURE__ */ new Map();
|
|
930
|
+
/** Job definitions keyed by `type` — memory mirror of the `job` table. */
|
|
931
|
+
jobs = /* @__PURE__ */ new Map();
|
|
932
|
+
/** Reset everything. Tests call this in `beforeEach`. */
|
|
933
|
+
clear() {
|
|
934
|
+
this.runs.clear();
|
|
935
|
+
this.steps.clear();
|
|
936
|
+
this.jobs.clear();
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
// runtime/subsystems/jobs/job-step-service.memory-backend.ts
|
|
919
941
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
920
|
-
import { Inject as Inject4, Injectable as Injectable4
|
|
942
|
+
import { Inject as Inject4, Injectable as Injectable4 } from "@nestjs/common";
|
|
943
|
+
var MemoryJobStepService = class {
|
|
944
|
+
// ADR-037 (package-mode DI): explicit `@Inject(MemoryJobStore)` — the
|
|
945
|
+
// published bundle carries no `design:paramtypes`, so a by-type inject
|
|
946
|
+
// would resolve to `undefined` in package mode.
|
|
947
|
+
constructor(store) {
|
|
948
|
+
this.store = store;
|
|
949
|
+
}
|
|
950
|
+
store;
|
|
951
|
+
async findStep(runId, stepId) {
|
|
952
|
+
const rows = this.store.steps.get(runId);
|
|
953
|
+
if (!rows) return null;
|
|
954
|
+
const match = rows.find(
|
|
955
|
+
(r) => r.stepId === stepId && r.status === "completed"
|
|
956
|
+
);
|
|
957
|
+
return match ?? null;
|
|
958
|
+
}
|
|
959
|
+
async recordStep(input) {
|
|
960
|
+
const rows = this.getOrCreateRows(input.jobRunId);
|
|
961
|
+
const existingIdx = rows.findIndex((r) => r.stepId === input.stepId);
|
|
962
|
+
const normalisedInput = input.input ?? null;
|
|
963
|
+
const normalisedOutput = input.output ?? null;
|
|
964
|
+
if (existingIdx >= 0) {
|
|
965
|
+
const prev = rows[existingIdx];
|
|
966
|
+
const next = {
|
|
967
|
+
...prev,
|
|
968
|
+
status: input.status,
|
|
969
|
+
input: normalisedInput ?? prev.input,
|
|
970
|
+
output: normalisedOutput ?? prev.output,
|
|
971
|
+
error: input.error ?? prev.error,
|
|
972
|
+
attempts: input.attempts ?? prev.attempts,
|
|
973
|
+
startedAt: input.startedAt ?? prev.startedAt,
|
|
974
|
+
finishedAt: input.finishedAt ?? prev.finishedAt
|
|
975
|
+
};
|
|
976
|
+
rows[existingIdx] = next;
|
|
977
|
+
return next;
|
|
978
|
+
}
|
|
979
|
+
const seq = input.seq ?? this.nextSeq(rows);
|
|
980
|
+
const row = {
|
|
981
|
+
id: randomUUID2(),
|
|
982
|
+
jobRunId: input.jobRunId,
|
|
983
|
+
stepId: input.stepId,
|
|
984
|
+
kind: input.kind,
|
|
985
|
+
seq,
|
|
986
|
+
status: input.status,
|
|
987
|
+
input: normalisedInput,
|
|
988
|
+
output: normalisedOutput,
|
|
989
|
+
error: input.error ?? null,
|
|
990
|
+
attempts: input.attempts ?? 0,
|
|
991
|
+
startedAt: input.startedAt ?? null,
|
|
992
|
+
finishedAt: input.finishedAt ?? null
|
|
993
|
+
};
|
|
994
|
+
rows.push(row);
|
|
995
|
+
return row;
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Replay helper — wipe every step row for a run. Mirrors the `scratch`
|
|
999
|
+
* replay mode of the Drizzle backend (`DELETE FROM job_step WHERE job_run_id = …`).
|
|
1000
|
+
*/
|
|
1001
|
+
clearStepsForRun(runId) {
|
|
1002
|
+
this.store.steps.delete(runId);
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Remove every non-`completed` row for the run. Memoized (`completed`)
|
|
1006
|
+
* rows are preserved — this is the `last_checkpoint` / `last_step`
|
|
1007
|
+
* semantics the Drizzle backend implements via
|
|
1008
|
+
* `DELETE … WHERE status != 'completed'`. Both replay modes route here
|
|
1009
|
+
* (Phase 1 collapses `last_step` onto this behaviour; see JOB-3 notes).
|
|
1010
|
+
*/
|
|
1011
|
+
clearIncompleteSteps(runId) {
|
|
1012
|
+
const rows = this.store.steps.get(runId);
|
|
1013
|
+
if (!rows) return;
|
|
1014
|
+
const kept = rows.filter((r) => r.status === "completed");
|
|
1015
|
+
if (kept.length === 0) {
|
|
1016
|
+
this.store.steps.delete(runId);
|
|
1017
|
+
} else {
|
|
1018
|
+
this.store.steps.set(runId, kept);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
getOrCreateRows(runId) {
|
|
1022
|
+
let rows = this.store.steps.get(runId);
|
|
1023
|
+
if (!rows) {
|
|
1024
|
+
rows = [];
|
|
1025
|
+
this.store.steps.set(runId, rows);
|
|
1026
|
+
}
|
|
1027
|
+
return rows;
|
|
1028
|
+
}
|
|
1029
|
+
nextSeq(rows) {
|
|
1030
|
+
let max = 0;
|
|
1031
|
+
for (const r of rows) {
|
|
1032
|
+
if (r.seq > max) max = r.seq;
|
|
1033
|
+
}
|
|
1034
|
+
return max + 1;
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
MemoryJobStepService = __decorateClass([
|
|
1038
|
+
Injectable4(),
|
|
1039
|
+
__decorateParam(0, Inject4(MemoryJobStore))
|
|
1040
|
+
], MemoryJobStepService);
|
|
1041
|
+
|
|
1042
|
+
// runtime/subsystems/jobs/job-orchestrator.memory-backend.ts
|
|
921
1043
|
var QUEUED_RUN_AT = /* @__PURE__ */ new Date(864e13);
|
|
922
1044
|
var TERMINAL_STATUSES2 = [
|
|
923
1045
|
"completed",
|
|
@@ -1088,7 +1210,7 @@ var MemoryJobOrchestrator = class {
|
|
|
1088
1210
|
}
|
|
1089
1211
|
}
|
|
1090
1212
|
}
|
|
1091
|
-
const newId =
|
|
1213
|
+
const newId = randomUUID3();
|
|
1092
1214
|
let rootRunId = newId;
|
|
1093
1215
|
if (opts.parentRunId) {
|
|
1094
1216
|
const parent = this.store.runs.get(opts.parentRunId);
|
|
@@ -1486,9 +1608,12 @@ var MemoryJobOrchestrator = class {
|
|
|
1486
1608
|
}
|
|
1487
1609
|
};
|
|
1488
1610
|
MemoryJobOrchestrator = __decorateClass([
|
|
1489
|
-
|
|
1490
|
-
__decorateParam(
|
|
1491
|
-
__decorateParam(
|
|
1611
|
+
Injectable5(),
|
|
1612
|
+
__decorateParam(0, Inject5(MemoryJobStore)),
|
|
1613
|
+
__decorateParam(1, Inject5(MemoryJobStepService)),
|
|
1614
|
+
__decorateParam(2, Inject5(JOBS_MULTI_TENANT)),
|
|
1615
|
+
__decorateParam(3, Optional()),
|
|
1616
|
+
__decorateParam(3, Inject5(ModuleRef))
|
|
1492
1617
|
], MemoryJobOrchestrator);
|
|
1493
1618
|
function classifyError(err, policy, currentAttempts) {
|
|
1494
1619
|
if (!policy) return "fail";
|
|
@@ -1522,7 +1647,7 @@ function serialiseError(err, attempt, retryable) {
|
|
|
1522
1647
|
}
|
|
1523
1648
|
|
|
1524
1649
|
// runtime/subsystems/jobs/job-run-service.memory-backend.ts
|
|
1525
|
-
import { Inject as
|
|
1650
|
+
import { Inject as Inject6, Injectable as Injectable6 } from "@nestjs/common";
|
|
1526
1651
|
var NON_TERMINAL_STATUSES2 = [
|
|
1527
1652
|
"pending",
|
|
1528
1653
|
"running",
|
|
@@ -1689,9 +1814,10 @@ var MemoryJobRunService = class {
|
|
|
1689
1814
|
}
|
|
1690
1815
|
};
|
|
1691
1816
|
MemoryJobRunService = __decorateClass([
|
|
1692
|
-
|
|
1693
|
-
__decorateParam(
|
|
1694
|
-
__decorateParam(
|
|
1817
|
+
Injectable6(),
|
|
1818
|
+
__decorateParam(0, Inject6(MemoryJobStore)),
|
|
1819
|
+
__decorateParam(1, Inject6(JOB_ORCHESTRATOR)),
|
|
1820
|
+
__decorateParam(2, Inject6(JOBS_MULTI_TENANT))
|
|
1695
1821
|
], MemoryJobRunService);
|
|
1696
1822
|
function compareBy(a, b, order) {
|
|
1697
1823
|
switch (order) {
|
|
@@ -1707,120 +1833,6 @@ function compareBy(a, b, order) {
|
|
|
1707
1833
|
}
|
|
1708
1834
|
}
|
|
1709
1835
|
|
|
1710
|
-
// runtime/subsystems/jobs/job-step-service.memory-backend.ts
|
|
1711
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
1712
|
-
import { Injectable as Injectable6 } from "@nestjs/common";
|
|
1713
|
-
var MemoryJobStepService = class {
|
|
1714
|
-
constructor(store) {
|
|
1715
|
-
this.store = store;
|
|
1716
|
-
}
|
|
1717
|
-
store;
|
|
1718
|
-
async findStep(runId, stepId) {
|
|
1719
|
-
const rows = this.store.steps.get(runId);
|
|
1720
|
-
if (!rows) return null;
|
|
1721
|
-
const match = rows.find(
|
|
1722
|
-
(r) => r.stepId === stepId && r.status === "completed"
|
|
1723
|
-
);
|
|
1724
|
-
return match ?? null;
|
|
1725
|
-
}
|
|
1726
|
-
async recordStep(input) {
|
|
1727
|
-
const rows = this.getOrCreateRows(input.jobRunId);
|
|
1728
|
-
const existingIdx = rows.findIndex((r) => r.stepId === input.stepId);
|
|
1729
|
-
const normalisedInput = input.input ?? null;
|
|
1730
|
-
const normalisedOutput = input.output ?? null;
|
|
1731
|
-
if (existingIdx >= 0) {
|
|
1732
|
-
const prev = rows[existingIdx];
|
|
1733
|
-
const next = {
|
|
1734
|
-
...prev,
|
|
1735
|
-
status: input.status,
|
|
1736
|
-
input: normalisedInput ?? prev.input,
|
|
1737
|
-
output: normalisedOutput ?? prev.output,
|
|
1738
|
-
error: input.error ?? prev.error,
|
|
1739
|
-
attempts: input.attempts ?? prev.attempts,
|
|
1740
|
-
startedAt: input.startedAt ?? prev.startedAt,
|
|
1741
|
-
finishedAt: input.finishedAt ?? prev.finishedAt
|
|
1742
|
-
};
|
|
1743
|
-
rows[existingIdx] = next;
|
|
1744
|
-
return next;
|
|
1745
|
-
}
|
|
1746
|
-
const seq = input.seq ?? this.nextSeq(rows);
|
|
1747
|
-
const row = {
|
|
1748
|
-
id: randomUUID3(),
|
|
1749
|
-
jobRunId: input.jobRunId,
|
|
1750
|
-
stepId: input.stepId,
|
|
1751
|
-
kind: input.kind,
|
|
1752
|
-
seq,
|
|
1753
|
-
status: input.status,
|
|
1754
|
-
input: normalisedInput,
|
|
1755
|
-
output: normalisedOutput,
|
|
1756
|
-
error: input.error ?? null,
|
|
1757
|
-
attempts: input.attempts ?? 0,
|
|
1758
|
-
startedAt: input.startedAt ?? null,
|
|
1759
|
-
finishedAt: input.finishedAt ?? null
|
|
1760
|
-
};
|
|
1761
|
-
rows.push(row);
|
|
1762
|
-
return row;
|
|
1763
|
-
}
|
|
1764
|
-
/**
|
|
1765
|
-
* Replay helper — wipe every step row for a run. Mirrors the `scratch`
|
|
1766
|
-
* replay mode of the Drizzle backend (`DELETE FROM job_step WHERE job_run_id = …`).
|
|
1767
|
-
*/
|
|
1768
|
-
clearStepsForRun(runId) {
|
|
1769
|
-
this.store.steps.delete(runId);
|
|
1770
|
-
}
|
|
1771
|
-
/**
|
|
1772
|
-
* Remove every non-`completed` row for the run. Memoized (`completed`)
|
|
1773
|
-
* rows are preserved — this is the `last_checkpoint` / `last_step`
|
|
1774
|
-
* semantics the Drizzle backend implements via
|
|
1775
|
-
* `DELETE … WHERE status != 'completed'`. Both replay modes route here
|
|
1776
|
-
* (Phase 1 collapses `last_step` onto this behaviour; see JOB-3 notes).
|
|
1777
|
-
*/
|
|
1778
|
-
clearIncompleteSteps(runId) {
|
|
1779
|
-
const rows = this.store.steps.get(runId);
|
|
1780
|
-
if (!rows) return;
|
|
1781
|
-
const kept = rows.filter((r) => r.status === "completed");
|
|
1782
|
-
if (kept.length === 0) {
|
|
1783
|
-
this.store.steps.delete(runId);
|
|
1784
|
-
} else {
|
|
1785
|
-
this.store.steps.set(runId, kept);
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
getOrCreateRows(runId) {
|
|
1789
|
-
let rows = this.store.steps.get(runId);
|
|
1790
|
-
if (!rows) {
|
|
1791
|
-
rows = [];
|
|
1792
|
-
this.store.steps.set(runId, rows);
|
|
1793
|
-
}
|
|
1794
|
-
return rows;
|
|
1795
|
-
}
|
|
1796
|
-
nextSeq(rows) {
|
|
1797
|
-
let max = 0;
|
|
1798
|
-
for (const r of rows) {
|
|
1799
|
-
if (r.seq > max) max = r.seq;
|
|
1800
|
-
}
|
|
1801
|
-
return max + 1;
|
|
1802
|
-
}
|
|
1803
|
-
};
|
|
1804
|
-
MemoryJobStepService = __decorateClass([
|
|
1805
|
-
Injectable6()
|
|
1806
|
-
], MemoryJobStepService);
|
|
1807
|
-
|
|
1808
|
-
// runtime/subsystems/jobs/memory-job-store.ts
|
|
1809
|
-
var MemoryJobStore = class {
|
|
1810
|
-
/** Runs keyed by `id` (single source of truth for status/scope/lineage). */
|
|
1811
|
-
runs = /* @__PURE__ */ new Map();
|
|
1812
|
-
/** Steps keyed by `job_run_id`; array order matches insertion order. */
|
|
1813
|
-
steps = /* @__PURE__ */ new Map();
|
|
1814
|
-
/** Job definitions keyed by `type` — memory mirror of the `job` table. */
|
|
1815
|
-
jobs = /* @__PURE__ */ new Map();
|
|
1816
|
-
/** Reset everything. Tests call this in `beforeEach`. */
|
|
1817
|
-
clear() {
|
|
1818
|
-
this.runs.clear();
|
|
1819
|
-
this.steps.clear();
|
|
1820
|
-
this.jobs.clear();
|
|
1821
|
-
}
|
|
1822
|
-
};
|
|
1823
|
-
|
|
1824
1836
|
// runtime/subsystems/jobs/pool-config.loader.ts
|
|
1825
1837
|
import { existsSync, readFileSync } from "fs";
|
|
1826
1838
|
import { resolve } from "path";
|
|
@@ -2036,7 +2048,7 @@ JobsDomainModule = __decorateClass([
|
|
|
2036
2048
|
], JobsDomainModule);
|
|
2037
2049
|
|
|
2038
2050
|
// runtime/subsystems/jobs/job-worker.ts
|
|
2039
|
-
import { Inject as
|
|
2051
|
+
import { Inject as Inject7, Injectable as Injectable7, Logger as Logger3 } from "@nestjs/common";
|
|
2040
2052
|
import { and as and4, asc as asc2, desc as desc3, eq as eq4, inArray as inArray3, lt as lt2, lte, sql as sql4 } from "drizzle-orm";
|
|
2041
2053
|
var JOB_WORKER_OPTIONS = Symbol.for(tokenKey("jobs", "worker-options"));
|
|
2042
2054
|
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
@@ -2423,11 +2435,11 @@ var JobWorker = class {
|
|
|
2423
2435
|
};
|
|
2424
2436
|
JobWorker = __decorateClass([
|
|
2425
2437
|
Injectable7(),
|
|
2426
|
-
__decorateParam(0,
|
|
2427
|
-
__decorateParam(1,
|
|
2428
|
-
__decorateParam(2,
|
|
2429
|
-
__decorateParam(3,
|
|
2430
|
-
__decorateParam(4,
|
|
2438
|
+
__decorateParam(0, Inject7(DRIZZLE)),
|
|
2439
|
+
__decorateParam(1, Inject7(JOB_ORCHESTRATOR)),
|
|
2440
|
+
__decorateParam(2, Inject7(JOB_RUN_SERVICE)),
|
|
2441
|
+
__decorateParam(3, Inject7(JOB_STEP_SERVICE)),
|
|
2442
|
+
__decorateParam(4, Inject7(JOB_WORKER_OPTIONS))
|
|
2431
2443
|
], JobWorker);
|
|
2432
2444
|
|
|
2433
2445
|
// runtime/subsystems/jobs/job-worker.module.ts
|
|
@@ -2625,16 +2637,17 @@ var JobWorkerOrchestrator = class {
|
|
|
2625
2637
|
};
|
|
2626
2638
|
JobWorkerOrchestrator = __decorateClass([
|
|
2627
2639
|
Injectable8(),
|
|
2628
|
-
__decorateParam(0,
|
|
2629
|
-
__decorateParam(1,
|
|
2630
|
-
__decorateParam(2,
|
|
2631
|
-
__decorateParam(3,
|
|
2640
|
+
__decorateParam(0, Inject8(JOB_ORCHESTRATOR)),
|
|
2641
|
+
__decorateParam(1, Inject8(JOB_RUN_SERVICE)),
|
|
2642
|
+
__decorateParam(2, Inject8(JOB_STEP_SERVICE)),
|
|
2643
|
+
__decorateParam(3, Inject8(JOB_WORKER_MODULE_OPTIONS)),
|
|
2632
2644
|
__decorateParam(4, Optional2()),
|
|
2633
|
-
__decorateParam(4,
|
|
2645
|
+
__decorateParam(4, Inject8(DRIZZLE)),
|
|
2646
|
+
__decorateParam(5, Inject8(ModuleRef2)),
|
|
2634
2647
|
__decorateParam(6, Optional2()),
|
|
2635
|
-
__decorateParam(6,
|
|
2648
|
+
__decorateParam(6, Inject8(BULLMQ_CONNECTION)),
|
|
2636
2649
|
__decorateParam(7, Optional2()),
|
|
2637
|
-
__decorateParam(7,
|
|
2650
|
+
__decorateParam(7, Inject8(BULLMQ_RESOLVED_CONFIG))
|
|
2638
2651
|
], JobWorkerOrchestrator);
|
|
2639
2652
|
var JobWorkerModule = class {
|
|
2640
2653
|
static forRoot(opts) {
|
|
@@ -2774,11 +2787,11 @@ var MemoryBridgeDeliveryRepo = class {
|
|
|
2774
2787
|
* Unlike `insertDelivery`, this read does NOT call `assertTenantId`:
|
|
2775
2788
|
* `tenantId === undefined` is the supported cross-tenant admin view.
|
|
2776
2789
|
*/
|
|
2777
|
-
async getStatusHistogram(windowHours, tenantId) {
|
|
2790
|
+
async getStatusHistogram(windowHours, tenantId, nowMs = Date.now()) {
|
|
2778
2791
|
if (!Number.isFinite(windowHours) || windowHours <= 0) {
|
|
2779
2792
|
throw new RangeError("windowHours must be positive");
|
|
2780
2793
|
}
|
|
2781
|
-
const cutoffMs =
|
|
2794
|
+
const cutoffMs = nowMs - windowHours * 36e5;
|
|
2782
2795
|
const histogram = {
|
|
2783
2796
|
pending: 0,
|
|
2784
2797
|
delivered: 0,
|
|
@@ -2820,7 +2833,7 @@ var MemoryBridgeDeliveryRepo = class {
|
|
|
2820
2833
|
};
|
|
2821
2834
|
|
|
2822
2835
|
// runtime/subsystems/bridge/bridge-delivery.drizzle-backend.ts
|
|
2823
|
-
import { Inject as
|
|
2836
|
+
import { Inject as Inject9, Injectable as Injectable9, Optional as Optional3 } from "@nestjs/common";
|
|
2824
2837
|
import { eq as eq5, and as and5, gte as gte2, isNull as isNull2, sql as sql7 } from "drizzle-orm";
|
|
2825
2838
|
|
|
2826
2839
|
// runtime/subsystems/bridge/bridge-delivery.schema.ts
|
|
@@ -3080,17 +3093,17 @@ var DrizzleBridgeDeliveryRepo = class {
|
|
|
3080
3093
|
};
|
|
3081
3094
|
DrizzleBridgeDeliveryRepo = __decorateClass([
|
|
3082
3095
|
Injectable9(),
|
|
3083
|
-
__decorateParam(0,
|
|
3096
|
+
__decorateParam(0, Inject9(DRIZZLE)),
|
|
3084
3097
|
__decorateParam(1, Optional3()),
|
|
3085
|
-
__decorateParam(1,
|
|
3098
|
+
__decorateParam(1, Inject9(BRIDGE_MULTI_TENANT))
|
|
3086
3099
|
], DrizzleBridgeDeliveryRepo);
|
|
3087
3100
|
|
|
3088
3101
|
// runtime/subsystems/bridge/bridge-outbox-drain-hook.ts
|
|
3089
|
-
import { Inject as
|
|
3102
|
+
import { Inject as Inject11, Injectable as Injectable11, Logger as Logger6, Optional as Optional5 } from "@nestjs/common";
|
|
3090
3103
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
3091
3104
|
|
|
3092
3105
|
// runtime/subsystems/bridge/bridge-delivery-handler.ts
|
|
3093
|
-
import { Inject as
|
|
3106
|
+
import { Inject as Inject10, Injectable as Injectable10, Logger as Logger5, Optional as Optional4 } from "@nestjs/common";
|
|
3094
3107
|
|
|
3095
3108
|
// runtime/subsystems/events/events.tokens.ts
|
|
3096
3109
|
var EVENT_BUS = "EVENT_BUS";
|
|
@@ -3180,12 +3193,12 @@ BridgeDeliveryHandler = __decorateClass([
|
|
|
3180
3193
|
retry: { attempts: 3, backoff: "exponential", baseMs: 250 },
|
|
3181
3194
|
replayFrom: "last_step"
|
|
3182
3195
|
}),
|
|
3183
|
-
__decorateParam(0,
|
|
3184
|
-
__decorateParam(1,
|
|
3185
|
-
__decorateParam(2,
|
|
3186
|
-
__decorateParam(3,
|
|
3196
|
+
__decorateParam(0, Inject10(BRIDGE_DELIVERY_REPO)),
|
|
3197
|
+
__decorateParam(1, Inject10(JOB_ORCHESTRATOR)),
|
|
3198
|
+
__decorateParam(2, Inject10(EVENT_BUS)),
|
|
3199
|
+
__decorateParam(3, Inject10(BRIDGE_REGISTRY)),
|
|
3187
3200
|
__decorateParam(4, Optional4()),
|
|
3188
|
-
__decorateParam(4,
|
|
3201
|
+
__decorateParam(4, Inject10(BRIDGE_MULTI_TENANT))
|
|
3189
3202
|
], BridgeDeliveryHandler);
|
|
3190
3203
|
|
|
3191
3204
|
// runtime/subsystems/bridge/bridge-outbox-drain-hook.ts
|
|
@@ -3294,11 +3307,11 @@ var BridgeOutboxDrainHook = class {
|
|
|
3294
3307
|
BridgeOutboxDrainHook = __decorateClass([
|
|
3295
3308
|
Injectable11(),
|
|
3296
3309
|
__decorateParam(0, Optional5()),
|
|
3297
|
-
__decorateParam(0,
|
|
3310
|
+
__decorateParam(0, Inject11(BRIDGE_REGISTRY))
|
|
3298
3311
|
], BridgeOutboxDrainHook);
|
|
3299
3312
|
|
|
3300
3313
|
// runtime/subsystems/bridge/event-flow.service.ts
|
|
3301
|
-
import { Inject as
|
|
3314
|
+
import { Inject as Inject12, Injectable as Injectable12, Optional as Optional6 } from "@nestjs/common";
|
|
3302
3315
|
var EventFlowService = class {
|
|
3303
3316
|
constructor(db, eventBus, orchestrator, bridgeRepo, registry = {}, multiTenant = false) {
|
|
3304
3317
|
this.db = db;
|
|
@@ -3370,14 +3383,14 @@ var EventFlowService = class {
|
|
|
3370
3383
|
};
|
|
3371
3384
|
EventFlowService = __decorateClass([
|
|
3372
3385
|
Injectable12(),
|
|
3373
|
-
__decorateParam(0,
|
|
3374
|
-
__decorateParam(1,
|
|
3375
|
-
__decorateParam(2,
|
|
3376
|
-
__decorateParam(3,
|
|
3386
|
+
__decorateParam(0, Inject12(DRIZZLE)),
|
|
3387
|
+
__decorateParam(1, Inject12(EVENT_BUS)),
|
|
3388
|
+
__decorateParam(2, Inject12(JOB_ORCHESTRATOR)),
|
|
3389
|
+
__decorateParam(3, Inject12(BRIDGE_DELIVERY_REPO)),
|
|
3377
3390
|
__decorateParam(4, Optional6()),
|
|
3378
|
-
__decorateParam(4,
|
|
3391
|
+
__decorateParam(4, Inject12(BRIDGE_REGISTRY)),
|
|
3379
3392
|
__decorateParam(5, Optional6()),
|
|
3380
|
-
__decorateParam(5,
|
|
3393
|
+
__decorateParam(5, Inject12(BRIDGE_MULTI_TENANT))
|
|
3381
3394
|
], EventFlowService);
|
|
3382
3395
|
|
|
3383
3396
|
// runtime/subsystems/bridge/generated/registry.ts
|
|
@@ -3456,7 +3469,7 @@ var BridgeModule = class {
|
|
|
3456
3469
|
BridgeModule = __decorateClass([
|
|
3457
3470
|
Module3({}),
|
|
3458
3471
|
__decorateParam(0, Optional7()),
|
|
3459
|
-
__decorateParam(0,
|
|
3472
|
+
__decorateParam(0, Inject13(JOB_WORKER_MODULE_OPTIONS))
|
|
3460
3473
|
], BridgeModule);
|
|
3461
3474
|
export {
|
|
3462
3475
|
BridgeModule
|