@nimbus-cqrs/eventsourcingdb 2.4.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 (88) 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 +3 -0
  34. package/dist/lib/tracing.d.ts.map +1 -0
  35. package/{esm → dist}/lib/tracing.js +5 -1
  36. package/dist/lib/tracing.js.map +1 -0
  37. package/dist/lib/writeEvents.d.ts +29 -0
  38. package/dist/lib/writeEvents.d.ts.map +1 -0
  39. package/dist/lib/writeEvents.js +47 -0
  40. package/dist/lib/writeEvents.js.map +1 -0
  41. package/package.json +32 -13
  42. package/src/index.ts +7 -0
  43. package/src/lib/client.ts +166 -0
  44. package/src/lib/consumerRunner.ts +592 -0
  45. package/src/lib/eventConsumer.ts +95 -0
  46. package/src/lib/eventMapping.ts +115 -0
  47. package/src/lib/eventObserver.ts +620 -0
  48. package/src/lib/observerRegistry.ts +69 -0
  49. package/src/lib/readEvents.ts +28 -0
  50. package/src/lib/tracing.ts +295 -0
  51. package/src/lib/writeEvents.ts +72 -0
  52. package/esm/_dnt.polyfills.d.ts +0 -7
  53. package/esm/_dnt.polyfills.d.ts.map +0 -1
  54. package/esm/_dnt.polyfills.js +0 -1
  55. package/esm/_dnt.shims.d.ts +0 -2
  56. package/esm/_dnt.shims.d.ts.map +0 -1
  57. package/esm/_dnt.shims.js +0 -57
  58. package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts +0 -700
  59. package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts.map +0 -1
  60. package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.js +0 -903
  61. package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.d.ts +0 -14
  62. package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.d.ts.map +0 -1
  63. package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.js +0 -65
  64. package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.d.ts +0 -20
  65. package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.d.ts.map +0 -1
  66. package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.js +0 -43
  67. package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.d.ts +0 -44
  68. package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.d.ts.map +0 -1
  69. package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.js +0 -47
  70. package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.d.ts +0 -44
  71. package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.d.ts.map +0 -1
  72. package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.js +0 -51
  73. package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.d.ts +0 -31
  74. package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.d.ts.map +0 -1
  75. package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.js +0 -37
  76. package/esm/index.d.ts +0 -7
  77. package/esm/index.d.ts.map +0 -1
  78. package/esm/index.js +0 -6
  79. package/esm/lib/eventMapping.d.ts.map +0 -1
  80. package/esm/lib/eventObserver.d.ts +0 -133
  81. package/esm/lib/eventObserver.d.ts.map +0 -1
  82. package/esm/lib/eventObserver.js +0 -291
  83. package/esm/lib/tracing.d.ts.map +0 -1
  84. package/esm/lib/writeEvents.d.ts +0 -11
  85. package/esm/lib/writeEvents.d.ts.map +0 -1
  86. package/esm/lib/writeEvents.js +0 -31
  87. package/esm/package.json +0 -3
  88. /package/{esm → dist}/lib/readEvents.d.ts +0 -0
