@pattern-stack/codegen 0.15.2 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/{chunk-WEVWJKOW.js → chunk-24WXSC3C.js} +10 -10
  3. package/dist/{chunk-KMZCQASO.js → chunk-3RWMQC3K.js} +18 -12
  4. package/dist/chunk-3RWMQC3K.js.map +1 -0
  5. package/dist/{chunk-24CWKBK5.js → chunk-4MF3HKJA.js} +3 -3
  6. package/dist/chunk-4MF3HKJA.js.map +1 -0
  7. package/dist/{chunk-4JLJYWJC.js → chunk-4PFF3ED4.js} +98 -10
  8. package/dist/chunk-4PFF3ED4.js.map +1 -0
  9. package/dist/{chunk-32BMMV4H.js → chunk-5RT7JGKT.js} +5 -5
  10. package/dist/{chunk-JRVNVKN6.js → chunk-BHZP6LOV.js} +2 -2
  11. package/dist/{chunk-4MVGAMUA.js → chunk-BK5ICA2F.js} +4 -4
  12. package/dist/{chunk-4RFHUZXU.js → chunk-BULPAAD3.js} +2 -2
  13. package/dist/{chunk-OZZJDRGW.js → chunk-CEWLVVAH.js} +10 -10
  14. package/dist/{chunk-YTN6BKWA.js → chunk-DRCLNYH7.js} +7 -7
  15. package/dist/{chunk-TNXH7BJS.js → chunk-E45CSC33.js} +2 -2
  16. package/dist/{chunk-L7BNNRGI.js → chunk-EBKVKN75.js} +26 -6
  17. package/dist/chunk-EBKVKN75.js.map +1 -0
  18. package/dist/{chunk-EOLLMEAH.js → chunk-EJBK7I4F.js} +3 -3
  19. package/dist/chunk-EJBK7I4F.js.map +1 -0
  20. package/dist/{chunk-K2I6XIK5.js → chunk-KSTZIULO.js} +4 -4
  21. package/dist/{chunk-Z7PQCAVK.js → chunk-LQ6PYFU6.js} +4 -4
  22. package/dist/chunk-MYQIQ27N.js +118 -0
  23. package/dist/chunk-MYQIQ27N.js.map +1 -0
  24. package/dist/{chunk-5Y7W3XR6.js → chunk-OTR44OH6.js} +24 -5
  25. package/dist/chunk-OTR44OH6.js.map +1 -0
  26. package/dist/{chunk-WPXNN6QS.js → chunk-RUYLXR5F.js} +11 -8
  27. package/dist/chunk-RUYLXR5F.js.map +1 -0
  28. package/dist/{chunk-7LKAMLV4.js → chunk-T6SCOJF4.js} +4 -4
  29. package/dist/{chunk-OGIZXGPY.js → chunk-TDEHU73T.js} +4 -4
  30. package/dist/{chunk-I6MG4M3F.js → chunk-VNBC3VXM.js} +2 -2
  31. package/dist/{chunk-YPWODKD5.js → chunk-W2UIDI3R.js} +5 -5
  32. package/dist/chunk-W4HOHZVF.js +1 -0
  33. package/dist/{chunk-WRUUSZDJ.js → chunk-WWGYCIJX.js} +3 -3
  34. package/dist/{chunk-RC23QROE.js → chunk-XDIIVIIK.js} +79 -5
  35. package/dist/chunk-XDIIVIIK.js.map +1 -0
  36. package/dist/{chunk-DCCZB4UC.js → chunk-XWBK3XJK.js} +4 -4
  37. package/dist/{chunk-SR7F3TJY.js → chunk-YK5JEVLX.js} +4 -4
  38. package/dist/{chunk-4OMHBMZJ.js → chunk-YLPAPPLW.js} +3 -3
  39. package/dist/chunk-YLPAPPLW.js.map +1 -0
  40. package/dist/{chunk-BIO6F7YI.js → chunk-ZPL74UQN.js} +4 -2
  41. package/dist/{chunk-BIO6F7YI.js.map → chunk-ZPL74UQN.js.map} +1 -1
  42. package/dist/runtime/base-classes/index.js +15 -15
  43. package/dist/runtime/shared/openapi/index.js +3 -3
  44. package/dist/runtime/subsystems/auth/auth.module.js +3 -3
  45. package/dist/runtime/subsystems/auth/index.js +7 -7
  46. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js +3 -3
  47. package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js +3 -3
  48. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.d.ts +2 -1
  49. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +7 -6
  50. package/dist/runtime/subsystems/bridge/bridge.module.js +17 -16
  51. package/dist/runtime/subsystems/bridge/event-flow.service.js +3 -3
  52. package/dist/runtime/subsystems/bridge/index.js +24 -23
  53. package/dist/runtime/subsystems/cache/cache.module.js +1 -1
  54. package/dist/runtime/subsystems/cache/index.js +3 -3
  55. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.d.ts +20 -0
  56. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +4 -3
  57. package/dist/runtime/subsystems/events/event-bus.memory-backend.js +2 -2
  58. package/dist/runtime/subsystems/events/events.module.d.ts +14 -0
  59. package/dist/runtime/subsystems/events/events.module.js +6 -5
  60. package/dist/runtime/subsystems/events/index.js +9 -8
  61. package/dist/runtime/subsystems/index.d.ts +5 -1
  62. package/dist/runtime/subsystems/index.js +108 -96
  63. package/dist/runtime/subsystems/integration/build-change-source.js +3 -3
  64. package/dist/runtime/subsystems/integration/execute-integration.use-case.js +2 -2
  65. package/dist/runtime/subsystems/integration/index.js +35 -35
  66. package/dist/runtime/subsystems/integration/integration-cursor-store.drizzle-backend.js +2 -2
  67. package/dist/runtime/subsystems/integration/integration-run-recorder.drizzle-backend.js +2 -2
  68. package/dist/runtime/subsystems/integration/integration.module.js +5 -5
  69. package/dist/runtime/subsystems/integration/poll-change-source.d.ts +1 -1
  70. package/dist/runtime/subsystems/integration/poll-change-source.js +1 -1
  71. package/dist/runtime/subsystems/integration/webhook-change-source.d.ts +4 -3
  72. package/dist/runtime/subsystems/integration/webhook-change-source.js +1 -1
  73. package/dist/runtime/subsystems/jobs/index.d.ts +2 -1
  74. package/dist/runtime/subsystems/jobs/index.js +30 -18
  75. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +3 -2
  76. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
  77. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.d.ts +2 -1
  78. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js +3 -2
  79. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js +2 -2
  80. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +2 -2
  81. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +2 -2
  82. package/dist/runtime/subsystems/jobs/job-worker.d.ts +28 -0
  83. package/dist/runtime/subsystems/jobs/job-worker.js +3 -2
  84. package/dist/runtime/subsystems/jobs/job-worker.module.js +9 -8
  85. package/dist/runtime/subsystems/jobs/jobs-domain.module.d.ts +12 -7
  86. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +7 -6
  87. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.d.ts +13 -1
  88. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js +3 -1
  89. package/dist/runtime/subsystems/jobs/pg-notify.d.ts +85 -0
  90. package/dist/runtime/subsystems/jobs/pg-notify.js +14 -0
  91. package/dist/runtime/subsystems/jobs/pg-notify.js.map +1 -0
  92. package/dist/runtime/subsystems/observability/index.js +7 -7
  93. package/dist/runtime/subsystems/observability/observability.module.js +4 -4
  94. package/dist/runtime/subsystems/observability/observability.service.js +3 -3
  95. package/dist/runtime/subsystems/storage/index.js +4 -4
  96. package/dist/runtime/subsystems/storage/storage.module.js +2 -2
  97. package/dist/src/cli/index.js +57 -19
  98. package/dist/src/cli/index.js.map +1 -1
  99. package/dist/src/index.js +11 -11
  100. package/package.json +1 -1
  101. package/runtime/subsystems/bridge/bridge-outbox-drain-hook.ts +27 -0
  102. package/runtime/subsystems/events/event-bus.drizzle-backend.ts +108 -4
  103. package/runtime/subsystems/events/events.module.ts +14 -0
  104. package/runtime/subsystems/index.ts +27 -0
  105. package/runtime/subsystems/integration/poll-change-source.ts +10 -7
  106. package/runtime/subsystems/integration/webhook-change-source.ts +12 -8
  107. package/runtime/subsystems/jobs/index.ts +10 -0
  108. package/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.ts +29 -2
  109. package/runtime/subsystems/jobs/job-worker.module.ts +11 -0
  110. package/runtime/subsystems/jobs/job-worker.ts +98 -0
  111. package/runtime/subsystems/jobs/jobs-domain.module.ts +22 -7
  112. package/runtime/subsystems/jobs/jobs-domain.tokens.ts +13 -0
  113. package/runtime/subsystems/jobs/pg-notify.ts +216 -0
  114. package/templates/subsystem/events-config/codegen-config-events-block.ejs.t +14 -0
  115. package/templates/subsystem/jobs-config/codegen-config-jobs-block.ejs.t +13 -4
  116. package/dist/chunk-24CWKBK5.js.map +0 -1
  117. package/dist/chunk-4JLJYWJC.js.map +0 -1
  118. package/dist/chunk-4OMHBMZJ.js.map +0 -1
  119. package/dist/chunk-5Y7W3XR6.js.map +0 -1
  120. package/dist/chunk-EOLLMEAH.js.map +0 -1
  121. package/dist/chunk-KMZCQASO.js.map +0 -1
  122. package/dist/chunk-L7BNNRGI.js.map +0 -1
  123. package/dist/chunk-RC23QROE.js.map +0 -1
  124. package/dist/chunk-UTN4GBPQ.js +0 -1
  125. package/dist/chunk-WPXNN6QS.js.map +0 -1
  126. /package/dist/{chunk-WEVWJKOW.js.map → chunk-24WXSC3C.js.map} +0 -0
  127. /package/dist/{chunk-32BMMV4H.js.map → chunk-5RT7JGKT.js.map} +0 -0
  128. /package/dist/{chunk-JRVNVKN6.js.map → chunk-BHZP6LOV.js.map} +0 -0
  129. /package/dist/{chunk-4MVGAMUA.js.map → chunk-BK5ICA2F.js.map} +0 -0
  130. /package/dist/{chunk-4RFHUZXU.js.map → chunk-BULPAAD3.js.map} +0 -0
  131. /package/dist/{chunk-OZZJDRGW.js.map → chunk-CEWLVVAH.js.map} +0 -0
  132. /package/dist/{chunk-YTN6BKWA.js.map → chunk-DRCLNYH7.js.map} +0 -0
  133. /package/dist/{chunk-TNXH7BJS.js.map → chunk-E45CSC33.js.map} +0 -0
  134. /package/dist/{chunk-K2I6XIK5.js.map → chunk-KSTZIULO.js.map} +0 -0
  135. /package/dist/{chunk-Z7PQCAVK.js.map → chunk-LQ6PYFU6.js.map} +0 -0
  136. /package/dist/{chunk-7LKAMLV4.js.map → chunk-T6SCOJF4.js.map} +0 -0
  137. /package/dist/{chunk-OGIZXGPY.js.map → chunk-TDEHU73T.js.map} +0 -0
  138. /package/dist/{chunk-I6MG4M3F.js.map → chunk-VNBC3VXM.js.map} +0 -0
  139. /package/dist/{chunk-YPWODKD5.js.map → chunk-W2UIDI3R.js.map} +0 -0
  140. /package/dist/{chunk-UTN4GBPQ.js.map → chunk-W4HOHZVF.js.map} +0 -0
  141. /package/dist/{chunk-WRUUSZDJ.js.map → chunk-WWGYCIJX.js.map} +0 -0
  142. /package/dist/{chunk-DCCZB4UC.js.map → chunk-XWBK3XJK.js.map} +0 -0
  143. /package/dist/{chunk-SR7F3TJY.js.map → chunk-YK5JEVLX.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,86 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.16.0] — 2026-06-04
