@ddd-ts-kit/events 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/README.md +462 -2
  2. package/dist/asyncapi/async-api-document.d.ts +15 -0
  3. package/dist/asyncapi/async-api-document.d.ts.map +1 -0
  4. package/dist/asyncapi/async-api-document.js +2 -0
  5. package/dist/asyncapi/async-api-document.js.map +1 -0
  6. package/dist/asyncapi/async-api-info.d.ts +6 -0
  7. package/dist/asyncapi/async-api-info.d.ts.map +1 -0
  8. package/dist/asyncapi/async-api-info.js +2 -0
  9. package/dist/asyncapi/async-api-info.js.map +1 -0
  10. package/dist/asyncapi/async-api-server.d.ts +7 -0
  11. package/dist/asyncapi/async-api-server.d.ts.map +1 -0
  12. package/dist/asyncapi/async-api-server.js +2 -0
  13. package/dist/asyncapi/async-api-server.js.map +1 -0
  14. package/dist/asyncapi/check-async-api-drift.d.ts +6 -0
  15. package/dist/asyncapi/check-async-api-drift.d.ts.map +1 -0
  16. package/dist/asyncapi/check-async-api-drift.js +6 -0
  17. package/dist/asyncapi/check-async-api-drift.js.map +1 -0
  18. package/dist/asyncapi/generate-async-api.d.ts +16 -0
  19. package/dist/asyncapi/generate-async-api.d.ts.map +1 -0
  20. package/dist/asyncapi/generate-async-api.js +116 -0
  21. package/dist/asyncapi/generate-async-api.js.map +1 -0
  22. package/dist/asyncapi/stringify-async-api.d.ts +4 -0
  23. package/dist/asyncapi/stringify-async-api.d.ts.map +1 -0
  24. package/dist/asyncapi/stringify-async-api.js +5 -0
  25. package/dist/asyncapi/stringify-async-api.js.map +1 -0
  26. package/dist/asyncapi.d.ts +7 -0
  27. package/dist/asyncapi.d.ts.map +1 -0
  28. package/dist/asyncapi.js +7 -0
  29. package/dist/asyncapi.js.map +1 -0
  30. package/dist/catalog/generate-event-catalog.d.ts +12 -0
  31. package/dist/catalog/generate-event-catalog.d.ts.map +1 -0
  32. package/dist/catalog/generate-event-catalog.js +63 -0
  33. package/dist/catalog/generate-event-catalog.js.map +1 -0
  34. package/dist/catalog/payload-field.d.ts +8 -0
  35. package/dist/catalog/payload-field.d.ts.map +1 -0
  36. package/dist/catalog/payload-field.js +2 -0
  37. package/dist/catalog/payload-field.js.map +1 -0
  38. package/dist/catalog/payload-fields.d.ts +7 -0
  39. package/dist/catalog/payload-fields.d.ts.map +1 -0
  40. package/dist/catalog/payload-fields.js +105 -0
  41. package/dist/catalog/payload-fields.js.map +1 -0
  42. package/dist/definition/any-integration-event-definition.d.ts +4 -0
  43. package/dist/definition/any-integration-event-definition.d.ts.map +1 -0
  44. package/dist/definition/any-integration-event-definition.js +2 -0
  45. package/dist/definition/any-integration-event-definition.js.map +1 -0
  46. package/dist/definition/define-integration-event.d.ts +15 -0
  47. package/dist/definition/define-integration-event.d.ts.map +1 -0
  48. package/dist/definition/define-integration-event.js +73 -0
  49. package/dist/definition/define-integration-event.js.map +1 -0
  50. package/dist/definition/event-meta.d.ts +7 -0
  51. package/dist/definition/event-meta.d.ts.map +1 -0
  52. package/dist/definition/event-meta.js +2 -0
  53. package/dist/definition/event-meta.js.map +1 -0
  54. package/dist/definition/event-parse-error.d.ts +14 -0
  55. package/dist/definition/event-parse-error.d.ts.map +1 -0
  56. package/dist/definition/event-parse-error.js +2 -0
  57. package/dist/definition/event-parse-error.js.map +1 -0
  58. package/dist/definition/integration-event-definition.d.ts +20 -0
  59. package/dist/definition/integration-event-definition.d.ts.map +1 -0
  60. package/dist/definition/integration-event-definition.js +2 -0
  61. package/dist/definition/integration-event-definition.js.map +1 -0
  62. package/dist/definition/invalid-event-definition-error.d.ts +4 -0
  63. package/dist/definition/invalid-event-definition-error.d.ts.map +1 -0
  64. package/dist/definition/invalid-event-definition-error.js +7 -0
  65. package/dist/definition/invalid-event-definition-error.js.map +1 -0
  66. package/dist/definition/invalid-event-payload-error.d.ts +9 -0
  67. package/dist/definition/invalid-event-payload-error.d.ts.map +1 -0
  68. package/dist/definition/invalid-event-payload-error.js +16 -0
  69. package/dist/definition/invalid-event-payload-error.js.map +1 -0
  70. package/dist/definition/payload-of.d.ts +3 -0
  71. package/dist/definition/payload-of.d.ts.map +1 -0
  72. package/dist/definition/payload-of.js +2 -0
  73. package/dist/definition/payload-of.js.map +1 -0
  74. package/dist/index.d.ts +37 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +37 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/internal/json-schema/as-schema.d.ts +3 -0
  79. package/dist/internal/json-schema/as-schema.d.ts.map +1 -0
  80. package/dist/internal/json-schema/as-schema.js +5 -0
  81. package/dist/internal/json-schema/as-schema.js.map +1 -0
  82. package/dist/internal/json-schema/compare-text.d.ts +2 -0
  83. package/dist/internal/json-schema/compare-text.d.ts.map +1 -0
  84. package/dist/internal/json-schema/compare-text.js +5 -0
  85. package/dist/internal/json-schema/compare-text.js.map +1 -0
  86. package/dist/internal/json-schema/is-record.d.ts +2 -0
  87. package/dist/internal/json-schema/is-record.d.ts.map +1 -0
  88. package/dist/internal/json-schema/is-record.js +4 -0
  89. package/dist/internal/json-schema/is-record.js.map +1 -0
  90. package/dist/internal/json-schema/local-ref-name.d.ts +3 -0
  91. package/dist/internal/json-schema/local-ref-name.d.ts.map +1 -0
  92. package/dist/internal/json-schema/local-ref-name.js +6 -0
  93. package/dist/internal/json-schema/local-ref-name.js.map +1 -0
  94. package/dist/internal/json-schema/rewrite-local-refs.d.ts +2 -0
  95. package/dist/internal/json-schema/rewrite-local-refs.d.ts.map +1 -0
  96. package/dist/internal/json-schema/rewrite-local-refs.js +19 -0
  97. package/dist/internal/json-schema/rewrite-local-refs.js.map +1 -0
  98. package/dist/internal/json-schema/sort-keys.d.ts +2 -0
  99. package/dist/internal/json-schema/sort-keys.d.ts.map +1 -0
  100. package/dist/internal/json-schema/sort-keys.js +5 -0
  101. package/dist/internal/json-schema/sort-keys.js.map +1 -0
  102. package/dist/internal/json-schema/split-definitions.d.ts +8 -0
  103. package/dist/internal/json-schema/split-definitions.d.ts.map +1 -0
  104. package/dist/internal/json-schema/split-definitions.js +13 -0
  105. package/dist/internal/json-schema/split-definitions.js.map +1 -0
  106. package/dist/outbox/duplicate-outbox-event-error.d.ts +5 -0
  107. package/dist/outbox/duplicate-outbox-event-error.d.ts.map +1 -0
  108. package/dist/outbox/duplicate-outbox-event-error.js +8 -0
  109. package/dist/outbox/duplicate-outbox-event-error.js.map +1 -0
  110. package/dist/outbox/in-memory-outbox-store.d.ts +32 -0
  111. package/dist/outbox/in-memory-outbox-store.d.ts.map +1 -0
  112. package/dist/outbox/in-memory-outbox-store.js +121 -0
  113. package/dist/outbox/in-memory-outbox-store.js.map +1 -0
  114. package/dist/outbox/outbox-publisher.d.ts +14 -0
  115. package/dist/outbox/outbox-publisher.d.ts.map +1 -0
  116. package/dist/outbox/outbox-publisher.js +18 -0
  117. package/dist/outbox/outbox-publisher.js.map +1 -0
  118. package/dist/outbox/outbox-record.d.ts +14 -0
  119. package/dist/outbox/outbox-record.d.ts.map +1 -0
  120. package/dist/outbox/outbox-record.js +2 -0
  121. package/dist/outbox/outbox-record.js.map +1 -0
  122. package/dist/outbox/outbox-stats.d.ts +9 -0
  123. package/dist/outbox/outbox-stats.d.ts.map +1 -0
  124. package/dist/outbox/outbox-stats.js +2 -0
  125. package/dist/outbox/outbox-stats.js.map +1 -0
  126. package/dist/outbox/outbox-status.d.ts +2 -0
  127. package/dist/outbox/outbox-status.d.ts.map +1 -0
  128. package/dist/outbox/outbox-status.js +2 -0
  129. package/dist/outbox/outbox-status.js.map +1 -0
  130. package/dist/outbox/outbox-store.d.ts +33 -0
  131. package/dist/outbox/outbox-store.d.ts.map +1 -0
  132. package/dist/outbox/outbox-store.js +13 -0
  133. package/dist/outbox/outbox-store.js.map +1 -0
  134. package/dist/publishing/unknown-event-error.d.ts +7 -0
  135. package/dist/publishing/unknown-event-error.d.ts.map +1 -0
  136. package/dist/publishing/unknown-event-error.js +12 -0
  137. package/dist/publishing/unknown-event-error.js.map +1 -0
  138. package/dist/publishing/validating-publisher.d.ts +12 -0
  139. package/dist/publishing/validating-publisher.d.ts.map +1 -0
  140. package/dist/publishing/validating-publisher.js +33 -0
  141. package/dist/publishing/validating-publisher.js.map +1 -0
  142. package/dist/registry/create-event-registry.d.ts +4 -0
  143. package/dist/registry/create-event-registry.d.ts.map +1 -0
  144. package/dist/registry/create-event-registry.js +5 -0
  145. package/dist/registry/create-event-registry.js.map +1 -0
  146. package/dist/registry/duplicate-event-definition-error.d.ts +4 -0
  147. package/dist/registry/duplicate-event-definition-error.d.ts.map +1 -0
  148. package/dist/registry/duplicate-event-definition-error.js +7 -0
  149. package/dist/registry/duplicate-event-definition-error.js.map +1 -0
  150. package/dist/registry/event-registry.d.ts +11 -0
  151. package/dist/registry/event-registry.d.ts.map +1 -0
  152. package/dist/registry/event-registry.js +33 -0
  153. package/dist/registry/event-registry.js.map +1 -0
  154. package/dist/relay/claim-window.d.ts +6 -0
  155. package/dist/relay/claim-window.d.ts.map +1 -0
  156. package/dist/relay/claim-window.js +2 -0
  157. package/dist/relay/claim-window.js.map +1 -0
  158. package/dist/relay/error-code.d.ts +6 -0
  159. package/dist/relay/error-code.d.ts.map +1 -0
  160. package/dist/relay/error-code.js +16 -0
  161. package/dist/relay/error-code.js.map +1 -0
  162. package/dist/relay/event-key.d.ts +3 -0
  163. package/dist/relay/event-key.d.ts.map +1 -0
  164. package/dist/relay/event-key.js +2 -0
  165. package/dist/relay/event-key.js.map +1 -0
  166. package/dist/relay/hold-until.d.ts +3 -0
  167. package/dist/relay/hold-until.d.ts.map +1 -0
  168. package/dist/relay/hold-until.js +2 -0
  169. package/dist/relay/hold-until.js.map +1 -0
  170. package/dist/relay/in-memory-relay-signal.d.ts +9 -0
  171. package/dist/relay/in-memory-relay-signal.d.ts.map +1 -0
  172. package/dist/relay/in-memory-relay-signal.js +13 -0
  173. package/dist/relay/in-memory-relay-signal.js.map +1 -0
  174. package/dist/relay/key-order.d.ts +35 -0
  175. package/dist/relay/key-order.d.ts.map +1 -0
  176. package/dist/relay/key-order.js +102 -0
  177. package/dist/relay/key-order.js.map +1 -0
  178. package/dist/relay/keyed-record.d.ts +6 -0
  179. package/dist/relay/keyed-record.d.ts.map +1 -0
  180. package/dist/relay/keyed-record.js +2 -0
  181. package/dist/relay/keyed-record.js.map +1 -0
  182. package/dist/relay/outbox-backoff.d.ts +9 -0
  183. package/dist/relay/outbox-backoff.d.ts.map +1 -0
  184. package/dist/relay/outbox-backoff.js +2 -0
  185. package/dist/relay/outbox-backoff.js.map +1 -0
  186. package/dist/relay/outbox-ordering.d.ts +7 -0
  187. package/dist/relay/outbox-ordering.d.ts.map +1 -0
  188. package/dist/relay/outbox-ordering.js +2 -0
  189. package/dist/relay/outbox-ordering.js.map +1 -0
  190. package/dist/relay/outbox-relay-failure.d.ts +12 -0
  191. package/dist/relay/outbox-relay-failure.d.ts.map +1 -0
  192. package/dist/relay/outbox-relay-failure.js +2 -0
  193. package/dist/relay/outbox-relay-failure.js.map +1 -0
  194. package/dist/relay/outbox-relay.d.ts +85 -0
  195. package/dist/relay/outbox-relay.d.ts.map +1 -0
  196. package/dist/relay/outbox-relay.js +193 -0
  197. package/dist/relay/outbox-relay.js.map +1 -0
  198. package/dist/relay/record-group.d.ts +6 -0
  199. package/dist/relay/record-group.d.ts.map +1 -0
  200. package/dist/relay/record-group.js +2 -0
  201. package/dist/relay/record-group.js.map +1 -0
  202. package/dist/relay/relay-signal.d.ts +15 -0
  203. package/dist/relay/relay-signal.d.ts.map +1 -0
  204. package/dist/relay/relay-signal.js +12 -0
  205. package/dist/relay/relay-signal.js.map +1 -0
  206. package/dist/subscription/consume.d.ts +18 -0
  207. package/dist/subscription/consume.d.ts.map +1 -0
  208. package/dist/subscription/consume.js +10 -0
  209. package/dist/subscription/consume.js.map +1 -0
  210. package/dist/subscription/event-scope-options.d.ts +13 -0
  211. package/dist/subscription/event-scope-options.d.ts.map +1 -0
  212. package/dist/subscription/event-scope-options.js +2 -0
  213. package/dist/subscription/event-scope-options.js.map +1 -0
  214. package/dist/subscription/event-subscription.d.ts +6 -0
  215. package/dist/subscription/event-subscription.d.ts.map +1 -0
  216. package/dist/subscription/event-subscription.js +2 -0
  217. package/dist/subscription/event-subscription.js.map +1 -0
  218. package/dist/subscription/on.d.ts +26 -0
  219. package/dist/subscription/on.d.ts.map +1 -0
  220. package/dist/subscription/on.js +62 -0
  221. package/dist/subscription/on.js.map +1 -0
  222. package/dist/subscription/releasable-processed-event-store.d.ts +14 -0
  223. package/dist/subscription/releasable-processed-event-store.d.ts.map +1 -0
  224. package/dist/subscription/releasable-processed-event-store.js +2 -0
  225. package/dist/subscription/releasable-processed-event-store.js.map +1 -0
  226. package/dist/subscription/subscribe-all.d.ts +5 -0
  227. package/dist/subscription/subscribe-all.d.ts.map +1 -0
  228. package/dist/subscription/subscribe-all.js +12 -0
  229. package/dist/subscription/subscribe-all.js.map +1 -0
  230. package/package.json +37 -4
