@nimbus-cqrs/eventsourcingdb 2.0.0-beta.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 (46) hide show
  1. package/LICENSE +85 -0
  2. package/README.md +26 -0
  3. package/esm/_dnt.shims.d.ts +2 -0
  4. package/esm/_dnt.shims.d.ts.map +1 -0
  5. package/esm/_dnt.shims.js +57 -0
  6. package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts +700 -0
  7. package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts.map +1 -0
  8. package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.js +903 -0
  9. package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.d.ts +14 -0
  10. package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.d.ts.map +1 -0
  11. package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.js +65 -0
  12. package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.d.ts +20 -0
  13. package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.d.ts.map +1 -0
  14. package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.js +43 -0
  15. package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.d.ts +44 -0
  16. package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.d.ts.map +1 -0
  17. package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.js +47 -0
  18. package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.d.ts +44 -0
  19. package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.d.ts.map +1 -0
  20. package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.js +51 -0
  21. package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.d.ts +31 -0
  22. package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.d.ts.map +1 -0
  23. package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.js +37 -0
  24. package/esm/index.d.ts +6 -0
  25. package/esm/index.d.ts.map +1 -0
  26. package/esm/index.js +5 -0
  27. package/esm/lib/client.d.ts +90 -0
  28. package/esm/lib/client.d.ts.map +1 -0
  29. package/esm/lib/client.js +112 -0
  30. package/esm/lib/eventMapping.d.ts +51 -0
  31. package/esm/lib/eventMapping.d.ts.map +1 -0
  32. package/esm/lib/eventMapping.js +71 -0
  33. package/esm/lib/eventObserver.d.ts +85 -0
  34. package/esm/lib/eventObserver.d.ts.map +1 -0
  35. package/esm/lib/eventObserver.js +154 -0
  36. package/esm/lib/readEvents.d.ts +16 -0
  37. package/esm/lib/readEvents.d.ts.map +1 -0
  38. package/esm/lib/readEvents.js +21 -0
  39. package/esm/lib/tracing.d.ts +37 -0
  40. package/esm/lib/tracing.d.ts.map +1 -0
  41. package/esm/lib/tracing.js +114 -0
  42. package/esm/lib/writeEvents.d.ts +11 -0
  43. package/esm/lib/writeEvents.d.ts.map +1 -0
  44. package/esm/lib/writeEvents.js +20 -0
  45. package/esm/package.json +3 -0
  46. package/package.json +35 -0
