@event-driven-io/emmett-mongodb 0.43.0-beta.12 → 0.43.0-beta.14
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 +484 -0
- package/dist/index.cjs +513 -1180
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +142 -137
- package/dist/index.d.ts +142 -137
- package/dist/index.js +489 -1173
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/README.md
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
# @event-driven-io/emmett-mongodb
|
|
2
|
+
|
|
3
|
+
MongoDB adapter for the Emmett event sourcing library, providing event store implementation with stream management, inline projections, and flexible storage strategies.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This package enables MongoDB as a persistence backend for event-sourced applications built with Emmett. It stores event streams as documents with support for atomic inline projections, multiple storage strategies, and optimistic concurrency control using BigInt stream positions.
|
|
8
|
+
|
|
9
|
+
## Key Concepts
|
|
10
|
+
|
|
11
|
+
- **Event Stream**: A document containing all events for a single aggregate, stored with metadata and optional projections
|
|
12
|
+
- **Stream Name**: Composite identifier in format `streamType:streamId` (e.g., `shopping_cart:abc-123`)
|
|
13
|
+
- **Inline Projections**: Read models stored alongside events in the same document, updated atomically during append
|
|
14
|
+
- **Storage Strategy**: Configurable collection organization (per stream type, single collection, or custom)
|
|
15
|
+
- **Collection Prefix**: All collections use the `emt:` prefix (e.g., `emt:shopping_cart`)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @event-driven-io/emmett-mongodb
|
|
21
|
+
# or
|
|
22
|
+
pnpm add @event-driven-io/emmett-mongodb
|
|
23
|
+
# or
|
|
24
|
+
yarn add @event-driven-io/emmett-mongodb
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Peer dependencies** (must be installed separately):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @event-driven-io/emmett mongodb
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### Basic Event Store Setup
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import {
|
|
39
|
+
getMongoDBEventStore,
|
|
40
|
+
toStreamName,
|
|
41
|
+
} from '@event-driven-io/emmett-mongodb';
|
|
42
|
+
import { type Event, STREAM_DOES_NOT_EXIST } from '@event-driven-io/emmett';
|
|
43
|
+
|
|
44
|
+
// Define your events
|
|
45
|
+
type ProductItemAdded = Event<
|
|
46
|
+
'ProductItemAdded',
|
|
47
|
+
{ productItem: { productId: string; quantity: number; price: number } }
|
|
48
|
+
>;
|
|
49
|
+
type DiscountApplied = Event<
|
|
50
|
+
'DiscountApplied',
|
|
51
|
+
{ percent: number; couponId: string }
|
|
52
|
+
>;
|
|
53
|
+
type ShoppingCartEvent = ProductItemAdded | DiscountApplied;
|
|
54
|
+
|
|
55
|
+
// Create event store with connection string
|
|
56
|
+
const eventStore = getMongoDBEventStore({
|
|
57
|
+
connectionString: 'mongodb://localhost:27017/mydb',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Append events to a stream
|
|
61
|
+
const streamName = toStreamName('shopping_cart', 'cart-123');
|
|
62
|
+
|
|
63
|
+
await eventStore.appendToStream<ShoppingCartEvent>(
|
|
64
|
+
streamName,
|
|
65
|
+
[
|
|
66
|
+
{
|
|
67
|
+
type: 'ProductItemAdded',
|
|
68
|
+
data: { productItem: { productId: 'prod-1', quantity: 2, price: 29.99 } },
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
{ expectedStreamVersion: STREAM_DOES_NOT_EXIST },
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Read events from stream
|
|
75
|
+
const { events, currentStreamVersion } =
|
|
76
|
+
await eventStore.readStream(streamName);
|
|
77
|
+
|
|
78
|
+
// Close the event store when done (only needed when using connection string)
|
|
79
|
+
await eventStore.close();
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Using an Existing MongoClient
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { MongoClient } from 'mongodb';
|
|
86
|
+
import { getMongoDBEventStore } from '@event-driven-io/emmett-mongodb';
|
|
87
|
+
|
|
88
|
+
const client = new MongoClient('mongodb://localhost:27017/mydb');
|
|
89
|
+
await client.connect();
|
|
90
|
+
|
|
91
|
+
// Client lifecycle is managed externally - no close() method on event store
|
|
92
|
+
const eventStore = getMongoDBEventStore({ client });
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Aggregating Stream State
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
type ShoppingCart = {
|
|
99
|
+
productItems: Array<{ productId: string; quantity: number; price: number }>;
|
|
100
|
+
totalAmount: number;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const evolve = (
|
|
104
|
+
state: ShoppingCart,
|
|
105
|
+
event: ShoppingCartEvent,
|
|
106
|
+
): ShoppingCart => {
|
|
107
|
+
switch (event.type) {
|
|
108
|
+
case 'ProductItemAdded':
|
|
109
|
+
return {
|
|
110
|
+
productItems: [...state.productItems, event.data.productItem],
|
|
111
|
+
totalAmount:
|
|
112
|
+
state.totalAmount +
|
|
113
|
+
event.data.productItem.price * event.data.productItem.quantity,
|
|
114
|
+
};
|
|
115
|
+
case 'DiscountApplied':
|
|
116
|
+
return {
|
|
117
|
+
...state,
|
|
118
|
+
totalAmount: state.totalAmount * (1 - event.data.percent / 100),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const { state, currentStreamVersion } = await eventStore.aggregateStream(
|
|
124
|
+
streamName,
|
|
125
|
+
{
|
|
126
|
+
evolve,
|
|
127
|
+
initialState: () => ({ productItems: [], totalAmount: 0 }),
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## How-to Guides
|
|
133
|
+
|
|
134
|
+
### Configure Storage Strategies
|
|
135
|
+
|
|
136
|
+
The MongoDB event store supports three storage strategies:
|
|
137
|
+
|
|
138
|
+
#### Collection Per Stream Type (Default, Recommended)
|
|
139
|
+
|
|
140
|
+
Each stream type gets its own collection, named `emt:{streamType}`:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
const eventStore = getMongoDBEventStore({
|
|
144
|
+
connectionString: 'mongodb://localhost:27017/mydb',
|
|
145
|
+
storage: 'COLLECTION_PER_STREAM_TYPE',
|
|
146
|
+
});
|
|
147
|
+
// shopping_cart streams -> emt:shopping_cart collection
|
|
148
|
+
// order streams -> emt:order collection
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Single Collection
|
|
152
|
+
|
|
153
|
+
All streams stored in one collection:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const eventStore = getMongoDBEventStore({
|
|
157
|
+
connectionString: 'mongodb://localhost:27017/mydb',
|
|
158
|
+
storage: {
|
|
159
|
+
type: 'SINGLE_COLLECTION',
|
|
160
|
+
collectionName: 'emt:all_events', // optional, defaults to 'emt:streams'
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### Custom Resolution
|
|
166
|
+
|
|
167
|
+
Define your own collection mapping:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const eventStore = getMongoDBEventStore({
|
|
171
|
+
connectionString: 'mongodb://localhost:27017/mydb',
|
|
172
|
+
storage: {
|
|
173
|
+
type: 'CUSTOM',
|
|
174
|
+
collectionFor: (streamType) => ({
|
|
175
|
+
collectionName: `custom_${streamType}`,
|
|
176
|
+
databaseName: 'events_db', // optional
|
|
177
|
+
}),
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Define Inline Projections
|
|
183
|
+
|
|
184
|
+
Inline projections are stored alongside events and updated atomically:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import {
|
|
188
|
+
mongoDBInlineProjection,
|
|
189
|
+
getMongoDBEventStore,
|
|
190
|
+
} from '@event-driven-io/emmett-mongodb';
|
|
191
|
+
import { projections } from '@event-driven-io/emmett';
|
|
192
|
+
|
|
193
|
+
type ShoppingCartDetails = {
|
|
194
|
+
productItems: Array<{ productId: string; quantity: number; price: number }>;
|
|
195
|
+
totalAmount: number;
|
|
196
|
+
itemCount: number;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const shoppingCartDetailsProjection = mongoDBInlineProjection<
|
|
200
|
+
ShoppingCartDetails,
|
|
201
|
+
ShoppingCartEvent
|
|
202
|
+
>({
|
|
203
|
+
name: 'shopping_cart_details', // optional, defaults to '_default'
|
|
204
|
+
schemaVersion: 1,
|
|
205
|
+
canHandle: ['ProductItemAdded', 'DiscountApplied'],
|
|
206
|
+
evolve: (document, event) => {
|
|
207
|
+
const doc = document ?? { productItems: [], totalAmount: 0, itemCount: 0 };
|
|
208
|
+
|
|
209
|
+
switch (event.type) {
|
|
210
|
+
case 'ProductItemAdded':
|
|
211
|
+
return {
|
|
212
|
+
productItems: [...doc.productItems, event.data.productItem],
|
|
213
|
+
totalAmount:
|
|
214
|
+
doc.totalAmount +
|
|
215
|
+
event.data.productItem.price * event.data.productItem.quantity,
|
|
216
|
+
itemCount: doc.itemCount + 1,
|
|
217
|
+
};
|
|
218
|
+
case 'DiscountApplied':
|
|
219
|
+
return {
|
|
220
|
+
...doc,
|
|
221
|
+
totalAmount: doc.totalAmount * (1 - event.data.percent / 100),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const eventStore = getMongoDBEventStore({
|
|
228
|
+
connectionString: 'mongodb://localhost:27017/mydb',
|
|
229
|
+
projections: projections.inline([shoppingCartDetailsProjection]),
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
With an initial state:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
const projectionWithInitialState = mongoDBInlineProjection<
|
|
237
|
+
ShoppingCartDetails,
|
|
238
|
+
ShoppingCartEvent
|
|
239
|
+
>({
|
|
240
|
+
canHandle: ['ProductItemAdded', 'DiscountApplied'],
|
|
241
|
+
initialState: () => ({ productItems: [], totalAmount: 0, itemCount: 0 }),
|
|
242
|
+
evolve: (document, event) => {
|
|
243
|
+
// document is never null here due to initialState
|
|
244
|
+
switch (event.type) {
|
|
245
|
+
case 'ProductItemAdded':
|
|
246
|
+
return {
|
|
247
|
+
productItems: [...document.productItems, event.data.productItem],
|
|
248
|
+
totalAmount:
|
|
249
|
+
document.totalAmount +
|
|
250
|
+
event.data.productItem.price * event.data.productItem.quantity,
|
|
251
|
+
itemCount: document.itemCount + 1,
|
|
252
|
+
};
|
|
253
|
+
case 'DiscountApplied':
|
|
254
|
+
return {
|
|
255
|
+
...document,
|
|
256
|
+
totalAmount: document.totalAmount * (1 - event.data.percent / 100),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Query Inline Projections
|
|
264
|
+
|
|
265
|
+
The event store provides helpers for querying inline projections:
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// Find a single projection by stream name
|
|
269
|
+
const details =
|
|
270
|
+
await eventStore.projections.inline.findOne<ShoppingCartDetails>(
|
|
271
|
+
{ streamName: 'shopping_cart:cart-123' },
|
|
272
|
+
{ totalAmount: { $gt: 100 } }, // optional MongoDB filter
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
// Find by stream type and ID
|
|
276
|
+
const details2 =
|
|
277
|
+
await eventStore.projections.inline.findOne<ShoppingCartDetails>({
|
|
278
|
+
streamType: 'shopping_cart',
|
|
279
|
+
streamId: 'cart-123',
|
|
280
|
+
projectionName: 'shopping_cart_details',
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Find multiple projections
|
|
284
|
+
const allCarts = await eventStore.projections.inline.find<ShoppingCartDetails>(
|
|
285
|
+
{ streamType: 'shopping_cart' },
|
|
286
|
+
{ totalAmount: { $gt: 50 } },
|
|
287
|
+
{ skip: 0, limit: 10, sort: { totalAmount: -1 } },
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
// Count projections
|
|
291
|
+
const count = await eventStore.projections.inline.count<ShoppingCartDetails>(
|
|
292
|
+
{ streamType: 'shopping_cart' },
|
|
293
|
+
{ itemCount: { $gte: 5 } },
|
|
294
|
+
);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Access Raw Collections
|
|
298
|
+
|
|
299
|
+
For advanced queries, access the underlying MongoDB collection:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
const collection =
|
|
303
|
+
await eventStore.collectionFor<ShoppingCartEvent>('shopping_cart');
|
|
304
|
+
|
|
305
|
+
// Use standard MongoDB operations
|
|
306
|
+
const streams = await collection
|
|
307
|
+
.find({ 'metadata.streamPosition': { $gt: 10n } })
|
|
308
|
+
.toArray();
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Test Inline Projections
|
|
312
|
+
|
|
313
|
+
Use the BDD-style specification helpers:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import {
|
|
317
|
+
MongoDBInlineProjectionSpec,
|
|
318
|
+
eventInStream,
|
|
319
|
+
expectInlineReadModel,
|
|
320
|
+
} from '@event-driven-io/emmett-mongodb';
|
|
321
|
+
|
|
322
|
+
const given = MongoDBInlineProjectionSpec.for<
|
|
323
|
+
`shopping_cart:${string}`,
|
|
324
|
+
ShoppingCartEvent
|
|
325
|
+
>({
|
|
326
|
+
projection: shoppingCartDetailsProjection,
|
|
327
|
+
connectionString: 'mongodb://localhost:27017/testdb',
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
await given(
|
|
331
|
+
eventInStream('shopping_cart:test-1', {
|
|
332
|
+
type: 'ProductItemAdded',
|
|
333
|
+
data: { productItem: { productId: 'p1', quantity: 1, price: 100 } },
|
|
334
|
+
}),
|
|
335
|
+
)
|
|
336
|
+
.when([
|
|
337
|
+
{ type: 'DiscountApplied', data: { percent: 10, couponId: 'SAVE10' } },
|
|
338
|
+
])
|
|
339
|
+
.then(
|
|
340
|
+
expectInlineReadModel
|
|
341
|
+
.withName('shopping_cart_details')
|
|
342
|
+
.toHave({ totalAmount: 90 }),
|
|
343
|
+
);
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## API Reference
|
|
347
|
+
|
|
348
|
+
### getMongoDBEventStore
|
|
349
|
+
|
|
350
|
+
Creates a MongoDB event store instance.
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
function getMongoDBEventStore(
|
|
354
|
+
options: MongoDBEventStoreOptions,
|
|
355
|
+
): MongoDBEventStore;
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**Options:**
|
|
359
|
+
|
|
360
|
+
| Property | Type | Description |
|
|
361
|
+
| ------------------ | --------------------------------- | -------------------------------------------------------------------- |
|
|
362
|
+
| `client` | `MongoClient` | Existing MongoDB client (mutually exclusive with `connectionString`) |
|
|
363
|
+
| `connectionString` | `string` | MongoDB connection URI (mutually exclusive with `client`) |
|
|
364
|
+
| `clientOptions` | `MongoClientOptions` | Options for MongoClient when using connection string |
|
|
365
|
+
| `projections` | `ProjectionRegistration[]` | Array of inline projection definitions |
|
|
366
|
+
| `storage` | `MongoDBEventStoreStorageOptions` | Storage strategy configuration |
|
|
367
|
+
|
|
368
|
+
### MongoDBEventStore
|
|
369
|
+
|
|
370
|
+
Extended EventStore interface with MongoDB-specific features:
|
|
371
|
+
|
|
372
|
+
| Method | Description |
|
|
373
|
+
| --------------------------------------------------- | ------------------------------------------------------------------- |
|
|
374
|
+
| `readStream(streamName, options?)` | Read events from a stream |
|
|
375
|
+
| `appendToStream(streamName, events, options?)` | Append events to a stream |
|
|
376
|
+
| `aggregateStream(streamName, options)` | Fold events into aggregate state |
|
|
377
|
+
| `collectionFor(streamType)` | Get raw MongoDB collection for a stream type |
|
|
378
|
+
| `projections.inline.findOne(filter, query?)` | Find single inline projection |
|
|
379
|
+
| `projections.inline.find(filter, query?, options?)` | Find multiple inline projections |
|
|
380
|
+
| `projections.inline.count(filter, query?)` | Count inline projections |
|
|
381
|
+
| `close()` | Close the MongoDB client (only when created with connection string) |
|
|
382
|
+
|
|
383
|
+
### Stream Naming Functions
|
|
384
|
+
|
|
385
|
+
| Function | Description |
|
|
386
|
+
| ------------------------------------------ | ------------------------------------------------- |
|
|
387
|
+
| `toStreamName(streamType, streamId)` | Create stream name: `streamType:streamId` |
|
|
388
|
+
| `fromStreamName(streamName)` | Parse stream name into `{ streamType, streamId }` |
|
|
389
|
+
| `toStreamCollectionName(streamType)` | Create collection name: `emt:streamType` |
|
|
390
|
+
| `fromStreamCollectionName(collectionName)` | Parse collection name into `{ streamType }` |
|
|
391
|
+
|
|
392
|
+
### mongoDBInlineProjection
|
|
393
|
+
|
|
394
|
+
Creates an inline projection definition.
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
function mongoDBInlineProjection<Doc, EventType>(
|
|
398
|
+
options: MongoDBInlineProjectionOptions<Doc, EventType>,
|
|
399
|
+
): MongoDBInlineProjectionDefinition;
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**Options:**
|
|
403
|
+
|
|
404
|
+
| Property | Type | Description |
|
|
405
|
+
| --------------- | ----------- | -------------------------------------------- |
|
|
406
|
+
| `name` | `string` | Projection name (default: `_default`) |
|
|
407
|
+
| `schemaVersion` | `number` | Schema version for migrations (default: `1`) |
|
|
408
|
+
| `canHandle` | `string[]` | Event types this projection handles |
|
|
409
|
+
| `evolve` | `Function` | State evolution function |
|
|
410
|
+
| `initialState` | `() => Doc` | Optional initial state factory |
|
|
411
|
+
|
|
412
|
+
## Architecture
|
|
413
|
+
|
|
414
|
+
### Document Structure
|
|
415
|
+
|
|
416
|
+
Each event stream is stored as a single MongoDB document:
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
interface EventStream {
|
|
420
|
+
streamName: string; // e.g., "shopping_cart:abc-123"
|
|
421
|
+
messages: ReadEvent[]; // Array of events with metadata
|
|
422
|
+
metadata: {
|
|
423
|
+
streamId: string;
|
|
424
|
+
streamType: string;
|
|
425
|
+
streamPosition: bigint; // Current version (BigInt)
|
|
426
|
+
createdAt: Date;
|
|
427
|
+
updatedAt: Date;
|
|
428
|
+
};
|
|
429
|
+
projections: {
|
|
430
|
+
// Inline projections
|
|
431
|
+
[projectionName: string]: MongoDBReadModel;
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Read Model Structure
|
|
437
|
+
|
|
438
|
+
Inline projections include metadata for version tracking:
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
interface MongoDBReadModel<Doc> {
|
|
442
|
+
...Doc; // Your projection fields
|
|
443
|
+
_metadata: {
|
|
444
|
+
streamId: string;
|
|
445
|
+
name: string; // Projection name
|
|
446
|
+
schemaVersion: number;
|
|
447
|
+
streamPosition: bigint; // Last processed event position
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Optimistic Concurrency
|
|
453
|
+
|
|
454
|
+
The event store uses MongoDB's atomic update operations with version checking:
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
await eventStore.appendToStream(
|
|
458
|
+
streamName,
|
|
459
|
+
events,
|
|
460
|
+
{ expectedStreamVersion: 5n }, // Fails if current version !== 5
|
|
461
|
+
);
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Special version constants:
|
|
465
|
+
|
|
466
|
+
- `STREAM_DOES_NOT_EXIST` - Expect stream to not exist
|
|
467
|
+
- `STREAM_EXISTS` - Expect stream to exist (any version)
|
|
468
|
+
- `NO_CONCURRENCY_CHECK` - Skip version validation
|
|
469
|
+
|
|
470
|
+
## Dependencies
|
|
471
|
+
|
|
472
|
+
### Peer Dependencies
|
|
473
|
+
|
|
474
|
+
| Package | Version | Purpose |
|
|
475
|
+
| ------------------------- | --------- | -------------------------------- |
|
|
476
|
+
| `@event-driven-io/emmett` | `0.38.3` | Core event sourcing abstractions |
|
|
477
|
+
| `mongodb` | `^6.10.0` | MongoDB driver |
|
|
478
|
+
|
|
479
|
+
### Development Dependencies
|
|
480
|
+
|
|
481
|
+
| Package | Purpose |
|
|
482
|
+
| ---------------------------------------- | ------------------------ |
|
|
483
|
+
| `@event-driven-io/emmett-testcontainers` | Test container utilities |
|
|
484
|
+
| `@testcontainers/mongodb` | MongoDB testcontainer |
|