package/README.md CHANGED
@@ -1,3 +1,463 @@
1
- # Temporary Holding Version
1
+ # @ddd-ts-kit/events
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Integration events between bounded contexts, independent of any broker: typed declarations,
4
+ a registry, validated publication and consumption, a transactional outbox, and the AsyncAPI
5
+ document and event catalog generated from the declarations.
6
+
7
+ ```
8
+ npm i @ddd-ts-kit/events @ddd-ts-kit/validation zod
9
+ ```
10
+
11
+ ## Declare
12
+
13
+ One declaration per event and version, in the producer's `published-language/`. It gives the
14
+ payload type, validates what is published and consumed, and feeds the generators.
15
+
16
+ ```ts
17
+ // contexts/ordering/published-language/order-placed.v1.integration-event.ts
18
+ import { z } from 'zod';
19
+ import { zodSchema } from '@ddd-ts-kit/validation/zod';
20
+ import { defineIntegrationEvent, type PayloadOf } from '@ddd-ts-kit/events';
21
+
22
+ export const orderPlaced = defineIntegrationEvent({
23
+ type: 'ordering.order.placed', // <context>.<aggregate>.<fact>, kebab-case segments
24
+ version: 1,
25
+ description: 'An order was placed by a customer.',
26
+ payload: zodSchema(
27
+ z.object({
28
+ orderId: z.string().describe('Identifier of the order.'),
29
+ total: z.object({ amount: z.number().int(), currency: z.enum(['EUR', 'USD']) }),
30
+ lines: z.array(z.object({ sku: z.string(), quantity: z.number().int().positive() })),
31
+ }),
32
+ ),
33
+ });
34
+
35
+ export type OrderPlaced = PayloadOf<typeof orderPlaced>;
36
+ ```
37
+
38
+ A breaking change is a new declaration with `version: 2`; mark the old one `deprecated: true`
39
+ and keep publishing both until the consumers have moved.
40
+
41
+ ## Publish through the outbox
42
+
43
+ A publisher is a handler of a domain event of its own context: it maps the fact to the public
44
+ payload and publishes to an `IntegrationEventPublisher`, knowing nothing else. With a
45
+ database, that publisher is the `OutboxPublisher`: it only stores. Domain events are dispatched
46
+ at the end of the unit of work of the command, before its commit (the `before-commit` phase,
47
+ the default), so the integration event commits or rolls back with the business write: the
48
+ relay publishes it after the commit, never for a command that rolled back. A publisher is
49
+ never declared `phase: 'after-commit'`: the transaction that would hold its outbox row is over.
50
+
51
+ ```ts
52
+ // contexts/ordering/application/integration/publishers/order-placed.publisher.ts
53
+ import { IdGenerator, IntegrationEventPublisher } from '@ddd-ts-kit/kernel';
54
+ import { DomainEventHandler } from '@ddd-ts-kit/kernel/declarations';
55
+
56
+ @DomainEventHandler(OrderPlacedEvent, { name: 'ordering.order.placed' }, [IntegrationEventPublisher, IdGenerator])
57
+ export class OrderPlacedPublisher implements DomainEventHandler<OrderPlacedEvent> {
58
+ constructor(
59
+ private readonly publisher: IntegrationEventPublisher,
60
+ private readonly ids: IdGenerator,
61
+ ) {}
62
+
63
+ async handle(event: OrderPlacedEvent): Promise<void> {
64
+ const published = orderPlaced.create(
65
+ { orderId: event.aggregateId, total: event.total, lines: event.lines },
66
+ { id: this.ids.next(), occurredAt: event.occurredAt },
67
+ ); // throws InvalidEventPayloadError: publishing an invalid event is a bug
68
+
69
+ await this.publisher.publish([published]);
70
+ }
71
+ }
72
+ ```
73
+
74
+ The correlation id is not passed by hand: `scopedPublisher` stamps it (see "The execution
75
+ context of a consumed event").
76
+
77
+ The composition root binds the store under its port, `OutboxStore`, and replaces the
78
+ publisher of the kernel by the outbox. Ports are abstract classes and the class is what a
79
+ module binds and resolves:
80
+
81
+ ```ts
82
+ // platform/messaging.module.ts
83
+ import { defineContextModule } from '@ddd-ts-kit/di';
84
+ import { IntegrationEventPublisher } from '@ddd-ts-kit/kernel';
85
+ import { createEventRegistry, OutboxPublisher, OutboxStore, validatingPublisher } from '@ddd-ts-kit/events';
86
+
87
+ const registry = createEventRegistry([orderPlaced, invoiceIssued]);
88
+
89
+ export const messagingModule = defineContextModule({
90
+ name: 'messaging',
91
+ register(builder) {
92
+ // The adapter of your database, or InMemoryOutboxStore in a single process.
93
+ builder.bind(OutboxStore).toClass(SqlOutboxStore, [Database]);
94
+ builder
95
+ .rebind(IntegrationEventPublisher)
96
+ .toFactory((resolve) =>
97
+ validatingPublisher(new OutboxPublisher(resolve(OutboxStore)), registry, { unknown: 'reject' }),
98
+ );
99
+ },
100
+ });
101
+ ```
102
+
103
+ ```ts
104
+ // main.ts
105
+ import { createApplication, kernelModule } from '@ddd-ts-kit/di';
106
+ import { Clock, Logger } from '@ddd-ts-kit/kernel';
107
+ import { OutboxRelay, OutboxStore } from '@ddd-ts-kit/events';
108
+
109
+ const app = createApplication({ modules: [kernelModule(), persistenceModule, messagingModule, orderingModule] });
110
+ const logger = app.resolve(Logger);
111
+
112
+ const relay = new OutboxRelay({
113
+ store: app.resolve(OutboxStore),
114
+ publisher: broker, // the real provider: any IntegrationEventPublisher
115
+ clock: app.resolve(Clock),
116
+ maxAttempts: 8,
117
+ onFailure: (failure) => logger.warn('outbox relay failure', { ...failure }), // codes only, never an error message
118
+ });
119
+ relay.start({ intervalMs: 500 });
120
+ process.on('SIGTERM', () => void relay.stop()); // waits for the tick in progress
121
+ ```
122
+
123
+ An adapter `implements OutboxStore` and never extends it: the port has abstract members only.
124
+ Without a container the same objects are built by hand:
125
+ `new OutboxPublisher(store)`, `new OutboxRelay({ store, publisher: broker, clock })`.
126
+
127
+ `relay.tick()` claims a batch, publishes each event, marks it published, or schedules a retry
128
+ with an exponential backoff; after `maxAttempts` the record is `dead` until
129
+ `store.retryDead(ids, now)`. `store.stats()` gives `{ pending, failed, dead }` for a health
130
+ check, `store.purgePublished(before)` keeps the table small.
131
+
132
+ **Tracing across the outbox.** The envelope carries `traceparent`, and every outbox store
133
+ persists and restores it exactly (the contract suite checks it). With
134
+ `kernelModule({ instrumentation: traceApplication() })` the context of the request is stamped
135
+ when the event is appended, and `new OutboxRelay({ store, publisher: tracedPublisher(broker), clock })`
136
+ continues that trace when it publishes later: the relay has no span of its own, and a tick
137
+ that publishes nothing makes none. `OutboxPublisher` names itself `messagingSystem: 'outbox'`,
138
+ which `validatingPublisher` and `scopedPublisher` pass on. See the README of
139
+ `@ddd-ts-kit/tracing`.
140
+
141
+ Delivery is at least once. **By default ordering is not guaranteed, not even per aggregate**:
142
+ a failed event is retried while its successors go out, and several relays may run side by
143
+ side. When order matters, carry a sequence or a version in the payload, or ask the relay for
144
+ it (below).
145
+
146
+ ### Waking the relay instead of polling fast
147
+
148
+ A relay can sleep on a long interval and still publish at once, when the store tells it that
149
+ something was appended. `wakeOn` takes a `RelaySignal`, a port of one method
150
+ (`subscribe(listener): { unsubscribe() }`): PostgreSQL `LISTEN/NOTIFY`, a change stream,
151
+ or `InMemoryRelaySignal` in a single process. A module binds it like the store,
152
+ `builder.bind(RelaySignal).toClass(PostgresRelaySignal, [Database])`, and the relay takes
153
+ `wakeOn: app.resolve(RelaySignal)`.
154
+
155
+ ```ts
156
+ const signal = new InMemoryRelaySignal();
157
+ const store = new InMemoryOutboxStore({ signal }); // notifies after each append
158
+ const relay = new OutboxRelay({ store, publisher: broker, clock, wakeOn: signal });
159
+ relay.start({ intervalMs: 30_000 }); // the interval is now only the safety net
160
+ ```
161
+
162
+ Bursts are coalesced: one tick runs, at most one more is queued, whatever the number of
163
+ notifications. **Polling stays as the safety net**: a signal is a hint, and a notification
164
+ missed while the listener reconnects or the process restarts must only delay the publication
165
+ until the next interval, never lose it. So keep `intervalMs` at the longest delay you accept
166
+ for that case, and never rely on the signal alone. A transactional store must notify after
167
+ the commit (PostgreSQL does it by itself: `NOTIFY` is delivered at commit).
168
+
169
+ ### Ordering per key
170
+
171
+ ```ts
172
+ const relay = new OutboxRelay({
173
+ store,
174
+ publisher: broker,
175
+ clock,
176
+ ordering: 'per-key',
177
+ keyOf: (event) => (orderPlaced.is(event) ? event.payload.orderId : undefined),
178
+ });
179
+ ```
180
+
181
+ With `ordering: 'per-key'` a record is never published while an earlier record with the same
182
+ key is still pending or failed. The price is head-of-line blocking: one event that cannot be
183
+ published holds back every later event of its key (and only of its key) until it goes out or
184
+ is dead. Events for which `keyOf` gives `undefined` are not ordered.
185
+
186
+ What it relies on, and what it does not give:
187
+
188
+ - **Run a single relay.** The memory of which record blocks which key lives in the relay, not
189
+ in the store: the `OutboxStore` port is unchanged, and `claimBatch` alone cannot tell that an
190
+ earlier record is waiting for its retry. Two relays side by side publish concurrently.
191
+ - **A restart forgets it.** If the process restarts while a record is in backoff or leased,
192
+ a later record of the same key that becomes due first is published first, once. Strict
193
+ ordering across restarts needs the key stored with the record and a claim that skips keys
194
+ with an earlier unpublished row: a change of the port and of the table, not done here.
195
+ - A record held back is rescheduled with the head of its key and shows in `stats().failed`
196
+ with the code `OUTBOX_HELD_FOR_ORDER`; the claims it spends waiting are not counted as
197
+ delivery attempts by this relay.
198
+ - A **dead** record stops blocking its key (`onFailure` reports it with `dead: true`). Set
199
+ `maxAttempts: Infinity` if a key must rather stay blocked than skip an event.
200
+ - The broker and the consumer must keep the order too: one partition or one queue with a
201
+ single active consumer per key, and a consumer that does not retry out of order.
202
+
203
+ An `OutboxStore` leases what it hands out: `claimBatch` hides the claimed records for the
204
+ lease of the store, so a relay that dies lets them come back. Adapters are held to
205
+ `outboxStoreContract` of `@ddd-ts-kit/testing/contracts`.
206
+
207
+ ## Consume idempotently
208
+
209
+ With `@ddd-ts-kit/di`, a declared integration event handler is idempotent with nothing to
210
+ write: its idempotency mark and its work run in one unit of work whenever a `UnitOfWork` and a
211
+ `ProcessedEventStore` are bound, which `kernelModule()` does. The composition root builds no
212
+ handler by hand:
213
+
214
+ ```ts
215
+ // contexts/billing/application/integration/handlers/invoice-on-order-placed.integration-handler.ts
216
+ import { CommandBus, type IntegrationEvent } from '@ddd-ts-kit/kernel';
217
+ import { IntegrationEventHandler } from '@ddd-ts-kit/kernel/declarations';
218
+ import { orderPlaced, type OrderPlaced } from '@/contexts/ordering/published-language/index.js';
219
+
220
+ @IntegrationEventHandler(orderPlaced, { consumer: 'billing.invoice-on-order-placed' }, [CommandBus])
221
+ export class InvoiceOnOrderPlaced {
222
+ constructor(private readonly commands: CommandBus) {}
223
+
224
+ async handle(event: IntegrationEvent<OrderPlaced>): Promise<void> {
225
+ await this.commands.execute(new IssueInvoice(event.payload.orderId, event.payload.total));
226
+ }
227
+ }
228
+ ```
229
+
230
+ `consume` is the tool of a project that wires without di. It gives the same semantics to a
231
+ plain function:
232
+
233
+ ```ts
234
+ // contexts/billing/billing.subscriptions.ts
235
+ import { consume, subscribeAll } from '@ddd-ts-kit/events';
236
+
237
+ export const billingSubscriptions = ({ commandBus, processed, unitOfWork }: BillingDependencies) => [
238
+ consume(orderPlaced, { consumer: 'billing.invoice-on-order-placed', processed, unitOfWork }, async (event) => {
239
+ await commandBus.execute(new IssueInvoice(event.payload.orderId, event.payload.total));
240
+ }),
241
+ ];
242
+
243
+ // main.ts
244
+ const subscription = subscribeAll(subscriber, billingSubscriptions(dependencies));
245
+ ```
246
+
247
+ The handler receives a typed, validated event. An event of another version of the same type
248
+ is skipped (subscribe to it with its own declaration); an invalid payload throws, so the
249
+ provider retries or dead-letters.
250
+
251
+ `consume` runs the deduplication and the handler in **one unit of work**: the mark in the
252
+ processed-event store and the state change of the consumer commit together. A redelivery is
253
+ handled once per consumer, and an event whose handler threw is handled again when it comes
254
+ back, because its mark was rolled back with the rest. The command bus middleware joins that
255
+ unit of work (a nested `run` joins the outer transaction), so the handler needs nothing
256
+ special.
257
+
258
+ `on(definition, handler, { consumer, processed, unitOfWork })` is the same thing with
259
+ optional parts, for a consumer that needs no deduplication or has no transaction. Mind one
260
+ case: with `processed` and no `unitOfWork`, the mark is taken before the handler runs. If the
261
+ handler then fails, a transactional store keeps the mark (it was committed on its own) and the
262
+ redelivery is skipped: the event is lost for that consumer. Without a database, use a store
263
+ that can take its mark back, a `ReleasableProcessedEventStore` such as the
264
+ `InMemoryProcessedEventStore` of the kernel: `on` and `consume` release the mark of a handler
265
+ that failed. `ReleasableProcessedEventStore` is a type, not a port of its own: it is the
266
+ `ProcessedEventStore` port of the kernel with its optional `release` made mandatory, and such
267
+ a store is bound under `ProcessedEventStore`.
268
+
269
+ ### The execution context of a consumed event
270
+
271
+ `consume` and `on` take the `ExecutionScope` of the kernel and run everything, the unit of
272
+ work and the idempotency mark included, inside it:
273
+
274
+ ```ts
275
+ consume(orderPlaced, { consumer: 'billing.invoice-on-order-placed', processed, unitOfWork, scope }, handler);
276
+ ```
277
+
278
+ | Field of the context | Value |
279
+ |---|---|
280
+ | `origin` | `event` |
281
+ | `correlationId` | the `correlationId` of the event, or its `id` when it names none |
282
+ | `causationId` | the `id` of the event |
283
+ | `actor` | `systemActor(consumer)`, unless `actorOf(event)` answers one |
284
+
285
+ So the repositories of the consuming context stamp audit columns with
286
+ `system:billing.invoice-on-order-placed`, and a log line of the consumer carries the correlation
287
+ of the request that started it all. The envelope carries no actor by default, on purpose: an
288
+ actor is personal data, and a broker keeps messages. A project that decides to put a reference
289
+ in its own payload or envelope maps it with `actorOf: (event) => Actor | undefined`.
290
+
291
+ On the publishing side, `scopedPublisher` of the kernel fills the correlation in, so use cases
292
+ never pass it by hand:
293
+
294
+ ```ts
295
+ import { ExecutionScope, IntegrationEventPublisher, scopedPublisher } from '@ddd-ts-kit/kernel';
296
+
297
+ builder
298
+ .rebind(IntegrationEventPublisher) // what handlers publish to
299
+ .toFactory((resolve) => scopedPublisher(new OutboxPublisher(resolve(OutboxStore)), resolve(ExecutionScope)));
300
+ ```
301
+
302
+ An event created with a `correlationId` keeps it; one published outside of any execution
303
+ context is left as it is. It is a decorator, not an option of `OutboxPublisher` or of
304
+ `defineIntegrationEvent(...).create`: both stay pure, and the stamp applies to every provider.
305
+
306
+ ## Generate the AsyncAPI document and the catalog
307
+
308
+ ```ts
309
+ // scripts/generate-contracts.ts
310
+ import { generateEventCatalog } from '@ddd-ts-kit/events';
311
+ import { checkAsyncApiDrift, generateAsyncApi, stringifyAsyncApi } from '@ddd-ts-kit/events/asyncapi';
312
+
313
+ const document = generateAsyncApi({
314
+ info: { title: 'Shop events', version: '1.4.0' },
315
+ registry,
316
+ servers: { production: { host: 'broker.example.com:5671', protocol: 'amqps' } },
317
+ });
318
+ await writeFile('docs/integration/asyncapi.json', stringifyAsyncApi(document));
319
+ await writeFile(
320
+ 'docs/integration/event-catalog.md',
321
+ generateEventCatalog(registry, { consumers: { 'ordering.order.placed': ['billing', 'shipping'] } }),
322
+ );
323
+
324
+ // In CI: fail when the committed file is stale.
325
+ if (checkAsyncApiDrift(document, await readFile('docs/integration/asyncapi.json', 'utf8')).drift) {
326
+ process.exit(1);
327
+ }
328
+ ```
329
+
330
+ The AsyncAPI 3.0.0 document has one channel per event type (its address is the type), one
331
+ message per version whose payload is the envelope (`id`, `type`, `version`, `occurredAt`,
332
+ `payload`, `traceparent`, `correlationId`), and one `send` operation per channel. The catalog
333
+ is Markdown: one section per producer, a table of versions per event, and the payload fields
334
+ flattened (`customer.email`, `lines[].sku`). Both outputs depend only on the declarations,
335
+ never on their registration order, so they can be committed and diffed.
336
+ A project generated by the CLI has this script as `scripts/events.ts`, with the same two
337
+ paths.
338
+
339
+ ## Choosing the strategy for your transport
340
+
341
+ The rule is one, whatever the tools: the handler writes the event to the outbox in the
342
+ transaction of the command, a relay publishes it afterwards, the consumer deduplicates. What
343
+ changes with the transport is how the relay runs and what the consumer side must do.
344
+
345
+ | Transport | Relay | Consumer side |
346
+ |---|---|---|
347
+ | same process, no broker | polling relay to the in-memory bus (or woken by the database: PostgreSQL `LISTEN/NOTIFY`) | processed-event store in the consumer's transaction |
348
+ | RabbitMQ and other queue brokers | polling relay publishing with publisher confirms, mark published on confirm | durable queue per consumer, manual ack after commit, dead-letter exchange after bounded retries |
349
+ | Kafka and other logs | polling relay, or change data capture reading the outbox table (Debezium outbox router); key = aggregate id for per-aggregate order | consumer group per consumer, offset committed after the handler's transaction, processed-event store for redelivery |
350
+ | cloud queues and topics (SQS/SNS, Pub/Sub) | polling relay, batch publish | visibility timeout above the handler's worst case, redrive policy to a dead-letter queue |
351
+
352
+ Without a database there is nothing to be atomic with: publish to the bus directly, no outbox.
353
+
354
+ The kit has a provider package for RabbitMQ (`@ddd-ts-kit/rabbitmq`), Kafka
355
+ (`@ddd-ts-kit/kafka`), NATS JetStream (`@ddd-ts-kit/nats`) and Redis Streams
356
+ (`@ddd-ts-kit/redis/event-bus`); each README says how it meets the requirements below and what it does
357
+ not guarantee.
358
+
359
+ ### Same process, no broker
360
+
361
+ - **Relay mode.** `OutboxRelay` publishing to the `InMemoryEventBus`. Wake it with `wakeOn`
362
+ (the in-memory signal, or `LISTEN/NOTIFY` on PostgreSQL) and keep a long interval as the
363
+ safety net.
364
+ - **Publisher requirements.** None beyond the port. The in-memory bus runs the handlers inside
365
+ `publish` and rejects when one fails: the relay then retries the record, so the retry
366
+ policy of the consumers IS the backoff of the relay.
367
+ - **Consumer requirements.** `consume` with the processed-event store and the unit of work of
368
+ the consuming context. A redelivery runs every handler of the type again, so every one of
369
+ them deduplicates.
370
+ - **Ordering.** `per-key` gives it here with nothing else to set up: one relay, one process,
371
+ handlers awaited in order (the restart caveat of the relay still applies).
372
+ - **Dead-lettering.** The outbox is the dead-letter store: a record is `dead` after
373
+ `maxAttempts`, and comes back with `store.retryDead(ids, now)`.
374
+ - **Monitor.** `stats()`: `dead > 0` is an alert; `failed` rising means a consumer keeps
375
+ failing; the age of the oldest pending record is the delivery delay.
376
+
377
+ ### Queue brokers (RabbitMQ, ActiveMQ)
378
+
379
+ - **Relay mode.** Polling or woken relay. The record is marked published only when the broker
380
+ confirmed it.
381
+ - **Publisher requirements.** `publish` must resolve only on the confirmation of the broker
382
+ (publisher confirms) and reject otherwise, with durable exchanges and persistent messages.
383
+ A confirmation lost after the broker took the message makes the relay publish it again:
384
+ duplicates are expected. `@ddd-ts-kit/rabbitmq` implements this row.
385
+ - **Consumer requirements.** One durable queue per consumer and event type, manual
386
+ acknowledgement after the unit of work of the handler committed, `consume` for the
387
+ deduplication. Never acknowledge before the commit.
388
+ - **Ordering.** None by default: retries and competing consumers reorder. Per key only with
389
+ the `per-key` relay AND a single active consumer on the queue AND no retry queue, which is
390
+ rarely worth it; prefer a version in the payload.
391
+ - **Dead-lettering.** Two places. Publishing side: the outbox (`dead`, `retryDead`).
392
+ Consuming side: a bounded number of delayed retries, then a dead queue per consumer queue.
393
+ - **Monitor.** Outbox `stats()`, and on the broker the depth of each work queue (a consumer
394
+ that lags), of each dead queue (must be empty) and the unacknowledged count.
395
+
396
+ ### Logs (Kafka, Redpanda, Pulsar, Kinesis)
397
+
398
+ - **Relay mode.** Polling relay, or change data capture (below).
399
+ - **Publisher requirements.** Idempotent producer with `acks=all`; `publish` resolves on the
400
+ acknowledgement of the write. The message key is the aggregate id, so one aggregate lives in
401
+ one partition. Topic per event type or per context, chosen once.
402
+ - **Consumer requirements.** One consumer group per consuming context. The offset is committed
403
+ after the transaction of the handler, never before; a crash in between redelivers, hence
404
+ the processed-event store. A handler that keeps failing blocks its partition: retry in
405
+ place a bounded number of times, then publish the record to a dead-letter topic and commit.
406
+ - **Ordering.** Per partition, so per key, provided the relay keeps it: `ordering: 'per-key'`
407
+ with `keyOf` returning the same aggregate id, a single relay, and
408
+ `max.in.flight.requests.per.connection` at most 5 with idempotence on.
409
+ - **Dead-lettering.** Outbox on the publishing side, a dead-letter topic per consumer group on
410
+ the consuming side.
411
+ - **Monitor.** Outbox `stats()`, consumer lag per group and partition, dead-letter topic size.
412
+
413
+ ### Cloud queues and topics (SQS/SNS, Pub/Sub)
414
+
415
+ - **Relay mode.** Polling relay. Batch publication (10 entries per call on SQS and SNS) is
416
+ allowed, but the result is per entry: publish, then reject if any entry failed, so the relay
417
+ retries; the entries that did pass are published twice, which the consumer absorbs.
418
+ - **Publisher requirements.** Topic with one subscription (queue) per consumer. `publish`
419
+ resolves on the service acknowledgement. Message attributes carry type, version and trace.
420
+ - **Consumer requirements.** Visibility timeout (ack deadline) above the worst case of the
421
+ handler, extended while it runs if needed; delete (ack) the message after the commit.
422
+ `consume` for the deduplication: these services redeliver by design.
423
+ - **Ordering.** None on standard queues. FIFO queues and ordering keys give it per message
424
+ group, at a throughput cost, with the `per-key` relay and the group id = `keyOf`.
425
+ - **Dead-lettering.** Outbox on the publishing side; a redrive policy (`maxReceiveCount`) to a
426
+ dead-letter queue per consumer queue.
427
+ - **Monitor.** Outbox `stats()`, age of the oldest message per queue, dead-letter queue depth.
428
+
429
+ ### What to monitor on the outbox
430
+
431
+ `store.stats()` gives `{ pending, failed, dead }`; expose them as gauges.
432
+
433
+ | Signal | Meaning | Reaction |
434
+ |---|---|---|
435
+ | `dead > 0` | records gave up after `maxAttempts` | alert; fix the cause, then `retryDead` |
436
+ | `failed > 0` for long | the provider refuses or is down; under `per-key`, held records count here too (`OUTBOX_HELD_FOR_ORDER`) | look at the codes given to `onFailure` |
437
+ | `pending` growing | the relay is slower than the producers, or stopped | check that it runs; raise `batchSize` |
438
+ | age of the oldest pending record | the real delivery delay | alert above your tolerance; it is one query on the table (`min(created_at) where status = 'pending'`), `stats()` does not carry it |
439
+ | `onFailure({ kind: 'tick' })` | the store itself failed | database health |
440
+
441
+ Purge published records regularly (`purgePublished(before)`), or the table and its index grow
442
+ without bound.
443
+
444
+ ### Where change data capture fits
445
+
446
+ Change data capture (Debezium and its outbox event router, or a logical replication reader)
447
+ replaces **the relay, never the table**. Handlers still write the event to the outbox table
448
+ in the transaction of the command; instead of a process polling that table, the capture
449
+ reads the same rows from the transaction log of the database and publishes them, in commit
450
+ order. Consequences:
451
+
452
+ - nothing changes in the handlers, in `OutboxPublisher` or in the consumers;
453
+ - do not run an `OutboxRelay` on the same table for the same destination: one or the other;
454
+ - `status`, `attempts` and `next_attempt_at` are no longer maintained: retries and failures
455
+ are those of the connector, so monitor the connector (lag, failed tasks) instead of
456
+ `stats()`, and purge by age;
457
+ - the event router must map the columns: `id` to the message id, `type` to the topic or
458
+ routing key, the aggregate id to the message key, `payload` and the trace columns to the
459
+ body and headers, so the consumer receives the same envelope as from the relay;
460
+ - ordering per key comes for free (the log is ordered), which is the main reason to choose it.
461
+
462
+ Writing to the broker from the handler and "capturing" the business tables instead is not
463
+ this pattern: it publishes rows, not events, and couples consumers to the schema.
@@ -0,0 +1,15 @@
1
+ import type { JsonSchema } from '@ddd-ts-kit/validation';
2
+ import type { AsyncApiInfo } from './async-api-info.js';
3
+ import type { AsyncApiServer } from './async-api-server.js';
4
+ export interface AsyncApiDocument {
5
+ readonly asyncapi: '3.0.0';
6
+ readonly info: AsyncApiInfo;
7
+ readonly servers?: Record<string, AsyncApiServer>;
8
+ readonly channels: Record<string, JsonSchema>;
9
+ readonly operations: Record<string, JsonSchema>;
10
+ readonly components: {
11
+ readonly messages: Record<string, JsonSchema>;
12
+ readonly schemas?: Record<string, JsonSchema>;
13
+ };
14
+ }
15
+ //# sourceMappingURL=async-api-document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-api-document.d.ts","sourceRoot":"","sources":["../../src/asyncapi/async-api-document.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAClD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAChD,QAAQ,CAAC,UAAU,EAAE;QACnB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAC9C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;KAC/C,CAAC;CACH"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=async-api-document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-api-document.js","sourceRoot":"","sources":["../../src/asyncapi/async-api-document.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ export interface AsyncApiInfo {
2
+ readonly title: string;
3
+ readonly version: string;
4
+ readonly description?: string;
5
+ }
6
+ //# sourceMappingURL=async-api-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-api-info.d.ts","sourceRoot":"","sources":["../../src/asyncapi/async-api-info.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=async-api-info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-api-info.js","sourceRoot":"","sources":["../../src/asyncapi/async-api-info.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ export interface AsyncApiServer {
2
+ readonly host: string;
3
+ /** `amqp`, `kafka`, `nats`, `sqs`... */
4
+ readonly protocol: string;
5
+ readonly description?: string;
6
+ }
7
+ //# sourceMappingURL=async-api-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-api-server.d.ts","sourceRoot":"","sources":["../../src/asyncapi/async-api-server.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=async-api-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-api-server.js","sourceRoot":"","sources":["../../src/asyncapi/async-api-server.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import type { AsyncApiDocument } from './async-api-document.js';
2
+ /** Compares the generated document with the committed one, ignoring line-ending style. */
3
+ export declare function checkAsyncApiDrift(document: AsyncApiDocument, existingFileContent: string): {
4
+ drift: boolean;
5
+ };
6
+ //# sourceMappingURL=check-async-api-drift.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-async-api-drift.d.ts","sourceRoot":"","sources":["../../src/asyncapi/check-async-api-drift.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAE9G"}
@@ -0,0 +1,6 @@
1
+ import { stringifyAsyncApi } from './stringify-async-api.js';
2
+ /** Compares the generated document with the committed one, ignoring line-ending style. */
3
+ export function checkAsyncApiDrift(document, existingFileContent) {
4
+ return { drift: stringifyAsyncApi(document) !== existingFileContent.replaceAll('\r\n', '\n') };
5
+ }
6
+ //# sourceMappingURL=check-async-api-drift.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-async-api-drift.js","sourceRoot":"","sources":["../../src/asyncapi/check-async-api-drift.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,0FAA0F;AAC1F,MAAM,UAAU,kBAAkB,CAAC,QAA0B,EAAE,mBAA2B;IACxF,OAAO,EAAE,KAAK,EAAE,iBAAiB,CAAC,QAAQ,CAAC,KAAK,mBAAmB,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC;AACjG,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { EventRegistry } from '../registry/event-registry.js';
2
+ import type { AsyncApiDocument } from './async-api-document.js';
3
+ import type { AsyncApiInfo } from './async-api-info.js';
4
+ import type { AsyncApiServer } from './async-api-server.js';
5
+ export interface GenerateAsyncApiOptions {
6
+ readonly info: AsyncApiInfo;
7
+ readonly registry: EventRegistry;
8
+ readonly servers?: Readonly<Record<string, AsyncApiServer>>;
9
+ }
10
+ /**
11
+ * One channel per event type (its address is the type), one message per version, one `send`
12
+ * operation per channel. The output depends only on the registry content, never on the
13
+ * registration order, so the document can be committed and diffed in CI.
14
+ */
15
+ export declare function generateAsyncApi(options: GenerateAsyncApiOptions): AsyncApiDocument;
16
+ //# sourceMappingURL=generate-async-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-async-api.d.ts","sourceRoot":"","sources":["../../src/asyncapi/generate-async-api.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;CAC7D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,CAwBnF"}