@pattern-stack/codegen 0.18.0 → 0.20.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 (129) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +51 -0
  3. package/consumer-skills/codegen/SKILL.md +32 -0
  4. package/consumer-skills/entities/SKILL.md +2 -0
  5. package/consumer-skills/events/authoring-events.md +31 -0
  6. package/dist/{chunk-COGHTKXY.js → chunk-27ETSJ2X.js} +2 -2
  7. package/dist/{chunk-X6BP6LI5.js → chunk-3YCUIGPG.js} +10 -10
  8. package/dist/{chunk-BK5ICA2F.js → chunk-4MVGAMUA.js} +4 -4
  9. package/dist/{chunk-LQ6PYFU6.js → chunk-4OC5MSHO.js} +47 -1
  10. package/dist/chunk-4OC5MSHO.js.map +1 -0
  11. package/dist/{chunk-3A34R6CI.js → chunk-5LXOJGO2.js} +4 -4
  12. package/dist/{chunk-T6C4LFLC.js → chunk-7YGORYZD.js} +4 -4
  13. package/dist/{chunk-PKDS6QIJ.js → chunk-ATVGYF3D.js} +7 -7
  14. package/dist/{chunk-2TVVBC53.js → chunk-BORNCTH3.js} +2 -2
  15. package/dist/{chunk-7MMS36AN.js → chunk-CUSMC2KK.js} +10 -10
  16. package/dist/{chunk-V4AF6DI4.js → chunk-DUBZOXJC.js} +9 -2
  17. package/dist/{chunk-V4AF6DI4.js.map → chunk-DUBZOXJC.js.map} +1 -1
  18. package/dist/chunk-DUUCU77W.js +211 -0
  19. package/dist/chunk-DUUCU77W.js.map +1 -0
  20. package/dist/{chunk-OFRRBC7M.js → chunk-E2BRT5IB.js} +15 -1
  21. package/dist/chunk-E2BRT5IB.js.map +1 -0
  22. package/dist/{chunk-VQOAATIG.js → chunk-FLYF76CU.js} +4 -4
  23. package/dist/{chunk-43SBT72G.js → chunk-I6UXRJ3Q.js} +4 -4
  24. package/dist/{chunk-VHAR2BGH.js → chunk-INO47JXD.js} +5 -5
  25. package/dist/{chunk-BGULBWKJ.js → chunk-JOBQ6RUU.js} +1 -1
  26. package/dist/chunk-JOBQ6RUU.js.map +1 -0
  27. package/dist/{chunk-C5E7H553.js → chunk-KBO5OOON.js} +4 -4
  28. package/dist/{chunk-R4BPUUB5.js → chunk-KHQ72A5F.js} +54 -6
  29. package/dist/chunk-KHQ72A5F.js.map +1 -0
  30. package/dist/{chunk-CFFTPWHM.js → chunk-KK5A7B2T.js} +70 -5
  31. package/dist/chunk-KK5A7B2T.js.map +1 -0
  32. package/dist/{chunk-RKNW56RU.js → chunk-LARB26EI.js} +73 -6
  33. package/dist/chunk-LARB26EI.js.map +1 -0
  34. package/dist/{chunk-2VGVSL2D.js → chunk-LQXBQO72.js} +6 -6
  35. package/dist/{chunk-K2I6XIK5.js → chunk-MVKW2BCR.js} +2 -2
  36. package/dist/{chunk-EWYI5GGJ.js → chunk-NYJYK6J4.js} +15 -15
  37. package/dist/{chunk-IN3EWFB4.js → chunk-QSJ3J4HE.js} +6 -6
  38. package/dist/{chunk-TBGTMALE.js → chunk-SGSWVNNB.js} +4 -4
  39. package/dist/chunk-SYVZ4MD2.js +1 -0
  40. package/dist/{chunk-NXHL5YII.js → chunk-T6SCOJF4.js} +4 -4
  41. package/dist/{chunk-YZLBU6O2.js → chunk-WKNOEVWQ.js} +5 -5
  42. package/dist/runtime/base-classes/index.js +17 -17
  43. package/dist/runtime/subsystems/auth/auth.module.js +3 -3
  44. package/dist/runtime/subsystems/auth/index.js +10 -10
  45. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js +2 -2
  46. package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js +3 -3
  47. package/dist/runtime/subsystems/bridge/bridge-delivery.schema.js +2 -2
  48. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +6 -6
  49. package/dist/runtime/subsystems/bridge/bridge.module.js +18 -18
  50. package/dist/runtime/subsystems/bridge/index.js +21 -21
  51. package/dist/runtime/subsystems/cache/cache.drizzle-backend.js +2 -2
  52. package/dist/runtime/subsystems/cache/cache.module.js +3 -3
  53. package/dist/runtime/subsystems/cache/index.js +5 -5
  54. package/dist/runtime/subsystems/events/domain-events.schema.js +1 -1
  55. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.d.ts +19 -32
  56. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +3 -3
  57. package/dist/runtime/subsystems/events/event-bus.memory-backend.d.ts +18 -1
  58. package/dist/runtime/subsystems/events/event-bus.memory-backend.js +1 -1
  59. package/dist/runtime/subsystems/events/event-bus.protocol.d.ts +45 -1
  60. package/dist/runtime/subsystems/events/event-scheduler.d.ts +96 -0
  61. package/dist/runtime/subsystems/events/event-scheduler.js +25 -0
  62. package/dist/runtime/subsystems/events/event-scheduler.js.map +1 -0
  63. package/dist/runtime/subsystems/events/events-errors.d.ts +12 -1
  64. package/dist/runtime/subsystems/events/events-errors.js +5 -3
  65. package/dist/runtime/subsystems/events/events.module.d.ts +41 -2
  66. package/dist/runtime/subsystems/events/events.module.js +11 -8
  67. package/dist/runtime/subsystems/events/generated/bus.js +3 -3
  68. package/dist/runtime/subsystems/events/generated/index.js +3 -3
  69. package/dist/runtime/subsystems/events/generated/registry.d.ts +6 -0
  70. package/dist/runtime/subsystems/events/generated/registry.js +1 -1
  71. package/dist/runtime/subsystems/events/index.d.ts +4 -3
  72. package/dist/runtime/subsystems/events/index.js +38 -14
  73. package/dist/runtime/subsystems/index.d.ts +1 -0
  74. package/dist/runtime/subsystems/index.js +117 -116
  75. package/dist/runtime/subsystems/integration/build-change-source.js +2 -2
  76. package/dist/runtime/subsystems/integration/index.js +36 -36
  77. package/dist/runtime/subsystems/integration/integration.module.js +4 -4
  78. package/dist/runtime/subsystems/jobs/index.js +33 -33
  79. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +5 -5
  80. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js +3 -3
  81. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js +2 -2
  82. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +2 -2
  83. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +3 -3
  84. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +3 -3
  85. package/dist/runtime/subsystems/jobs/job-worker.js +2 -2
  86. package/dist/runtime/subsystems/jobs/job-worker.module.js +11 -11
  87. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +9 -9
  88. package/dist/runtime/subsystems/storage/index.js +4 -4
  89. package/dist/runtime/subsystems/storage/storage.module.js +2 -2
  90. package/dist/src/cli/index.js +168 -22
  91. package/dist/src/cli/index.js.map +1 -1
  92. package/dist/src/index.js +12 -12
  93. package/package.json +1 -1
  94. package/runtime/subsystems/events/domain-events.schema.ts +16 -0
  95. package/runtime/subsystems/events/event-bus.drizzle-backend.ts +103 -1
  96. package/runtime/subsystems/events/event-bus.memory-backend.ts +57 -1
  97. package/runtime/subsystems/events/event-bus.protocol.ts +47 -0
  98. package/runtime/subsystems/events/event-scheduler.ts +351 -0
  99. package/runtime/subsystems/events/events-errors.ts +14 -0
  100. package/runtime/subsystems/events/events.module.ts +78 -1
  101. package/runtime/subsystems/events/generated/registry.ts +1 -0
  102. package/runtime/subsystems/events/index.ts +25 -3
  103. package/dist/chunk-BGULBWKJ.js.map +0 -1
  104. package/dist/chunk-CFFTPWHM.js.map +0 -1
  105. package/dist/chunk-FN2PYDPP.js +0 -1
  106. package/dist/chunk-LQ6PYFU6.js.map +0 -1
  107. package/dist/chunk-OFRRBC7M.js.map +0 -1
  108. package/dist/chunk-R4BPUUB5.js.map +0 -1
  109. package/dist/chunk-RKNW56RU.js.map +0 -1
  110. /package/dist/{chunk-COGHTKXY.js.map → chunk-27ETSJ2X.js.map} +0 -0
  111. /package/dist/{chunk-X6BP6LI5.js.map → chunk-3YCUIGPG.js.map} +0 -0
  112. /package/dist/{chunk-BK5ICA2F.js.map → chunk-4MVGAMUA.js.map} +0 -0
  113. /package/dist/{chunk-3A34R6CI.js.map → chunk-5LXOJGO2.js.map} +0 -0
  114. /package/dist/{chunk-T6C4LFLC.js.map → chunk-7YGORYZD.js.map} +0 -0
  115. /package/dist/{chunk-PKDS6QIJ.js.map → chunk-ATVGYF3D.js.map} +0 -0
  116. /package/dist/{chunk-2TVVBC53.js.map → chunk-BORNCTH3.js.map} +0 -0
  117. /package/dist/{chunk-7MMS36AN.js.map → chunk-CUSMC2KK.js.map} +0 -0
  118. /package/dist/{chunk-VQOAATIG.js.map → chunk-FLYF76CU.js.map} +0 -0
  119. /package/dist/{chunk-43SBT72G.js.map → chunk-I6UXRJ3Q.js.map} +0 -0
  120. /package/dist/{chunk-VHAR2BGH.js.map → chunk-INO47JXD.js.map} +0 -0
  121. /package/dist/{chunk-C5E7H553.js.map → chunk-KBO5OOON.js.map} +0 -0
  122. /package/dist/{chunk-2VGVSL2D.js.map → chunk-LQXBQO72.js.map} +0 -0
  123. /package/dist/{chunk-K2I6XIK5.js.map → chunk-MVKW2BCR.js.map} +0 -0
  124. /package/dist/{chunk-EWYI5GGJ.js.map → chunk-NYJYK6J4.js.map} +0 -0
  125. /package/dist/{chunk-IN3EWFB4.js.map → chunk-QSJ3J4HE.js.map} +0 -0
  126. /package/dist/{chunk-TBGTMALE.js.map → chunk-SGSWVNNB.js.map} +0 -0
  127. /package/dist/{chunk-FN2PYDPP.js.map → chunk-SYVZ4MD2.js.map} +0 -0
  128. /package/dist/{chunk-NXHL5YII.js.map → chunk-T6SCOJF4.js.map} +0 -0
  129. /package/dist/{chunk-YZLBU6O2.js.map → chunk-WKNOEVWQ.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,94 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.20.0] — 2026-06-06