8
+
9
+ **Postgres LISTEN/NOTIFY wakeups** for the jobs worker + events outbox drainer
10
+ (LISTEN-NOTIFY-1, dogfood gap #7 — swe-brain's live-inbound latency). The
11
+ scaffold has documented `jobs.extensions.drizzle.listen_notify` since JOB-6 and
12
+ `JobsDomainModule` reserved the typed slot, but neither knob was wired (no
13
+ runtime, and the barrel never threaded `jobs.extensions.drizzle.*` into
14
+ `JobWorkerModule.forRoot`). This makes both real.
15
+
16
+ NOTIFY wakes the polling loop the instant work becomes claimable; **interval
17
+ polling remains the safety net** (alongside, never instead). Every `pg_notify`
18
+ is emitted **inside the same transaction** as the row write it announces, so
19
+ Postgres delivers it only on commit — durability is byte-for-byte unchanged. A
20
+ lost notification (listener down, transaction-mode pooler) degrades to today's
21
+ poll latency, never to lost work.
22
+
23
+ Measured on swe-brain's inbound spine (webhook → outbox drain → bridge wrapper →
24
+ user job): **~1.4–3.0 s → sub-500 ms** with `listen_notify: true`, zero
25
+ durability change.
26
+
27
+ ### Added
28
+
29
+ - **`runtime/subsystems/jobs/pg-notify.ts`** — `PgNotifyListener` (dedicated
30
+ listener connection off `DRIZZLE.$client`, debounced dispatch,
31
+ reconnect-with-capped-backoff, WARN-once degradation) + in-tx `pgNotify(tx,
32
+ channel, payload)` + channel constants. Shared by jobs + events.
33
+ - **Jobs worker** — `JobWorkerOptions.listenNotify`; LISTEN on
34
+ `codegen_jobs_wake`, a notify for the worker's pool drives an immediate
35
+ debounced claim cycle. `DrizzleJobOrchestrator.start()` emits the in-tx wake
36
+ on enqueue, gated on the new `JOBS_LISTEN_NOTIFY` token (provided from
37
+ `jobs.extensions.drizzle.listen_notify`).
38
+ - **Events drainer** — `EventsModuleOptions.listenNotify`; LISTEN on
39
+ `codegen_events_wake`, `publish`/`publishMany` emit the in-tx wake; a
40
+ pool-filtered drainer wakes only for its lanes.
41
+ - **Bridge** — the `BridgeOutboxDrainHook` wrapper `job_run` insert emits the
42
+ jobs wake in the per-event drain tx, so reserved-pool wrappers wake too.
43
+ - **Generator threading** — `jobs.extensions.drizzle.{listen_notify,
44
+ poll_interval_ms}` flow into the generated barrel's `JobsDomainModule.forRoot`
45
+ + embedded `JobWorkerModule.forRoot({ domainModuleExtensions: { drizzle: …
46
+ } })`; `events.extensions.drizzle.listen_notify` flows into
47
+ `EventsModule.forRoot({ listenNotify })`. Package and vendored emission both
48
+ covered. (The runtime already honored `JobWorkerOptions.pollIntervalMs`; it
49
+ just never received a config value — now it does.)
50
+ - Scaffold config comments + jobs skill updated to implemented reality, with the
51
+ **PgBouncer caveat**: LISTEN/NOTIFY requires a direct (or session-mode)
52
+ connection — session-scoped `LISTEN` does not survive a transaction-mode
53
+ pooler; behind one the feature degrades to polling.
54
+
55
+ ## [0.15.3] — 2026-06-03
56
+
57
+ Package-mode **inbound webhook drain** — the first real exercise of
58
+ `WebhookChangeSource` by a `runtime: package` consumer (swe-brain's Slack
59
+ inbound pipeline-parity drain, ADR-0009 §6). One latent transposition this path
60
+ exercises; the fix is framework-only.
61
+
62
+ ### Fixed
63
+
64
+ - **`WebhookChangeSource` (and the identically-shaped `PollChangeSource`) now
65
+ derive `Change<T>.externalId` from the mapping `source`, not `target`.** Both
66
+ primitives located the `DetectionConfig` mapping entry with `target ===
67
+ 'external_id'` correctly, then read the emitted record off
68
+ `mapping.target` — a transposition. Mapping semantics are `{ source: <field on
69
+ the emitted record>, target: <canonical column> }`, and `fetch()` reads
70
+ `record[externalIdSourceField]` off the emitted record, so it must use
71
+ `.source`. The two diverge only when the canonical record is vendor-neutral
72
+ camelCase (`source: 'externalId'` → `target: 'external_id'`): such a consumer
73
+ hit `record missing string 'external_id'` and the primitive was unusable. The
74
+ original unit fixtures masked it by keying records `external_id` (== the
75
+ target). Regression tests now cover the camelCase consumer shape directly.
76
+
77
+ ### Added
78
+
79
+ - **Webhook/poll/detection symbols re-exported from
80
+ `@pattern-stack/codegen/subsystems`.** `WebhookChangeSource`,
81
+ `WebhookChangeSourceOptions`, `WebhookFetchCallback`, `WebhookFetchContext`,
82
+ `WebhookCursor`, `buildChangeSource`, `DetectionConfigSchema`,
83
+ `DetectionConfig` (+ poll equivalents) were previously reachable only via the
84
+ deep `.../integration/index` path; they now ride the public barrel alongside
85
+ the curated `IncrementalReadBase` / `ExecuteIntegrationUseCase` forwards.
86
+
7
87
  ## [0.15.2] — 2026-06-03
8
88
 
9
89
  Package-mode **bridge *delivery*** — the first time a `runtime: package` consumer
@@ -1,18 +1,15 @@
1
- import {
2
- MemoryCursorStore
3
- } from "./chunk-AHV4GDYM.js";
4
- import {
5
- DrizzleIntegrationRunRecorder
6
- } from "./chunk-SR7F3TJY.js";
7
1
  import {
8
2
  MemoryRunRecorder
9
3
  } from "./chunk-EO2QPOKH.js";
10
4
  import {
11
5
  PostgresCursorStore
12
- } from "./chunk-DCCZB4UC.js";
6
+ } from "./chunk-XWBK3XJK.js";
13
7
  import {
14
- DeepEqualDiffer
15
- } from "./chunk-36U5UGIO.js";
8
+ MemoryCursorStore
9
+ } from "./chunk-AHV4GDYM.js";
10
+ import {
11
+ DrizzleIntegrationRunRecorder
12
+ } from "./chunk-YK5JEVLX.js";
16
13
  import {
17
14
  INTEGRATION_CURSOR_STORE,
18
15
  INTEGRATION_FIELD_DIFFER,
@@ -20,6 +17,9 @@ import {
20
17
  INTEGRATION_MULTI_TENANT,
21
18
  INTEGRATION_RUN_RECORDER
22
19
  } from "./chunk-S7C6TIIF.js";
20
+ import {
21
+ DeepEqualDiffer
22
+ } from "./chunk-36U5UGIO.js";
23
23
  import {
24
24
  __decorateClass
25
25
  } from "./chunk-2E224ZSN.js";
@@ -78,4 +78,4 @@ IntegrationModule = __decorateClass([
78
78
  export {
79
79
  IntegrationModule
80
80
  };
81
- //# sourceMappingURL=chunk-WEVWJKOW.js.map
81
+ //# sourceMappingURL=chunk-24WXSC3C.js.map
@@ -1,21 +1,21 @@
1
- import {
2
- DrizzleJobRunService
3
- } from "./chunk-I6MG4M3F.js";
4
- import {
5
- MemoryJobRunService
6
- } from "./chunk-JRVNVKN6.js";
7
1
  import {
8
2
  DrizzleJobStepService
9
3
  } from "./chunk-DV4RV2DC.js";
10
4
  import {
11
5
  DrizzleJobOrchestrator
12
- } from "./chunk-5Y7W3XR6.js";
6
+ } from "./chunk-OTR44OH6.js";
13
7
  import {
14
8
  MemoryJobOrchestrator
15
- } from "./chunk-4RFHUZXU.js";
9
+ } from "./chunk-BULPAAD3.js";
16
10
  import {
17
11
  MemoryJobStepService
18
12
  } from "./chunk-PNZSGAB2.js";
13
+ import {
14
+ DrizzleJobRunService
15
+ } from "./chunk-VNBC3VXM.js";
16
+ import {
17
+ MemoryJobRunService
18
+ } from "./chunk-BHZP6LOV.js";
19
19
  import {
20
20
  MemoryJobStore
21
21
  } from "./chunk-SNQ3TOWP.js";
@@ -25,11 +25,12 @@ import {
25
25
  resolveBullMqConfig
26
26
  } from "./chunk-I6MVCB5A.js";
27
27
  import {
28
+ JOBS_LISTEN_NOTIFY,
28
29
  JOBS_MULTI_TENANT,
29
30
  JOB_ORCHESTRATOR,
30
31
  JOB_RUN_SERVICE,
31
32
  JOB_STEP_SERVICE
32
- } from "./chunk-BIO6F7YI.js";
33
+ } from "./chunk-ZPL74UQN.js";
33
34
  import {
34
35
  DRIZZLE
35
36
  } from "./chunk-U64T4YZE.js";