@@ -0,0 +1,51 @@
1
+ import { type Event } from '../deps/jsr.io/@nimbus/core/2.0.0/src/index.js';
2
+ import type { Event as EventSourcingDBEvent, EventCandidate } from 'eventsourcingdb';
3
+ /**
4
+ * Metadata that Nimbus attaches to events stored in EventSourcingDB
5
+ * to preserve correlation and schema information.
6
+ *
7
+ * @property {string} correlationid - A globally unique identifier that indicates a correlation to previous and subsequent messages.
8
+ * @property {string} dataschema - An absolute URL to the schema that the data adheres to (optional).
9
+ */
10
+ export type NimbusEventMetadata = {
11
+ correlationid: string;
12
+ dataschema?: string;
13
+ };
14
+ /**
15
+ * The data structure used to store Nimbus events in EventSourcingDB.
16
+ * It wraps the original event payload together with Nimbus-specific metadata.
17
+ *
18
+ * @property {Record<string, unknown>} payload - The actual business data of the event.
19
+ * @property {NimbusEventMetadata} nimbusMeta - Nimbus-specific metadata such as correlation id and data schema.
20
+ */
21
+ export type EventData = {
22
+ payload: Record<string, unknown>;
23
+ nimbusMeta: NimbusEventMetadata;
24
+ };
25
+ /**
26
+ * Type guard that checks whether the given value conforms to the {@link EventData} structure
27
+ * by verifying the presence of both `payload` and `nimbusMeta` properties.
28
+ *
29
+ * @param data - The value to check.
30
+ * @returns `true` if the value is an {@link EventData}, `false` otherwise.
31
+ */
32
+ export declare const isEventData: (data: unknown) => data is EventData;
33
+ /**
34
+ * Converts a Nimbus {@link Event} into an EventSourcingDB {@link EventCandidate}
35
+ * by mapping the event properties and wrapping the data with Nimbus metadata.
36
+ *
37
+ * @param event - The Nimbus event to convert.
38
+ * @returns An EventSourcingDB event candidate ready to be written.
39
+ */
40
+ export declare const nimbusEventToEventSourcingDBEventCandidate: (event: Event, traceparent?: string, tracestate?: string) => EventCandidate;
41
+ /**
42
+ * Converts an EventSourcingDB event back into a Nimbus {@link Event}.
43
+ * If the event data contains Nimbus metadata, it extracts the original payload
44
+ * and correlation information. Otherwise, it treats the entire data as the payload
45
+ * and generates a new correlation id.
46
+ *
47
+ * @param eventSourcingDBEvent - The EventSourcingDB event to convert.
48
+ * @returns A Nimbus event.
49
+ */
50
+ export declare const eventSourcingDBEventToNimbusEvent: <TEvent extends Event>(eventSourcingDBEvent: EventSourcingDBEvent) => TEvent;
51
+ //# sourceMappingURL=eventMapping.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eventMapping.d.ts","sourceRoot":"","sources":["../../src/lib/eventMapping.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,gDAAgD,CAAC;AAEzF,OAAO,KAAK,EACR,KAAK,IAAI,oBAAoB,EAC7B,cAAc,EACjB,MAAM,iBAAiB,CAAC;AAEzB;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,UAAU,EAAE,mBAAmB,CAAC;CACnC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,OAAO,KAAG,IAAI,IAAI,SAOnD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,0CAA0C,GACnD,OAAO,KAAK,EACZ,cAAc,MAAM,EACpB,aAAa,MAAM,KACpB,cAeF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,iCAAiC,GAAI,MAAM,SAAS,KAAK,EAClE,sBAAsB,oBAAoB,KAC3C,MAwBF,CAAC"}
@@ -0,0 +1,71 @@
1
+ import { createEvent } from '../deps/jsr.io/@nimbus/core/2.0.0/src/index.js';
2
+ import { ulid } from '../deps/jsr.io/@std/ulid/1.0.0/mod.js';
3
+ /**
4
+ * Type guard that checks whether the given value conforms to the {@link EventData} structure
5
+ * by verifying the presence of both `payload` and `nimbusMeta` properties.
6
+ *
7
+ * @param data - The value to check.
8
+ * @returns `true` if the value is an {@link EventData}, `false` otherwise.
9
+ */
10
+ export const isEventData = (data) => {
11
+ return (typeof data === 'object' &&
12
+ data !== null &&
13
+ 'payload' in data &&
14
+ 'nimbusMeta' in data);
15
+ };
16
+ /**
17
+ * Converts a Nimbus {@link Event} into an EventSourcingDB {@link EventCandidate}
18
+ * by mapping the event properties and wrapping the data with Nimbus metadata.
19
+ *
20
+ * @param event - The Nimbus event to convert.
21
+ * @returns An EventSourcingDB event candidate ready to be written.
22
+ */
23
+ export const nimbusEventToEventSourcingDBEventCandidate = (event, traceparent, tracestate) => {
24
+ return {
25
+ source: event.source,
26
+ subject: event.subject,
27
+ type: event.type,
28
+ data: {
29
+ payload: event.data,
30
+ nimbusMeta: {
31
+ correlationid: event.correlationid,
32
+ ...(event.dataschema && { dataschema: event.dataschema }),
33
+ },
34
+ },
35
+ ...(traceparent && { traceparent: traceparent }),
36
+ ...(tracestate && { tracestate: tracestate }),
37
+ };
38
+ };
39
+ /**
40
+ * Converts an EventSourcingDB event back into a Nimbus {@link Event}.
41
+ * If the event data contains Nimbus metadata, it extracts the original payload
42
+ * and correlation information. Otherwise, it treats the entire data as the payload
43
+ * and generates a new correlation id.
44
+ *
45
+ * @param eventSourcingDBEvent - The EventSourcingDB event to convert.
46
+ * @returns A Nimbus event.
47
+ */
48
+ export const eventSourcingDBEventToNimbusEvent = (eventSourcingDBEvent) => {
49
+ let data;
50
+ let correlationid;
51
+ let dataschema;
52
+ if (isEventData(eventSourcingDBEvent.data)) {
53
+ data = eventSourcingDBEvent.data.payload;
54
+ correlationid = eventSourcingDBEvent.data.nimbusMeta.correlationid;
55
+ dataschema = eventSourcingDBEvent.data.nimbusMeta.dataschema;
56
+ }
57
+ else {
58
+ data = eventSourcingDBEvent.data;
59
+ correlationid = ulid();
60
+ }
61
+ return createEvent({
62
+ id: eventSourcingDBEvent.id,
63
+ time: eventSourcingDBEvent.time.toISOString(),
64
+ source: eventSourcingDBEvent.source,
65
+ subject: eventSourcingDBEvent.subject,
66
+ type: eventSourcingDBEvent.type,
67
+ correlationid: correlationid,
68
+ data: data,
69
+ ...(dataschema && { dataschema: dataschema }),
70
+ });
71
+ };
@@ -0,0 +1,85 @@
1
+ import type { Event as EventSourcingDBEvent } from 'eventsourcingdb';
2
+ type Bound = {
3
+ id: string;
4
+ type: 'inclusive' | 'exclusive';
5
+ };
6
+ type ObserveFromLatestEvent = {
7
+ subject: string;
8
+ type: string;
9
+ ifEventIsMissing: 'read-everything' | 'wait-for-event';
10
+ };
11
+ export type RetryOptions = {
12
+ /**
13
+ * The maximum number of retry attempts before giving up.
14
+ * Defaults to 3.
15
+ */
16
+ maxRetries: number;
17
+ /**
18
+ * The initial delay in milliseconds before the first retry.
19
+ * Subsequent retries will use exponential backoff with jitter.
20
+ * Defaults to 3000ms.
21
+ */
22
+ initialRetryDelayMs: number;
23
+ };
24
+ /**
25
+ * An event observer defines a handler function which will be applied to each event
26
+ * and the options to observe the events according to the EventSourcingDB API.
27
+ *
28
+ * See https://docs.eventsourcingdb.io/getting-started/observing-events for more information.
29
+ */
30
+ export type EventObserver = {
31
+ /**
32
+ * The subject of the events to observe.
33
+ */
34
+ subject: string;
35
+ /**
36
+ * Whether to observe events recursively.
37
+ * Defaults to false.
38
+ */
39
+ recursive?: boolean;
40
+ /**
41
+ * The lower bound of the events to observe.
42
+ * Defaults to undefined.
43
+ */
44
+ lowerBound?: Bound;
45
+ /**
46
+ * The from latest event to observe.
47
+ * Defaults to undefined.
48
+ */
49
+ fromLatestEvent?: ObserveFromLatestEvent;
50
+ /**
51
+ * The event handler which will be called when an event is observed.
52
+ *
53
+ * @param event - The EventSourcingDB event that was observed.
54
+ * @returns A promise that resolves when the event has been handled.
55
+ */
56
+ eventHandler: (event: EventSourcingDBEvent) => Promise<void> | void;
57
+ /**
58
+ * Options for retry behavior when the connection fails.
59
+ * Uses exponential backoff with jitter between retries.
60
+ * Defaults to { maxRetries: 3, initialRetryDelayMs: 3000 }.
61
+ */
62
+ retryOptions?: RetryOptions;
63
+ };
64
+ /**
65
+ * Calculates an exponential backoff delay with jitter for a given
66
+ * retry attempt. The jitter adds up to 30% of the base delay to
67
+ * avoid thundering-herd effects.
68
+ *
69
+ * @param initialDelayMs - The base delay in milliseconds before
70
+ * exponential scaling.
71
+ * @param attempt - The zero-based retry attempt number.
72
+ * @returns The backoff delay in milliseconds.
73
+ */
74
+ export declare const calculateBackoffDelay: (initialDelayMs: number, attempt: number) => number;
75
+ /**
76
+ * Initializes an event observer by starting the observation loop in
77
+ * the background (non-blocking). The observer will keep running and
78
+ * reconnecting according to its retry options until the stream ends
79
+ * or retries are exhausted.
80
+ *
81
+ * @param eventObserver - The event observer configuration.
82
+ */
83
+ export declare const initEventObserver: (eventObserver: EventObserver) => void;
84
+ export {};
85
+ //# sourceMappingURL=eventObserver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eventObserver.d.ts","sourceRoot":"","sources":["../../src/lib/eventObserver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,IAAI,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAIrE,KAAK,KAAK,GAAG;IACT,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,GAAG,WAAW,CAAC;CACnC,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,iBAAiB,GAAG,gBAAgB,CAAC;CAC1D,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACvB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,mBAAmB,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG;IACxB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,KAAK,CAAC;IACnB;;;OAGG;IACH,eAAe,CAAC,EAAE,sBAAsB,CAAC;IACzC;;;;;OAKG;IACH,YAAY,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACpE;;;;OAIG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC/B,CAAC;AAQF;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,GAC9B,gBAAgB,MAAM,EACtB,SAAS,MAAM,KAChB,MAOF,CAAC;AA+KF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAAI,eAAe,aAAa,KAAG,IAEhE,CAAC"}
@@ -0,0 +1,154 @@
1
+ import { getLogger } from '../deps/jsr.io/@nimbus/core/2.0.0/src/index.js';
2
+ import { getEventSourcingDBClient } from './client.js';
3
+ import { withSpan } from './tracing.js';
4
+ /**
5
+ * Returns a promise that resolves after the given number of milliseconds.
6
+ */
7
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
8
+ /**
9
+ * Calculates an exponential backoff delay with jitter for a given
10
+ * retry attempt. The jitter adds up to 30% of the base delay to
11
+ * avoid thundering-herd effects.
12
+ *
13
+ * @param initialDelayMs - The base delay in milliseconds before
14
+ * exponential scaling.
15
+ * @param attempt - The zero-based retry attempt number.
16
+ * @returns The backoff delay in milliseconds.
17
+ */
18
+ export const calculateBackoffDelay = (initialDelayMs, attempt) => {
19
+ const baseDelay = initialDelayMs * Math.pow(2, attempt);
20
+ // Add jitter: random value between 0 and 30% of the base delay
21
+ const jitter = Math.random() * baseDelay * 0.3;
22
+ return Math.floor(baseDelay + jitter);
23
+ };
24
+ /**
25
+ * Logs an informational message when an event observer connects or
26
+ * reconnects to EventSourcingDB. When {@link retryCount} is greater
27
+ * than zero the message indicates a successful reconnection.
28
+ *
29
+ * @param subject - The observed subject.
30
+ * @param retryCount - The number of retries that preceded this
31
+ * connection (0 for the initial connection).
32
+ * @param data - Additional context logged alongside the message.
33
+ */
34
+ const logObserverConnection = (subject, retryCount, data) => {
35
+ const retryLabel = retryCount === 1 ? 'retry' : 'retries';
36
+ const message = retryCount > 0
37
+ ? `Reconnected event observer for subject "${subject}" after ${retryCount} ${retryLabel}`
38
+ : `Observing events for subject "${subject}"`;
39
+ getLogger().info({ category: 'Nimbus', message, data });
40
+ };
41
+ /**
42
+ * Handles an observer error by logging it and waiting with exponential
43
+ * backoff before the next retry attempt. When the maximum number of
44
+ * retries is exceeded a critical log entry is emitted and no further
45
+ * retries are attempted.
46
+ *
47
+ * @param error - The error that caused the observer to disconnect.
48
+ * @param subject - The observed subject.
49
+ * @param retryCount - The current (1-based) retry attempt number.
50
+ * @param maxRetries - The maximum number of allowed retries.
51
+ * @param initialRetryDelayMs - The base delay used for exponential
52
+ * backoff calculation.
53
+ * @returns `true` if the observer should retry, `false` if retries
54
+ * are exhausted.
55
+ */
56
+ const handleObserverError = async (error, subject, retryCount, maxRetries, initialRetryDelayMs) => {
57
+ if (retryCount > maxRetries) {
58
+ getLogger().critical({
59
+ category: 'Nimbus',
60
+ message: `Failed to observe events for subject "${subject}" after ${maxRetries} ${maxRetries === 1 ? 'retry' : 'retries'}.`,
61
+ });
62
+ return false;
63
+ }
64
+ const backoffDelay = calculateBackoffDelay(initialRetryDelayMs, retryCount - 1);
65
+ getLogger().error({
66
+ category: 'Nimbus',
67
+ message: `Error observing events for subject "${subject}" (retry ${retryCount}/${maxRetries}), retrying in ${backoffDelay}ms`,
68
+ error: error,
69
+ });
70
+ await delay(backoffDelay);
71
+ return true;
72
+ };
73
+ /**
74
+ * Starts observing events for the given {@link EventObserver} with
75
+ * automatic reconnection on failure.
76
+ *
77
+ * On each connection attempt the EventSourcingDB server is pinged
78
+ * first. Events are then consumed from the stream and each one is
79
+ * passed to the observer's event handler inside an OpenTelemetry
80
+ * span. If the event carries a `traceparent`, the span is linked to
81
+ * the original writer's trace for end-to-end distributed tracing.
82
+ *
83
+ * After every successfully handled event the lower bound is advanced
84
+ * so that a reconnection resumes from the last processed position.
85
+ *
86
+ * When the connection drops, exponential backoff with jitter is
87
+ * applied up to the configured maximum number of retries.
88
+ *
89
+ * @param eventObserver - The event observer configuration.
90
+ */
91
+ const observeWithRetry = async (eventObserver) => {
92
+ const eventSourcingDBClient = getEventSourcingDBClient();
93
+ const maxRetries = eventObserver.retryOptions?.maxRetries ?? 3;
94
+ const initialRetryDelayMs = eventObserver.retryOptions?.initialRetryDelayMs ?? 3000;
95
+ let retryCount = 0;
96
+ let lastProcessedEventId;
97
+ while (true) {
98
+ try {
99
+ // Once we have a concrete position, use it as lower bound and
100
+ // drop fromLatestEvent; otherwise fall back to the original options.
101
+ const lowerBound = lastProcessedEventId
102
+ ? { id: lastProcessedEventId, type: 'exclusive' }
103
+ : eventObserver.lowerBound;
104
+ const fromLatestEvent = lastProcessedEventId
105
+ ? undefined
106
+ : eventObserver.fromLatestEvent;
107
+ // Verify connection
108
+ await eventSourcingDBClient.ping();
109
+ logObserverConnection(eventObserver.subject, retryCount, {
110
+ recursive: eventObserver.recursive ?? false,
111
+ lowerBound,
112
+ fromLatestEvent,
113
+ });
114
+ retryCount = 0;
115
+ for await (const event of eventSourcingDBClient.observeEvents(eventObserver.subject, {
116
+ recursive: eventObserver.recursive ?? false,
117
+ ...(lowerBound ? { lowerBound } : {}),
118
+ ...(fromLatestEvent ? { fromLatestEvent } : {}),
119
+ })) {
120
+ const traceContext = event.traceparent
121
+ ? {
122
+ traceparent: event.traceparent,
123
+ tracestate: event.tracestate,
124
+ }
125
+ : undefined;
126
+ await withSpan('observeEvent', async () => {
127
+ await eventObserver.eventHandler(event);
128
+ }, traceContext);
129
+ // Track last processed position so retries resume from here
130
+ lastProcessedEventId = event.id;
131
+ }
132
+ // If the loop completes normally (stream ended), we're done
133
+ return;
134
+ }
135
+ catch (error) {
136
+ retryCount++;
137
+ const shouldRetry = await handleObserverError(error, eventObserver.subject, retryCount, maxRetries, initialRetryDelayMs);
138
+ if (!shouldRetry) {
139
+ return;
140
+ }
141
+ }
142
+ }
143
+ };
144
+ /**
145
+ * Initializes an event observer by starting the observation loop in
146
+ * the background (non-blocking). The observer will keep running and
147
+ * reconnecting according to its retry options until the stream ends
148
+ * or retries are exhausted.
149
+ *
150
+ * @param eventObserver - The event observer configuration.
151
+ */
152
+ export const initEventObserver = (eventObserver) => {
153
+ observeWithRetry(eventObserver);
154
+ };
@@ -0,0 +1,16 @@
1
+ import type { Event, ReadEventsOptions } from 'eventsourcingdb';
2
+ /**
3
+ * Reads events from EventSourcingDB for a given subject.
4
+ *
5
+ * Returns an async generator that yields raw EventSourcingDB
6
+ * {@link Event} instances (not Nimbus events). Use
7
+ * {@link eventSourcingDBEventToNimbusEvent} to convert them
8
+ * into Nimbus events if needed.
9
+ *
10
+ * @param subject - The subject to read events for.
11
+ * @param options - Options to control which events are read.
12
+ * @param signal - An optional abort signal to cancel the read.
13
+ * @returns An async generator yielding EventSourcingDB events.
14
+ */
15
+ export declare const readEvents: (subject: string, options: ReadEventsOptions, signal?: AbortSignal) => AsyncGenerator<Event, void, void>;
16
+ //# sourceMappingURL=readEvents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readEvents.d.ts","sourceRoot":"","sources":["../../src/lib/readEvents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAIhE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU,GACnB,SAAS,MAAM,EACf,SAAS,iBAAiB,EAC1B,SAAS,WAAW,KACrB,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAUlC,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { getEventSourcingDBClient } from './client.js';
2
+ import { withAsyncGeneratorSpan } from './tracing.js';
3
+ /**
4
+ * Reads events from EventSourcingDB for a given subject.
5
+ *
6
+ * Returns an async generator that yields raw EventSourcingDB
7
+ * {@link Event} instances (not Nimbus events). Use
8
+ * {@link eventSourcingDBEventToNimbusEvent} to convert them
9
+ * into Nimbus events if needed.
10
+ *
11
+ * @param subject - The subject to read events for.
12
+ * @param options - Options to control which events are read.
13
+ * @param signal - An optional abort signal to cancel the read.
14
+ * @returns An async generator yielding EventSourcingDB events.
15
+ */
16
+ export const readEvents = (subject, options, signal) => {
17
+ return withAsyncGeneratorSpan('readEvents', () => {
18
+ const eventSourcingDBClient = getEventSourcingDBClient();
19
+ return eventSourcingDBClient.readEvents(subject, options, signal);
20
+ });
21
+ };
@@ -0,0 +1,37 @@
1
+ export declare const tracer: import("@opentelemetry/api").Tracer;
2
+ export declare const DB_SYSTEM = "eventsourcingdb";
3
+ /**
4
+ * Trace context extracted from an EventSourcingDB event, used to link
5
+ * the processing span to the span that originally wrote the event.
6
+ */
7
+ export type TraceContext = {
8
+ traceparent: string;
9
+ tracestate?: string;
10
+ };
11
+ /**
12
+ * Wraps an async function with OpenTelemetry tracing and metrics.
13
+ *
14
+ * Records:
15
+ * - `eventsourcingdb_operation_total` counter with operation and status labels
16
+ * - `eventsourcingdb_operation_duration_seconds` histogram with operation label
17
+ *
18
+ * @param operation - The EventSourcingDB operation name (e.g., 'readEvents', 'writeEvents')
19
+ * @param fn - The async function to execute within the span
20
+ * @param traceContext - Optional trace context from an EventSourcingDB event to
21
+ * continue a distributed trace from the event writer.
22
+ * @returns The result of the async function
23
+ */
24
+ export declare const withSpan: <T>(operation: string, fn: () => Promise<T>, traceContext?: TraceContext) => Promise<T>;
25
+ /**
26
+ * Wraps an async generator with OpenTelemetry tracing and metrics.
27
+ *
28
+ * Records:
29
+ * - `eventsourcingdb_operation_total` counter with operation and status labels
30
+ * - `eventsourcingdb_operation_duration_seconds` histogram with operation label
31
+ *
32
+ * @param operation - The EventSourcingDB operation name (e.g., 'readEvents')
33
+ * @param fn - The function returning an async generator to execute within the span
34
+ * @returns An async generator that yields the same values as the inner generator
35
+ */
36
+ export declare function withAsyncGeneratorSpan<T>(operation: string, fn: () => AsyncGenerator<T, void, void>): AsyncGenerator<T, void, void>;
37
+ //# sourceMappingURL=tracing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../../src/lib/tracing.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,MAAM,qCAA4B,CAAC;AAEhD,eAAO,MAAM,SAAS,oBAAoB,CAAC;AAmB3C;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EACtB,WAAW,MAAM,EACjB,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,eAAe,YAAY,KAC5B,OAAO,CAAC,CAAC,CA6DX,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAuB,sBAAsB,CAAC,CAAC,EAC3C,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,GACxC,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CA0C/B"}
@@ -0,0 +1,114 @@
1
+ import { context as otelContext, metrics, propagation, SpanKind, SpanStatusCode, trace, } from '@opentelemetry/api';
2
+ export const tracer = trace.getTracer('nimbus');
3
+ export const DB_SYSTEM = 'eventsourcingdb';
4
+ const meter = metrics.getMeter('nimbus');
5
+ const operationCounter = meter.createCounter('eventsourcingdb_operation_total', {
6
+ description: 'Total number of EventSourcingDB operations',
7
+ });
8
+ const operationDuration = meter.createHistogram('eventsourcingdb_operation_duration_seconds', {
9
+ description: 'Duration of EventSourcingDB operations in seconds',
10
+ unit: 's',
11
+ });
12
+ /**
13
+ * Wraps an async function with OpenTelemetry tracing and metrics.
14
+ *
15
+ * Records:
16
+ * - `eventsourcingdb_operation_total` counter with operation and status labels
17
+ * - `eventsourcingdb_operation_duration_seconds` histogram with operation label
18
+ *
19
+ * @param operation - The EventSourcingDB operation name (e.g., 'readEvents', 'writeEvents')
20
+ * @param fn - The async function to execute within the span
21
+ * @param traceContext - Optional trace context from an EventSourcingDB event to
22
+ * continue a distributed trace from the event writer.
23
+ * @returns The result of the async function
24
+ */
25
+ export const withSpan = (operation, fn, traceContext) => {
26
+ const startTime = performance.now();
27
+ const metricLabels = {
28
+ operation,
29
+ };
30
+ const parentContext = traceContext
31
+ ? propagation.extract(otelContext.active(), traceContext)
32
+ : otelContext.active();
33
+ return tracer.startActiveSpan(`eventsourcingdb.${operation}`, {
34
+ kind: SpanKind.CLIENT,
35
+ attributes: {
36
+ 'db.system': DB_SYSTEM,
37
+ 'db.operation': operation,
38
+ },
39
+ }, parentContext, async (span) => {
40
+ try {
41
+ const result = await fn();
42
+ // Record success metrics
43
+ operationCounter.add(1, {
44
+ ...metricLabels,
45
+ status: 'success',
46
+ });
47
+ operationDuration.record((performance.now() - startTime) / 1000, metricLabels);
48
+ return result;
49
+ }
50
+ catch (error) {
51
+ // Record error metrics
52
+ operationCounter.add(1, {
53
+ ...metricLabels,
54
+ status: 'error',
55
+ });
56
+ operationDuration.record((performance.now() - startTime) / 1000, metricLabels);
57
+ span.setStatus({
58
+ code: SpanStatusCode.ERROR,
59
+ message: error instanceof Error
60
+ ? error.message
61
+ : 'Unknown error',
62
+ });
63
+ span.recordException(error instanceof Error ? error : new Error('Unknown error'));
64
+ throw error;
65
+ }
66
+ finally {
67
+ span.end();
68
+ }
69
+ });
70
+ };
71
+ /**
72
+ * Wraps an async generator with OpenTelemetry tracing and metrics.
73
+ *
74
+ * Records:
75
+ * - `eventsourcingdb_operation_total` counter with operation and status labels
76
+ * - `eventsourcingdb_operation_duration_seconds` histogram with operation label
77
+ *
78
+ * @param operation - The EventSourcingDB operation name (e.g., 'readEvents')
79
+ * @param fn - The function returning an async generator to execute within the span
80
+ * @returns An async generator that yields the same values as the inner generator
81
+ */
82
+ export async function* withAsyncGeneratorSpan(operation, fn) {
83
+ const startTime = performance.now();
84
+ const metricLabels = {
85
+ operation,
86
+ };
87
+ const span = tracer.startSpan(`eventsourcingdb.${operation}`, {
88
+ kind: SpanKind.CLIENT,
89
+ attributes: {
90
+ 'db.system': DB_SYSTEM,
91
+ 'db.operation': operation,
92
+ },
93
+ });
94
+ try {
95
+ yield* fn();
96
+ // Record success metrics
97
+ operationCounter.add(1, { ...metricLabels, status: 'success' });
98
+ operationDuration.record((performance.now() - startTime) / 1000, metricLabels);
99
+ }
100
+ catch (error) {
101
+ // Record error metrics
102
+ operationCounter.add(1, { ...metricLabels, status: 'error' });
103
+ operationDuration.record((performance.now() - startTime) / 1000, metricLabels);
104
+ span.setStatus({
105
+ code: SpanStatusCode.ERROR,
106
+ message: error instanceof Error ? error.message : 'Unknown error',
107
+ });
108
+ span.recordException(error instanceof Error ? error : new Error('Unknown error'));
109
+ throw error;
110
+ }
111
+ finally {
112
+ span.end();
113
+ }
114
+ }
@@ -0,0 +1,11 @@
1
+ import type { Event } from '../deps/jsr.io/@nimbus/core/2.0.0/src/index.js';
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writeEvents.d.ts","sourceRoot":"","sources":["../../src/lib/writeEvents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gDAAgD,CAAC;AAE5E,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAKpE;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GACpB,QAAQ,KAAK,EAAE,EACf,gBAAgB,YAAY,EAAE,KAC/B,OAAO,CAAC,IAAI,CAoBd,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { context, propagation } from '@opentelemetry/api';
2
+ import { getEventSourcingDBClient } from './client.js';
3
+ import { nimbusEventToEventSourcingDBEventCandidate } from './eventMapping.js';
4
+ import { withSpan } from './tracing.js';
5
+ /**
6
+ * Writes one or more Nimbus events to EventSourcingDB. Each event is
7
+ * converted into an EventSourcingDB event candidate before being persisted.
8
+ *
9
+ * @param events - The Nimbus events to write.
10
+ * @param preconditions - Optional preconditions that must be met for the write to succeed.
11
+ */
12
+ export const writeEvents = (events, preconditions) => {
13
+ return withSpan('writeEvents', async () => {
14
+ const eventSourcingDBClient = getEventSourcingDBClient();
15
+ const carrier = {};
16
+ propagation.inject(context.active(), carrier);
17
+ const eventCandidates = events.map((event) => nimbusEventToEventSourcingDBEventCandidate(event, carrier['traceparent'], carrier['tracestate']));
18
+ await eventSourcingDBClient.writeEvents(eventCandidates, preconditions);
19
+ });
20
+ };
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@nimbus-cqrs/eventsourcingdb",
3
+ "version": "2.0.0-beta.0",
4
+ "description": "EventSourcingDB integration for the Nimbus CQRS framework.",
5
+ "author": "Daniel Gördes <d.goerdes@overlap.at> (https://overlap.at)",
6
+ "homepage": "https://nimbus.overlap.at",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/overlap-dev/Nimbus"
10
+ },
11
+ "license": "Apache-2.0",
12
+ "bugs": {
13
+ "url": "https://github.com/overlap-dev/Nimbus/issues"
14
+ },
15
+ "module": "./esm/index.js",
16
+ "exports": {
17
+ ".": {
18
+ "import": "./esm/index.js"
19
+ }
20
+ },
21
+ "scripts": {},
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
25
+ "dependencies": {
26
+ "@opentelemetry/api": "^1.9.1",
27
+ "eventsourcingdb": "^1.8.1",
28
+ "zod": "^4.3.6",
29
+ "@nimbus-cqrs/core": "^2.0.0-beta.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22.0.0"
33
+ },
34
+ "_generatedBy": "dnt@dev"
35
+ }