8
+
9
+ **Declarative time-based scheduling: time as an event source** (ADR-039;
10
+ swe-brain consumer-test finding — an hourly reconcile poll had to be hand-rolled
11
+ as a self-perpetuating job chain because there was no time-based trigger, and
12
+ its flat dedupe key collapsed the chain into the running parent).
13
+
14
+ ### Added
15
+
16
+ - **`schedule:` on event YAML** (`definitions/events/<domain>/*.yaml`) —
17
+ `{ every, align?, catchUp?, maxCatchUpSlots? }`. Declares that the platform
18
+ emits this event on a cadence. `every` is a duration string (`'1h'`/`'30m'`/
19
+ `'15s'`/`'500ms'`/`'1d'`) or raw ms. Domain-tier only (audit rejected). Time
20
+ is a third event **source** (peer to use-case publishes + webhook receivers),
21
+ not a fourth activation tier — consumers react through ADR-023's existing
22
+ tiers (`subscribe` or `@JobHandler({ triggers })`); no new activation
23
+ mechanism. Carried into the generated `eventRegistry` + `EventMetadata`.
24
+ - **`EventScheduler`** (`runtime/subsystems/events/event-scheduler.ts`) — a
25
+ strict producer that materialises **exactly one `domain_events` row per
26
+ (type, slot)** on a cadence; the existing outbox drain + bridge activate them.
27
+ Reconcile-on-boot (materialise the current slot, or bounded `catchUp`
28
+ backfill) + a tick pass for the next slot. Wired by `EventsModule.forRoot`
29
+ for the drizzle + memory backends (the Redis bus retains no outbox history, so
30
+ slot idempotency can't be enforced there). Pure helpers exported:
31
+ `parseEvery`, `slotStartFor`, `nextSlotStart`, `slotKeyFor`,
32
+ `scheduledEventsFromRegistry`.
33
+ - **Partial UNIQUE expression index** `idx_domain_events_schedule_slot` on
34
+ `(type, metadata->>'scheduleSlot')` — the DB-level exactly-once-per-slot
35
+ invariant (no advisory lock, no leader election; multi-instance + boot/tick
36
+ races collapse on it). Partial on the slot key so ordinary events are
37
+ untouched. Additive Atlas migration.
38
+ - **`ScheduleConfigError`** + `IEventBus.materializeScheduledEvent` /
39
+ `lastScheduledSlotMs` (both backends).
40
+
41
+ ### Misfire policy
42
+
43
+ - Down across N slots → on recovery, materialise **one** tick for the current
44
+ slot (don't replay the misses). `catchUp: true` opts into bounded backfill.
45
+
46
+ ### Provenance
47
+
48
+ - A scheduled tick carries `metadata.triggerSource = 'schedule'` +
49
+ `metadata.scheduleSlot`. A bridge run from it reads `job_run.trigger_source =
50
+ 'event'`; the clock origin is joinable via `trigger_ref → domain_events.id`.
51
+ The dormant ADR-022 `triggerSource: 'schedule'` enum is the correct stamp for
52
+ the **direct-start** path (a Tier-1 subscriber calling `orchestrator.start(…,
53
+ { triggerSource: 'schedule' })`).
54
+
55
+ ### Retires
56
+
57
+ - The self-perpetuating job-chain pattern (a handler enqueuing its own
58
+ successor) and its slot-keyed-dedupe workaround — replaced by a `schedule:`
59
+ YAML + a `triggers:` entry. Supersedes the dangling "ADR-025 scheduling
60
+ territory" pointer in `docs/specs/BULLMQ-1.md` (the future BullMQ backend maps
61
+ `schedule:` onto `upsertJobScheduler`; the YAML contract is identical).
62
+
63
+ ## [0.19.0] — 2026-06-05
64
+
65
+ **Providers catalog emission + planned providers** (ADR-038 follow-on;
66
+ swe-brain consumer-test finding — the Connections surface hand-duplicated
67
+ provider knowledge that `definitions/providers/*.yaml` already owns).
68
+
69
+ ### Added
70
+
71
+ - **Frontend providers catalog (`generated/providers.ts`)** — emitted by the
72
+ frontend whole-set emitter when `definitions/providers/` exists (entity-only
73
+ consumers see no new file; the root barrel gains the export conditionally).
74
+ `PROVIDERS` (flat, slug-sorted) + `PROVIDER_CATALOG` (grouped by
75
+ `display.category` into the ordered `frontend.catalog.categories`). Providers
76
+ are gen-time knowledge — the catalog is emitted, not queried.
77
+ - **`display:` block on the provider schema** (`category`, `blurb`, `hint`) —
78
+ presentation metadata consumed only by the catalog emission; backend
79
+ provider/adapter codegen ignores it.
80
+ - **`status: active | planned` on the provider schema** (default `active`).
81
+ `planned` providers are roadmap stubs: catalog tile only — `auth`/`client`
82
+ optional, surface closed-set + import pre-flight cross-checks skipped (slug
83
+ uniqueness still enforced), and ALL backend emission (provider modules,
84
+ adapters, assemblies) filters them out. Flip to `active` when the
85
+ integration lands.
86
+ - **`frontend.catalog.categories`** in `codegen.config.yaml` — ordered display
87
+ groups (`id`, `name`, `blurb`) the catalog renders.
88
+
89
+ ### Changed
90
+
91
+ - `generateProviderModule` / `generateAdapterScaffold` now take
92
+ `ActiveProviderDefinition` (auth/client guaranteed); use the exported
93
+ `isActiveProvider` guard to narrow.
94
+
7
95
  ## [0.17.2] — 2026-06-04
8
96
 
9
97
  **Shutdown leak fix** (LISTEN-NOTIFY-2; swe-brain dogfood). With
package/README.md CHANGED
@@ -163,6 +163,7 @@ generated/
163
163
  entities/<entity>.ts createEntityHooks({ collection, api }) wiring
164
164
  store/index.ts createStore over the full set (+ resolvers, lookups)
165
165
  fields/<entity>.ts field metadata (FieldMeta, <entity>Fields)
166
+ providers.ts providers catalog — only when definitions/providers/ exists
166
167
  ```
167
168
 
168
169
  Entity types and Zod schemas are **imported** from `locations.dbEntities`
@@ -236,6 +237,56 @@ the **target entity's own YAML** via the registry — never re-pluralized from a
236
237
  string at emit time (so an explicit `plural:` like `person`→`persons` is honored
237
238
  by every consumer).
238
239
 
240
+ ### Providers catalog (`providers.ts`)
241
+
242
+ Providers are gen-time knowledge (`definitions/providers/<slug>.yaml`,
243
+ RFC-0001) — the provider set changes only when code deploys — so the frontend
244
+ catalog is **emitted, not queried**. When the project has provider definitions,
245
+ the emitter renders `generated/providers.ts`:
246
+
247
+ - `PROVIDERS` — every provider, flat (active + planned), slug-sorted:
248
+ `{ provider, name, planned, surfaces, blurb?, hint? }`. Join live rows on
249
+ `provider` (the canonical slug, e.g. `Connection.provider`).
250
+ - `PROVIDER_CATALOG` — grouped into `frontend.catalog.categories` (config
251
+ order) via each provider's `display.category`. Uncategorized providers
252
+ appear only in `PROVIDERS`.
253
+
254
+ Two provider-YAML additions feed it:
255
+
256
+ ```yaml
257
+ # definitions/providers/google.yaml (active — full definition)
258
+ slug: google
259
+ display_name: Google Workspace
260
+ display:
261
+ category: google-workspace # joins frontend.catalog.categories[].id
262
+ hint: connect # optional sub-line on an unconnected tile
263
+ surfaces: [calendar, mail, transcript]
264
+ auth: { ... } # required for active providers
265
+ client: { ... }
266
+
267
+ # definitions/providers/github.yaml (planned — roadmap stub)
268
+ slug: github
269
+ display_name: GitHub
270
+ status: planned # catalog tile only; NO backend emission,
271
+ display: # no auth/client required, surface + import
272
+ category: source-control # cross-checks skipped
273
+ surfaces: [source_control]
274
+ ```
275
+
276
+ ```yaml
277
+ # codegen.config.yaml — the ordered display groups
278
+ frontend:
279
+ catalog:
280
+ categories:
281
+ - id: source-control
282
+ name: Source Control & Issues
283
+ blurb: Repositories, pull requests, issues, and project planning
284
+ ```
285
+
286
+ When the integration for a `planned` provider lands, flip it to `status:
287
+ active` (or drop the key) and add `auth`/`client` — the definitions tree is
288
+ the integration roadmap.
289
+
239
290
  ## Integration Codegen (provider/adapter + assembly + read primitive)
240
291
 
241
292
  When an entity carries a `surface:` tag and `definitions/providers/*.yaml` exist,
@@ -55,6 +55,38 @@ generation run the two barrels are rewritten and you wire them into
55
55
  | React to an event with a durable async job (the event-to-job bridge) | the `bridge` skill |
56
56
  | Pull/push data from an external system (`IChangeSource` / `IIntegrationSink`) | the `integration` skill |
57
57
 
58
+ ## Frontend generation (`generate.frontend: true`)
59
+
60
+ When on, `entity new` (and `--all`) ends with a **whole-set frontend emitter**
61
+ (ADR-038): the complete data layer is rendered into
62
+ `locations.frontendGenerated` (default `apps/frontend/src/generated/`) from the
63
+ full entity set — REST api client, TanStack DB collections (per-entity
64
+ `sync: api | electric`), `createEntityHooks` wiring, field metadata, a
65
+ plural-keyed `createStore`, `config.ts`, `query-client.ts`, root barrel. Every
66
+ file is a complete write with a `@generated` banner; re-runs are byte-identical.
67
+
68
+ Non-obvious bits:
69
+
70
+ - **The dbEntities contract**: generated files import the plain `<Class>` type
71
+ AND a `<camel>Schema` Zod schema from `locations.dbEntities` per entity. If
72
+ the backend doesn't emit such a package (clean-lite-ps doesn't), the consumer
73
+ provides a shim barrel re-exporting each module's Output DTO.
74
+ - **The consumer mounts two providers** in the app root, both from generated
75
+ code: `QueryClientProvider(queryClient)` ▸ `EntityStoreProvider(store)`
76
+ (`EntityStoreProvider` from `@pattern-stack/frontend-patterns`).
77
+ - **Version pairing**: the emitted imports require
78
+ `@pattern-stack/frontend-patterns` + four TanStack packages — the exact
79
+ ranges are listed in the generated `index.ts` header comment.
80
+ - **Path aliases are assumed**: default imports use `@/…` (and your
81
+ `locations.*.import` values) — wire tsconfig `paths` + the bundler alias.
82
+ - **Providers catalog**: when `definitions/providers/` exists, the emitter also
83
+ renders `providers.ts` — `PROVIDERS` (flat) + `PROVIDER_CATALOG` (grouped by
84
+ each provider's `display.category` into `frontend.catalog.categories`).
85
+ Providers are gen-time knowledge: the catalog is emitted, never queried, and
86
+ never hand-duplicated in the frontend. `status: planned` provider YAMLs are
87
+ roadmap stubs — catalog tile only, no auth/client needed, skipped by all
88
+ backend emission.
89
+
58
90
  ## CLI quick reference
59
91
 
60
92
  ```bash
@@ -48,6 +48,8 @@ module — plus an entry in the `GENERATED_MODULES` and schema barrels.
48
48
  entity:
49
49
  name: account # singular snake_case
50
50
  pattern: Synced # Base | Synced | Activity | Metadata | Knowledge (or app-defined)
51
+ # sync: api # frontend collection mode (api | electric) — overrides
52
+ # # frontend.sync.mode; only meaningful with generate.frontend: true
51
53
 
52
54
  fields:
53
55
  name:
@@ -65,6 +65,7 @@ After editing any `events/*.yaml`, re-run codegen (e.g. `codegen entity new --al
65
65
  | `payload` | yes | Map of snake_case keys → field specs. Keys become camelCase TS props. |
66
66
  | `pool` | no | Override the direction's default pool. Only valid within the same category (a `change` event can't opt into `events_inbound`). User pools are never valid. Rarely needed — if you reach for it, revisit the direction. **Must be omitted for `tier: audit`.** |
67
67
  | `retry` | no | `{ attempts, backoff }`; hints surfaced to the bus. |
68
+ | `schedule` | no | `{ every, align?, catchUp?, maxCatchUpSlots? }` — the platform emits this event on a cadence (ADR-039). `every` is a duration (`'1h'`/`'30m'`/`'15s'`/`'500ms'`/`'1d'`) or raw ms. **Domain-tier only.** See §Scheduled events. |
68
69
  | `version` | no | Integer, defaults to 1. Stamped on every publish; multi-version coexistence is not exercised yet. |
69
70
 
70
71
  ### Payload field types
@@ -117,6 +118,36 @@ Codegen hard-errors on misuse:
117
118
  - `tier: audit` with a `direction` → error naming the event.
118
119
  - A job whose `@JobHandler.triggers` points at an audit event → `AuditEventTriggerError`. Use a domain event or remove the trigger.
119
120
 
121
+ ## Scheduled events (time as an event source)
122
+
123
+ Add a `schedule:` block and the platform emits this event on a cadence — you react with the **same** tiers you use for any event (a `subscribe(...)` or a `@JobHandler({ triggers })`). There is no separate "cron handler" or schedule decorator; time is just a third way an event gets produced.
124
+
125
+ ```yaml
126
+ # definitions/events/messaging/reconcile_due.yaml
127
+ type: reconcile_due
128
+ direction: inbound # scheduled events are domain-tier; route by direction
129
+ schedule:
130
+ every: 1h # '1h' | '30m' | '15s' | '500ms' | '1d' | raw ms
131
+ align: true # epoch-anchored boundaries (fires at :00) — default
132
+ # catchUp: false # down across N slots → run ONCE on recovery (default)
133
+ # maxCatchUpSlots: 1000 # bound when catchUp: true
134
+ payload: {} # scheduled events are payload-free facts
135
+ ```
136
+
137
+ ```ts
138
+ // React via Tier 3 (bridge) — the cadence is on the event, the reaction on the job.
139
+ @JobHandler<ReconcileInput>('reconcile-poll', {
140
+ pool: 'batch',
141
+ concurrency: { key: 'reconcile:{{provider}}', collisionMode: 'queue' }, // serialises overruns
142
+ triggers: [{ event: 'reconcile_due', map: () => ({ provider: 'slack', windowHours: 24 }) }],
143
+ })
144
+ export class ReconcilePollHandler extends JobHandlerBase<ReconcileInput, ReconcileOutput> { /* … */ }
145
+ ```
146
+
147
+ Behavior: the framework emits **exactly one** `domain_events` row per slot (idempotent across instances + restarts), runs **reconcile-on-boot** (one tick on startup — healing any downtime gap), and by default does **not** replay missed slots. **This replaces the old "self-perpetuating job chain"** (a handler enqueuing its own successor with a slot-keyed dedupe) — delete that pattern; declare `schedule:` instead.
148
+
149
+ Rules: domain-tier only; a malformed `every` fails `gen-validate`; available on the drizzle/memory backends. See ADR-039.
150
+
120
151
  ## Entity `events:` block is sugar for change events
121
152
 
122
153
  An entity's YAML may keep an `events:` block. At parse time each entry desugars into an equivalent `events/<name>.yaml` with `direction: change` and `aggregate: <entity>`. Both paths produce the same registry entry — inline blocks are convenience, not a second source of truth.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  DrizzleCacheService
3
- } from "./chunk-T6C4LFLC.js";
3
+ } from "./chunk-7YGORYZD.js";
4
4
  import {
5
5
  MemoryCacheService
6
6
  } from "./chunk-IF5I3DAA.js";
@@ -81,4 +81,4 @@ CacheModule = __decorateClass([
81
81
  export {
82
82
  CacheModule
83
83
  };
84
- //# sourceMappingURL=chunk-COGHTKXY.js.map
84
+ //# sourceMappingURL=chunk-27ETSJ2X.js.map
@@ -1,13 +1,9 @@
1
1
  import {
2
2
  JobWorker
3
- } from "./chunk-C5E7H553.js";
3
+ } from "./chunk-KBO5OOON.js";
4
4
  import {
5
5
  JobsDomainModule
6
- } from "./chunk-EWYI5GGJ.js";
7
- import {
8
- BootValidationError,
9
- ReservedPoolViolationError
10
- } from "./chunk-T4BIIU5E.js";
6
+ } from "./chunk-NYJYK6J4.js";
11
7
  import {
12
8
  BULLMQ_CONNECTION,
13
9
  BULLMQ_RESOLVED_CONFIG,
@@ -18,14 +14,18 @@ import {
18
14
  allPoolNames,
19
15
  loadPoolConfig
20
16
  } from "./chunk-RHVN6NA7.js";
21
- import {
22
- HandlerRegistry
23
- } from "./chunk-7P5ODGLA.js";
24
17
  import {
25
18
  JOB_ORCHESTRATOR,
26
19
  JOB_RUN_SERVICE,
27
20
  JOB_STEP_SERVICE
28
21
  } from "./chunk-ZPL74UQN.js";
22
+ import {
23
+ BootValidationError,
24
+ ReservedPoolViolationError
25
+ } from "./chunk-T4BIIU5E.js";
26
+ import {
27
+ HandlerRegistry
28
+ } from "./chunk-7P5ODGLA.js";
29
29
  import {
30
30
  tokenKey
31
31
  } from "./chunk-GYGNEQSC.js";
@@ -290,4 +290,4 @@ export {
290
290
  JobWorkerOrchestrator,
291
291
  JobWorkerModule
292
292
  };
293
- //# sourceMappingURL=chunk-X6BP6LI5.js.map
293
+ //# sourceMappingURL=chunk-3YCUIGPG.js.map
@@ -1,12 +1,12 @@
1
- import {
2
- STORAGE
3
- } from "./chunk-NYBCQZC7.js";
4
1
  import {
5
2
  LocalStorageBackend
6
3
  } from "./chunk-JWNHNUYL.js";
7
4
  import {
8
5
  MemoryStorageBackend
9
6
  } 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-BK5ICA2F.js.map
40
+ //# sourceMappingURL=chunk-4MVGAMUA.js.map
@@ -12,6 +12,7 @@ import {
12
12
  } from "./chunk-2E224ZSN.js";
13
13
 
14
14
  // runtime/subsystems/events/event-bus.memory-backend.ts
15
+ import { randomUUID } from "crypto";
15
16
  import { Inject, Injectable, Logger, Optional } from "@nestjs/common";
16
17
  function toEventSummary(event) {
17
18
  const metadata = event.metadata;
@@ -58,6 +59,51 @@ var MemoryEventBus = class {
58
59
  async findById(eventId) {
59
60
  return this.publishedEvents.find((e) => e.id === eventId) ?? null;
60
61
  }
62
+ // ============================================================================
63
+ // ADR-039 — scheduled-event materialisation (memory parity)
64
+ // ============================================================================
65
+ /** Slot keys already materialised — the in-memory mirror of the partial
66
+ * UNIQUE expression index `idx_domain_events_schedule_slot`. */
67
+ materialisedSlots = /* @__PURE__ */ new Set();
68
+ /**
69
+ * Mirror of the Drizzle `ON CONFLICT DO NOTHING` insert: emit one payload-free
70
+ * tick event per slot key, no-op if the slot was already materialised. The
71
+ * "constraint" is the `materialisedSlots` set. The tick is published through
72
+ * the normal `publish` path so subscribers fire synchronously (the memory bus
73
+ * has no future-slot/poll concept — a materialised slot dispatches now, which
74
+ * is the behaviour the unit suite pins).
75
+ */
76
+ async materializeScheduledEvent(spec) {
77
+ if (this.materialisedSlots.has(spec.slotKey)) return { created: false };
78
+ this.materialisedSlots.add(spec.slotKey);
79
+ const event = {
80
+ id: randomUUID(),
81
+ type: spec.type,
82
+ aggregateId: spec.type,
83
+ aggregateType: spec.type,
84
+ payload: {},
85
+ occurredAt: spec.slotStart,
86
+ metadata: {
87
+ pool: spec.pool,
88
+ direction: spec.direction,
89
+ scheduleSlot: spec.slotKey,
90
+ triggerSource: "schedule"
91
+ }
92
+ };
93
+ await this.publish(event);
94
+ return { created: true };
95
+ }
96
+ /** Most recent scheduled tick's `occurred_at` (epoch ms) for `type`, or null. */
97
+ async lastScheduledSlotMs(type) {
98
+ let best = null;
99
+ for (const e of this.publishedEvents) {
100
+ if (e.type !== type) continue;
101
+ if (e.metadata?.["triggerSource"] !== "schedule") continue;
102
+ const ms = e.occurredAt.getTime();
103
+ if (best === null || ms > best) best = ms;
104
+ }
105
+ return best;
106
+ }
61
107
  subscribe(eventType, handler) {
62
108
  if (!this.handlers.has(eventType)) {
63
109
  this.handlers.set(eventType, /* @__PURE__ */ new Set());
@@ -197,4 +243,4 @@ MemoryEventBus = __decorateClass([
197
243
  export {
198
244
  MemoryEventBus
199
245
  };
200
- //# sourceMappingURL=chunk-LQ6PYFU6.js.map
246
+ //# sourceMappingURL=chunk-4OC5MSHO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../runtime/subsystems/events/event-bus.memory-backend.ts"],"sourcesContent":["/**\n * MemoryEventBus — in-memory backend for the event bus.\n *\n * Dispatches events synchronously to registered subscribers. The `tx`\n * parameter is ignored — all events are dispatched immediately.\n *\n * Use this backend in tests to assert event publication without a database.\n * Swap via EventsModule.forRoot({ backend: 'memory' }).\n *\n * Pool awareness (EVT-5):\n * - Mirrors the `DrizzleEventBus` per-process restriction (EVT-4). When\n * `opts.pools` is set, `publish`/`publishMany` still push the event into\n * `publishedEvents` (so test code can assert the full set of emitted\n * events regardless of pool filter), but handlers are NOT invoked for\n * events whose `metadata.pool` is outside the configured pools.\n * - `publishedEventsForPool(pool)` and `publishedEventsForDirection(dir)`\n * helpers are provided for targeted assertions.\n * - Shares the `EventsModuleOptions` shape (same token as Drizzle) rather\n * than introducing a memory-only options type — the surface is the same\n * and keeping them unified avoids drift between backends.\n */\nimport { randomUUID } from 'node:crypto';\nimport { Inject, Injectable, Logger, Optional } from '@nestjs/common';\nimport type {\n DomainEvent,\n IEventBus,\n ScheduledEventSpec,\n} from './event-bus.protocol';\nimport type {\n EventPage,\n EventSummary,\n IEventReadPort,\n ListEventsQuery,\n} from './event-read.protocol';\nimport {\n clampEventLimit,\n decodeEventCursor,\n encodeEventCursor,\n} from './event-keyset-cursor';\nimport { EVENTS_MODULE_OPTIONS } from './events.tokens';\nimport type { EventsModuleOptions } from './events.module';\n\n/**\n * Project an in-memory `DomainEvent` into the narrow `EventSummary` shape.\n * The memory backend has no first-class columns, so `pool` / `direction` /\n * `tier` / `tenantId` / `rootRunId` are read from `metadata` (mirroring how\n * the Drizzle backend stamps them onto columns at publish time). `status`\n * is reported as `'processed'` — the memory bus dispatches synchronously,\n * so once an event is in `publishedEvents` it has been handled.\n */\nfunction toEventSummary(event: DomainEvent): EventSummary {\n const metadata = event.metadata;\n const str = (key: string): string | null => {\n const v = metadata?.[key];\n return typeof v === 'string' ? v : null;\n };\n return {\n id: event.id,\n type: event.type,\n aggregateId: event.aggregateId,\n aggregateType: event.aggregateType,\n status: 'processed',\n pool: str('pool'),\n direction: str('direction'),\n tier: str('tier') ?? 'domain',\n rootRunId: str('rootRunId'),\n tenantId: str('tenantId'),\n occurredAt: event.occurredAt,\n processedAt: event.occurredAt,\n };\n}\n\n@Injectable()\nexport class MemoryEventBus implements IEventBus, IEventReadPort {\n private readonly logger = new Logger(MemoryEventBus.name);\n\n /** All events published since construction (or last clear). */\n readonly publishedEvents: DomainEvent[] = [];\n\n private readonly handlers = new Map<string, Set<(event: DomainEvent) => Promise<void>>>();\n private readonly opts: EventsModuleOptions;\n\n constructor(\n @Optional() @Inject(EVENTS_MODULE_OPTIONS) opts?: EventsModuleOptions,\n ) {\n // Default so direct construction (e.g. `new MemoryEventBus()` from a\n // unit test outside NestJS DI) keeps working without an explicit\n // options object.\n this.opts = opts ?? { backend: 'memory' };\n }\n\n async publish(event: DomainEvent): Promise<void> {\n // Mirror the `domain_events_tier_routing_check` DB constraint at the\n // memory backend boundary so misuse fails the same way regardless of\n // backend (AUDIT-1).\n this.assertTierRouting(event);\n\n // Always record the event — even if this process is configured with a\n // pool filter that excludes it. Test code relies on `publishedEvents`\n // being a complete log of what was published, not a filtered view.\n this.publishedEvents.push(event);\n\n if (this.shouldDispatch(event)) {\n await this.dispatch(event);\n }\n }\n\n async publishMany(events: DomainEvent[]): Promise<void> {\n for (const event of events) {\n await this.publish(event);\n }\n }\n\n async findById(eventId: string): Promise<DomainEvent | null> {\n return this.publishedEvents.find((e) => e.id === eventId) ?? null;\n }\n\n // ============================================================================\n // ADR-039 — scheduled-event materialisation (memory parity)\n // ============================================================================\n\n /** Slot keys already materialised — the in-memory mirror of the partial\n * UNIQUE expression index `idx_domain_events_schedule_slot`. */\n private readonly materialisedSlots = new Set<string>();\n\n /**\n * Mirror of the Drizzle `ON CONFLICT DO NOTHING` insert: emit one payload-free\n * tick event per slot key, no-op if the slot was already materialised. The\n * \"constraint\" is the `materialisedSlots` set. The tick is published through\n * the normal `publish` path so subscribers fire synchronously (the memory bus\n * has no future-slot/poll concept — a materialised slot dispatches now, which\n * is the behaviour the unit suite pins).\n */\n async materializeScheduledEvent(\n spec: ScheduledEventSpec,\n ): Promise<{ created: boolean }> {\n if (this.materialisedSlots.has(spec.slotKey)) return { created: false };\n this.materialisedSlots.add(spec.slotKey);\n const event: DomainEvent = {\n id: randomUUID(),\n type: spec.type,\n aggregateId: spec.type,\n aggregateType: spec.type,\n payload: {},\n occurredAt: spec.slotStart,\n metadata: {\n pool: spec.pool,\n direction: spec.direction,\n scheduleSlot: spec.slotKey,\n triggerSource: 'schedule',\n },\n };\n await this.publish(event);\n return { created: true };\n }\n\n /** Most recent scheduled tick's `occurred_at` (epoch ms) for `type`, or null. */\n async lastScheduledSlotMs(type: string): Promise<number | null> {\n let best: number | null = null;\n for (const e of this.publishedEvents) {\n if (e.type !== type) continue;\n if (e.metadata?.['triggerSource'] !== 'schedule') continue;\n const ms = e.occurredAt.getTime();\n if (best === null || ms > best) best = ms;\n }\n return best;\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 // Cast is safe — callers pass a typed handler; we store as the base type\n const set = this.handlers.get(eventType)!;\n const h = handler as (event: DomainEvent) => Promise<void>;\n set.add(h);\n\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 keyset = query.cursor ? decodeEventCursor(query.cursor) : null;\n\n const str = (e: DomainEvent, key: string): string | null => {\n const v = e.metadata?.[key];\n return typeof v === 'string' ? v : null;\n };\n\n const matched = this.publishedEvents.filter((e) => {\n if (query.poolId && str(e, 'pool') !== query.poolId) return false;\n if (query.direction && str(e, 'direction') !== query.direction)\n return false;\n if (query.rootRunId && str(e, 'rootRunId') !== query.rootRunId)\n return false;\n if (query.since && e.occurredAt.getTime() < query.since.getTime())\n return false;\n if (query.tenantId !== undefined) {\n const t = str(e, 'tenantId');\n if (query.tenantId === null) {\n if (t !== null) return false;\n } else if (t !== query.tenantId) {\n return false;\n }\n }\n return true;\n });\n\n // Order occurred_at DESC, id DESC to match the Drizzle backend keyset.\n matched.sort((a, b) => {\n const dt = b.occurredAt.getTime() - a.occurredAt.getTime();\n if (dt !== 0) return dt;\n return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;\n });\n\n const seeked = keyset\n ? matched.filter((e) => {\n const ct = e.occurredAt.getTime();\n const kt = keyset.occurredAt.getTime();\n if (ct < kt) return true;\n if (ct > kt) return false;\n return e.id < keyset.id;\n })\n : matched;\n\n const hasMore = seeked.length > limit;\n const page = hasMore ? seeked.slice(0, limit) : seeked;\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 /** Remove all published events and subscriptions. Useful in beforeEach. */\n clear(): void {\n this.publishedEvents.length = 0;\n this.handlers.clear();\n }\n\n /** Filter published events by `metadata.pool`. */\n publishedEventsForPool(pool: string): DomainEvent[] {\n return this.publishedEvents.filter((e) => e.metadata?.['pool'] === pool);\n }\n\n /** Filter published events by `metadata.direction`. */\n publishedEventsForDirection(direction: string): DomainEvent[] {\n return this.publishedEvents.filter((e) => e.metadata?.['direction'] === direction);\n }\n\n /**\n * Decide whether `event` should be dispatched to handlers given the\n * current pool filter.\n *\n * Semantics (mirroring `DrizzleEventBus.processBatch`):\n * - `opts.pools` undefined → dispatch everything (no filter).\n * - `opts.pools` empty array → treated as \"no filter\" to match the\n * Drizzle backend, where `pools && pools.length > 0` is the gate on\n * the `inArray` WHERE clause. Empty arrays dispatch everything.\n * - `opts.pools` non-empty → dispatch only when `event.metadata.pool`\n * is in the list. Events without `metadata.pool` do NOT match — they\n * are out of all configured pools by definition.\n */\n private shouldDispatch(event: DomainEvent): boolean {\n const pools = this.opts.pools;\n if (!pools || pools.length === 0) return true;\n const eventPool = event.metadata?.['pool'];\n return typeof eventPool === 'string' && pools.includes(eventPool);\n }\n\n /**\n * Mirror the `domain_events_tier_routing_check` DB CHECK at the memory\n * backend (AUDIT-1). Audit-tier events MUST have null/undefined\n * `pool` and `direction` in metadata; the bridge dispatcher relies on\n * this invariant.\n */\n private assertTierRouting(event: DomainEvent): void {\n const tier = event.metadata?.['tier'];\n if (tier !== 'audit') return;\n const pool = event.metadata?.['pool'];\n const direction = event.metadata?.['direction'];\n const poolIsNull = pool === null || pool === undefined;\n const directionIsNull = direction === null || direction === undefined;\n if (!poolIsNull || !directionIsNull) {\n throw new Error(\n `MemoryEventBus: tier='audit' events must have null pool and direction ` +\n `(got pool=${String(pool)}, direction=${String(direction)}). ` +\n `This mirrors the domain_events CHECK constraint.`,\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":[]}
@@ -4,9 +4,6 @@ import {
4
4
  encodeKeysetCursor,
5
5
  toJobRunSummary
6
6
  } from "./chunk-L3LZWWSX.js";
7
- import {
8
- MissingTenantIdError
9
- } from "./chunk-T4BIIU5E.js";
10
7
  import {
11
8
  jobRuns
12
9
  } from "./chunk-OKXZ63IA.js";
@@ -14,6 +11,9 @@ import {
14
11
  JOBS_MULTI_TENANT,
15
12
  JOB_ORCHESTRATOR
16
13
  } from "./chunk-ZPL74UQN.js";
14
+ import {
15
+ MissingTenantIdError
16
+ } from "./chunk-T4BIIU5E.js";
17
17
  import {
18
18
  DRIZZLE
19
19
  } from "./chunk-U64T4YZE.js";
@@ -198,4 +198,4 @@ DrizzleJobRunService = __decorateClass([
198
198
  export {
199
199
  DrizzleJobRunService
200
200
  };
201
- //# sourceMappingURL=chunk-3A34R6CI.js.map
201
+ //# sourceMappingURL=chunk-5LXOJGO2.js.map
@@ -1,9 +1,9 @@
1
- import {
2
- cacheEntries
3
- } from "./chunk-FASRXRX5.js";
4
1
  import {
5
2
  CACHE_DEFAULT_TTL
6
3
  } from "./chunk-L6FTY45T.js";
4
+ import {
5
+ cacheEntries
6
+ } from "./chunk-FASRXRX5.js";
7
7
  import {
8
8
  DRIZZLE
9
9
  } from "./chunk-U64T4YZE.js";
@@ -109,4 +109,4 @@ DrizzleCacheService = __decorateClass([
109
109
  export {
110
110
  DrizzleCacheService
111
111
  };
112
- //# sourceMappingURL=chunk-T6C4LFLC.js.map
112
+ //# sourceMappingURL=chunk-7YGORYZD.js.map
@@ -7,6 +7,12 @@ import {
7
7
  import {
8
8
  PostgresCursorStore
9
9
  } from "./chunk-XWBK3XJK.js";
10
+ import {
11
+ MemoryCursorStore
12
+ } from "./chunk-AHV4GDYM.js";
13
+ import {
14
+ DeepEqualDiffer
15
+ } from "./chunk-JEINYUJH.js";
10
16
  import {
11
17
  INTEGRATION_CURSOR_STORE,
12
18
  INTEGRATION_FIELD_DIFFER,
@@ -14,12 +20,6 @@ import {
14
20
  INTEGRATION_MULTI_TENANT,
15
21
  INTEGRATION_RUN_RECORDER
16
22
  } from "./chunk-S7C6TIIF.js";
17
- import {
18
- MemoryCursorStore
19
- } from "./chunk-AHV4GDYM.js";
20
- import {
21
- DeepEqualDiffer
22
- } from "./chunk-JEINYUJH.js";
23
23
  import {
24
24
  __decorateClass
25
25
  } from "./chunk-2E224ZSN.js";
@@ -84,4 +84,4 @@ IntegrationModule = __decorateClass([
84
84
  export {
85
85
  IntegrationModule
86
86
  };
87
- //# sourceMappingURL=chunk-PKDS6QIJ.js.map
87
+ //# sourceMappingURL=chunk-ATVGYF3D.js.map
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-OKXZ63IA.js";
4
4
  import {
5
5
  domainEvents
6
- } from "./chunk-OFRRBC7M.js";
6
+ } from "./chunk-E2BRT5IB.js";
7
7
 
8
8
  // runtime/subsystems/bridge/bridge-delivery.schema.ts
9
9
  import {
@@ -89,4 +89,4 @@ export {
89
89
  bridgeDeliveryStatusEnum,
90
90
  bridgeDelivery
91
91
  };
92
- //# sourceMappingURL=chunk-2TVVBC53.js.map
92
+ //# sourceMappingURL=chunk-BORNCTH3.js.map
@@ -1,3 +1,12 @@
1
+ import {
2
+ jobRuns,
3
+ jobSteps,
4
+ jobs
5
+ } from "./chunk-OKXZ63IA.js";
6
+ import {
7
+ JOBS_LISTEN_NOTIFY,
8
+ JOBS_MULTI_TENANT
9
+ } from "./chunk-ZPL74UQN.js";
1
10
  import {
2
11
  JobCollisionError,
3
12
  JobNotReplayableError,
@@ -9,15 +18,6 @@ import {
9
18
  keySelectorToTemplate,
10
19
  resolveJobKey
11
20
  } from "./chunk-7P5ODGLA.js";
12
- import {
13
- jobRuns,
14
- jobSteps,
15
- jobs
16
- } from "./chunk-OKXZ63IA.js";
17
- import {
18
- JOBS_LISTEN_NOTIFY,
19
- JOBS_MULTI_TENANT
20
- } from "./chunk-ZPL74UQN.js";
21
21
  import {
22
22
  JOBS_WAKE_CHANNEL,
23
23
  pgNotify
@@ -393,4 +393,4 @@ export {
393
393
  evaluateKeyTemplate,
394
394
  DrizzleJobOrchestrator
395
395
  };
396
- //# sourceMappingURL=chunk-7MMS36AN.js.map
396
+ //# sourceMappingURL=chunk-CUSMC2KK.js.map
@@ -9,8 +9,15 @@ var MissingTenantIdError = class extends Error {
9
9
  eventType;
10
10
  name = "MissingTenantIdError";
11
11
  };
12
+ var ScheduleConfigError = class extends Error {
13
+ name = "ScheduleConfigError";
14
+ constructor(message) {
15
+ super(`ScheduleConfigError: ${message}`);
16
+ }
17
+ };
12
18
 
13
19
  export {
14
- MissingTenantIdError
20
+ MissingTenantIdError,
21
+ ScheduleConfigError
15
22
  };
16
- //# sourceMappingURL=chunk-V4AF6DI4.js.map
23
+ //# sourceMappingURL=chunk-DUBZOXJC.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../runtime/subsystems/events/events-errors.ts"],"sourcesContent":["/**\n * Typed errors for the events subsystem (ADR-024, EVT-6).\n *\n * All thrown from the publish path of `TypedEventBus`. They exist as\n * classes so consumers can `instanceof` them in catch blocks and\n * exception filters can map them to HTTP codes.\n */\n\n/**\n * Thrown by `TypedEventBus.publish()` when the EventsModule is configured\n * with `multiTenant: true` and the caller did not supply\n * `opts.metadata.tenantId`. Multi-tenant mode requires every outbox row to\n * be attributable to a tenant — the `domain_events.tenant_id` column is\n * populated from this value and the drain loop uses it for future\n * tenant-scoped filtering (deferred — see ADR-024 §Multi-tenancy).\n *\n * Disable multi-tenancy at the module level (`multiTenant: false`, the\n * default) to opt out of the requirement entirely.\n */\nexport class MissingTenantIdError extends Error {\n override readonly name = 'MissingTenantIdError';\n constructor(public readonly eventType: string) {\n super(\n `Missing tenantId for event '${eventType}'. EventsModule is configured ` +\n `with multiTenant: true — every publish must include ` +\n `opts.metadata.tenantId. Either pass the tenantId or disable ` +\n `multi-tenancy on the module.`,\n );\n }\n}\n"],"mappings":";AAmBO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAE9C,YAA4B,WAAmB;AAC7C;AAAA,MACE,+BAA+B,SAAS;AAAA,IAI1C;AAN0B;AAAA,EAO5B;AAAA,EAP4B;AAAA,EADV,OAAO;AAS3B;","names":[]}
1
+ {"version":3,"sources":["../runtime/subsystems/events/events-errors.ts"],"sourcesContent":["/**\n * Typed errors for the events subsystem (ADR-024, EVT-6).\n *\n * All thrown from the publish path of `TypedEventBus`. They exist as\n * classes so consumers can `instanceof` them in catch blocks and\n * exception filters can map them to HTTP codes.\n */\n\n/**\n * Thrown by `TypedEventBus.publish()` when the EventsModule is configured\n * with `multiTenant: true` and the caller did not supply\n * `opts.metadata.tenantId`. Multi-tenant mode requires every outbox row to\n * be attributable to a tenant — the `domain_events.tenant_id` column is\n * populated from this value and the drain loop uses it for future\n * tenant-scoped filtering (deferred — see ADR-024 §Multi-tenancy).\n *\n * Disable multi-tenancy at the module level (`multiTenant: false`, the\n * default) to opt out of the requirement entirely.\n */\nexport class MissingTenantIdError extends Error {\n override readonly name = 'MissingTenantIdError';\n constructor(public readonly eventType: string) {\n super(\n `Missing tenantId for event '${eventType}'. EventsModule is configured ` +\n `with multiTenant: true — every publish must include ` +\n `opts.metadata.tenantId. Either pass the tenantId or disable ` +\n `multi-tenancy on the module.`,\n );\n }\n}\n\n/**\n * Thrown (ADR-039) when a scheduled event's `schedule` config is invalid at\n * runtime — a malformed `schedule.every` duration, or a scheduled event with no\n * direction/pool to route by. Codegen validates `schedule` at `gen-all` time;\n * this is the boot backstop for a hand-edited registry or version skew. Raised\n * by `EventScheduler` construction / `parseEvery`.\n */\nexport class ScheduleConfigError extends Error {\n override readonly name = 'ScheduleConfigError';\n constructor(message: string) {\n super(`ScheduleConfigError: ${message}`);\n }\n}\n"],"mappings":";AAmBO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAE9C,YAA4B,WAAmB;AAC7C;AAAA,MACE,+BAA+B,SAAS;AAAA,IAI1C;AAN0B;AAAA,EAO5B;AAAA,EAP4B;AAAA,EADV,OAAO;AAS3B;AASO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAC3B,OAAO;AAAA,EACzB,YAAY,SAAiB;AAC3B,UAAM,wBAAwB,OAAO,EAAE;AAAA,EACzC;AACF;","names":[]}