@@ -42,13 +43,17 @@ import { Module } from "@nestjs/common";
42
43
  var JobsDomainModule = class {
43
44
  static forRoot(opts) {
44
45
  const multiTenant = opts.multiTenant ?? false;
46
+ const listenNotify = opts.backend === "drizzle" && Boolean(opts.extensions?.drizzle?.listenNotify);
45
47
  const providers = [
46
48
  // JOB-8 — boolean provider consumed by the four service-layer backends.
47
49
  // Always provided (even when `multiTenant === false`) so `@Inject`
48
50
  // always resolves; backends short-circuit the enforcement path when
49
51
  // the value is `false`. See `jobs-domain.tokens.ts` for the claim-loop
50
52
  // cross-tenant-by-design decision.
51
- { provide: JOBS_MULTI_TENANT, useValue: multiTenant }
53
+ { provide: JOBS_MULTI_TENANT, useValue: multiTenant },
54
+ // LISTEN-NOTIFY-1 — always provided so the orchestrator's `@Inject`
55
+ // resolves; the orchestrator skips the `pg_notify` emit when `false`.
56
+ { provide: JOBS_LISTEN_NOTIFY, useValue: listenNotify }
52
57
  ];
53
58
  if (opts.backend === "memory") {
54
59
  const store = new MemoryJobStore();
@@ -88,7 +93,8 @@ var JobsDomainModule = class {
88
93
  JOB_ORCHESTRATOR,
89
94
  JOB_RUN_SERVICE,
90
95
  JOB_STEP_SERVICE,
91
- JOBS_MULTI_TENANT
96
+ JOBS_MULTI_TENANT,
97
+ JOBS_LISTEN_NOTIFY
92
98
  ];
93
99
  if (opts.backend === "bullmq") {
94
100
  exports.push(BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG);
@@ -108,4 +114,4 @@ JobsDomainModule = __decorateClass([
108
114
  export {
109
115
  JobsDomainModule
110
116
  };
111
- //# sourceMappingURL=chunk-KMZCQASO.js.map
117
+ //# sourceMappingURL=chunk-3RWMQC3K.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../runtime/subsystems/jobs/jobs-domain.module.ts"],"sourcesContent":["/**\n * JobsDomainModule — `DynamicModule.forRoot({ backend })` factory wiring\n * the three jobs-domain protocol tokens to a backend implementation\n * (ADR-022, JOB-5).\n *\n * Mirrors `EventsModule.forRoot()` exactly:\n * - `global: true` so consumer entity modules don't have to import this\n * individually — `JOB_ORCHESTRATOR` / `JOB_RUN_SERVICE` /\n * `JOB_STEP_SERVICE` are available project-wide.\n * - One backend at a time (Drizzle for production, Memory for tests).\n *\n * Backend swappability follows the core/extension protocol from CLAUDE.md:\n * the three tokens are the **core contract**; backend-specific tunables\n * live under `extensions.<backend>` so opting into a feature is explicit\n * and the type system reserves the slot.\n */\nimport { Module, type DynamicModule, type Provider } from '@nestjs/common';\nimport { DRIZZLE } from '../../constants/tokens';\nimport {\n JOB_ORCHESTRATOR,\n JOB_RUN_SERVICE,\n JOB_STEP_SERVICE,\n JOBS_MULTI_TENANT,\n JOBS_LISTEN_NOTIFY,\n} from './jobs-domain.tokens';\nimport { DrizzleJobOrchestrator } from './job-orchestrator.drizzle-backend';\nimport { DrizzleJobRunService } from './job-run-service.drizzle-backend';\nimport { DrizzleJobStepService } from './job-step-service.drizzle-backend';\n// #6 — `BullMQJobOrchestrator` is lazy-loaded only when `backend: 'bullmq'`\n// is selected. The backend file is filtered out of drizzle/memory installs\n// (see `backendFileFilter`); a non-literal dynamic import below sidesteps\n// consumer-side tsc resolution of an absent file.\nimport { MemoryJobOrchestrator } from './job-orchestrator.memory-backend';\nimport { MemoryJobRunService } from './job-run-service.memory-backend';\nimport { MemoryJobStepService } from './job-step-service.memory-backend';\nimport { MemoryJobStore } from './memory-job-store';\nimport {\n BULLMQ_CONNECTION,\n BULLMQ_RESOLVED_CONFIG,\n resolveBullMqConfig,\n type BullMqExtensionsConfig,\n} from './bullmq.config';\n\n/**\n * Drizzle backend extensions surface (LISTEN-NOTIFY-1 wires both fields).\n *\n * - `listenNotify` → provided as `JOBS_LISTEN_NOTIFY` so the orchestrator emits\n * in-tx `pg_notify` on enqueue, and threaded into each spawned `JobWorker`\n * (which holds the listener connection). Off by default.\n * - `pollIntervalMs` → threaded into the spawned `JobWorker`'s\n * `JobWorkerOptions.pollIntervalMs` (the worker already honored this; it just\n * never received a config value). Default 1000.\n *\n * Both run ALONGSIDE interval polling — `listenNotify` only adds an early wake;\n * polling remains the durability heartbeat.\n */\nexport interface DrizzleBackendExtensions {\n /** Use Postgres LISTEN/NOTIFY to wake the polling loop. Default false. */\n listenNotify?: boolean;\n /** Polling interval (ms). Default 1000. */\n pollIntervalMs?: number;\n}\n\nexport interface JobsDomainModuleOptions {\n backend: 'drizzle' | 'memory' | 'bullmq';\n /**\n * Backend-specific extensions. Only the matching backend's extensions\n * are read at boot; non-matching keys are ignored. This is the\n * core/extension protocol surface — see CLAUDE.md.\n */\n extensions?: {\n drizzle?: DrizzleBackendExtensions;\n /**\n * BullMQ backend extensions (BULLMQ-1). Snake_case mirrors the YAML\n * under `jobs.extensions.bullmq`. `redis_url` falls back to\n * `process.env.REDIS_URL` then `redis://localhost:6379`.\n */\n bullmq?: BullMqExtensionsConfig;\n };\n /** Multi-tenancy opt-in. Wired by JOB-8; module signature stays stable. */\n multiTenant?: boolean;\n}\n\n@Module({})\nexport class JobsDomainModule {\n static forRoot(opts: JobsDomainModuleOptions): DynamicModule {\n const multiTenant = opts.multiTenant ?? false;\n // LISTEN-NOTIFY-1 — drizzle-only extension. `listen_notify` is meaningless\n // for memory (no DB) and redundant for bullmq (native wakeups); only the\n // drizzle backend's orchestrator reads it.\n const listenNotify =\n opts.backend === 'drizzle' && Boolean(opts.extensions?.drizzle?.listenNotify);\n\n const providers: Provider[] = [\n // JOB-8 — boolean provider consumed by the four service-layer backends.\n // Always provided (even when `multiTenant === false`) so `@Inject`\n // always resolves; backends short-circuit the enforcement path when\n // the value is `false`. See `jobs-domain.tokens.ts` for the claim-loop\n // cross-tenant-by-design decision.\n { provide: JOBS_MULTI_TENANT, useValue: multiTenant },\n // LISTEN-NOTIFY-1 — always provided so the orchestrator's `@Inject`\n // resolves; the orchestrator skips the `pg_notify` emit when `false`.\n { provide: JOBS_LISTEN_NOTIFY, useValue: listenNotify },\n ];\n\n if (opts.backend === 'memory') {\n // The store is a plain class — wired as a singleton `useValue` so\n // unit tests can pull it out via `.get(MemoryJobStore)` for direct\n // assertions (matches the access pattern in JOB-4 specs).\n const store = new MemoryJobStore();\n providers.push({ provide: MemoryJobStore, useValue: store });\n providers.push(MemoryJobStepService);\n providers.push({ provide: JOB_STEP_SERVICE, useExisting: MemoryJobStepService });\n providers.push(MemoryJobOrchestrator);\n providers.push({ provide: JOB_ORCHESTRATOR, useExisting: MemoryJobOrchestrator });\n providers.push(MemoryJobRunService);\n providers.push({ provide: JOB_RUN_SERVICE, useExisting: MemoryJobRunService });\n } else if (opts.backend === 'bullmq') {\n // BULLMQ-1 — BullMQ orchestrator over a Postgres source of truth. The\n // run/step services stay Drizzle (domain reads + `listForScope` are\n // Postgres queries, unchanged per spec). Only the orchestrator's\n // claim/dispatch half swaps to BullMQ.\n //\n // #6 — the bullmq backend module is filtered out of drizzle/memory\n // installs (no `bullmq` peer dep, no consumer-side tsc compile of an\n // unused file). The factory below dynamic-imports it via a non-literal\n // specifier so TS treats the module type as `any` and never tries to\n // resolve the absent file on a drizzle/memory consumer.\n const resolved = resolveBullMqConfig(opts.extensions?.bullmq);\n providers.push({ provide: BULLMQ_CONNECTION, useValue: resolved.connection });\n providers.push({ provide: BULLMQ_RESOLVED_CONFIG, useValue: resolved });\n providers.push({\n provide: JOB_ORCHESTRATOR,\n useFactory: async (...args: unknown[]): Promise<object> => {\n const specifier = './job-orchestrator.bullmq-backend';\n const mod = (await import(specifier)) as {\n BullMQJobOrchestrator: new (...args: unknown[]) => object;\n };\n return new mod.BullMQJobOrchestrator(...args);\n },\n // The bullmq orchestrator constructor mirrors DrizzleJobOrchestrator's\n // injection list: DRIZZLE + JOBS_MULTI_TENANT + the resolved BullMQ\n // tokens. Importing token references would force a static dep on the\n // tokens file in this module's import graph; using the existing\n // symbols already in scope is sufficient.\n inject: [DRIZZLE, JOBS_MULTI_TENANT, BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG],\n });\n providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });\n providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });\n } else {\n providers.push({ provide: JOB_ORCHESTRATOR, useClass: DrizzleJobOrchestrator });\n providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });\n providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });\n }\n\n const exports = [\n JOB_ORCHESTRATOR,\n JOB_RUN_SERVICE,\n JOB_STEP_SERVICE,\n JOBS_MULTI_TENANT,\n JOBS_LISTEN_NOTIFY,\n ];\n // BULLMQ-1 — only export the BullMQ tokens when they were actually\n // provided. Nest throws \"exported but not provided\" otherwise. Exported so\n // JobWorkerModule (which imports this module) can read the resolved\n // connection/config to spawn BullMQ workers.\n if (opts.backend === 'bullmq') {\n exports.push(BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG);\n }\n\n return {\n module: JobsDomainModule,\n global: true,\n providers,\n exports,\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgBA,SAAS,cAAiD;AAoEnD,IAAM,mBAAN,MAAuB;AAAA,EAC5B,OAAO,QAAQ,MAA8C;AAC3D,UAAM,cAAc,KAAK,eAAe;AAIxC,UAAM,eACJ,KAAK,YAAY,aAAa,QAAQ,KAAK,YAAY,SAAS,YAAY;AAE9E,UAAM,YAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAM5B,EAAE,SAAS,mBAAmB,UAAU,YAAY;AAAA;AAAA;AAAA,MAGpD,EAAE,SAAS,oBAAoB,UAAU,aAAa;AAAA,IACxD;AAEA,QAAI,KAAK,YAAY,UAAU;AAI7B,YAAM,QAAQ,IAAI,eAAe;AACjC,gBAAU,KAAK,EAAE,SAAS,gBAAgB,UAAU,MAAM,CAAC;AAC3D,gBAAU,KAAK,oBAAoB;AACnC,gBAAU,KAAK,EAAE,SAAS,kBAAkB,aAAa,qBAAqB,CAAC;AAC/E,gBAAU,KAAK,qBAAqB;AACpC,gBAAU,KAAK,EAAE,SAAS,kBAAkB,aAAa,sBAAsB,CAAC;AAChF,gBAAU,KAAK,mBAAmB;AAClC,gBAAU,KAAK,EAAE,SAAS,iBAAiB,aAAa,oBAAoB,CAAC;AAAA,IAC/E,WAAW,KAAK,YAAY,UAAU;AAWpC,YAAM,WAAW,oBAAoB,KAAK,YAAY,MAAM;AAC5D,gBAAU,KAAK,EAAE,SAAS,mBAAmB,UAAU,SAAS,WAAW,CAAC;AAC5E,gBAAU,KAAK,EAAE,SAAS,wBAAwB,UAAU,SAAS,CAAC;AACtE,gBAAU,KAAK;AAAA,QACb,SAAS;AAAA,QACT,YAAY,UAAU,SAAqC;AACzD,gBAAM,YAAY;AAClB,gBAAM,MAAO,MAAM,OAAO;AAG1B,iBAAO,IAAI,IAAI,sBAAsB,GAAG,IAAI;AAAA,QAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMA,QAAQ,CAAC,SAAS,mBAAmB,mBAAmB,sBAAsB;AAAA,MAChF,CAAC;AACD,gBAAU,KAAK,EAAE,SAAS,iBAAiB,UAAU,qBAAqB,CAAC;AAC3E,gBAAU,KAAK,EAAE,SAAS,kBAAkB,UAAU,sBAAsB,CAAC;AAAA,IAC/E,OAAO;AACL,gBAAU,KAAK,EAAE,SAAS,kBAAkB,UAAU,uBAAuB,CAAC;AAC9E,gBAAU,KAAK,EAAE,SAAS,iBAAiB,UAAU,qBAAqB,CAAC;AAC3E,gBAAU,KAAK,EAAE,SAAS,kBAAkB,UAAU,sBAAsB,CAAC;AAAA,IAC/E;AAEA,UAAM,UAAU;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAKA,QAAI,KAAK,YAAY,UAAU;AAC7B,cAAQ,KAAK,mBAAmB,sBAAsB;AAAA,IACxD;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AA7Fa,mBAAN;AAAA,EADN,OAAO,CAAC,CAAC;AAAA,GACG;","names":[]}
@@ -29,7 +29,7 @@ var PollChangeSource = class {
29
29
  "PollChangeSource: DetectionConfig.mapping must include an entry with target 'external_id' so emitted Change<T>.externalId can be populated"
30
30
  );
31
31
  }
32
- this.externalIdSourceField = externalIdMapping.target;
32
+ this.externalIdSourceField = externalIdMapping.source;
33
33
  this.adapter = opts.adapter;
34
34
  this.filters = config.filters;
35
35
  const isCdc = config.poll.provenance === "cdc";
@@ -56,7 +56,7 @@ var PollChangeSource = class {
56
56
  const externalIdRaw = record[this.externalIdSourceField];
57
57
  if (typeof externalIdRaw !== "string" || externalIdRaw.length === 0) {
58
58
  throw new Error(
59
- `PollChangeSource: record missing string '${this.externalIdSourceField}' \u2014 emitted records MUST carry the canonical external id keyed by the mapping target`
59
+ `PollChangeSource: record missing string '${this.externalIdSourceField}' \u2014 emitted records MUST carry the canonical external id keyed by the mapping source`
60
60
  );
61
61
  }
62
62
  let dedupKey;
@@ -91,4 +91,4 @@ var PollChangeSource = class {
91
91
  export {
92
92
  PollChangeSource
93
93
  };
94
- //# sourceMappingURL=chunk-24CWKBK5.js.map
94
+ //# sourceMappingURL=chunk-4MF3HKJA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../runtime/subsystems/integration/poll-change-source.ts"],"sourcesContent":["/**\n * Integration subsystem — `PollChangeSource<T>` primitive (#226-3, ADR-033).\n *\n * Generic poll-mode `IChangeSource<T>` implementation parameterized by a\n * parsed `DetectionConfig` (poll mode) and a consumer-supplied\n * `PollFetchCallback<T>`. The primitive owns:\n *\n * - filter resolution (flat-AND vocabulary per epic decision Q3 — richer\n * boolean expressions deferred);\n * - field mapping → `Change.externalId` derivation;\n * - cursor strategy passthrough (the orchestrator passes the prior cursor\n * by-value per ADR-033 / #226-2; the callback yields the next cursor\n * per record; the primitive simply stamps it onto `Change<T>`);\n * - `Change<T>.source` provenance (`'poll'` by default);\n * - middleware-chain composition (the `ChangeMiddleware<T>` shape\n * locked in #226-1).\n *\n * Shape locks (decision memo Q5):\n * - `PollFetchContext = { subscription, cursor, filters }` — explicitly\n * NO `userId` / `tenantId`. Run-scope identity is closed over by the\n * consumer at adapter construction (or resolved inside the callback\n * via consumer services). Threading it through the seam would force\n * port expansion every time run-context grows.\n *\n * The adapter callback returns `{ record: T; cursor: PollCursor }` — the\n * primitive does not reach into the record to extract a cursor itself.\n * `cursor.field` from `DetectionConfig.poll.cursor` is metadata for codegen\n * + adapters; the primitive trusts what the callback yielded.\n */\n\nimport type {\n DetectionConfig,\n ResolvedFilter,\n} from './detection-config.schema';\nimport type {\n Change,\n ChangeSource,\n IChangeSource,\n IntegrationSubscriptionView,\n} from './integration-change-source.protocol';\nimport type {\n ChangeIterator,\n ChangeMiddleware,\n} from './integration-middleware.protocol';\n\n// ============================================================================\n// Cursor + adapter callback shapes\n// ============================================================================\n\n/**\n * Opaque poll-cursor shape. Each provider/entity pair binds it concretely\n * via the cursor strategy (`{ systemModstamp }`, `{ replayId }`, etc.); the\n * primitive treats it as an opaque value to pass through.\n */\nexport type PollCursor = unknown;\n\n/**\n * The context the primitive forwards to the adapter callback. Locked to\n * exactly three fields per decision memo Q5 — `userId` / `tenantId` are\n * NOT here on purpose.\n */\nexport interface PollFetchContext {\n readonly subscription: IntegrationSubscriptionView;\n readonly cursor: PollCursor | null;\n readonly filters: readonly ResolvedFilter[];\n}\n\n/**\n * Consumer-supplied fetch callback. Returns an async iterable of\n * `{ record, cursor }` pairs — `record` is already the canonical `T`\n * (the adapter does provider-side translation), `cursor` is the post-record\n * cursor the orchestrator should persist if the run completes successfully.\n */\nexport type PollFetchCallback<T> = (\n ctx: PollFetchContext,\n) => AsyncIterable<{ record: T; cursor: PollCursor }>;\n\n// ============================================================================\n// Constructor options\n// ============================================================================\n\nexport interface PollChangeSourceOptions<T> {\n /** Consumer-supplied fetch callback. */\n readonly adapter: PollFetchCallback<T>;\n /**\n * Parsed detection config. MUST be `mode: 'poll'`; the constructor\n * throws if a webhook config is supplied. Codegen-emitted factories\n * call `DetectionConfigSchema.parse(...)` upstream so this is a safety\n * net, not the primary validation point.\n */\n readonly config: DetectionConfig;\n /**\n * Optional middleware chain. First element is the outermost layer:\n * sees `(subscription, cursor)` first and yielded `Change<T>` last.\n * Locked shape (#226-1) — the primitive composes them with its own\n * `listChanges` implementation as the innermost iterator.\n */\n readonly middlewares?: ReadonlyArray<ChangeMiddleware<T>>;\n /**\n * Optional human label for run logs (e.g. `'salesforce-poll-opportunity'`).\n * Defaults to a derived label based on the subscription domain at\n * construction time fallback — adapters are encouraged to provide one.\n */\n readonly label?: string;\n}\n\n// ============================================================================\n// PollChangeSource<T>\n// ============================================================================\n\nexport class PollChangeSource<T> implements IChangeSource<T> {\n public readonly label: string;\n\n private readonly adapter: PollFetchCallback<T>;\n private readonly filters: readonly ResolvedFilter[];\n private readonly externalIdSourceField: string;\n private readonly source: ChangeSource;\n /**\n * When `poll.provenance === 'cdc'`, the field on the emitted record from\n * which `Change<T>.dedupKey` is read — sourced from `poll.cursor.field`\n * (Stripe-style event endpoints carry the event id on the record itself\n * and the cursor advances over the same id). `null` for default poll\n * provenance, which does NOT populate `dedupKey`.\n */\n private readonly dedupKeySourceField: string | null;\n private readonly composed: ChangeIterator<T>;\n\n constructor(opts: PollChangeSourceOptions<T>) {\n if (opts.config.mode !== 'poll') {\n throw new Error(\n `PollChangeSource requires DetectionConfig.mode === 'poll'; got '${(opts.config as { mode: string }).mode}'`,\n );\n }\n const config = opts.config;\n\n // Field mapping: locate the entry whose canonical `target` is `external_id`.\n // Adapters emit T already-mapped, but the primitive needs to know which key\n // on T carries the external id so it can stamp `Change.externalId`. That key\n // is the mapping's `source` (the field on the emitted record), NOT its\n // `target` (the canonical column) — they differ whenever the canonical\n // record is vendor-neutral camelCase (e.g. `source: 'externalId'` →\n // `target: 'external_id'`). Source of truth is the mapping table — codegen\n // emits it from YAML, the primitive reads it here.\n const externalIdMapping = config.mapping.find(\n (m) => m.target === 'external_id',\n );\n if (!externalIdMapping) {\n throw new Error(\n \"PollChangeSource: DetectionConfig.mapping must include an entry with target 'external_id' so emitted Change<T>.externalId can be populated\",\n );\n }\n this.externalIdSourceField = externalIdMapping.source;\n\n this.adapter = opts.adapter;\n this.filters = config.filters;\n // Provenance: `mode: 'poll'` defaults to `'poll'`; opt into `'cdc'` via\n // `poll.provenance` (Stripe-style event endpoints — wired in #226-4).\n // CDC provenance also stamps `dedupKey` from the cursor's `field`, since\n // those endpoints surface a per-event id on each record (the same id the\n // cursor advances over).\n const isCdc = config.poll.provenance === 'cdc';\n this.source = isCdc ? 'cdc' : 'poll';\n this.dedupKeySourceField = isCdc ? config.poll.cursor.field : null;\n\n this.label =\n opts.label ?? `poll-change-source:${externalIdMapping.source}`;\n\n // Compose middleware chain. The terminal iterator is `this.fetch`\n // bound to `this`. First middleware in the array is the outermost\n // layer (sees subscription/cursor first, yielded changes last).\n const inner: ChangeIterator<T> = (sub, cur) => this.fetch(sub, cur);\n const middlewares = opts.middlewares ?? [];\n this.composed = middlewares.reduceRight<ChangeIterator<T>>(\n (next, mw) => mw(next),\n inner,\n );\n }\n\n listChanges(\n subscription: IntegrationSubscriptionView,\n cursor: unknown | null,\n ): AsyncIterable<Change<T>> {\n return this.composed(subscription, cursor);\n }\n\n private async *fetch(\n subscription: IntegrationSubscriptionView,\n cursor: unknown | null,\n ): AsyncIterable<Change<T>> {\n const ctx: PollFetchContext = {\n subscription,\n cursor: cursor as PollCursor | null,\n filters: this.filters,\n };\n\n for await (const { record, cursor: nextCursor } of this.adapter(ctx)) {\n const externalIdRaw = (record as Record<string, unknown>)[\n this.externalIdSourceField\n ];\n if (typeof externalIdRaw !== 'string' || externalIdRaw.length === 0) {\n throw new Error(\n `PollChangeSource: record missing string '${this.externalIdSourceField}' — emitted records MUST carry the canonical external id keyed by the mapping source`,\n );\n }\n let dedupKey: string | undefined;\n if (this.dedupKeySourceField !== null) {\n const dedupRaw = (record as Record<string, unknown>)[\n this.dedupKeySourceField\n ];\n if (typeof dedupRaw !== 'string' || dedupRaw.length === 0) {\n throw new Error(\n `PollChangeSource: cdc-provenance record missing string '${this.dedupKeySourceField}' — when poll.provenance === 'cdc' the cursor.field must be present on each record so dedupKey can be populated`,\n );\n }\n dedupKey = dedupRaw;\n }\n\n const change: Change<T> = {\n externalId: externalIdRaw,\n // Polling cannot distinguish create vs. update vs. delete on its\n // own — all yielded records are surfaced as 'updated'. The\n // orchestrator's diff stage classifies create-vs-update against\n // local state; soft-delete detection is out of scope for the\n // primitive (consumer drives via tombstone records or a separate\n // sweep — see ADR-033).\n operation: 'updated',\n record,\n cursor: nextCursor,\n source: this.source,\n ...(dedupKey !== undefined ? { dedupKey } : {}),\n };\n yield change;\n }\n }\n}\n"],"mappings":";AA8GO,IAAM,mBAAN,MAAsD;AAAA,EAC3C;AAAA,EAEC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA;AAAA,EACA;AAAA,EAEjB,YAAY,MAAkC;AAC5C,QAAI,KAAK,OAAO,SAAS,QAAQ;AAC/B,YAAM,IAAI;AAAA,QACR,mEAAoE,KAAK,OAA4B,IAAI;AAAA,MAC3G;AAAA,IACF;AACA,UAAM,SAAS,KAAK;AAUpB,UAAM,oBAAoB,OAAO,QAAQ;AAAA,MACvC,CAAC,MAAM,EAAE,WAAW;AAAA,IACtB;AACA,QAAI,CAAC,mBAAmB;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,SAAK,wBAAwB,kBAAkB;AAE/C,SAAK,UAAU,KAAK;AACpB,SAAK,UAAU,OAAO;AAMtB,UAAM,QAAQ,OAAO,KAAK,eAAe;AACzC,SAAK,SAAS,QAAQ,QAAQ;AAC9B,SAAK,sBAAsB,QAAQ,OAAO,KAAK,OAAO,QAAQ;AAE9D,SAAK,QACH,KAAK,SAAS,sBAAsB,kBAAkB,MAAM;AAK9D,UAAM,QAA2B,CAAC,KAAK,QAAQ,KAAK,MAAM,KAAK,GAAG;AAClE,UAAM,cAAc,KAAK,eAAe,CAAC;AACzC,SAAK,WAAW,YAAY;AAAA,MAC1B,CAAC,MAAM,OAAO,GAAG,IAAI;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,YACE,cACA,QAC0B;AAC1B,WAAO,KAAK,SAAS,cAAc,MAAM;AAAA,EAC3C;AAAA,EAEA,OAAe,MACb,cACA,QAC0B;AAC1B,UAAM,MAAwB;AAAA,MAC5B;AAAA,MACA;AAAA,MACA,SAAS,KAAK;AAAA,IAChB;AAEA,qBAAiB,EAAE,QAAQ,QAAQ,WAAW,KAAK,KAAK,QAAQ,GAAG,GAAG;AACpE,YAAM,gBAAiB,OACrB,KAAK,qBACP;AACA,UAAI,OAAO,kBAAkB,YAAY,cAAc,WAAW,GAAG;AACnE,cAAM,IAAI;AAAA,UACR,4CAA4C,KAAK,qBAAqB;AAAA,QACxE;AAAA,MACF;AACA,UAAI;AACJ,UAAI,KAAK,wBAAwB,MAAM;AACrC,cAAM,WAAY,OAChB,KAAK,mBACP;AACA,YAAI,OAAO,aAAa,YAAY,SAAS,WAAW,GAAG;AACzD,gBAAM,IAAI;AAAA,YACR,2DAA2D,KAAK,mBAAmB;AAAA,UACrF;AAAA,QACF;AACA,mBAAW;AAAA,MACb;AAEA,YAAM,SAAoB;AAAA,QACxB,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOZ,WAAW;AAAA,QACX;AAAA,QACA,QAAQ;AAAA,QACR,QAAQ,KAAK;AAAA,QACb,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC/C;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACF;","names":[]}
@@ -1,17 +1,22 @@
1
1
  import {
2
- clampEventLimit,
3
- decodeEventCursor,
4
- encodeEventCursor
5
- } from "./chunk-UQ5EHOH2.js";
6
- import {
7
- EVENTS_MODULE_OPTIONS
8
- } from "./chunk-H5NH7KPE.js";
2
+ EVENTS_WAKE_CHANNEL,
3
+ PgNotifyListener,
4
+ pgNotify
5
+ } from "./chunk-MYQIQ27N.js";
9
6
  import {
10
7
  BRIDGE_OUTBOX_DRAIN_HOOK
11
8
  } from "./chunk-4LH67P4U.js";
12
9
  import {
13
10
  domainEvents
14
11
  } from "./chunk-OFRRBC7M.js";
12
+ import {
13
+ EVENTS_MODULE_OPTIONS
14
+ } from "./chunk-H5NH7KPE.js";
15
+ import {
16
+ clampEventLimit,
17
+ decodeEventCursor,
18
+ encodeEventCursor
19
+ } from "./chunk-UQ5EHOH2.js";
15
20
  import {
16
21
  DRIZZLE
17
22
  } from "./chunk-U64T4YZE.js";
@@ -83,12 +88,35 @@ var DrizzleEventBus = class {
83
88
  pollTimer = null;
84
89
  handlers = /* @__PURE__ */ new Map();
85
90
  opts;
91
+ // LISTEN-NOTIFY-1 — dedicated wake listener + debounce state. `null` when
92
+ // `listenNotify` is off (the common case); polling is the only driver then.
93
+ notifyListener = null;
94
+ /** True while a wake-driven drain is in flight (debounce gate). */
95
+ wakeDraining = false;
96
+ /** A notify arrived mid-drain → re-drain once when the current drain ends. */
97
+ wakeRecheckPending = false;
86
98
  // ============================================================================
87
99
  // Lifecycle
88
100
  // ============================================================================
89
101
  async onModuleInit() {
90
102
  this.polling = true;
91
103
  this.schedulePoll();
104
+ if (this.opts.listenNotify) {
105
+ const pool = this.db.$client;
106
+ if (!pool || typeof pool.connect !== "function") {
107
+ this.logger.warn(
108
+ `listen_notify enabled but the Drizzle client exposes no pg Pool ($client.connect missing) \u2014 falling back to interval polling only.`
109
+ );
110
+ } else {
111
+ this.notifyListener = new PgNotifyListener({
112
+ channel: EVENTS_WAKE_CHANNEL,
113
+ pool,
114
+ label: "events",
115
+ onNotify: (payload) => this.onWake(payload)
116
+ });
117
+ await this.notifyListener.start();
118
+ }
119
+ }
92
120
  }
93
121
  async onModuleDestroy() {
94
122
  this.polling = false;
@@ -96,6 +124,43 @@ var DrizzleEventBus = class {
96
124
  clearTimeout(this.pollTimer);
97
125
  this.pollTimer = null;
98
126
  }
127
+ if (this.notifyListener) {
128
+ try {
129
+ await this.notifyListener.stop();
130
+ } catch (err) {
131
+ this.logger.error(`notify listener stop failed: ${err}`);
132
+ }
133
+ this.notifyListener = null;
134
+ }
135
+ }
136
+ /**
137
+ * Wake handler — a `codegen_events_wake` notification arrived. A pool-filtered
138
+ * drainer (`opts.pools` set) ignores payloads naming a pool it doesn't own; an
139
+ * all-pools drainer wakes for any. Debounced: a notify mid-drain just flags a
140
+ * re-check so a burst collapses to at most one extra drain (D3).
141
+ */
142
+ onWake(payload) {
143
+ if (!this.polling) return;
144
+ const pools = this.opts.pools;
145
+ if (pools && pools.length > 0 && !pools.includes(payload)) return;
146
+ if (this.wakeDraining) {
147
+ this.wakeRecheckPending = true;
148
+ return;
149
+ }
150
+ void this.drainOnWake();
151
+ }
152
+ async drainOnWake() {
153
+ this.wakeDraining = true;
154
+ try {
155
+ do {
156
+ this.wakeRecheckPending = false;
157
+ await this.processBatch();
158
+ } while (this.wakeRecheckPending && this.polling);
159
+ } catch (err) {
160
+ this.logger.error(`wake drain error: ${err}`);
161
+ } finally {
162
+ this.wakeDraining = false;
163
+ }
99
164
  }
100
165
  // ============================================================================
101
166
  // IEventBus
@@ -103,13 +168,36 @@ var DrizzleEventBus = class {
103
168
  async publish(event, tx) {
104
169
  const client = tx ?? this.db;
105
170
  const multiTenant = this.opts.multiTenant ?? false;
106
- await client.insert(domainEvents).values(toInsertValues(event, multiTenant));
171
+ const values = toInsertValues(event, multiTenant);
172
+ await client.insert(domainEvents).values(values);
173
+ await this.emitWakeNotify(client, [values.pool]);
107
174
  }
108
175
  async publishMany(events, tx) {
109
176
  if (events.length === 0) return;
110
177
  const client = tx ?? this.db;
111
178
  const multiTenant = this.opts.multiTenant ?? false;
112
- await client.insert(domainEvents).values(events.map((e) => toInsertValues(e, multiTenant)));
179
+ const valuesList = events.map((e) => toInsertValues(e, multiTenant));
180
+ await client.insert(domainEvents).values(valuesList);
181
+ await this.emitWakeNotify(client, valuesList.map((v) => v.pool));
182
+ }
183
+ /**
184
+ * Emit one in-tx `pg_notify(codegen_events_wake, <pool>)` per distinct pool in
185
+ * the just-inserted batch. No-op unless `listenNotify` is on. Best-effort: a
186
+ * notify failure is non-fatal (interval polling still drains the rows), so we
187
+ * log + swallow rather than failing the publish.
188
+ */
189
+ async emitWakeNotify(client, pools) {
190
+ if (!this.opts.listenNotify) return;
191
+ const distinct = new Set(pools.map((p) => p ?? ""));
192
+ for (const pool of distinct) {
193
+ try {
194
+ await pgNotify(client, EVENTS_WAKE_CHANNEL, pool);
195
+ } catch (err) {
196
+ this.logger.warn(
197
+ `pg_notify(${EVENTS_WAKE_CHANNEL}, '${pool}') failed: ${err} (non-fatal \u2014 interval polling still drains the outbox).`
198
+ );
199
+ }
200
+ }
113
201
  }
114
202
  async findById(eventId) {
115
203
  const rows = await this.db.select().from(domainEvents).where(eq(domainEvents.id, eventId)).limit(1);
@@ -305,4 +393,4 @@ DrizzleEventBus = __decorateClass([
305
393
  export {
306
394
  DrizzleEventBus
307
395
  };
308
- //# sourceMappingURL=chunk-4JLJYWJC.js.map
396
+ //# sourceMappingURL=chunk-4PFF3ED4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../runtime/subsystems/events/event-bus.drizzle-backend.ts"],"sourcesContent":["/**\n * DrizzleEventBus — Postgres-backed event bus using the transactional outbox pattern.\n *\n * Events are inserted into the `domain_events` table within the caller's\n * Drizzle transaction. A background polling loop (started on module init)\n * reads unprocessed events and dispatches them to registered subscribers.\n *\n * When the transaction rolls back, the event is never persisted — no\n * phantom events.\n *\n * Pool awareness (EVT-4):\n * - On `publish`/`publishMany` the backend writes `metadata.pool`,\n * `metadata.direction`, and `metadata.tenantId` into the first-class\n * `pool` / `direction` / `tenant_id` columns (metadata JSON is still\n * written unchanged for protocol stability).\n * - The drain loop filters by `opts.pools` when provided, so separate\n * processes (e.g. one per `events_inbound` / `events_change` /\n * `events_outbound`) can claim only their own lane. `pools: undefined`\n * drains all pending rows (backwards-compatible behaviour).\n *\n * EVT-Q7: No stale-event sweeper. `FOR UPDATE SKIP LOCKED` is\n * self-healing — the row is only locked for the duration of the\n * enclosing polling transaction; the `status='processed'` update happens\n * within that same transaction. There is no `claimed_at` semantic (unlike\n * jobs), so no stale rows can exist.\n *\n * This backend is suitable until you need real-time fan-out or very high\n * throughput. At that point, swap the backend for Redis Streams or similar\n * via EventsModule.forRoot({ backend: '...' }) without touching use cases.\n */\nimport { Injectable, OnModuleDestroy, OnModuleInit, Inject, Logger, Optional } from '@nestjs/common';\nimport { eq, and, inArray, asc, desc, gte, lt, or, sql, type SQL } from 'drizzle-orm';\nimport type { DomainEvent, DrizzleTransaction, IEventBus } from './event-bus.protocol';\nimport type {\n EventPage,\n IEventReadPort,\n ListEventsQuery,\n} from './event-read.protocol';\nimport {\n clampEventLimit,\n decodeEventCursor,\n encodeEventCursor,\n} from './event-keyset-cursor';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport { domainEvents, type DomainEventRecord } from './domain-events.schema';\nimport { DRIZZLE } from '../../constants/tokens';\nimport { EVENTS_MODULE_OPTIONS } from './events.tokens';\nimport type { EventsModuleOptions } from './events.module';\nimport { BRIDGE_OUTBOX_DRAIN_HOOK } from '../bridge/bridge.tokens';\nimport type { IBridgeOutboxDrainHook } from '../bridge/bridge.protocol';\nimport {\n EVENTS_WAKE_CHANNEL,\n PgNotifyListener,\n pgNotify,\n} from '../jobs/pg-notify';\n\n/** How long to wait between polling cycles (ms). */\nconst POLL_INTERVAL_MS = 1_000;\n/** Max events claimed per polling cycle to bound memory usage. */\nconst POLL_BATCH_SIZE = 50;\n\n/**\n * Row shape built from `metadata` for writing into `domain_events`. Keeps\n * the per-event extraction logic in one place so publish/publishMany stay\n * in sync.\n */\nfunction toInsertValues(event: DomainEvent, multiTenant: boolean) {\n const metadata = event.metadata ?? undefined;\n const pool = (metadata?.['pool'] as string | undefined) ?? null;\n const direction = (metadata?.['direction'] as string | undefined) ?? null;\n // AUDIT-1: tier defaults to 'domain' when absent. The DB CHECK\n // constraint (`domain_events_tier_routing_check`) enforces the\n // tier ⇔ routing-fields invariant at the storage boundary; no\n // JS-side assertion is needed here.\n const tier = (metadata?.['tier'] as string | undefined) ?? 'domain';\n const base = {\n id: event.id,\n type: event.type,\n aggregateId: event.aggregateId,\n aggregateType: event.aggregateType,\n payload: event.payload,\n occurredAt: event.occurredAt,\n processedAt: null,\n status: 'pending' as const,\n metadata: event.metadata,\n pool,\n direction,\n tier,\n };\n // EVT-8: `tenant_id` is a scaffold-time conditional column, emitted only\n // when `events.multi_tenant: true`. Only write it when multi-tenancy is\n // on — under single-tenant scaffolds the column does not exist, so the\n // key must be omitted from the insert.\n if (!multiTenant) return base;\n const tenantId = (metadata?.['tenantId'] as string | undefined) ?? null;\n return { ...base, tenantId };\n}\n\n/**\n * Project a raw `domain_events` row into the narrow `EventSummary` shape.\n * Shared with the memory backend via this helper kept module-local to each\n * backend (the events subsystem has no cross-backend projection file yet;\n * the two are byte-identical and small).\n */\nfunction toEventSummary(r: DomainEventRecord) {\n const metadata = (r.metadata ?? undefined) as\n | Record<string, unknown>\n | undefined;\n const rootRunId = metadata?.['rootRunId'];\n return {\n id: r.id,\n type: r.type,\n aggregateId: r.aggregateId,\n aggregateType: r.aggregateType,\n status: r.status,\n pool: r.pool,\n direction: r.direction,\n tier: r.tier,\n rootRunId: typeof rootRunId === 'string' ? rootRunId : null,\n // EVT-8: `tenant_id` is a scaffold-time conditional column. Read it\n // structurally so this projection typechecks against both the\n // multi-tenant schema (column present) and the single-tenant schema\n // (column absent → undefined → null).\n tenantId: (r as { tenantId?: string | null }).tenantId ?? null,\n occurredAt:\n r.occurredAt instanceof Date\n ? r.occurredAt\n : new Date(r.occurredAt as unknown as string),\n processedAt:\n r.processedAt == null\n ? null\n : r.processedAt instanceof Date\n ? r.processedAt\n : new Date(r.processedAt as unknown as string),\n };\n}\n\n@Injectable()\nexport class DrizzleEventBus implements IEventBus, IEventReadPort, OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(DrizzleEventBus.name);\n private polling = false;\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private readonly handlers = new Map<string, Set<(event: DomainEvent) => Promise<void>>>();\n private readonly opts: EventsModuleOptions;\n\n // LISTEN-NOTIFY-1 — dedicated wake listener + debounce state. `null` when\n // `listenNotify` is off (the common case); polling is the only driver then.\n private notifyListener: PgNotifyListener | null = null;\n /** True while a wake-driven drain is in flight (debounce gate). */\n private wakeDraining = false;\n /** A notify arrived mid-drain → re-drain once when the current drain ends. */\n private wakeRecheckPending = false;\n\n constructor(\n @Inject(DRIZZLE) private readonly db: DrizzleClient,\n @Optional() @Inject(EVENTS_MODULE_OPTIONS) opts?: EventsModuleOptions,\n /**\n * Bridge subsystem hook (BRIDGE-4). Optional — when the bridge\n * subsystem is not installed in the consuming app, this token is\n * undefined and the drain skips the bridge block entirely (preserves\n * EVT-4 baseline behaviour).\n *\n * When provided, `processEvent` is invoked once per drained event\n * INSIDE the per-event tx, before `processed_at` is stamped. The\n * hook owns all knowledge of `bridge_delivery + wrapper job_run`\n * shapes; the events subsystem stays unaware of bridge schemas.\n */\n @Optional()\n @Inject(BRIDGE_OUTBOX_DRAIN_HOOK)\n private readonly bridgeHook: IBridgeOutboxDrainHook | null = null,\n ) {\n // Default so direct construction (e.g. integration tests not going\n // through Nest DI) keeps working without an explicit options object.\n this.opts = opts ?? { backend: 'drizzle' };\n }\n\n // ============================================================================\n // Lifecycle\n // ============================================================================\n\n async onModuleInit(): Promise<void> {\n this.polling = true;\n this.schedulePoll();\n\n // LISTEN-NOTIFY-1 — start the wake listener ALONGSIDE the poll timer. A\n // notify for one of this drainer's pools triggers an immediate drain; the\n // interval timer above stays the durability heartbeat. Startup is\n // fire-and-forget — a connect failure self-heals via the listener's backoff.\n if (this.opts.listenNotify) {\n const pool = (this.db as unknown as { $client?: unknown }).$client;\n if (!pool || typeof (pool as { connect?: unknown }).connect !== 'function') {\n this.logger.warn(\n `listen_notify enabled but the Drizzle client exposes no pg Pool ` +\n `($client.connect missing) — falling back to interval polling only.`,\n );\n } else {\n this.notifyListener = new PgNotifyListener({\n channel: EVENTS_WAKE_CHANNEL,\n pool: pool as { connect(): Promise<never> },\n label: 'events',\n onNotify: (payload) => this.onWake(payload),\n });\n await this.notifyListener.start();\n }\n }\n }\n\n async onModuleDestroy(): Promise<void> {\n this.polling = false;\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n if (this.notifyListener) {\n try {\n await this.notifyListener.stop();\n } catch (err) {\n this.logger.error(`notify listener stop failed: ${err}`);\n }\n this.notifyListener = null;\n }\n }\n\n /**\n * Wake handler — a `codegen_events_wake` notification arrived. A pool-filtered\n * drainer (`opts.pools` set) ignores payloads naming a pool it doesn't own; an\n * all-pools drainer wakes for any. Debounced: a notify mid-drain just flags a\n * re-check so a burst collapses to at most one extra drain (D3).\n */\n private onWake(payload: string): void {\n if (!this.polling) return;\n const pools = this.opts.pools;\n if (pools && pools.length > 0 && !pools.includes(payload)) return;\n if (this.wakeDraining) {\n this.wakeRecheckPending = true;\n return;\n }\n void this.drainOnWake();\n }\n\n private async drainOnWake(): Promise<void> {\n this.wakeDraining = true;\n try {\n do {\n this.wakeRecheckPending = false;\n await this.processBatch();\n } while (this.wakeRecheckPending && this.polling);\n } catch (err) {\n this.logger.error(`wake drain error: ${err}`);\n } finally {\n this.wakeDraining = false;\n }\n }\n\n // ============================================================================\n // IEventBus\n // ============================================================================\n\n async publish(event: DomainEvent, tx?: DrizzleTransaction): Promise<void> {\n const client = (tx ?? this.db) as DrizzleClient;\n const multiTenant = this.opts.multiTenant ?? false;\n const values = toInsertValues(event, multiTenant);\n await client.insert(domainEvents).values(values);\n // LISTEN-NOTIFY-1 — wake the drainer on commit (D2: emitted through the same\n // `client`, so a rolled-back publish emits no phantom wake). The pool is the\n // payload; the drainer re-runs its own pool-filtered claim on wake.\n await this.emitWakeNotify(client, [values.pool]);\n }\n\n async publishMany(events: DomainEvent[], tx?: DrizzleTransaction): Promise<void> {\n if (events.length === 0) return;\n const client = (tx ?? this.db) as DrizzleClient;\n const multiTenant = this.opts.multiTenant ?? false;\n const valuesList = events.map((e) => toInsertValues(e, multiTenant));\n await client.insert(domainEvents).values(valuesList);\n // De-dup pools so a batch into one lane emits a single wake.\n await this.emitWakeNotify(client, valuesList.map((v) => v.pool));\n }\n\n /**\n * Emit one in-tx `pg_notify(codegen_events_wake, <pool>)` per distinct pool in\n * the just-inserted batch. No-op unless `listenNotify` is on. Best-effort: a\n * notify failure is non-fatal (interval polling still drains the rows), so we\n * log + swallow rather than failing the publish.\n */\n private async emitWakeNotify(\n client: DrizzleClient,\n pools: Array<string | null>,\n ): Promise<void> {\n if (!this.opts.listenNotify) return;\n const distinct = new Set(pools.map((p) => p ?? ''));\n for (const pool of distinct) {\n try {\n await pgNotify(client, EVENTS_WAKE_CHANNEL, pool);\n } catch (err) {\n this.logger.warn(\n `pg_notify(${EVENTS_WAKE_CHANNEL}, '${pool}') failed: ${err} ` +\n `(non-fatal — interval polling still drains the outbox).`,\n );\n }\n }\n }\n\n async findById(eventId: string): Promise<DomainEvent | null> {\n const rows = await this.db\n .select()\n .from(domainEvents)\n .where(eq(domainEvents.id, eventId))\n .limit(1);\n const row = rows[0];\n if (!row) return null;\n return {\n id: row.id,\n type: row.type,\n aggregateId: row.aggregateId,\n aggregateType: row.aggregateType,\n payload: row.payload as Record<string, unknown>,\n occurredAt:\n row.occurredAt instanceof Date\n ? row.occurredAt\n : new Date(row.occurredAt as unknown as string),\n metadata: (row.metadata ?? undefined) as\n | Record<string, unknown>\n | undefined,\n };\n }\n\n subscribe<T extends DomainEvent = DomainEvent>(\n eventType: string,\n handler: (event: T) => Promise<void>,\n ): () => void {\n if (!this.handlers.has(eventType)) {\n this.handlers.set(eventType, new Set());\n }\n const set = this.handlers.get(eventType)!;\n const h = handler as (event: DomainEvent) => Promise<void>;\n set.add(h);\n return () => {\n set.delete(h);\n };\n }\n\n // ============================================================================\n // IEventReadPort (OBS-LIST-1)\n // ============================================================================\n\n async listEvents(query: ListEventsQuery = {}): Promise<EventPage> {\n const limit = clampEventLimit(query.limit);\n const conditions: SQL<unknown>[] = [];\n\n if (query.poolId) conditions.push(eq(domainEvents.pool, query.poolId));\n if (query.direction)\n conditions.push(eq(domainEvents.direction, query.direction));\n if (query.since) conditions.push(gte(domainEvents.occurredAt, query.since));\n if (query.rootRunId) {\n // Filter on the JSON correlation id: metadata->>'rootRunId'.\n conditions.push(\n sql`${domainEvents.metadata}->>'rootRunId' = ${query.rootRunId}`,\n );\n }\n // EVT-8: `tenant_id` is a scaffold-time conditional column (emitted only\n // under `events.multi_tenant: true`). Guard the filter behind the same\n // `multiTenant` flag, and read the column structurally so this backend\n // typechecks against both the multi-tenant schema (column present) and\n // the single-tenant schema (column absent). When multi-tenancy is off\n // there is no `tenant_id` column to filter on.\n if (this.opts.multiTenant && query.tenantId !== undefined) {\n const tenantIdColumn = (\n domainEvents as unknown as { tenantId: typeof domainEvents.pool }\n ).tenantId;\n conditions.push(\n query.tenantId === null\n ? (sql`${tenantIdColumn} is null` as SQL<unknown>)\n : eq(tenantIdColumn, query.tenantId),\n );\n }\n\n // Keyset seek: WHERE (occurred_at, id) < (cursorOccurredAt, cursorId).\n if (query.cursor) {\n const keyset = decodeEventCursor(query.cursor);\n if (keyset) {\n conditions.push(\n or(\n lt(domainEvents.occurredAt, keyset.occurredAt),\n and(\n eq(domainEvents.occurredAt, keyset.occurredAt),\n lt(domainEvents.id, keyset.id),\n ),\n )!,\n );\n }\n }\n\n const rows = (await this.db\n .select()\n .from(domainEvents)\n .where(conditions.length > 0 ? and(...conditions) : undefined)\n .orderBy(desc(domainEvents.occurredAt), desc(domainEvents.id))\n .limit(limit + 1)) as DomainEventRecord[];\n\n const hasMore = rows.length > limit;\n const page = hasMore ? rows.slice(0, limit) : rows;\n const items = page.map(toEventSummary);\n const last = page[page.length - 1];\n const nextCursor =\n hasMore && last\n ? encodeEventCursor({ occurredAt: last.occurredAt, id: last.id })\n : null;\n\n return { items, nextCursor };\n }\n\n // ============================================================================\n // Polling\n // ============================================================================\n\n /**\n * Test-only hook. Runs exactly one drain cycle and returns. Production\n * code goes through `onModuleInit` → `schedulePoll`, which calls the\n * same `processBatch` under a timer.\n */\n async drainOnce(): Promise<void> {\n await this.processBatch();\n }\n\n private schedulePoll(): void {\n if (!this.polling) return;\n this.pollTimer = setTimeout(async () => {\n try {\n await this.processBatch();\n } catch (err) {\n this.logger.error(`Poll cycle error: ${err}`);\n } finally {\n this.schedulePoll();\n }\n }, POLL_INTERVAL_MS);\n }\n\n /**\n * Drain one batch (BRIDGE-4 restructure of EVT-4).\n *\n * Two-phase per drained event:\n *\n * 1. **Per-event transaction** — bridge fanout (`bridgeHook.processEvent`)\n * + `processed_at` stamp. Both write through the same `tx`. A throw\n * inside the tx (only infra-level failures should reach here, since\n * the hook tolerates null direction and registry misses inline)\n * rolls back the bridge inserts AND the `processed_at` stamp; the\n * event re-claims on the next drain cycle. Bridge `UNIQUE\n * (event_id, trigger_id)` makes the retry idempotent.\n *\n * 2. **After commit** — dispatch in-process subscribers (`IEventBus.subscribe`\n * handlers). This deliberately runs OUTSIDE the per-event tx (lead\n * decision 2026-04-22): subscribers are best-effort and must not\n * gate forward progress or roll back bridge fanout. Subscriber\n * errors are caught + logged; `processed_at` is already committed.\n * The old `MAX_RETRIES=3` in-process retry loop and the\n * `failed`-stamping path were removed in BRIDGE-4 along with their\n * coupling.\n *\n * The `processed_at` UPDATE carries `AND status='pending'` (BRIDGE-4\n * tightening — without it, a hypothetical double-claim could double-stamp\n * the timestamp). The per-event tx + `FOR UPDATE SKIP LOCKED` claim\n * make this defensive belt-and-suspenders.\n */\n private async processBatch(): Promise<void> {\n const pools = this.opts.pools;\n\n // Build WHERE: status='pending' [AND pool IN (...)]\n const whereClause: SQL<unknown> = pools && pools.length > 0\n ? (and(eq(domainEvents.status, 'pending'), inArray(domainEvents.pool, pools)) as SQL<unknown>)\n : eq(domainEvents.status, 'pending');\n\n // Claim a batch with FOR UPDATE SKIP LOCKED so multiple pollers don't\n // double-dispatch. The lock is released when the outer transaction\n // commits — which is fine because the immediately-following per-event\n // tx flips status='processed' under its own `AND status='pending'`\n // guard, so a re-claim of the same row in a subsequent batch is a\n // no-op UPDATE.\n const rows = await this.db.transaction(async (tx) => {\n return tx\n .select()\n .from(domainEvents)\n .where(whereClause)\n .orderBy(asc(domainEvents.occurredAt))\n .limit(POLL_BATCH_SIZE)\n .for('update', { skipLocked: true });\n }) as Array<typeof domainEvents.$inferSelect>;\n\n for (const row of rows) {\n const event: DomainEvent = {\n id: row.id,\n type: row.type,\n aggregateId: row.aggregateId,\n aggregateType: row.aggregateType,\n payload: row.payload as Record<string, unknown>,\n occurredAt: row.occurredAt instanceof Date ? row.occurredAt : new Date(row.occurredAt as unknown as string),\n metadata: (row.metadata ?? undefined) as Record<string, unknown> | undefined,\n };\n\n // Phase 1 — per-event tx: bridge fanout + processed_at stamp.\n try {\n await this.db.transaction(async (tx) => {\n if (this.bridgeHook) {\n await this.bridgeHook.processEvent(event, tx);\n }\n await tx\n .update(domainEvents)\n .set({ status: 'processed', processedAt: new Date() })\n .where(\n and(\n eq(domainEvents.id, event.id),\n eq(domainEvents.status, 'pending'),\n ),\n );\n });\n } catch (err) {\n // Infra-level failure inside the per-event tx — bridge inserts\n // and processed_at both rolled back. Log and move on; the next\n // drain cycle re-claims the row. UNIQUE on bridge_delivery makes\n // the retry idempotent.\n this.logger.error(\n `Per-event tx failed for event id=${event.id} type=${event.type}: ${err}`,\n );\n continue;\n }\n\n // Phase 2 — best-effort subscriber dispatch. Errors are logged\n // and discarded; processed_at is already committed. Subscribers\n // are observability + cache-busts + small ancillary work; they\n // must not gate forward progress.\n try {\n await this.dispatch(event);\n } catch (err) {\n this.logger.error(\n `Subscriber dispatch failed for event id=${event.id} type=${event.type} ` +\n `(processed_at already committed; failure does not retry): ${err}`,\n );\n }\n }\n }\n\n private async dispatch(event: DomainEvent): Promise<void> {\n const set = this.handlers.get(event.type);\n if (!set) return;\n\n let firstError: unknown;\n for (const handler of set) {\n try {\n await handler(event);\n } catch (err) {\n this.logger.error(\n `Handler error for event type \"${event.type}\" (id: ${event.id}): ${err}`,\n );\n if (firstError === undefined) {\n firstError = err;\n }\n }\n }\n\n if (firstError !== undefined) {\n throw firstError;\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -3,15 +3,15 @@ import {
3
3
  } from "./chunk-6DWFJNIK.js";
4
4
  import {
5
5
  JOB_ORCHESTRATOR
6
- } from "./chunk-BIO6F7YI.js";
7
- import {
8
- EVENT_BUS
9
- } from "./chunk-H5NH7KPE.js";
6
+ } from "./chunk-ZPL74UQN.js";
10
7
  import {
11
8
  BRIDGE_DELIVERY_REPO,
12
9
  BRIDGE_MULTI_TENANT,
13
10
  BRIDGE_REGISTRY
14
11
  } from "./chunk-4LH67P4U.js";
12
+ import {
13
+ EVENT_BUS
14
+ } from "./chunk-H5NH7KPE.js";
15
15
  import {
16
16
  DRIZZLE
17
17
  } from "./chunk-U64T4YZE.js";
@@ -106,4 +106,4 @@ EventFlowService = __decorateClass([
106
106
  export {
107
107
  EventFlowService
108
108
  };
109
- //# sourceMappingURL=chunk-32BMMV4H.js.map
109
+ //# sourceMappingURL=chunk-5RT7JGKT.js.map
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  JOBS_MULTI_TENANT,
15
15
  JOB_ORCHESTRATOR
16
- } from "./chunk-BIO6F7YI.js";
16
+ } from "./chunk-ZPL74UQN.js";
17
17
  import {
18
18
  __decorateClass,
19
19
  __decorateParam
@@ -209,4 +209,4 @@ function compareBy(a, b, order) {
209
209
  export {
210
210
  MemoryJobRunService
211
211
  };
212
- //# sourceMappingURL=chunk-JRVNVKN6.js.map
212
+ //# sourceMappingURL=chunk-BHZP6LOV.js.map
@@ -1,12 +1,12 @@
1
+ import {
2
+ STORAGE
3
+ } from "./chunk-NYBCQZC7.js";
1
4
  import {
2
5
  LocalStorageBackend
3
6
  } from "./chunk-JWNHNUYL.js";
4
7
  import {
5
8
  MemoryStorageBackend
6
9
  } from "./chunk-3SZFUTXE.js";
7
- import {
8
- STORAGE
9
- } from "./chunk-NYBCQZC7.js";
10
10
  import {
11
11
  __decorateClass
12
12
  } from "./chunk-2E224ZSN.js";
@@ -37,4 +37,4 @@ StorageModule = __decorateClass([
37
37
  export {
38
38
  StorageModule
39
39
  };
40
- //# sourceMappingURL=chunk-4MVGAMUA.js.map
40
+ //# sourceMappingURL=chunk-BK5ICA2F.js.map
@@ -13,7 +13,7 @@ import {
13
13
  } from "./chunk-T4BIIU5E.js";
14
14
  import {
15
15
  JOBS_MULTI_TENANT
16
- } from "./chunk-BIO6F7YI.js";
16
+ } from "./chunk-ZPL74UQN.js";
17
17
  import {
18
18
  __decorateClass,
19
19
  __decorateParam
@@ -632,4 +632,4 @@ function serialiseError(err, attempt, retryable) {
632
632
  export {
633
633
  MemoryJobOrchestrator
634
634
  };
635
- //# sourceMappingURL=chunk-4RFHUZXU.js.map
635
+ //# sourceMappingURL=chunk-BULPAAD3.js.map
@@ -1,30 +1,30 @@
1
1
  import {
2
2
  bridgeRegistry
3
3
  } from "./chunk-5A432NZJ.js";
4
- import {
5
- BridgeOutboxDrainHook
6
- } from "./chunk-L7BNNRGI.js";
7
4
  import {
8
5
  EventFlowService
9
- } from "./chunk-32BMMV4H.js";
6
+ } from "./chunk-5RT7JGKT.js";
10
7
  import {
11
8
  BRIDGE_RESERVED_POOLS
12
9
  } from "./chunk-EDKJU5BO.js";
13
- import {
14
- BridgeDeliveryHandler
15
- } from "./chunk-YTN6BKWA.js";
16
10
  import {
17
11
  DrizzleBridgeDeliveryRepo
18
- } from "./chunk-K2I6XIK5.js";
12
+ } from "./chunk-KSTZIULO.js";
19
13
  import {
20
14
  MemoryBridgeDeliveryRepo
21
15
  } from "./chunk-4DOJBQTP.js";
16
+ import {
17
+ BridgeOutboxDrainHook
18
+ } from "./chunk-EBKVKN75.js";
19
+ import {
20
+ BridgeDeliveryHandler
21
+ } from "./chunk-DRCLNYH7.js";
22
22
  import {
23
23
  BridgeReservedPoolsNotPolledError
24
24
  } from "./chunk-NXXDZ6ZF.js";
25
25
  import {
26
26
  JOB_WORKER_MODULE_OPTIONS
27
- } from "./chunk-WPXNN6QS.js";
27
+ } from "./chunk-RUYLXR5F.js";
28
28
  import {
29
29
  BRIDGE_DELIVERY_REPO,
30
30
  BRIDGE_MODULE_OPTIONS,
@@ -119,4 +119,4 @@ BridgeModule = __decorateClass([
119
119
  export {
120
120
  BridgeModule
121
121
  };
122
- //# sourceMappingURL=chunk-OZZJDRGW.js.map
122
+ //# sourceMappingURL=chunk-CEWLVVAH.js.map