@nimbus-cqrs/eventsourcingdb 2.0.0-beta.0 → 2.0.2

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.
package/README.md CHANGED
@@ -5,10 +5,206 @@
5
5
 
6
6
  # Nimbus EventSourcingDB
7
7
 
8
- Use the [EventSourcingDB](https://eventsourcingdb.io) with Nimbus.
8
+ Integration between Nimbus and [EventSourcingDB](https://www.eventsourcingdb.io/). The package wraps the official `eventsourcingdb` client with:
9
+
10
+ - a **singleton setup** that pings the server, verifies the API token and registers long-running observers in one call,
11
+ - typed **`writeEvents` / `readEvents`** helpers that translate between Nimbus events and EventSourcingDB events while preserving correlation IDs, data schemas and W3C trace context (`traceparent` / `tracestate`),
12
+ - resilient **event observers** with exponential-backoff retries, jitter, position tracking across reconnects and OpenTelemetry span linking back to the original writer.
9
13
 
10
14
  Refer to the [Nimbus main repository](https://github.com/overlap-dev/Nimbus) or the [Nimbus documentation](https://nimbus.overlap.at) for more information about the Nimbus framework.
11
15
 
16
+ Also refer to the [EventSourcingDB documentation](https://docs.eventsourcingdb.io/) directly for more information about the EventSourcingDB features.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ # Deno
22
+ deno add jsr:@nimbus-cqrs/eventsourcingdb
23
+
24
+ # NPM
25
+ npm install @nimbus-cqrs/eventsourcingdb
26
+
27
+ # Bun
28
+ bun add @nimbus-cqrs/eventsourcingdb
29
+ ```
30
+
31
+ `eventsourcingdb` is a peer dependency — install it (or use one of the runtimes that resolves it via `npm:`/`jsr:` specifiers).
32
+
33
+ # Examples
34
+
35
+ For detailed documentation, please refer to the [Nimbus documentation](https://nimbus.overlap.at).
36
+
37
+ The snippets below use a tiny `Todo` domain to walk through the package — events live under the `/todos` subject and we react to `com.example.todo.added` events.
38
+
39
+ ## Quick start
40
+
41
+ A typical wiring at application startup: configure the client, register the observers you want to keep running, then write events from your command handlers.
42
+
43
+ ```typescript
44
+ import { createEvent } from "@nimbus-cqrs/core";
45
+ import {
46
+ eventSourcingDBEventToNimbusEvent,
47
+ setupEventSourcingDBClient,
48
+ writeEvents,
49
+ } from "@nimbus-cqrs/eventsourcingdb";
50
+
51
+ await setupEventSourcingDBClient({
52
+ url: new URL(process.env.ESDB_URL ?? ""),
53
+ apiToken: process.env.ESDB_API_TOKEN ?? "",
54
+ eventObservers: [
55
+ {
56
+ subject: "/todos",
57
+ recursive: true,
58
+ eventHandler: async (event) => {
59
+ const nimbusEvent = eventSourcingDBEventToNimbusEvent(event);
60
+ console.log("reacting to", nimbusEvent.type, nimbusEvent.data);
61
+ },
62
+ },
63
+ ],
64
+ });
65
+
66
+ await writeEvents([
67
+ createEvent({
68
+ type: "com.example.todo.added",
69
+ source: "https://app.example.com",
70
+ subject: "/todos/todo-1",
71
+ data: { id: "todo-1", title: "Write the README" },
72
+ }),
73
+ ]);
74
+ ```
75
+
76
+ ## setupEventSourcingDBClient
77
+
78
+ `setupEventSourcingDBClient` builds the singleton client. It pings the server and verifies the API token at startup, so misconfiguration fails loudly before any business code runs. Anywhere later in the app you can grab the underlying client through `getEventSourcingDBClient()` if you need to call the raw driver.
79
+
80
+ ```typescript
81
+ import {
82
+ getEventSourcingDBClient,
83
+ setupEventSourcingDBClient,
84
+ } from "@nimbus-cqrs/eventsourcingdb";
85
+
86
+ await setupEventSourcingDBClient({
87
+ url: new URL(process.env.ESDB_URL ?? ""),
88
+ apiToken: process.env.ESDB_API_TOKEN ?? "",
89
+ });
90
+
91
+ // Anywhere else:
92
+ const client = getEventSourcingDBClient();
93
+ ```
94
+
95
+ The `eventObservers` array is optional; passing observers here is just a convenience that calls `initEventObserver` for each entry once the client is ready.
96
+
97
+ ## writeEvents
98
+
99
+ `writeEvents` takes an array of Nimbus events and persists them to EventSourcingDB. Before writing it wraps each event payload with Nimbus metadata (`payload` + `nimbusMeta`) so the correlation ID and optional `dataschema` survive a round-trip, and it injects the active OpenTelemetry context as `traceparent` / `tracestate` so distributed traces stitch together end-to-end.
100
+
101
+ ```typescript
102
+ import { createEvent } from "@nimbus-cqrs/core";
103
+ import { writeEvents } from "@nimbus-cqrs/eventsourcingdb";
104
+
105
+ await writeEvents([
106
+ createEvent({
107
+ type: "com.example.todo.added",
108
+ source: "https://app.example.com",
109
+ subject: "/todos/todo-1",
110
+ data: { id: "todo-1", title: "Write the README" },
111
+ }),
112
+ ]);
113
+ ```
114
+
115
+ The second argument accepts EventSourcingDB [preconditions](https://docs.eventsourcingdb.io/getting-started/writing-events/#using-preconditions) — useful for optimistic concurrency, e.g. only appending an event when no event of a given type already exists for a subject:
116
+
117
+ ```typescript
118
+ await writeEvents(
119
+ [todoAddedEvent],
120
+ [
121
+ {
122
+ type: "isSubjectPristine",
123
+ payload: { subject: "/todos/todo-1" },
124
+ },
125
+ ]
126
+ );
127
+ ```
128
+
129
+ ## readEvents
130
+
131
+ `readEvents` returns an async generator that yields raw EventSourcingDB events for a subject. Use `eventSourcingDBEventToNimbusEvent` to lift each event back into a typed Nimbus event when you want to feed it into your domain.
132
+
133
+ ```typescript
134
+ import {
135
+ eventSourcingDBEventToNimbusEvent,
136
+ readEvents,
137
+ } from "@nimbus-cqrs/eventsourcingdb";
138
+
139
+ for await (const event of readEvents("/todos/todo-1", { recursive: false })) {
140
+ const nimbusEvent = eventSourcingDBEventToNimbusEvent(event);
141
+ // ...rebuild your aggregate, replay state, etc.
142
+ }
143
+ ```
144
+
145
+ Pass an `AbortSignal` as the third argument if you need to cancel a long-running read.
146
+
147
+ ## Event observers
148
+
149
+ An `EventObserver` is a long-running consumer attached to a subject. `initEventObserver` (or the `eventObservers` array on `setupEventSourcingDBClient`) starts it in the background and keeps it alive: on connection failures it retries with exponential backoff plus jitter (defaults: 3 retries, 3000 ms initial delay) and on every successful event it advances its lower bound, so a reconnection resumes from exactly where it left off — no replays, no gaps.
150
+
151
+ Each event handler runs inside an OpenTelemetry span. If the source event carries a `traceparent`, that span is linked back to the writer's trace, giving you end-to-end visibility from the command that produced the event to every subscriber that reacted to it.
152
+
153
+ ```typescript
154
+ import { initEventObserver } from "@nimbus-cqrs/eventsourcingdb";
155
+
156
+ initEventObserver({
157
+ subject: "/todos",
158
+ recursive: true,
159
+ fromLatestEvent: {
160
+ subject: "/todos",
161
+ type: "com.example.todo.added",
162
+ ifEventIsMissing: "read-everything",
163
+ },
164
+ eventHandler: async (event) => {
165
+ if (event.type === "com.example.todo.added") {
166
+ // ...update a read model, send a notification, ...
167
+ }
168
+ },
169
+ retryOptions: {
170
+ maxRetries: 5,
171
+ initialRetryDelayMs: 1000,
172
+ },
173
+ });
174
+ ```
175
+
176
+ Use `lowerBound` to resume from a known event ID, or `fromLatestEvent` to start at the most recent matching event (with a fallback policy when no such event exists yet).
177
+
178
+ ## Event mapping
179
+
180
+ Every event written by `writeEvents` is wrapped in this shape:
181
+
182
+ ```ts
183
+ type EventData = {
184
+ payload: Record<string, unknown>;
185
+ nimbusMeta: {
186
+ correlationid: string;
187
+ dataschema?: string;
188
+ };
189
+ };
190
+ ```
191
+
192
+ That wrapping is what allows `eventSourcingDBEventToNimbusEvent` to recover a fully-formed Nimbus event later — including the original correlation ID — instead of just the raw payload. If you read events that were _not_ written through Nimbus (e.g. from another system writing to the same store), the helper falls back to treating the entire `data` field as the payload and assigns a fresh correlation ID.
193
+
194
+ The package exposes the building blocks directly so you can opt out of the helpers when you need to:
195
+
196
+ ```typescript
197
+ import {
198
+ eventSourcingDBEventToNimbusEvent,
199
+ isEventData,
200
+ nimbusEventToEventSourcingDBEventCandidate,
201
+ } from "@nimbus-cqrs/eventsourcingdb";
202
+ ```
203
+
204
+ - `nimbusEventToEventSourcingDBEventCandidate(event)` — manual conversion, e.g. when batching with the raw client.
205
+ - `eventSourcingDBEventToNimbusEvent<TEvent>(event)` — typed conversion when reading or observing.
206
+ - `isEventData(value)` — type guard to detect the Nimbus envelope on arbitrary stored data.
207
+
12
208
  # License
13
209
 
14
210
  Copyright 2024-present Overlap GmbH & Co KG (https://overlap.at)
@@ -37,7 +37,7 @@ export type SetupEventSourcingDBClientInput = {
37
37
  *
38
38
  * @example
39
39
  * ```ts
40
- * import { setupEventSourcingDBClient } from '@nimbus/eventsourcingdb';
40
+ * import { setupEventSourcingDBClient } from '@nimbus-cqrs/eventsourcingdb';
41
41
  * import type { Event } from 'eventsourcingdb';
42
42
  *
43
43
  * await setupEventSourcingDBClient({
@@ -81,7 +81,7 @@ export declare const setupEventSourcingDBClient: ({ url, apiToken, eventObserver
81
81
  *
82
82
  * @example
83
83
  * ```ts
84
- * import { getEventSourcingDBClient } from '@nimbus/eventsourcingdb';
84
+ * import { getEventSourcingDBClient } from '@nimbus-cqrs/eventsourcingdb';
85
85
  *
86
86
  * const client = getEventSourcingDBClient();
87
87
  * ```
package/esm/lib/client.js CHANGED
@@ -1,4 +1,4 @@
1
- import { GenericException, getLogger } from '../deps/jsr.io/@nimbus/core/2.0.0/src/index.js';
1
+ import { GenericException, getLogger } from '@nimbus-cqrs/core';
2
2
  import { Client } from 'eventsourcingdb';
3
3
  import { initEventObserver } from './eventObserver.js';
4
4
  let eventSourcingDBClient = null;
@@ -22,7 +22,7 @@ let eventSourcingDBClient = null;
22
22
  *
23
23
  * @example
24
24
  * ```ts
25
- * import { setupEventSourcingDBClient } from '@nimbus/eventsourcingdb';
25
+ * import { setupEventSourcingDBClient } from '@nimbus-cqrs/eventsourcingdb';
26
26
  * import type { Event } from 'eventsourcingdb';
27
27
  *
28
28
  * await setupEventSourcingDBClient({
@@ -99,7 +99,7 @@ export const setupEventSourcingDBClient = async ({ url, apiToken, eventObservers
99
99
  *
100
100
  * @example
101
101
  * ```ts
102
- * import { getEventSourcingDBClient } from '@nimbus/eventsourcingdb';
102
+ * import { getEventSourcingDBClient } from '@nimbus-cqrs/eventsourcingdb';
103
103
  *
104
104
  * const client = getEventSourcingDBClient();
105
105
  * ```
@@ -1,4 +1,4 @@
1
- import { type Event } from '../deps/jsr.io/@nimbus/core/2.0.0/src/index.js';
1
+ import { type Event } from '@nimbus-cqrs/core';
2
2
  import type { Event as EventSourcingDBEvent, EventCandidate } from 'eventsourcingdb';
3
3
  /**
4
4
  * Metadata that Nimbus attaches to events stored in EventSourcingDB
@@ -1 +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"}
1
+ {"version":3,"file":"eventMapping.d.ts","sourceRoot":"","sources":["../../src/lib/eventMapping.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,qDAAqD,CAAC;AAE9F,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"}
@@ -1,4 +1,4 @@
1
- import { createEvent } from '../deps/jsr.io/@nimbus/core/2.0.0/src/index.js';
1
+ import { createEvent } from '@nimbus-cqrs/core';
2
2
  import { ulid } from '../deps/jsr.io/@std/ulid/1.0.0/mod.js';
3
3
  /**
4
4
  * Type guard that checks whether the given value conforms to the {@link EventData} structure
@@ -1,4 +1,4 @@
1
- import { getLogger } from '../deps/jsr.io/@nimbus/core/2.0.0/src/index.js';
1
+ import { getLogger } from '@nimbus-cqrs/core';
2
2
  import { getEventSourcingDBClient } from './client.js';
3
3
  import { withSpan } from './tracing.js';
4
4
  /**
@@ -1,4 +1,4 @@
1
- import type { Event } from '../deps/jsr.io/@nimbus/core/2.0.0/src/index.js';
1
+ import type { Event } from '@nimbus-cqrs/core';
2
2
  import type { Precondition } from 'eventsourcingdb';
3
3
  /**
4
4
  * Writes one or more Nimbus events to EventSourcingDB. Each event is
@@ -1 +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"}
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;AAKpE;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GACpB,QAAQ,KAAK,EAAE,EACf,gBAAgB,YAAY,EAAE,KAC/B,OAAO,CAAC,IAAI,CAoBd,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,18 @@
1
1
  {
2
2
  "name": "@nimbus-cqrs/eventsourcingdb",
3
- "version": "2.0.0-beta.0",
4
- "description": "EventSourcingDB integration for the Nimbus CQRS framework.",
3
+ "version": "2.0.2",
4
+ "description": "Simplify Event-Driven Applications - EventSourcingDB integration for the Nimbus framework.",
5
+ "keywords": [
6
+ "nimbus",
7
+ "cqrs",
8
+ "event-sourcing",
9
+ "event-driven",
10
+ "typescript",
11
+ "eventsourcingdb",
12
+ "database",
13
+ "event-store",
14
+ "event-stream"
15
+ ],
5
16
  "author": "Daniel Gördes <d.goerdes@overlap.at> (https://overlap.at)",
6
17
  "homepage": "https://nimbus.overlap.at",
7
18
  "repository": {
@@ -26,7 +37,7 @@
26
37
  "@opentelemetry/api": "^1.9.1",
27
38
  "eventsourcingdb": "^1.8.1",
28
39
  "zod": "^4.3.6",
29
- "@nimbus-cqrs/core": "^2.0.0-beta.0"
40
+ "@nimbus-cqrs/core": "^2.0.2"
30
41
  },
31
42
  "devDependencies": {
32
43
  "@types/node": "^22.0.0"