@nimbus-cqrs/eventsourcingdb 2.5.0 → 3.0.0-beta.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 (87) hide show
  1. package/README.md +82 -46
  2. package/dist/index.d.ts +8 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +8 -0
  5. package/dist/index.js.map +1 -0
  6. package/{esm → dist}/lib/client.d.ts +28 -17
  7. package/{esm → dist}/lib/client.d.ts.map +1 -1
  8. package/{esm → dist}/lib/client.js +25 -19
  9. package/dist/lib/client.js.map +1 -0
  10. package/dist/lib/consumerRunner.d.ts +62 -0
  11. package/dist/lib/consumerRunner.d.ts.map +1 -0
  12. package/dist/lib/consumerRunner.js +386 -0
  13. package/dist/lib/consumerRunner.js.map +1 -0
  14. package/dist/lib/eventConsumer.d.ts +94 -0
  15. package/dist/lib/eventConsumer.d.ts.map +1 -0
  16. package/dist/lib/eventConsumer.js +2 -0
  17. package/dist/lib/eventConsumer.js.map +1 -0
  18. package/{esm → dist}/lib/eventMapping.d.ts +1 -1
  19. package/dist/lib/eventMapping.d.ts.map +1 -0
  20. package/{esm → dist}/lib/eventMapping.js +2 -1
  21. package/dist/lib/eventMapping.js.map +1 -0
  22. package/dist/lib/eventObserver.d.ts +158 -0
  23. package/dist/lib/eventObserver.d.ts.map +1 -0
  24. package/dist/lib/eventObserver.js +339 -0
  25. package/dist/lib/eventObserver.js.map +1 -0
  26. package/dist/lib/observerRegistry.d.ts +39 -0
  27. package/dist/lib/observerRegistry.d.ts.map +1 -0
  28. package/dist/lib/observerRegistry.js +56 -0
  29. package/dist/lib/observerRegistry.js.map +1 -0
  30. package/{esm → dist}/lib/readEvents.d.ts.map +1 -1
  31. package/{esm → dist}/lib/readEvents.js +3 -2
  32. package/dist/lib/readEvents.js.map +1 -0
  33. package/{esm → dist}/lib/tracing.d.ts.map +1 -1
  34. package/{esm → dist}/lib/tracing.js +2 -1
  35. package/dist/lib/tracing.js.map +1 -0
  36. package/dist/lib/writeEvents.d.ts +29 -0
  37. package/dist/lib/writeEvents.d.ts.map +1 -0
  38. package/dist/lib/writeEvents.js +47 -0
  39. package/dist/lib/writeEvents.js.map +1 -0
  40. package/package.json +32 -13
  41. package/src/index.ts +7 -0
  42. package/src/lib/client.ts +166 -0
  43. package/src/lib/consumerRunner.ts +592 -0
  44. package/src/lib/eventConsumer.ts +95 -0
  45. package/src/lib/eventMapping.ts +115 -0
  46. package/src/lib/eventObserver.ts +620 -0
  47. package/src/lib/observerRegistry.ts +69 -0
  48. package/src/lib/readEvents.ts +28 -0
  49. package/src/lib/tracing.ts +295 -0
  50. package/src/lib/writeEvents.ts +72 -0
  51. package/esm/_dnt.polyfills.d.ts +0 -7
  52. package/esm/_dnt.polyfills.d.ts.map +0 -1
  53. package/esm/_dnt.polyfills.js +0 -1
  54. package/esm/_dnt.shims.d.ts +0 -2
  55. package/esm/_dnt.shims.d.ts.map +0 -1
  56. package/esm/_dnt.shims.js +0 -57
  57. package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts +0 -700
  58. package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts.map +0 -1
  59. package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.js +0 -903
  60. package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.d.ts +0 -14
  61. package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.d.ts.map +0 -1
  62. package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.js +0 -65
  63. package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.d.ts +0 -20
  64. package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.d.ts.map +0 -1
  65. package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.js +0 -43
  66. package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.d.ts +0 -44
  67. package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.d.ts.map +0 -1
  68. package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.js +0 -47
  69. package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.d.ts +0 -44
  70. package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.d.ts.map +0 -1
  71. package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.js +0 -51
  72. package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.d.ts +0 -31
  73. package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.d.ts.map +0 -1
  74. package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.js +0 -37
  75. package/esm/index.d.ts +0 -7
  76. package/esm/index.d.ts.map +0 -1
  77. package/esm/index.js +0 -6
  78. package/esm/lib/eventMapping.d.ts.map +0 -1
  79. package/esm/lib/eventObserver.d.ts +0 -133
  80. package/esm/lib/eventObserver.d.ts.map +0 -1
  81. package/esm/lib/eventObserver.js +0 -299
  82. package/esm/lib/writeEvents.d.ts +0 -11
  83. package/esm/lib/writeEvents.d.ts.map +0 -1
  84. package/esm/lib/writeEvents.js +0 -28
  85. package/esm/package.json +0 -3
  86. /package/{esm → dist}/lib/readEvents.d.ts +0 -0
  87. /package/{esm → dist}/lib/tracing.d.ts +0 -0