@@ -0,0 +1,28 @@
1
+ import type { Event, ReadEventsOptions } from 'eventsourcingdb';
2
+ import { getEventSourcingDBClient } from './client.ts';
3
+ import { withAsyncGeneratorSpan } from './tracing.ts';
4
+
5
+ /**
6
+ * Reads events from EventSourcingDB for a given subject.
7
+ *
8
+ * Returns an async generator that yields raw EventSourcingDB
9
+ * {@link Event} instances (not Nimbus events). Use
10
+ * {@link eventSourcingDBEventToNimbusEvent} to convert them
11
+ * into Nimbus events if needed.
12
+ *
13
+ * @param subject - The subject to read events for.
14
+ * @param options - Options to control which events are read.
15
+ * @param signal - An optional abort signal to cancel the read.
16
+ * @returns An async generator yielding EventSourcingDB events.
17
+ */
18
+ export const readEvents = (
19
+ subject: string,
20
+ options: ReadEventsOptions,
21
+ signal?: AbortSignal,
22
+ ): AsyncGenerator<Event, void, void> => {
23
+ return withAsyncGeneratorSpan('readEvents', () => {
24
+ const eventSourcingDBClient = getEventSourcingDBClient();
25
+
26
+ return eventSourcingDBClient.readEvents(subject, options, signal);
27
+ });
28
+ };
@@ -0,0 +1,295 @@
1
+ import {
2
+ metrics,
3
+ context as otelContext,
4
+ propagation,
5
+ type Span,
6
+ SpanKind,
7
+ SpanStatusCode,
8
+ trace,
9
+ } from '@opentelemetry/api';
10
+
11
+ export const tracer = trace.getTracer('nimbus');
12
+
13
+ export const DB_SYSTEM = 'eventsourcingdb';
14
+
15
+ const meter = metrics.getMeter('nimbus');
16
+
17
+ const operationCounter = meter.createCounter(
18
+ 'eventsourcingdb_operation_total',
19
+ {
20
+ description: 'Total number of EventSourcingDB operations',
21
+ },
22
+ );
23
+
24
+ const operationDuration = meter.createHistogram(
25
+ 'eventsourcingdb_operation_duration_seconds',
26
+ {
27
+ description: 'Duration of EventSourcingDB operations in seconds',
28
+ unit: 's',
29
+ },
30
+ );
31
+
32
+ /**
33
+ * Histogram of serialized Nimbus event sizes written via writeEvents.
34
+ *
35
+ * Recorded without attributes so the series count does not grow with
36
+ * streams or event types.
37
+ */
38
+ export const eventSizeBytes = meter.createHistogram(
39
+ 'eventsourcingdb_event_size_bytes',
40
+ {
41
+ description: 'Size of events written to EventSourcingDB in bytes',
42
+ unit: 'By',
43
+ },
44
+ );
45
+
46
+ /**
47
+ * Counter of events handled by an event observer, labeled with
48
+ * `status: success|skipped`.
49
+ */
50
+ export const observerEventsHandledCounter = meter.createCounter(
51
+ 'eventsourcingdb_observer_events_handled_total',
52
+ {
53
+ description:
54
+ 'Total number of events handled by EventSourcingDB observers',
55
+ },
56
+ );
57
+
58
+ /**
59
+ * Histogram of observer handler duration in seconds (includes retries).
60
+ */
61
+ export const observerHandlingDuration = meter.createHistogram(
62
+ 'eventsourcingdb_observer_handling_duration_seconds',
63
+ {
64
+ description:
65
+ 'Duration of EventSourcingDB observer event handling in seconds',
66
+ unit: 's',
67
+ },
68
+ );
69
+
70
+ /**
71
+ * Counter of in-place handler retry attempts for event observers.
72
+ */
73
+ export const observerHandlerRetryAttemptsCounter = meter.createCounter(
74
+ 'eventsourcingdb_observer_handler_retry_attempts_total',
75
+ {
76
+ description:
77
+ 'Total number of handler retry attempts for EventSourcingDB observers',
78
+ },
79
+ );
80
+
81
+ /**
82
+ * Counter of connection reconnect attempts scheduled after stream failure.
83
+ */
84
+ export const observerConnectionRetryAttemptsCounter = meter.createCounter(
85
+ 'eventsourcingdb_observer_connection_retry_attempts_total',
86
+ {
87
+ description:
88
+ 'Total number of connection retry attempts for EventSourcingDB observers',
89
+ },
90
+ );
91
+
92
+ /**
93
+ * Counter of successful reconnects after one or more connection retries.
94
+ */
95
+ export const observerConnectionReconnectsCounter = meter.createCounter(
96
+ 'eventsourcingdb_observer_connection_reconnects_total',
97
+ {
98
+ description:
99
+ 'Total number of successful connection reconnects for EventSourcingDB observers',
100
+ },
101
+ );
102
+
103
+ /**
104
+ * Trace context extracted from an EventSourcingDB event, used to link
105
+ * the processing span to the span that originally wrote the event.
106
+ */
107
+ export type TraceContext = {
108
+ traceparent: string;
109
+ tracestate?: string;
110
+ };
111
+
112
+ /**
113
+ * Wraps an async function with OpenTelemetry tracing and metrics.
114
+ *
115
+ * Records:
116
+ * - `eventsourcingdb_operation_total` counter with operation and status labels
117
+ * - `eventsourcingdb_operation_duration_seconds` histogram with operation label
118
+ *
119
+ * @param operation - The EventSourcingDB operation name (e.g., 'readEvents', 'writeEvents')
120
+ * @param fn - The async function to execute within the span
121
+ * @param traceContext - Optional trace context from an EventSourcingDB event to
122
+ * continue a distributed trace from the event writer.
123
+ * @returns The result of the async function
124
+ */
125
+ export const withSpan = <T>(
126
+ operation: string,
127
+ fn: () => Promise<T>,
128
+ traceContext?: TraceContext,
129
+ ): Promise<T> => {
130
+ const startTime = performance.now();
131
+ const metricLabels = {
132
+ operation,
133
+ };
134
+
135
+ const parentContext = traceContext
136
+ ? propagation.extract(otelContext.active(), traceContext)
137
+ : otelContext.active();
138
+
139
+ return tracer.startActiveSpan(
140
+ `eventsourcingdb.${operation}`,
141
+ {
142
+ kind: SpanKind.CLIENT,
143
+ attributes: {
144
+ 'db.system': DB_SYSTEM,
145
+ 'db.operation': operation,
146
+ },
147
+ },
148
+ parentContext,
149
+ async (span) => {
150
+ try {
151
+ const result = await fn();
152
+
153
+ // Record success metrics
154
+ operationCounter.add(1, {
155
+ ...metricLabels,
156
+ status: 'success',
157
+ });
158
+ operationDuration.record(
159
+ (performance.now() - startTime) / 1000,
160
+ metricLabels,
161
+ );
162
+
163
+ return result;
164
+ } catch (error) {
165
+ // Record error metrics
166
+ operationCounter.add(1, {
167
+ ...metricLabels,
168
+ status: 'error',
169
+ });
170
+ operationDuration.record(
171
+ (performance.now() - startTime) / 1000,
172
+ metricLabels,
173
+ );
174
+
175
+ span.setStatus({
176
+ code: SpanStatusCode.ERROR,
177
+ message:
178
+ error instanceof Error
179
+ ? error.message
180
+ : 'Unknown error',
181
+ });
182
+ span.recordException(
183
+ error instanceof Error ? error : new Error('Unknown error'),
184
+ );
185
+ throw error;
186
+ } finally {
187
+ span.end();
188
+ }
189
+ },
190
+ );
191
+ };
192
+
193
+ /**
194
+ * Starts a consumer span for observing a single EventSourcingDB event,
195
+ * optionally linked to the writer's trace via {@link TraceContext}.
196
+ *
197
+ * @param event - The observed EventSourcingDB event.
198
+ * @param observerSubject - The observer's configured subject.
199
+ * @param traceContext - Optional writer trace context from the event.
200
+ * @param fn - Work to run inside the active span.
201
+ */
202
+ export const withObserveEventSpan = <T>(
203
+ event: { id: string; type: string; subject: string },
204
+ observerSubject: string,
205
+ traceContext: TraceContext | undefined,
206
+ fn: (span: Span) => Promise<T>,
207
+ ): Promise<T> => {
208
+ const parentContext = traceContext
209
+ ? propagation.extract(otelContext.active(), traceContext)
210
+ : otelContext.active();
211
+
212
+ return tracer.startActiveSpan(
213
+ 'eventsourcingdb.observeEvent',
214
+ {
215
+ kind: SpanKind.CONSUMER,
216
+ attributes: {
217
+ 'db.system': DB_SYSTEM,
218
+ 'db.operation': 'observeEvent',
219
+ 'messaging.system': 'eventsourcingdb',
220
+ 'messaging.operation': 'process',
221
+ 'messaging.destination': event.type,
222
+ 'cloudevents.event_id': event.id,
223
+ 'cloudevents.event_type': event.type,
224
+ 'cloudevents.event_subject': event.subject,
225
+ 'eventsourcingdb.observer.subject': observerSubject,
226
+ },
227
+ },
228
+ parentContext,
229
+ async (span) => {
230
+ try {
231
+ return await fn(span);
232
+ } finally {
233
+ span.end();
234
+ }
235
+ },
236
+ );
237
+ };
238
+
239
+ /**
240
+ * Wraps an async generator with OpenTelemetry tracing and metrics.
241
+ *
242
+ * Records:
243
+ * - `eventsourcingdb_operation_total` counter with operation and status labels
244
+ * - `eventsourcingdb_operation_duration_seconds` histogram with operation label
245
+ *
246
+ * @param operation - The EventSourcingDB operation name (e.g., 'readEvents')
247
+ * @param fn - The function returning an async generator to execute within the span
248
+ * @returns An async generator that yields the same values as the inner generator
249
+ */
250
+ export async function* withAsyncGeneratorSpan<T>(
251
+ operation: string,
252
+ fn: () => AsyncGenerator<T, void, void>,
253
+ ): AsyncGenerator<T, void, void> {
254
+ const startTime = performance.now();
255
+ const metricLabels = {
256
+ operation,
257
+ };
258
+
259
+ const span = tracer.startSpan(`eventsourcingdb.${operation}`, {
260
+ kind: SpanKind.CLIENT,
261
+ attributes: {
262
+ 'db.system': DB_SYSTEM,
263
+ 'db.operation': operation,
264
+ },
265
+ });
266
+
267
+ try {
268
+ yield* fn();
269
+
270
+ // Record success metrics
271
+ operationCounter.add(1, { ...metricLabels, status: 'success' });
272
+ operationDuration.record(
273
+ (performance.now() - startTime) / 1000,
274
+ metricLabels,
275
+ );
276
+ } catch (error) {
277
+ // Record error metrics
278
+ operationCounter.add(1, { ...metricLabels, status: 'error' });
279
+ operationDuration.record(
280
+ (performance.now() - startTime) / 1000,
281
+ metricLabels,
282
+ );
283
+
284
+ span.setStatus({
285
+ code: SpanStatusCode.ERROR,
286
+ message: error instanceof Error ? error.message : 'Unknown error',
287
+ });
288
+ span.recordException(
289
+ error instanceof Error ? error : new Error('Unknown error'),
290
+ );
291
+ throw error;
292
+ } finally {
293
+ span.end();
294
+ }
295
+ }
@@ -0,0 +1,72 @@
1
+ import type { Event } from '@nimbus-cqrs/core';
2
+ import { context, propagation } from '@opentelemetry/api';
3
+ import type {
4
+ EventCandidate,
5
+ Event as EventSourcingDBEvent,
6
+ Precondition,
7
+ } from 'eventsourcingdb';
8
+ import { getEventSourcingDBClient } from './client.ts';
9
+ import { nimbusEventToEventSourcingDBEventCandidate } from './eventMapping.ts';
10
+ import { eventSizeBytes, withSpan } from './tracing.ts';
11
+
12
+ const textEncoder = new TextEncoder();
13
+
14
+ /**
15
+ * Returns the UTF-8 byte length of a serialized Nimbus event.
16
+ */
17
+ const getEventSizeBytes = (event: Event): number =>
18
+ textEncoder.encode(JSON.stringify(event)).length;
19
+
20
+ /**
21
+ * Writes one or more Nimbus events to EventSourcingDB. Each event is
22
+ * converted into an EventSourcingDB event candidate before being persisted.
23
+ *
24
+ * Returns the written EventSourcingDB events including the fields assigned
25
+ * by the server (`id`, `time`, `hash`, ...). The `id` of the last written
26
+ * event is the new revision of the subject and can be used with the
27
+ * `isSubjectOnEventId` precondition for subsequent writes.
28
+ *
29
+ * The SDK never retries a write on its own. Errors are passed through:
30
+ * - A `DbApiError` (from `eventsourcingdb`) when the server rejects the
31
+ * write, e.g. with `code === ErrorCode.preconditionViolated` when a
32
+ * precondition does not hold.
33
+ * - An `OutcomeUnknownError` when the write may have been stored although
34
+ * the response was lost (stalled connection, abort after sending, ...).
35
+ * Its `cause` holds the original error. Read the subject back before
36
+ * writing again.
37
+ *
38
+ * @param events - The Nimbus events to write.
39
+ * @param preconditions - Optional preconditions that must be met for the write to succeed.
40
+ * @param signal - An optional abort signal to cancel the write. Aborting
41
+ * after the request has been sent does not undo the write; the call then
42
+ * throws an `OutcomeUnknownError`.
43
+ * @returns The written EventSourcingDB events.
44
+ */
45
+ export const writeEvents = (
46
+ events: Event[],
47
+ preconditions?: Precondition[],
48
+ signal?: AbortSignal,
49
+ ): Promise<EventSourcingDBEvent[]> => {
50
+ return withSpan('writeEvents', async () => {
51
+ const eventSourcingDBClient = getEventSourcingDBClient();
52
+
53
+ const carrier: Record<string, string> = {};
54
+ propagation.inject(context.active(), carrier);
55
+
56
+ const eventCandidates: EventCandidate[] = events.map((event) => {
57
+ eventSizeBytes.record(getEventSizeBytes(event));
58
+
59
+ return nimbusEventToEventSourcingDBEventCandidate(
60
+ event,
61
+ carrier.traceparent,
62
+ carrier.tracestate,
63
+ );
64
+ });
65
+
66
+ return await eventSourcingDBClient.writeEvents(
67
+ eventCandidates,
68
+ preconditions,
69
+ signal,
70
+ );
71
+ });
72
+ };
@@ -1,7 +0,0 @@
1
- declare global {
2
- interface Error {
3
- cause?: unknown;
4
- }
5
- }
6
- export {};
7
- //# sourceMappingURL=_dnt.polyfills.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_dnt.polyfills.d.ts","sourceRoot":"","sources":["../src/_dnt.polyfills.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,KAAK;QACb,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB;CACF;AAED,OAAO,EAAE,CAAC"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- export declare const dntGlobalThis: Omit<typeof globalThis, never>;
2
- //# sourceMappingURL=_dnt.shims.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_dnt.shims.d.ts","sourceRoot":"","sources":["../src/_dnt.shims.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,gCAA2C,CAAC"}
package/esm/_dnt.shims.js DELETED
@@ -1,57 +0,0 @@
1
- const dntGlobals = {};
2
- export const dntGlobalThis = createMergeProxy(globalThis, dntGlobals);
3
- function createMergeProxy(baseObj, extObj) {
4
- return new Proxy(baseObj, {
5
- get(_target, prop, _receiver) {
6
- if (prop in extObj) {
7
- return extObj[prop];
8
- }
9
- else {
10
- return baseObj[prop];
11
- }
12
- },
13
- set(_target, prop, value) {
14
- if (prop in extObj) {
15
- delete extObj[prop];
16
- }
17
- baseObj[prop] = value;
18
- return true;
19
- },
20
- deleteProperty(_target, prop) {
21
- let success = false;
22
- if (prop in extObj) {
23
- delete extObj[prop];
24
- success = true;
25
- }
26
- if (prop in baseObj) {
27
- delete baseObj[prop];
28
- success = true;
29
- }
30
- return success;
31
- },
32
- ownKeys(_target) {
33
- const baseKeys = Reflect.ownKeys(baseObj);
34
- const extKeys = Reflect.ownKeys(extObj);
35
- const extKeysSet = new Set(extKeys);
36
- return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
37
- },
38
- defineProperty(_target, prop, desc) {
39
- if (prop in extObj) {
40
- delete extObj[prop];
41
- }
42
- Reflect.defineProperty(baseObj, prop, desc);
43
- return true;
44
- },
45
- getOwnPropertyDescriptor(_target, prop) {
46
- if (prop in extObj) {
47
- return Reflect.getOwnPropertyDescriptor(extObj, prop);
48
- }
49
- else {
50
- return Reflect.getOwnPropertyDescriptor(baseObj, prop);
51
- }
52
- },
53
- has(_target, prop) {
54
- return prop in extObj || prop in baseObj;
55
- },
56
- });
57
- }