@@ -1,299 +0,0 @@
1
- import { calculateBackoffDelay as coreCalculateBackoffDelay, getLogger, withRetry, } from '@nimbus-cqrs/core';
2
- import { SpanStatusCode } from '@opentelemetry/api';
3
- import { getEventSourcingDBClient } from './client.js';
4
- import { isEventData } from './eventMapping.js';
5
- import { observerConnectionReconnectsCounter, observerConnectionRetryAttemptsCounter, observerEventsHandledCounter, observerHandlerRetryAttemptsCounter, observerHandlingDuration, withObserveEventSpan, } from './tracing.js';
6
- const DEFAULT_RETRY_OPTIONS = {
7
- maxRetries: 3,
8
- initialRetryDelayMs: 3000,
9
- };
10
- /**
11
- * Returns a promise that resolves after the given number of milliseconds.
12
- */
13
- const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
14
- const OBSERVER_BACKOFF_OPTIONS = {
15
- jitterFactor: 0.3,
16
- maxDelayMs: Infinity,
17
- };
18
- /**
19
- * Calculates an exponential backoff delay with jitter for a given
20
- * retry attempt. The jitter adds up to 30% of the base delay to
21
- * avoid thundering-herd effects.
22
- *
23
- * @param initialDelayMs - The base delay in milliseconds before
24
- * exponential scaling.
25
- * @param attempt - The zero-based retry attempt number.
26
- * @returns The backoff delay in milliseconds.
27
- *
28
- * @deprecated Import {@link calculateBackoffDelay} from
29
- * `@nimbus-cqrs/core` instead. This wrapper keeps the historical
30
- * 30% jitter default used by EventSourcingDB observers.
31
- */
32
- export const calculateBackoffDelay = (initialDelayMs, attempt) => coreCalculateBackoffDelay(initialDelayMs, attempt, OBSERVER_BACKOFF_OPTIONS);
33
- /**
34
- * Reads the legacy connection-retry alias accepted on an event observer.
35
- */
36
- const readLegacyRetryOptions = (eventObserver) => eventObserver.retryOptions;
37
- /**
38
- * Resolves connection retry options, preferring
39
- * {@link EventObserver.connectionRetryOptions} and falling back to the
40
- * legacy retryOptions alias.
41
- */
42
- const resolveConnectionRetryOptions = (eventObserver) => {
43
- const legacyRetryOptions = readLegacyRetryOptions(eventObserver);
44
- return {
45
- maxRetries: eventObserver.connectionRetryOptions?.maxRetries ??
46
- legacyRetryOptions?.maxRetries ??
47
- DEFAULT_RETRY_OPTIONS.maxRetries,
48
- initialRetryDelayMs: eventObserver.connectionRetryOptions?.initialRetryDelayMs ??
49
- legacyRetryOptions?.initialRetryDelayMs ??
50
- DEFAULT_RETRY_OPTIONS.initialRetryDelayMs,
51
- };
52
- };
53
- /**
54
- * Resolves handler retry options from {@link EventObserver.handlerRetryOptions}.
55
- */
56
- const resolveHandlerRetryOptions = (eventObserver) => ({
57
- maxRetries: eventObserver.handlerRetryOptions?.maxRetries ??
58
- DEFAULT_RETRY_OPTIONS.maxRetries,
59
- initialRetryDelayMs: eventObserver.handlerRetryOptions?.initialRetryDelayMs ??
60
- DEFAULT_RETRY_OPTIONS.initialRetryDelayMs,
61
- });
62
- /**
63
- * Logs an informational message when an event observer connects or
64
- * reconnects to EventSourcingDB. When {@link retryCount} is greater
65
- * than zero the message indicates a successful reconnection.
66
- *
67
- * @param subject - The observed subject.
68
- * @param retryCount - The number of retries that preceded this
69
- * connection (0 for the initial connection).
70
- * @param data - Additional context logged alongside the message.
71
- */
72
- const logObserverConnection = (subject, retryCount, data) => {
73
- const retryLabel = retryCount === 1 ? 'retry' : 'retries';
74
- const message = retryCount > 0
75
- ? `Reconnected event observer for subject "${subject}" after ${retryCount} ${retryLabel}`
76
- : `Observing events for subject "${subject}"`;
77
- getLogger().info({ category: 'Nimbus', message, data });
78
- };
79
- /**
80
- * Handles a connection / stream error by logging it and waiting with
81
- * exponential backoff before the next reconnect attempt. When the
82
- * maximum number of retries is exceeded a critical log entry is
83
- * emitted and no further retries are attempted.
84
- *
85
- * @param error - The error that caused the observer to disconnect.
86
- * @param subject - The observed subject.
87
- * @param retryCount - The current (1-based) retry attempt number.
88
- * @param maxRetries - The maximum number of allowed retries.
89
- * @param initialRetryDelayMs - The base delay used for exponential
90
- * backoff calculation.
91
- * @returns `true` if the observer should retry, `false` if retries
92
- * are exhausted.
93
- */
94
- const handleConnectionError = async (error, subject, retryCount, maxRetries, initialRetryDelayMs) => {
95
- if (retryCount > maxRetries) {
96
- getLogger().critical({
97
- category: 'Nimbus',
98
- message: `Failed to observe events for subject "${subject}" after ${maxRetries} ${maxRetries === 1 ? 'retry' : 'retries'}.`,
99
- });
100
- return false;
101
- }
102
- observerConnectionRetryAttemptsCounter.add(1, { subject });
103
- const backoffDelay = coreCalculateBackoffDelay(initialRetryDelayMs, retryCount - 1, OBSERVER_BACKOFF_OPTIONS);
104
- getLogger().error({
105
- category: 'Nimbus',
106
- message: `Error observing events for subject "${subject}" (retry ${retryCount}/${maxRetries}), retrying in ${backoffDelay}ms`,
107
- error: error,
108
- });
109
- await delay(backoffDelay);
110
- return true;
111
- };
112
- const recordHandlerMetrics = (metricLabels, status, startTime) => {
113
- observerEventsHandledCounter.add(1, { ...metricLabels, status });
114
- observerHandlingDuration.record((performance.now() - startTime) / 1000, metricLabels);
115
- };
116
- /**
117
- * Invokes the event handler with in-place retries. Does not reconnect
118
- * the observe stream. When all retries are exhausted, calls
119
- * {@link EventObserver.onHandlerError} or logs critically, then
120
- * returns without throwing so the observer can skip the event.
121
- */
122
- const handleEventWithRetry = async (eventObserver, event, handlerRetryOptions, span, startTime) => {
123
- const { maxRetries, initialRetryDelayMs } = handlerRetryOptions;
124
- const metricLabels = {
125
- subject: eventObserver.subject,
126
- event_type: event.type,
127
- };
128
- try {
129
- await withRetry(() => eventObserver.eventHandler(event), {
130
- maxRetries,
131
- initialDelayMs: initialRetryDelayMs,
132
- maxDelayMs: Infinity,
133
- jitterFactor: 0.3,
134
- onRetry: ({ error, attempt, delayMs }) => {
135
- observerHandlerRetryAttemptsCounter.add(1, metricLabels);
136
- span.addEvent('retry', {
137
- attempt,
138
- delay_ms: delayMs,
139
- });
140
- getLogger().error({
141
- category: 'Nimbus',
142
- message: `Error handling event "${event.id}" (${event.type}) for subject "${eventObserver.subject}" (retry ${attempt}/${maxRetries}), retrying in ${delayMs}ms`,
143
- error,
144
- ...(isEventData(event.data)
145
- ? {
146
- correlationId: event.data.nimbusMeta.correlationid,
147
- }
148
- : {}),
149
- });
150
- },
151
- });
152
- recordHandlerMetrics(metricLabels, 'success', startTime);
153
- }
154
- catch (error) {
155
- const handlerError = error instanceof Error
156
- ? error
157
- : new Error(String(error));
158
- recordHandlerMetrics(metricLabels, 'skipped', startTime);
159
- span.setStatus({
160
- code: SpanStatusCode.ERROR,
161
- message: handlerError.message,
162
- });
163
- span.recordException(handlerError);
164
- await handleHandlerExhausted(eventObserver, event, handlerError, maxRetries);
165
- }
166
- };
167
- /**
168
- * Handles exhausted handler retries by invoking
169
- * {@link EventObserver.onHandlerError} or emitting a critical log.
170
- */
171
- const handleHandlerExhausted = async (eventObserver, event, handlerError, maxRetries) => {
172
- if (eventObserver.onHandlerError) {
173
- try {
174
- await eventObserver.onHandlerError(handlerError, event);
175
- }
176
- catch (err) {
177
- // Isolate user callback failures so the poison
178
- // event is still skipped and observation continues.
179
- const callbackError = err instanceof Error
180
- ? err
181
- : new Error(String(err));
182
- getLogger().critical({
183
- category: 'Nimbus',
184
- message: `onHandlerError failed for event "${event.id}" (${event.type}) for subject "${eventObserver.subject}". Skipping event.`,
185
- error: callbackError,
186
- ...(isEventData(event.data)
187
- ? {
188
- correlationId: event.data.nimbusMeta.correlationid,
189
- }
190
- : {}),
191
- });
192
- }
193
- return;
194
- }
195
- getLogger().critical({
196
- category: 'Nimbus',
197
- message: `Failed to handle event "${event.id}" (${event.type}) for subject "${eventObserver.subject}" after ${maxRetries} ${maxRetries === 1 ? 'retry' : 'retries'}. Skipping event.`,
198
- error: handlerError,
199
- ...(isEventData(event.data)
200
- ? {
201
- correlationId: event.data.nimbusMeta.correlationid,
202
- }
203
- : {}),
204
- });
205
- };
206
- /**
207
- * Starts observing events for the given {@link EventObserver} with
208
- * automatic reconnection on stream failure and separate in-place
209
- * retries for handler failures.
210
- *
211
- * Events are consumed from the stream and each one is passed to the
212
- * observer's event handler inside an OpenTelemetry span. If the event
213
- * carries a `traceparent`, the span is linked to the original writer's
214
- * trace for end-to-end distributed tracing.
215
- *
216
- * Handler failures are retried without reconnecting. After handler
217
- * retries are exhausted the event is skipped (lower bound advanced)
218
- * so observation can continue. When the connection drops, exponential
219
- * backoff with jitter is applied up to the configured maximum number
220
- * of connection retries.
221
- *
222
- * @param eventObserver - The event observer configuration.
223
- * @returns A promise that resolves when the observe stream ends or
224
- * connection retries are exhausted.
225
- */
226
- export const observeWithRetry = async (eventObserver) => {
227
- const connectionRetryOptions = resolveConnectionRetryOptions(eventObserver);
228
- const handlerRetryOptions = resolveHandlerRetryOptions(eventObserver);
229
- let connectionRetryCount = 0;
230
- let lastProcessedEventId;
231
- while (true) {
232
- try {
233
- // Resolve the client on every attempt so a re-initialized
234
- // singleton is picked up after reconnecting to EventSourcingDB.
235
- const eventSourcingDBClient = getEventSourcingDBClient();
236
- // Once we have a concrete position, use it as lower bound and
237
- // drop fromLatestEvent; otherwise fall back to the original options.
238
- const lowerBound = lastProcessedEventId
239
- ? { id: lastProcessedEventId, type: 'exclusive' }
240
- : eventObserver.lowerBound;
241
- const fromLatestEvent = lastProcessedEventId
242
- ? undefined
243
- : eventObserver.fromLatestEvent;
244
- logObserverConnection(eventObserver.subject, connectionRetryCount, {
245
- recursive: eventObserver.recursive ?? false,
246
- lowerBound,
247
- fromLatestEvent,
248
- });
249
- for await (const event of eventSourcingDBClient.observeEvents(eventObserver.subject, {
250
- recursive: eventObserver.recursive ?? false,
251
- ...(lowerBound ? { lowerBound } : {}),
252
- ...(fromLatestEvent ? { fromLatestEvent } : {}),
253
- })) {
254
- // Stream is healthy once events flow again. Count a successful
255
- // reconnect only here so failed reconnect attempts are excluded.
256
- if (connectionRetryCount > 0) {
257
- observerConnectionReconnectsCounter.add(1, {
258
- subject: eventObserver.subject,
259
- });
260
- }
261
- connectionRetryCount = 0;
262
- const traceContext = event.traceparent
263
- ? {
264
- traceparent: event.traceparent,
265
- tracestate: event.tracestate,
266
- }
267
- : undefined;
268
- const startTime = performance.now();
269
- await withObserveEventSpan(event, eventObserver.subject, traceContext, async (span) => {
270
- await handleEventWithRetry(eventObserver, event, handlerRetryOptions, span, startTime);
271
- });
272
- // Advance after success or skip so poison events do not block
273
- // the stream and reconnects resume past the last attempted event.
274
- lastProcessedEventId = event.id;
275
- }
276
- // If the loop completes normally (stream ended), we're done
277
- return;
278
- }
279
- catch (error) {
280
- connectionRetryCount++;
281
- const shouldRetry = await handleConnectionError(error, eventObserver.subject, connectionRetryCount, connectionRetryOptions.maxRetries, connectionRetryOptions.initialRetryDelayMs);
282
- if (!shouldRetry) {
283
- return;
284
- }
285
- }
286
- }
287
- };
288
- /**
289
- * Initializes an event observer by starting the observation loop in
290
- * the background (non-blocking). The observer will keep running and
291
- * reconnecting according to its connection retry options until the
292
- * stream ends or connection retries are exhausted. Handler failures
293
- * are retried separately and do not stop the observer.
294
- *
295
- * @param eventObserver - The event observer configuration.
296
- */
297
- export const initEventObserver = (eventObserver) => {
298
- void observeWithRetry(eventObserver);
299
- };
@@ -1,11 +0,0 @@
1
- import type { Event } from '@nimbus-cqrs/core';
2
- import type { Precondition } from 'eventsourcingdb';
3
- /**
4
- * Writes one or more Nimbus events to EventSourcingDB. Each event is
5
- * converted into an EventSourcingDB event candidate before being persisted.
6
- *
7
- * @param events - The Nimbus events to write.
8
- * @param preconditions - Optional preconditions that must be met for the write to succeed.
9
- */
10
- export declare const writeEvents: (events: Event[], preconditions?: Precondition[]) => Promise<void>;
11
- //# sourceMappingURL=writeEvents.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"writeEvents.d.ts","sourceRoot":"","sources":["../../src/lib/writeEvents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qDAAqD,CAAC;AAEjF,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAapE;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GACpB,QAAQ,KAAK,EAAE,EACf,gBAAgB,YAAY,EAAE,KAC/B,OAAO,CAAC,IAAI,CAsBd,CAAC"}
@@ -1,28 +0,0 @@
1
- import { context, propagation } from '@opentelemetry/api';
2
- import { getEventSourcingDBClient } from './client.js';
3
- import { nimbusEventToEventSourcingDBEventCandidate } from './eventMapping.js';
4
- import { eventSizeBytes, withSpan } from './tracing.js';
5
- const textEncoder = new TextEncoder();
6
- /**
7
- * Returns the UTF-8 byte length of a serialized Nimbus event.
8
- */
9
- const getEventSizeBytes = (event) => textEncoder.encode(JSON.stringify(event)).length;
10
- /**
11
- * Writes one or more Nimbus events to EventSourcingDB. Each event is
12
- * converted into an EventSourcingDB event candidate before being persisted.
13
- *
14
- * @param events - The Nimbus events to write.
15
- * @param preconditions - Optional preconditions that must be met for the write to succeed.
16
- */
17
- export const writeEvents = (events, preconditions) => {
18
- return withSpan('writeEvents', async () => {
19
- const eventSourcingDBClient = getEventSourcingDBClient();
20
- const carrier = {};
21
- propagation.inject(context.active(), carrier);
22
- const eventCandidates = events.map((event) => {
23
- eventSizeBytes.record(getEventSizeBytes(event));
24
- return nimbusEventToEventSourcingDBEventCandidate(event, carrier['traceparent'], carrier['tracestate']);
25
- });
26
- await eventSourcingDBClient.writeEvents(eventCandidates, preconditions);
27
- });
28
- };
package/esm/package.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "type": "module"
3
- }
File without changes
File without changes