@emmett-community/emmett-google-firestore 0.3.0 → 0.5.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.
package/README.md CHANGED
@@ -147,6 +147,15 @@ const eventStore = getFirestoreEventStore(firestore, {
147
147
  });
148
148
  ```
149
149
 
150
+ ### `asEventStore(firestoreEventStore)`
151
+
152
+ Wraps a `FirestoreEventStore` instance in Emmett's `EventStore` interface so it can be passed into helpers that expect the canonical contract (CommandHandlers, workflows, etc.).
153
+
154
+ ```typescript
155
+ const firestoreEventStore = getFirestoreEventStore(firestore);
156
+ const eventStore = asEventStore(firestoreEventStore);
157
+ ```
158
+
150
159
  ### `eventStore.appendToStream(streamName, events, options?)`
151
160
 
152
161
  Appends events to a stream.
@@ -560,16 +569,6 @@ import type {
560
569
  - **Emmett**: ^0.39.0
561
570
  - **Firestore**: ^7.10.0
562
571
 
563
- ## Contributing
564
-
565
- Contributions are welcome! Please:
566
-
567
- 1. Fork the repository
568
- 2. Create a feature branch
569
- 3. Add tests for new functionality
570
- 4. Ensure all tests pass
571
- 5. Submit a pull request
572
-
573
572
  ## Development
574
573
 
575
574
  ```bash
@@ -589,6 +588,16 @@ npm run lint
589
588
  npm run format
590
589
  ```
591
590
 
591
+ ## Contributing
592
+
593
+ Contributions are welcome! Please:
594
+
595
+ 1. Fork the repository
596
+ 2. Create a feature branch
597
+ 3. Add tests for new functionality
598
+ 4. Ensure all tests pass
599
+ 5. Submit a pull request
600
+
592
601
  ## License
593
602
 
594
603
  MIT © Emmett Community
@@ -611,3 +620,7 @@ MIT © Emmett Community
611
620
  - Built for the [Emmett](https://event-driven-io.github.io/emmett/) framework by [Oskar Dudycz](https://github.com/oskardudycz)
612
621
  - Inspired by [emmett-mongodb](https://github.com/event-driven-io/emmett/tree/main/src/packages/emmett-mongodb)
613
622
  - Part of the [Emmett Community](https://github.com/emmett-community)
623
+
624
+ ---
625
+
626
+ Made with ❤️ by the Emmett Community
package/dist/index.d.mts CHANGED
@@ -1,16 +1,56 @@
1
1
  import { Firestore, Timestamp } from '@google-cloud/firestore';
2
- import { Event, ReadEvent, ReadEventMetadataWithGlobalPosition, ExpectedStreamVersion as ExpectedStreamVersion$1, STREAM_DOES_NOT_EXIST } from '@event-driven-io/emmett';
2
+ import { Event, ReadEvent, ReadEventMetadataWithGlobalPosition, ExpectedStreamVersion as ExpectedStreamVersion$1, STREAM_DOES_NOT_EXIST, EventStore } from '@event-driven-io/emmett';
3
3
  export { NO_CONCURRENCY_CHECK, STREAM_DOES_NOT_EXIST, STREAM_EXISTS } from '@event-driven-io/emmett';
4
4
 
5
5
  /**
6
- * Minimal logger interface compatible with Pino and other loggers.
7
- * All methods are optional to support varying logger implementations.
6
+ * Canonical Logger contract for the Emmett ecosystem.
7
+ *
8
+ * DO NOT MODIFY this interface without updating ALL packages in the ecosystem.
9
+ *
10
+ * This package defines the canonical Logger interface.
11
+ * Implementations (Pino, Winston, etc.) MUST adapt to this contract.
12
+ * This contract MUST NOT adapt to any specific implementation.
13
+ *
14
+ * Semantic Rules:
15
+ * - context (first parameter): ALWAYS structured data as Record<string, unknown>
16
+ * - message (second parameter): ALWAYS the human-readable log message
17
+ * - The order is NEVER inverted
18
+ * - The (message, data) form is NOT valid for this contract
19
+ * - Error objects MUST use the 'err' key (frozen semantic)
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * // Pino - native compatibility
24
+ * import pino from 'pino';
25
+ * const logger = pino();
26
+ * // logger.info({ orderId }, 'Order created') matches our contract
27
+ * ```
8
28
  */
9
29
  interface Logger {
10
- debug?(msg: string, data?: unknown): void;
11
- info?(msg: string, data?: unknown): void;
12
- warn?(msg: string, data?: unknown): void;
13
- error?(msg: string, err?: unknown): void;
30
+ /**
31
+ * Log debug-level message with structured context.
32
+ * @param context - Structured data to include in the log entry
33
+ * @param message - Optional human-readable message
34
+ */
35
+ debug(context: Record<string, unknown>, message?: string): void;
36
+ /**
37
+ * Log info-level message with structured context.
38
+ * @param context - Structured data to include in the log entry
39
+ * @param message - Optional human-readable message
40
+ */
41
+ info(context: Record<string, unknown>, message?: string): void;
42
+ /**
43
+ * Log warn-level message with structured context.
44
+ * @param context - Structured data to include in the log entry
45
+ * @param message - Optional human-readable message
46
+ */
47
+ warn(context: Record<string, unknown>, message?: string): void;
48
+ /**
49
+ * Log error-level message with structured context.
50
+ * @param context - Structured data to include in the log entry (MUST use 'err' key for Error objects)
51
+ * @param message - Optional human-readable message
52
+ */
53
+ error(context: Record<string, unknown>, message?: string): void;
14
54
  }
15
55
  /**
16
56
  * Observability configuration options
@@ -44,6 +84,7 @@ interface AppendToStreamResult {
44
84
  }
45
85
  /**
46
86
  * Options for reading events from a stream
87
+ * Reuses the canonical `ReadStreamOptions` shape from the Emmett core types.
47
88
  */
48
89
  interface ReadStreamOptions {
49
90
  from?: bigint;
@@ -138,6 +179,7 @@ declare class ExpectedVersionConflictError extends Error {
138
179
  constructor(streamName: string, expected: ExpectedStreamVersion, actual: bigint | typeof STREAM_DOES_NOT_EXIST);
139
180
  }
140
181
 
182
+ declare function asEventStore(store: FirestoreEventStore): EventStore<FirestoreReadEventMetadata>;
141
183
  /**
142
184
  * Factory function to create a Firestore event store
143
185
  *
@@ -216,4 +258,4 @@ declare function getCurrentStreamVersion(streamExists: boolean, version?: number
216
258
  */
217
259
  declare function calculateNextVersion(currentVersion: bigint | typeof STREAM_DOES_NOT_EXIST, eventCount: number): bigint;
218
260
 
219
- export { type AppendToStreamOptions, type AppendToStreamResult, type CollectionConfig, type EventDocument, type ExpectedStreamVersion, ExpectedVersionConflictError, type FirestoreEventStore, type FirestoreEventStoreOptions, type FirestoreReadEvent, type FirestoreReadEventMetadata, type Logger, type ObservabilityOptions, type ReadStreamOptions, type StreamMetadata, assertExpectedVersionMatchesCurrent, calculateNextVersion, getCurrentStreamVersion, getFirestoreEventStore, padVersion, parseStreamName, timestampToDate };
261
+ export { type AppendToStreamOptions, type AppendToStreamResult, type CollectionConfig, type EventDocument, type ExpectedStreamVersion, ExpectedVersionConflictError, type FirestoreEventStore, type FirestoreEventStoreOptions, type FirestoreReadEvent, type FirestoreReadEventMetadata, type Logger, type ObservabilityOptions, type ReadStreamOptions, type StreamMetadata, asEventStore, assertExpectedVersionMatchesCurrent, calculateNextVersion, getCurrentStreamVersion, getFirestoreEventStore, padVersion, parseStreamName, timestampToDate };
package/dist/index.d.ts CHANGED
@@ -1,16 +1,56 @@
1
1
  import { Firestore, Timestamp } from '@google-cloud/firestore';
2
- import { Event, ReadEvent, ReadEventMetadataWithGlobalPosition, ExpectedStreamVersion as ExpectedStreamVersion$1, STREAM_DOES_NOT_EXIST } from '@event-driven-io/emmett';
2
+ import { Event, ReadEvent, ReadEventMetadataWithGlobalPosition, ExpectedStreamVersion as ExpectedStreamVersion$1, STREAM_DOES_NOT_EXIST, EventStore } from '@event-driven-io/emmett';
3
3
  export { NO_CONCURRENCY_CHECK, STREAM_DOES_NOT_EXIST, STREAM_EXISTS } from '@event-driven-io/emmett';
4
4
 
5
5
  /**
6
- * Minimal logger interface compatible with Pino and other loggers.
7
- * All methods are optional to support varying logger implementations.
6
+ * Canonical Logger contract for the Emmett ecosystem.
7
+ *
8
+ * DO NOT MODIFY this interface without updating ALL packages in the ecosystem.
9
+ *
10
+ * This package defines the canonical Logger interface.
11
+ * Implementations (Pino, Winston, etc.) MUST adapt to this contract.
12
+ * This contract MUST NOT adapt to any specific implementation.
13
+ *
14
+ * Semantic Rules:
15
+ * - context (first parameter): ALWAYS structured data as Record<string, unknown>
16
+ * - message (second parameter): ALWAYS the human-readable log message
17
+ * - The order is NEVER inverted
18
+ * - The (message, data) form is NOT valid for this contract
19
+ * - Error objects MUST use the 'err' key (frozen semantic)
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * // Pino - native compatibility
24
+ * import pino from 'pino';
25
+ * const logger = pino();
26
+ * // logger.info({ orderId }, 'Order created') matches our contract
27
+ * ```
8
28
  */
9
29
  interface Logger {
10
- debug?(msg: string, data?: unknown): void;
11
- info?(msg: string, data?: unknown): void;
12
- warn?(msg: string, data?: unknown): void;
13
- error?(msg: string, err?: unknown): void;
30
+ /**
31
+ * Log debug-level message with structured context.
32
+ * @param context - Structured data to include in the log entry
33
+ * @param message - Optional human-readable message
34
+ */
35
+ debug(context: Record<string, unknown>, message?: string): void;
36
+ /**
37
+ * Log info-level message with structured context.
38
+ * @param context - Structured data to include in the log entry
39
+ * @param message - Optional human-readable message
40
+ */
41
+ info(context: Record<string, unknown>, message?: string): void;
42
+ /**
43
+ * Log warn-level message with structured context.
44
+ * @param context - Structured data to include in the log entry
45
+ * @param message - Optional human-readable message
46
+ */
47
+ warn(context: Record<string, unknown>, message?: string): void;
48
+ /**
49
+ * Log error-level message with structured context.
50
+ * @param context - Structured data to include in the log entry (MUST use 'err' key for Error objects)
51
+ * @param message - Optional human-readable message
52
+ */
53
+ error(context: Record<string, unknown>, message?: string): void;
14
54
  }
15
55
  /**
16
56
  * Observability configuration options
@@ -44,6 +84,7 @@ interface AppendToStreamResult {
44
84
  }
45
85
  /**
46
86
  * Options for reading events from a stream
87
+ * Reuses the canonical `ReadStreamOptions` shape from the Emmett core types.
47
88
  */
48
89
  interface ReadStreamOptions {
49
90
  from?: bigint;
@@ -138,6 +179,7 @@ declare class ExpectedVersionConflictError extends Error {
138
179
  constructor(streamName: string, expected: ExpectedStreamVersion, actual: bigint | typeof STREAM_DOES_NOT_EXIST);
139
180
  }
140
181
 
182
+ declare function asEventStore(store: FirestoreEventStore): EventStore<FirestoreReadEventMetadata>;
141
183
  /**
142
184
  * Factory function to create a Firestore event store
143
185
  *
@@ -216,4 +258,4 @@ declare function getCurrentStreamVersion(streamExists: boolean, version?: number
216
258
  */
217
259
  declare function calculateNextVersion(currentVersion: bigint | typeof STREAM_DOES_NOT_EXIST, eventCount: number): bigint;
218
260
 
219
- export { type AppendToStreamOptions, type AppendToStreamResult, type CollectionConfig, type EventDocument, type ExpectedStreamVersion, ExpectedVersionConflictError, type FirestoreEventStore, type FirestoreEventStoreOptions, type FirestoreReadEvent, type FirestoreReadEventMetadata, type Logger, type ObservabilityOptions, type ReadStreamOptions, type StreamMetadata, assertExpectedVersionMatchesCurrent, calculateNextVersion, getCurrentStreamVersion, getFirestoreEventStore, padVersion, parseStreamName, timestampToDate };
261
+ export { type AppendToStreamOptions, type AppendToStreamResult, type CollectionConfig, type EventDocument, type ExpectedStreamVersion, ExpectedVersionConflictError, type FirestoreEventStore, type FirestoreEventStoreOptions, type FirestoreReadEvent, type FirestoreReadEventMetadata, type Logger, type ObservabilityOptions, type ReadStreamOptions, type StreamMetadata, asEventStore, assertExpectedVersionMatchesCurrent, calculateNextVersion, getCurrentStreamVersion, getFirestoreEventStore, padVersion, parseStreamName, timestampToDate };
package/dist/index.js CHANGED
@@ -85,13 +85,41 @@ function calculateNextVersion(currentVersion, eventCount) {
85
85
 
86
86
  // src/eventStore/firestoreEventStore.ts
87
87
  var tracer = api.trace.getTracer("@emmett-community/emmett-google-firestore");
88
- function safeLog(logger, level, msg, data) {
89
- if (!logger) return;
90
- const logFn = logger[level];
91
- if (typeof logFn === "function") {
92
- logFn.call(logger, msg, data);
88
+ function normalizeContext(data) {
89
+ if (data === void 0 || data === null) return {};
90
+ if (typeof data === "object" && !Array.isArray(data)) {
91
+ return { ...data };
93
92
  }
93
+ return { data };
94
94
  }
95
+ function normalizeErrorContext(error) {
96
+ if (error === void 0 || error === null) return {};
97
+ if (error instanceof Error) {
98
+ return { err: error };
99
+ }
100
+ if (typeof error === "object" && !Array.isArray(error)) {
101
+ return { ...error };
102
+ }
103
+ return { err: error };
104
+ }
105
+ var safeLog = {
106
+ debug: (logger, msg, data) => {
107
+ if (!logger) return;
108
+ logger.debug(normalizeContext(data), msg);
109
+ },
110
+ info: (logger, msg, data) => {
111
+ if (!logger) return;
112
+ logger.info(normalizeContext(data), msg);
113
+ },
114
+ warn: (logger, msg, data) => {
115
+ if (!logger) return;
116
+ logger.warn(normalizeContext(data), msg);
117
+ },
118
+ error: (logger, msg, error) => {
119
+ if (!logger) return;
120
+ logger.error(normalizeErrorContext(error), msg);
121
+ }
122
+ };
95
123
  var DEFAULT_COLLECTIONS = {
96
124
  streams: "streams",
97
125
  counters: "_counters"
@@ -104,25 +132,27 @@ var FirestoreEventStoreImpl = class {
104
132
  ...options.collections
105
133
  };
106
134
  this.logger = options.observability?.logger;
107
- safeLog(this.logger, "info", "FirestoreEventStore initialized");
135
+ safeLog.info(this.logger, "FirestoreEventStore initialized");
108
136
  }
109
137
  collections;
110
138
  logger;
111
139
  /**
112
140
  * Read events from a stream
113
141
  */
114
- async readStream(streamName, options = {}) {
142
+ async readStream(streamName, options) {
115
143
  const span = tracer.startSpan("emmett.firestore.read_stream", {
116
144
  attributes: { "emmett.stream_name": streamName }
117
145
  });
118
146
  try {
119
- safeLog(this.logger, "debug", "Reading stream", {
147
+ const from = options && "from" in options ? options.from : void 0;
148
+ const to = options && "to" in options ? options.to : void 0;
149
+ const maxCountLimit = options && "maxCount" in options && options.maxCount !== void 0 ? Number(options.maxCount) : void 0;
150
+ safeLog.debug(this.logger, "Reading stream", {
120
151
  streamName,
121
- from: options.from?.toString(),
122
- to: options.to?.toString(),
123
- maxCount: options.maxCount
152
+ from: from?.toString(),
153
+ to: to?.toString(),
154
+ maxCount: maxCountLimit
124
155
  });
125
- const { from, to, maxCount } = options;
126
156
  let query = this.firestore.collection(this.collections.streams).doc(streamName).collection("events").orderBy("streamVersion", "asc");
127
157
  if (from !== void 0) {
128
158
  query = query.where("streamVersion", ">=", Number(from));
@@ -130,8 +160,8 @@ var FirestoreEventStoreImpl = class {
130
160
  if (to !== void 0) {
131
161
  query = query.where("streamVersion", "<=", Number(to));
132
162
  }
133
- if (maxCount !== void 0 && maxCount > 0) {
134
- query = query.limit(maxCount);
163
+ if (maxCountLimit !== void 0 && maxCountLimit > 0) {
164
+ query = query.limit(maxCountLimit);
135
165
  }
136
166
  const snapshot = await query.get();
137
167
  const events = snapshot.docs.map((doc) => {
@@ -151,7 +181,7 @@ var FirestoreEventStoreImpl = class {
151
181
  });
152
182
  span.setAttribute("emmett.event_count", events.length);
153
183
  span.setStatus({ code: api.SpanStatusCode.OK });
154
- safeLog(this.logger, "debug", "Stream read completed", {
184
+ safeLog.debug(this.logger, "Stream read completed", {
155
185
  streamName,
156
186
  eventCount: events.length
157
187
  });
@@ -159,7 +189,7 @@ var FirestoreEventStoreImpl = class {
159
189
  } catch (error) {
160
190
  span.recordException(error);
161
191
  span.setStatus({ code: api.SpanStatusCode.ERROR });
162
- safeLog(this.logger, "error", "Failed to read stream", {
192
+ safeLog.error(this.logger, "Failed to read stream", {
163
193
  streamName,
164
194
  error
165
195
  });
@@ -198,7 +228,7 @@ var FirestoreEventStoreImpl = class {
198
228
  throw new Error("Cannot append empty event array");
199
229
  }
200
230
  const { expectedStreamVersion = emmett.NO_CONCURRENCY_CHECK } = options;
201
- safeLog(this.logger, "debug", "Appending to stream", {
231
+ safeLog.debug(this.logger, "Appending to stream", {
202
232
  streamName,
203
233
  eventCount: events.length,
204
234
  eventTypes: events.map((e) => e.type),
@@ -215,7 +245,7 @@ var FirestoreEventStoreImpl = class {
215
245
  span.setAttribute("emmett.new_version", Number(result.nextExpectedStreamVersion));
216
246
  span.setAttribute("emmett.created_new_stream", result.createdNewStream);
217
247
  span.setStatus({ code: api.SpanStatusCode.OK });
218
- safeLog(this.logger, "debug", "Append completed", {
248
+ safeLog.debug(this.logger, "Append completed", {
219
249
  streamName,
220
250
  newVersion: result.nextExpectedStreamVersion.toString(),
221
251
  createdNewStream: result.createdNewStream
@@ -225,13 +255,13 @@ var FirestoreEventStoreImpl = class {
225
255
  span.recordException(error);
226
256
  span.setStatus({ code: api.SpanStatusCode.ERROR });
227
257
  if (error instanceof ExpectedVersionConflictError) {
228
- safeLog(this.logger, "warn", "Version conflict during append", {
258
+ safeLog.warn(this.logger, "Version conflict during append", {
229
259
  streamName,
230
260
  expected: String(error.expected),
231
261
  actual: String(error.actual)
232
262
  });
233
263
  } else {
234
- safeLog(this.logger, "error", "Failed to append to stream", {
264
+ safeLog.error(this.logger, "Failed to append to stream", {
235
265
  streamName,
236
266
  error
237
267
  });
@@ -257,7 +287,7 @@ var FirestoreEventStoreImpl = class {
257
287
  streamExists,
258
288
  streamData?.version
259
289
  );
260
- safeLog(this.logger, "debug", "Read stream metadata", {
290
+ safeLog.debug(this.logger, "Read stream metadata", {
261
291
  streamName,
262
292
  exists: streamExists,
263
293
  currentVersion: currentVersion === emmett.STREAM_DOES_NOT_EXIST ? "none" : currentVersion.toString()
@@ -298,7 +328,7 @@ var FirestoreEventStoreImpl = class {
298
328
  value: globalPosition,
299
329
  updatedAt: now
300
330
  });
301
- safeLog(this.logger, "debug", "Events written to transaction", {
331
+ safeLog.debug(this.logger, "Events written to transaction", {
302
332
  streamName,
303
333
  count: events.length,
304
334
  newVersion
@@ -309,6 +339,47 @@ var FirestoreEventStoreImpl = class {
309
339
  };
310
340
  }
311
341
  };
342
+ function normalizeReadOptions(options) {
343
+ if (!options) return void 0;
344
+ const result = {};
345
+ if ("from" in options) {
346
+ result.from = options.from;
347
+ if ("maxCount" in options && options.maxCount !== void 0) {
348
+ result.maxCount = Number(options.maxCount);
349
+ }
350
+ }
351
+ if ("to" in options) {
352
+ result.to = options.to;
353
+ }
354
+ return result;
355
+ }
356
+ function asEventStore(store) {
357
+ async function readStream(streamName, options) {
358
+ const events = await store.readStream(streamName, normalizeReadOptions(options));
359
+ const streamExists = events.length > 0;
360
+ const currentStreamVersion = streamExists ? events[events.length - 1].metadata.streamVersion : BigInt(0);
361
+ return {
362
+ currentStreamVersion,
363
+ events,
364
+ streamExists
365
+ };
366
+ }
367
+ function aggregateStream(streamName, options) {
368
+ return store.aggregateStream(streamName, {
369
+ evolve: options.evolve,
370
+ initialState: options.initialState,
371
+ read: normalizeReadOptions(options.read)
372
+ });
373
+ }
374
+ function appendToStream(streamName, events, options) {
375
+ return store.appendToStream(streamName, events, options);
376
+ }
377
+ return {
378
+ readStream,
379
+ aggregateStream,
380
+ appendToStream
381
+ };
382
+ }
312
383
  function getFirestoreEventStore(firestore, options) {
313
384
  return new FirestoreEventStoreImpl(firestore, options);
314
385
  }
@@ -326,6 +397,7 @@ Object.defineProperty(exports, "STREAM_EXISTS", {
326
397
  get: function () { return emmett.STREAM_EXISTS; }
327
398
  });
328
399
  exports.ExpectedVersionConflictError = ExpectedVersionConflictError;
400
+ exports.asEventStore = asEventStore;
329
401
  exports.assertExpectedVersionMatchesCurrent = assertExpectedVersionMatchesCurrent;
330
402
  exports.calculateNextVersion = calculateNextVersion;
331
403
  exports.getCurrentStreamVersion = getCurrentStreamVersion;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/eventStore/types.ts","../src/eventStore/utils.ts","../src/eventStore/firestoreEventStore.ts"],"names":["NO_CONCURRENCY_CHECK","STREAM_DOES_NOT_EXIST","STREAM_EXISTS","trace","SpanStatusCode"],"mappings":";;;;;;AA0KO,IAAM,4BAAA,GAAN,MAAM,6BAAA,SAAqC,KAAA,CAAM;AAAA,EACtD,WAAA,CACkB,UAAA,EACA,QAAA,EACA,MAAA,EAChB;AACA,IAAA,KAAA;AAAA,MACE,CAAA,sCAAA,EAAyC,UAAU,CAAA,YAAA,EAAe,MAAA,CAAO,QAAQ,CAAC,CAAA,SAAA,EAAY,MAAA,CAAO,MAAM,CAAC,CAAA;AAAA,KAC9G;AANgB,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AACA,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAKhB,IAAA,IAAA,CAAK,IAAA,GAAO,8BAAA;AACZ,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,6BAAA,CAA6B,SAAS,CAAA;AAAA,EACpE;AACF;;;ACjKO,SAAS,WAAW,OAAA,EAAkC;AAC3D,EAAA,OAAO,OAAA,CAAQ,QAAA,EAAS,CAAE,QAAA,CAAS,IAAI,GAAG,CAAA;AAC5C;AAYO,SAAS,gBAAgB,UAAA,EAG9B;AACA,EAAA,MAAM,cAAA,GAAiB,UAAA,CAAW,OAAA,CAAQ,GAAG,CAAA;AAE7C,EAAA,IAAI,mBAAmB,EAAA,EAAI;AACzB,IAAA,OAAO;AAAA,MACL,UAAA,EAAY,UAAA;AAAA,MACZ,QAAA,EAAU;AAAA,KACZ;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,UAAA,EAAY,UAAA,CAAW,SAAA,CAAU,CAAA,EAAG,cAAc,CAAA;AAAA,IAClD,QAAA,EAAU,UAAA,CAAW,SAAA,CAAU,cAAA,GAAiB,CAAC;AAAA,GACnD;AACF;AAQO,SAAS,gBAAgB,SAAA,EAA4B;AAC1D,EAAA,OAAO,UAAU,MAAA,EAAO;AAC1B;AAUO,SAAS,mCAAA,CACd,UAAA,EACA,eAAA,EACA,cAAA,EACM;AAEN,EAAA,IAAI,oBAAoBA,2BAAA,EAAsB;AAC5C,IAAA;AAAA,EACF;AAGA,EAAA,IAAI,oBAAoBC,4BAAA,EAAuB;AAC7C,IAAA,IAAI,mBAAmBA,4BAAA,EAAuB;AAC5C,MAAA,MAAM,IAAI,4BAAA;AAAA,QACR,UAAA;AAAA,QACA,eAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AACA,IAAA;AAAA,EACF;AAGA,EAAA,IAAI,oBAAoBC,oBAAA,EAAe;AACrC,IAAA,IAAI,mBAAmBD,4BAAA,EAAuB;AAC5C,MAAA,MAAM,IAAI,4BAAA;AAAA,QACR,UAAA;AAAA,QACA,eAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AACA,IAAA;AAAA,EACF;AAGA,EAAA,MAAM,cAAA,GAAiB,OAAO,eAAe,CAAA;AAC7C,EAAA,IAAI,cAAA,KAAmBA,4BAAA,IAAyB,cAAA,KAAmB,cAAA,EAAgB;AACjF,IAAA,MAAM,IAAI,4BAAA;AAAA,MACR,UAAA;AAAA,MACA,eAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF;AASO,SAAS,uBAAA,CACd,cACA,OAAA,EACuC;AACvC,EAAA,IAAI,CAAC,YAAA,EAAc;AACjB,IAAA,OAAOA,4BAAA;AAAA,EACT;AACA,EAAA,OAAO,MAAA,CAAO,WAAW,EAAE,CAAA;AAC7B;AASO,SAAS,oBAAA,CACd,gBACA,UAAA,EACQ;AACR,EAAA,IAAI,mBAAmBA,4BAAA,EAAuB;AAC5C,IAAA,OAAO,MAAA,CAAO,aAAa,CAAC,CAAA;AAAA,EAC9B;AAEA,EAAA,OAAQ,cAAA,GAA4B,OAAO,UAAU,CAAA;AACvD;;;AC9HA,IAAM,MAAA,GAASE,SAAA,CAAM,SAAA,CAAU,2CAA2C,CAAA;AAK1E,SAAS,OAAA,CACP,MAAA,EACA,KAAA,EACA,GAAA,EACA,IAAA,EACM;AACN,EAAA,IAAI,CAAC,MAAA,EAAQ;AACb,EAAA,MAAM,KAAA,GAAQ,OAAO,KAAK,CAAA;AAC1B,EAAA,IAAI,OAAO,UAAU,UAAA,EAAY;AAC/B,IAAA,KAAA,CAAM,IAAA,CAAK,MAAA,EAAQ,GAAA,EAAK,IAAI,CAAA;AAAA,EAC9B;AACF;AAEA,IAAM,mBAAA,GAAwC;AAAA,EAC5C,OAAA,EAAS,SAAA;AAAA,EACT,QAAA,EAAU;AACZ,CAAA;AAUO,IAAM,0BAAN,MAA6D;AAAA,EAIlE,WAAA,CACkB,SAAA,EAChB,OAAA,GAAsC,EAAC,EACvC;AAFgB,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAGhB,IAAA,IAAA,CAAK,WAAA,GAAc;AAAA,MACjB,GAAG,mBAAA;AAAA,MACH,GAAG,OAAA,CAAQ;AAAA,KACb;AACA,IAAA,IAAA,CAAK,MAAA,GAAS,QAAQ,aAAA,EAAe,MAAA;AAErC,IAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,MAAA,EAAQ,iCAAiC,CAAA;AAAA,EAChE;AAAA,EAdgB,WAAA;AAAA,EACC,MAAA;AAAA;AAAA;AAAA;AAAA,EAkBjB,MAAM,UAAA,CACJ,UAAA,EACA,OAAA,GAA6B,EAAC,EACY;AAC1C,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,SAAA,CAAU,8BAAA,EAAgC;AAAA,MAC5D,UAAA,EAAY,EAAE,oBAAA,EAAsB,UAAA;AAAW,KAChD,CAAA;AAED,IAAA,IAAI;AACF,MAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,OAAA,EAAS,gBAAA,EAAkB;AAAA,QAC9C,UAAA;AAAA,QACA,IAAA,EAAM,OAAA,CAAQ,IAAA,EAAM,QAAA,EAAS;AAAA,QAC7B,EAAA,EAAI,OAAA,CAAQ,EAAA,EAAI,QAAA,EAAS;AAAA,QACzB,UAAU,OAAA,CAAQ;AAAA,OACnB,CAAA;AAED,MAAA,MAAM,EAAE,IAAA,EAAM,EAAA,EAAI,QAAA,EAAS,GAAI,OAAA;AAG/B,MAAA,IAAI,QAAQ,IAAA,CAAK,SAAA,CACd,UAAA,CAAW,IAAA,CAAK,YAAY,OAAO,CAAA,CACnC,GAAA,CAAI,UAAU,EACd,UAAA,CAAW,QAAQ,CAAA,CACnB,OAAA,CAAQ,iBAAiB,KAAK,CAAA;AAGjC,MAAA,IAAI,SAAS,KAAA,CAAA,EAAW;AACtB,QAAA,KAAA,GAAQ,MAAM,KAAA,CAAM,eAAA,EAAiB,IAAA,EAAM,MAAA,CAAO,IAAI,CAAC,CAAA;AAAA,MACzD;AACA,MAAA,IAAI,OAAO,KAAA,CAAA,EAAW;AACpB,QAAA,KAAA,GAAQ,MAAM,KAAA,CAAM,eAAA,EAAiB,IAAA,EAAM,MAAA,CAAO,EAAE,CAAC,CAAA;AAAA,MACvD;AACA,MAAA,IAAI,QAAA,KAAa,KAAA,CAAA,IAAa,QAAA,GAAW,CAAA,EAAG;AAC1C,QAAA,KAAA,GAAQ,KAAA,CAAM,MAAM,QAAQ,CAAA;AAAA,MAC9B;AAGA,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAI;AAGjC,MAAA,MAAM,MAAA,GAAS,QAAA,CAAS,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,KAAQ;AACxC,QAAA,MAAM,IAAA,GAAO,IAAI,IAAA,EAAK;AACtB,QAAA,OAAO;AAAA,UACL,MAAM,IAAA,CAAK,IAAA;AAAA,UACX,MAAM,IAAA,CAAK,IAAA;AAAA,UACX,QAAA,EAAU;AAAA,YACR,GAAG,IAAA,CAAK,QAAA;AAAA,YACR,UAAA;AAAA,YACA,aAAA,EAAe,MAAA,CAAO,IAAA,CAAK,aAAa,CAAA;AAAA,YACxC,cAAA,EAAgB,MAAA,CAAO,IAAA,CAAK,aAAa,CAAA;AAAA,YACzC,cAAA,EAAgB,MAAA,CAAO,IAAA,CAAK,cAAc,CAAA;AAAA,YAC1C,SAAA,EAAW,eAAA,CAAgB,IAAA,CAAK,SAAS;AAAA;AAC3C,SACF;AAAA,MACF,CAAC,CAAA;AAED,MAAA,IAAA,CAAK,YAAA,CAAa,oBAAA,EAAsB,MAAA,CAAO,MAAM,CAAA;AACrD,MAAA,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAMC,kBAAA,CAAe,IAAI,CAAA;AAE1C,MAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,OAAA,EAAS,uBAAA,EAAyB;AAAA,QACrD,UAAA;AAAA,QACA,YAAY,MAAA,CAAO;AAAA,OACpB,CAAA;AAED,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,gBAAgB,KAAc,CAAA;AACnC,MAAA,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAMA,kBAAA,CAAe,OAAO,CAAA;AAE7C,MAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,OAAA,EAAS,uBAAA,EAAyB;AAAA,QACrD,UAAA;AAAA,QACA;AAAA,OACD,CAAA;AAED,MAAA,MAAM,KAAA;AAAA,IACR,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,GAAA,EAAI;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAA,CACJ,UAAA,EACA,OAAA,EASC;AACD,IAAA,MAAM,EAAE,MAAA,EAAQ,YAAA,EAAc,IAAA,EAAK,GAAI,OAAA;AACvC,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,UAAA,CAAsB,YAAY,IAAI,CAAA;AAEhE,IAAA,MAAM,YAAA,GAAe,OAAO,MAAA,GAAS,CAAA;AACrC,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,MAAA,CAAO,MAAA,EAAQ,cAAc,CAAA;AAClD,IAAA,MAAM,oBAAA,GAAuB,YAAA,GACzB,MAAA,CAAO,MAAA,CAAO,MAAA,GAAS,CAAC,CAAA,CAAE,QAAA,CAAS,aAAA,GACnC,MAAA,CAAO,CAAC,CAAA;AAEZ,IAAA,OAAO;AAAA,MACL,KAAA;AAAA,MACA,oBAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAA,CACJ,UAAA,EACA,MAAA,EACA,OAAA,GAAiC,EAAC,EACH;AAC/B,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,SAAA,CAAU,mCAAA,EAAqC;AAAA,MACjE,UAAA,EAAY;AAAA,QACV,oBAAA,EAAsB,UAAA;AAAA,QACtB,sBAAsB,MAAA,CAAO;AAAA;AAC/B,KACD,CAAA;AAED,IAAA,IAAI;AACF,MAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AACvB,QAAA,MAAM,IAAI,MAAM,iCAAiC,CAAA;AAAA,MACnD;AAEA,MAAA,MAAM,EAAE,qBAAA,GAAwBJ,2BAAA,EAAqB,GAAI,OAAA;AAEzD,MAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,OAAA,EAAS,qBAAA,EAAuB;AAAA,QACnD,UAAA;AAAA,QACA,YAAY,MAAA,CAAO,MAAA;AAAA,QACnB,YAAY,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,KAAM,EAAE,IAAI,CAAA;AAAA,QACpC,eAAA,EAAiB,OAAO,qBAAqB;AAAA,OAC9C,CAAA;AAGD,MAAA,MAAM,SAAS,MAAM,IAAA,CAAK,SAAA,CAAU,cAAA,CAAe,OAAO,WAAA,KAAgB;AACxE,QAAA,OAAO,MAAM,IAAA,CAAK,2BAAA;AAAA,UAChB,WAAA;AAAA,UACA,UAAA;AAAA,UACA,MAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF,CAAC,CAAA;AAED,MAAA,IAAA,CAAK,YAAA,CAAa,oBAAA,EAAsB,MAAA,CAAO,MAAA,CAAO,yBAAyB,CAAC,CAAA;AAChF,MAAA,IAAA,CAAK,YAAA,CAAa,2BAAA,EAA6B,MAAA,CAAO,gBAAgB,CAAA;AACtE,MAAA,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAMI,kBAAA,CAAe,IAAI,CAAA;AAE1C,MAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,OAAA,EAAS,kBAAA,EAAoB;AAAA,QAChD,UAAA;AAAA,QACA,UAAA,EAAY,MAAA,CAAO,yBAAA,CAA0B,QAAA,EAAS;AAAA,QACtD,kBAAkB,MAAA,CAAO;AAAA,OAC1B,CAAA;AAED,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,gBAAgB,KAAc,CAAA;AACnC,MAAA,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAMA,kBAAA,CAAe,OAAO,CAAA;AAE7C,MAAA,IAAI,iBAAiB,4BAAA,EAA8B;AACjD,QAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,MAAA,EAAQ,gCAAA,EAAkC;AAAA,UAC7D,UAAA;AAAA,UACA,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,QAAQ,CAAA;AAAA,UAC/B,MAAA,EAAQ,MAAA,CAAO,KAAA,CAAM,MAAM;AAAA,SAC5B,CAAA;AAAA,MACH,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,OAAA,EAAS,4BAAA,EAA8B;AAAA,UAC1D,UAAA;AAAA,UACA;AAAA,SACD,CAAA;AAAA,MACH;AAEA,MAAA,MAAM,KAAA;AAAA,IACR,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,GAAA,EAAI;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,2BAAA,CACZ,WAAA,EACA,UAAA,EACA,QACA,qBAAA,EAC+B;AAE/B,IAAA,MAAM,SAAA,GAAY,KAAK,SAAA,CACpB,UAAA,CAAW,KAAK,WAAA,CAAY,OAAO,CAAA,CACnC,GAAA,CAAI,UAAU,CAAA;AAEjB,IAAA,MAAM,SAAA,GAAY,MAAM,WAAA,CAAY,GAAA,CAAI,SAAS,CAAA;AACjD,IAAA,MAAM,eAAe,SAAA,CAAU,MAAA;AAC/B,IAAA,MAAM,UAAA,GAAa,UAAU,IAAA,EAAK;AAGlC,IAAA,MAAM,cAAA,GAAiB,uBAAA;AAAA,MACrB,YAAA;AAAA,MACA,UAAA,EAAY;AAAA,KACd;AAEA,IAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,OAAA,EAAS,sBAAA,EAAwB;AAAA,MACpD,UAAA;AAAA,MACA,MAAA,EAAQ,YAAA;AAAA,MACR,cAAA,EAAgB,cAAA,KAAmBH,4BAAA,GAAwB,MAAA,GAAS,eAAe,QAAA;AAAS,KAC7F,CAAA;AAED,IAAA,mCAAA;AAAA,MACE,UAAA;AAAA,MACA,qBAAA;AAAA,MACA;AAAA,KACF;AAGA,IAAA,MAAM,UAAA,GAAa,KAAK,SAAA,CACrB,UAAA,CAAW,KAAK,WAAA,CAAY,QAAQ,CAAA,CACpC,GAAA,CAAI,iBAAiB,CAAA;AAExB,IAAA,MAAM,UAAA,GAAa,MAAM,WAAA,CAAY,GAAA,CAAI,UAAU,CAAA;AACnD,IAAA,IAAI,iBAAiB,UAAA,CAAW,MAAA,GAC3B,WAAW,IAAA,EAAK,EAAG,SAAoB,CAAA,GACxC,CAAA;AAGJ,IAAA,MAAM,WAAA,GACJ,cAAA,KAAmBA,4BAAA,GAAwB,EAAA,GAAK,OAAO,cAAc,CAAA;AAGvE,IAAA,MAAM,cAAA,GAAiB,KAAK,SAAA,CAAU,WAAA;AACtC,IAAA,MAAM,GAAA,GAAM,cAAA,CAAe,SAAA,CAAU,GAAA,EAAI;AAEzC,IAAA,MAAA,CAAO,OAAA,CAAQ,CAAC,KAAA,EAAO,KAAA,KAAU;AAC/B,MAAA,MAAM,YAAA,GAAe,cAAc,CAAA,GAAI,KAAA;AACvC,MAAA,MAAM,QAAA,GAAW,UACd,UAAA,CAAW,QAAQ,EACnB,GAAA,CAAI,UAAA,CAAW,YAAY,CAAC,CAAA;AAE/B,MAAA,MAAM,WAAY,KAAA,CAAiD,QAAA;AACnE,MAAA,MAAM,aAAA,GAA+B;AAAA,QACnC,MAAM,KAAA,CAAM,IAAA;AAAA,QACZ,MAAM,KAAA,CAAM,IAAA;AAAA,QACZ,GAAI,QAAA,IAAY,EAAE,QAAA,EAAS;AAAA,QAC3B,SAAA,EAAW,GAAA;AAAA,QACX,cAAA,EAAgB,cAAA,EAAA;AAAA,QAChB,aAAA,EAAe;AAAA,OACjB;AAEA,MAAA,WAAA,CAAY,GAAA,CAAI,UAAU,aAAa,CAAA;AAAA,IACzC,CAAC,CAAA;AAGD,IAAA,MAAM,UAAA,GAAa,cAAc,MAAA,CAAO,MAAA;AACxC,IAAA,MAAM,eAAA,GAAkC;AAAA,MACtC,OAAA,EAAS,UAAA;AAAA,MACT,SAAA,EAAW,YAAY,SAAA,IAAa,GAAA;AAAA,MACpC,SAAA,EAAW;AAAA,KACb;AAEA,IAAA,WAAA,CAAY,GAAA,CAAI,WAAW,eAAe,CAAA;AAG1C,IAAA,WAAA,CAAY,IAAI,UAAA,EAAY;AAAA,MAC1B,KAAA,EAAO,cAAA;AAAA,MACP,SAAA,EAAW;AAAA,KACZ,CAAA;AAED,IAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,OAAA,EAAS,+BAAA,EAAiC;AAAA,MAC7D,UAAA;AAAA,MACA,OAAO,MAAA,CAAO,MAAA;AAAA,MACd;AAAA,KACD,CAAA;AAGD,IAAA,OAAO;AAAA,MACL,yBAAA,EAA2B,OAAO,UAAU,CAAA;AAAA,MAC5C,kBAAkB,CAAC;AAAA,KACrB;AAAA,EACF;AACF,CAAA;AAkBO,SAAS,sBAAA,CACd,WACA,OAAA,EACqB;AACrB,EAAA,OAAO,IAAI,uBAAA,CAAwB,SAAA,EAAW,OAAO,CAAA;AACvD","file":"index.js","sourcesContent":["import type { Firestore, Timestamp } from '@google-cloud/firestore';\nimport type { Event, ReadEvent, ReadEventMetadataWithGlobalPosition } from '@event-driven-io/emmett';\nimport {\n STREAM_DOES_NOT_EXIST,\n STREAM_EXISTS,\n NO_CONCURRENCY_CHECK,\n type ExpectedStreamVersion as EmmettExpectedStreamVersion,\n} from '@event-driven-io/emmett';\n\n/**\n * Minimal logger interface compatible with Pino and other loggers.\n * All methods are optional to support varying logger implementations.\n */\nexport interface Logger {\n debug?(msg: string, data?: unknown): void;\n info?(msg: string, data?: unknown): void;\n warn?(msg: string, data?: unknown): void;\n error?(msg: string, err?: unknown): void;\n}\n\n/**\n * Observability configuration options\n */\nexport interface ObservabilityOptions {\n /** Optional logger instance. If not provided, no logging occurs. */\n logger?: Logger;\n}\n\n/**\n * Expected version for stream operations\n * Uses Emmett's standard version constants for full compatibility\n * - number | bigint: Expect specific version\n * - STREAM_DOES_NOT_EXIST: Stream must not exist\n * - STREAM_EXISTS: Stream must exist (any version)\n * - NO_CONCURRENCY_CHECK: No version check\n */\nexport type ExpectedStreamVersion = EmmettExpectedStreamVersion<bigint>;\n\n// Re-export Emmett constants for convenience\nexport { STREAM_DOES_NOT_EXIST, STREAM_EXISTS, NO_CONCURRENCY_CHECK };\n\n/**\n * Options for appending events to a stream\n */\nexport interface AppendToStreamOptions {\n expectedStreamVersion?: ExpectedStreamVersion;\n}\n\n/**\n * Result of appending events to a stream\n */\nexport interface AppendToStreamResult {\n nextExpectedStreamVersion: bigint;\n createdNewStream: boolean;\n}\n\n/**\n * Options for reading events from a stream\n */\nexport interface ReadStreamOptions {\n from?: bigint;\n to?: bigint;\n maxCount?: number;\n}\n\n/**\n * Metadata stored in Firestore stream document\n */\nexport interface StreamMetadata {\n version: number;\n createdAt: Timestamp;\n updatedAt: Timestamp;\n}\n\n/**\n * Event document structure in Firestore\n */\nexport interface EventDocument {\n type: string;\n data: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n timestamp: Timestamp;\n globalPosition: number;\n streamVersion: number;\n}\n\n/**\n * Firestore-specific read event metadata\n */\nexport interface FirestoreReadEventMetadata extends ReadEventMetadataWithGlobalPosition {\n streamName: string;\n streamVersion: bigint;\n timestamp: Date;\n}\n\n/**\n * Firestore read event\n */\nexport type FirestoreReadEvent<EventType extends Event = Event> = ReadEvent<\n EventType,\n FirestoreReadEventMetadata\n>;\n\n/**\n * Collection configuration for Firestore event store\n */\nexport interface CollectionConfig {\n streams: string;\n counters: string;\n}\n\n/**\n * Firestore event store options\n */\nexport interface FirestoreEventStoreOptions {\n collections?: Partial<CollectionConfig>;\n observability?: ObservabilityOptions;\n}\n\n/**\n * Firestore event store interface\n */\nexport interface FirestoreEventStore {\n /**\n * The underlying Firestore instance\n */\n readonly firestore: Firestore;\n\n /**\n * Collection names configuration\n */\n readonly collections: CollectionConfig;\n\n /**\n * Read events from a stream\n */\n readStream<EventType extends Event>(\n streamName: string,\n options?: ReadStreamOptions,\n ): Promise<FirestoreReadEvent<EventType>[]>;\n\n /**\n * Aggregate stream by applying events to state\n */\n aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: {\n evolve: (state: State, event: FirestoreReadEvent<EventType>) => State;\n initialState: () => State;\n read?: ReadStreamOptions;\n },\n ): Promise<{\n state: State;\n currentStreamVersion: bigint;\n streamExists: boolean;\n }>;\n\n /**\n * Append events to a stream\n */\n appendToStream<EventType extends Event>(\n streamName: string,\n events: EventType[],\n options?: AppendToStreamOptions,\n ): Promise<AppendToStreamResult>;\n}\n\n/**\n * Error thrown when expected version doesn't match current version\n */\nexport class ExpectedVersionConflictError extends Error {\n constructor(\n public readonly streamName: string,\n public readonly expected: ExpectedStreamVersion,\n public readonly actual: bigint | typeof STREAM_DOES_NOT_EXIST,\n ) {\n super(\n `Expected version conflict for stream '${streamName}': expected ${String(expected)}, actual ${String(actual)}`,\n );\n this.name = 'ExpectedVersionConflictError';\n Object.setPrototypeOf(this, ExpectedVersionConflictError.prototype);\n }\n}\n","import type { Timestamp } from '@google-cloud/firestore';\nimport type { ExpectedStreamVersion } from './types';\nimport {\n STREAM_DOES_NOT_EXIST,\n STREAM_EXISTS,\n NO_CONCURRENCY_CHECK,\n ExpectedVersionConflictError,\n} from './types';\n\n/**\n * Pad version number with leading zeros for Firestore document IDs\n * This ensures automatic ordering by version in Firestore\n *\n * @param version - The version number to pad\n * @returns Zero-padded string of length 10\n *\n * @example\n * padVersion(0) // \"0000000000\"\n * padVersion(42) // \"0000000042\"\n * padVersion(12345) // \"0000012345\"\n */\nexport function padVersion(version: number | bigint): string {\n return version.toString().padStart(10, '0');\n}\n\n/**\n * Parse a stream name into type and ID components\n *\n * @param streamName - Stream name in format \"Type-id\" or \"Type-with-dashes-id\"\n * @returns Object with streamType and streamId\n *\n * @example\n * parseStreamName(\"User-123\") // { streamType: \"User\", streamId: \"123\" }\n * parseStreamName(\"ShoppingCart-abc-def-123\") // { streamType: \"ShoppingCart\", streamId: \"abc-def-123\" }\n */\nexport function parseStreamName(streamName: string): {\n streamType: string;\n streamId: string;\n} {\n const firstDashIndex = streamName.indexOf('-');\n\n if (firstDashIndex === -1) {\n return {\n streamType: streamName,\n streamId: '',\n };\n }\n\n return {\n streamType: streamName.substring(0, firstDashIndex),\n streamId: streamName.substring(firstDashIndex + 1),\n };\n}\n\n/**\n * Convert Firestore Timestamp to JavaScript Date\n *\n * @param timestamp - Firestore Timestamp\n * @returns JavaScript Date object\n */\nexport function timestampToDate(timestamp: Timestamp): Date {\n return timestamp.toDate();\n}\n\n/**\n * Validate expected version against current version\n *\n * @param streamName - Stream name for error messages\n * @param expectedVersion - Expected version constraint\n * @param currentVersion - Current stream version (or STREAM_DOES_NOT_EXIST if stream doesn't exist)\n * @throws ExpectedVersionConflictError if versions don't match\n */\nexport function assertExpectedVersionMatchesCurrent(\n streamName: string,\n expectedVersion: ExpectedStreamVersion,\n currentVersion: bigint | typeof STREAM_DOES_NOT_EXIST,\n): void {\n // NO_CONCURRENCY_CHECK - no validation needed\n if (expectedVersion === NO_CONCURRENCY_CHECK) {\n return;\n }\n\n // STREAM_DOES_NOT_EXIST - stream must not exist\n if (expectedVersion === STREAM_DOES_NOT_EXIST) {\n if (currentVersion !== STREAM_DOES_NOT_EXIST) {\n throw new ExpectedVersionConflictError(\n streamName,\n expectedVersion,\n currentVersion,\n );\n }\n return;\n }\n\n // STREAM_EXISTS - stream must exist\n if (expectedVersion === STREAM_EXISTS) {\n if (currentVersion === STREAM_DOES_NOT_EXIST) {\n throw new ExpectedVersionConflictError(\n streamName,\n expectedVersion,\n currentVersion,\n );\n }\n return;\n }\n\n // Specific version number\n const expectedBigInt = BigInt(expectedVersion);\n if (currentVersion === STREAM_DOES_NOT_EXIST || currentVersion !== expectedBigInt) {\n throw new ExpectedVersionConflictError(\n streamName,\n expectedVersion,\n currentVersion,\n );\n }\n}\n\n/**\n * Get the current stream version from metadata\n *\n * @param streamExists - Whether the stream document exists\n * @param version - Version number from Firestore (if stream exists)\n * @returns Current version as bigint or STREAM_DOES_NOT_EXIST\n */\nexport function getCurrentStreamVersion(\n streamExists: boolean,\n version?: number,\n): bigint | typeof STREAM_DOES_NOT_EXIST {\n if (!streamExists) {\n return STREAM_DOES_NOT_EXIST;\n }\n return BigInt(version ?? -1);\n}\n\n/**\n * Calculate the next expected stream version after appending events\n *\n * @param currentVersion - Current stream version\n * @param eventCount - Number of events being appended\n * @returns Next expected version as bigint\n */\nexport function calculateNextVersion(\n currentVersion: bigint | typeof STREAM_DOES_NOT_EXIST,\n eventCount: number,\n): bigint {\n if (currentVersion === STREAM_DOES_NOT_EXIST) {\n return BigInt(eventCount - 1);\n }\n // Type assertion needed because TypeScript doesn't narrow ExpectedStreamVersionGeneral properly\n return (currentVersion as bigint) + BigInt(eventCount);\n}\n","import type { Firestore, Transaction, Timestamp } from '@google-cloud/firestore';\nimport type { Event } from '@event-driven-io/emmett';\nimport { trace, SpanStatusCode } from '@opentelemetry/api';\nimport type {\n AppendToStreamOptions,\n AppendToStreamResult,\n CollectionConfig,\n EventDocument,\n ExpectedStreamVersion,\n FirestoreEventStore,\n FirestoreEventStoreOptions,\n FirestoreReadEvent,\n Logger,\n ReadStreamOptions,\n StreamMetadata,\n} from './types';\nimport { NO_CONCURRENCY_CHECK, STREAM_DOES_NOT_EXIST, ExpectedVersionConflictError } from './types';\nimport {\n assertExpectedVersionMatchesCurrent,\n getCurrentStreamVersion,\n padVersion,\n timestampToDate,\n} from './utils';\n\nconst tracer = trace.getTracer('@emmett-community/emmett-google-firestore');\n\n/**\n * Safe logging helper that handles undefined logger methods\n */\nfunction safeLog(\n logger: Logger | undefined,\n level: keyof Logger,\n msg: string,\n data?: unknown,\n): void {\n if (!logger) return;\n const logFn = logger[level];\n if (typeof logFn === 'function') {\n logFn.call(logger, msg, data);\n }\n}\n\nconst DEFAULT_COLLECTIONS: CollectionConfig = {\n streams: 'streams',\n counters: '_counters',\n};\n\n/**\n * Firestore Event Store Implementation\n *\n * Stores events in Firestore using a subcollection pattern:\n * - /streams/{streamName} - Stream metadata (version, timestamps)\n * - /streams/{streamName}/events/{version} - Individual events\n * - /_counters/global_position - Global event counter\n */\nexport class FirestoreEventStoreImpl implements FirestoreEventStore {\n public readonly collections: CollectionConfig;\n private readonly logger: Logger | undefined;\n\n constructor(\n public readonly firestore: Firestore,\n options: FirestoreEventStoreOptions = {},\n ) {\n this.collections = {\n ...DEFAULT_COLLECTIONS,\n ...options.collections,\n };\n this.logger = options.observability?.logger;\n\n safeLog(this.logger, 'info', 'FirestoreEventStore initialized');\n }\n\n /**\n * Read events from a stream\n */\n async readStream<EventType extends Event>(\n streamName: string,\n options: ReadStreamOptions = {},\n ): Promise<FirestoreReadEvent<EventType>[]> {\n const span = tracer.startSpan('emmett.firestore.read_stream', {\n attributes: { 'emmett.stream_name': streamName },\n });\n\n try {\n safeLog(this.logger, 'debug', 'Reading stream', {\n streamName,\n from: options.from?.toString(),\n to: options.to?.toString(),\n maxCount: options.maxCount,\n });\n\n const { from, to, maxCount } = options;\n\n // Reference to events subcollection\n let query = this.firestore\n .collection(this.collections.streams)\n .doc(streamName)\n .collection('events')\n .orderBy('streamVersion', 'asc');\n\n // Apply range filters\n if (from !== undefined) {\n query = query.where('streamVersion', '>=', Number(from));\n }\n if (to !== undefined) {\n query = query.where('streamVersion', '<=', Number(to));\n }\n if (maxCount !== undefined && maxCount > 0) {\n query = query.limit(maxCount);\n }\n\n // Execute query\n const snapshot = await query.get();\n\n // Transform Firestore documents to events\n const events = snapshot.docs.map((doc) => {\n const data = doc.data() as EventDocument;\n return {\n type: data.type,\n data: data.data,\n metadata: {\n ...data.metadata,\n streamName,\n streamVersion: BigInt(data.streamVersion),\n streamPosition: BigInt(data.streamVersion),\n globalPosition: BigInt(data.globalPosition),\n timestamp: timestampToDate(data.timestamp),\n },\n } as FirestoreReadEvent<EventType>;\n });\n\n span.setAttribute('emmett.event_count', events.length);\n span.setStatus({ code: SpanStatusCode.OK });\n\n safeLog(this.logger, 'debug', 'Stream read completed', {\n streamName,\n eventCount: events.length,\n });\n\n return events;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n\n safeLog(this.logger, 'error', 'Failed to read stream', {\n streamName,\n error,\n });\n\n throw error;\n } finally {\n span.end();\n }\n }\n\n /**\n * Aggregate stream by applying events to state\n */\n async aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: {\n evolve: (state: State, event: FirestoreReadEvent<EventType>) => State;\n initialState: () => State;\n read?: ReadStreamOptions;\n },\n ): Promise<{\n state: State;\n currentStreamVersion: bigint;\n streamExists: boolean;\n }> {\n const { evolve, initialState, read } = options;\n const events = await this.readStream<EventType>(streamName, read);\n\n const streamExists = events.length > 0;\n const state = events.reduce(evolve, initialState());\n const currentStreamVersion = streamExists\n ? events[events.length - 1].metadata.streamVersion\n : BigInt(0);\n\n return {\n state,\n currentStreamVersion,\n streamExists,\n };\n }\n\n /**\n * Append events to a stream with optimistic concurrency control\n */\n async appendToStream<EventType extends Event>(\n streamName: string,\n events: EventType[],\n options: AppendToStreamOptions = {},\n ): Promise<AppendToStreamResult> {\n const span = tracer.startSpan('emmett.firestore.append_to_stream', {\n attributes: {\n 'emmett.stream_name': streamName,\n 'emmett.event_count': events.length,\n },\n });\n\n try {\n if (events.length === 0) {\n throw new Error('Cannot append empty event array');\n }\n\n const { expectedStreamVersion = NO_CONCURRENCY_CHECK } = options;\n\n safeLog(this.logger, 'debug', 'Appending to stream', {\n streamName,\n eventCount: events.length,\n eventTypes: events.map((e) => e.type),\n expectedVersion: String(expectedStreamVersion),\n });\n\n // Execute in transaction for atomicity\n const result = await this.firestore.runTransaction(async (transaction) => {\n return await this.appendToStreamInTransaction(\n transaction,\n streamName,\n events,\n expectedStreamVersion,\n );\n });\n\n span.setAttribute('emmett.new_version', Number(result.nextExpectedStreamVersion));\n span.setAttribute('emmett.created_new_stream', result.createdNewStream);\n span.setStatus({ code: SpanStatusCode.OK });\n\n safeLog(this.logger, 'debug', 'Append completed', {\n streamName,\n newVersion: result.nextExpectedStreamVersion.toString(),\n createdNewStream: result.createdNewStream,\n });\n\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n\n if (error instanceof ExpectedVersionConflictError) {\n safeLog(this.logger, 'warn', 'Version conflict during append', {\n streamName,\n expected: String(error.expected),\n actual: String(error.actual),\n });\n } else {\n safeLog(this.logger, 'error', 'Failed to append to stream', {\n streamName,\n error,\n });\n }\n\n throw error;\n } finally {\n span.end();\n }\n }\n\n /**\n * Internal method to append events within a transaction\n *\n * Note: No separate span here - this method runs inside appendToStream's span,\n * and Firestore transaction operations are atomic. The parent span captures\n * the full transaction duration.\n */\n private async appendToStreamInTransaction<EventType extends Event>(\n transaction: Transaction,\n streamName: string,\n events: EventType[],\n expectedStreamVersion: ExpectedStreamVersion,\n ): Promise<AppendToStreamResult> {\n // 1. Get stream metadata reference\n const streamRef = this.firestore\n .collection(this.collections.streams)\n .doc(streamName);\n\n const streamDoc = await transaction.get(streamRef);\n const streamExists = streamDoc.exists;\n const streamData = streamDoc.data() as StreamMetadata | undefined;\n\n // 2. Get current version and validate expected version\n const currentVersion = getCurrentStreamVersion(\n streamExists,\n streamData?.version,\n );\n\n safeLog(this.logger, 'debug', 'Read stream metadata', {\n streamName,\n exists: streamExists,\n currentVersion: currentVersion === STREAM_DOES_NOT_EXIST ? 'none' : currentVersion.toString(),\n });\n\n assertExpectedVersionMatchesCurrent(\n streamName,\n expectedStreamVersion,\n currentVersion,\n );\n\n // 3. Get and increment global position counter\n const counterRef = this.firestore\n .collection(this.collections.counters)\n .doc('global_position');\n\n const counterDoc = await transaction.get(counterRef);\n let globalPosition = counterDoc.exists\n ? (counterDoc.data()?.value as number) ?? 0\n : 0;\n\n // 4. Calculate starting version for new events\n const baseVersion =\n currentVersion === STREAM_DOES_NOT_EXIST ? -1 : Number(currentVersion);\n\n // 5. Append events to subcollection\n const TimestampClass = this.firestore.constructor as unknown as { Timestamp: typeof Timestamp };\n const now = TimestampClass.Timestamp.now();\n\n events.forEach((event, index) => {\n const eventVersion = baseVersion + 1 + index;\n const eventRef = streamRef\n .collection('events')\n .doc(padVersion(eventVersion));\n\n const metadata = (event as { metadata?: Record<string, unknown> }).metadata;\n const eventDocument: EventDocument = {\n type: event.type,\n data: event.data,\n ...(metadata && { metadata }),\n timestamp: now,\n globalPosition: globalPosition++,\n streamVersion: eventVersion,\n };\n\n transaction.set(eventRef, eventDocument);\n });\n\n // 6. Update stream metadata\n const newVersion = baseVersion + events.length;\n const updatedMetadata: StreamMetadata = {\n version: newVersion,\n createdAt: streamData?.createdAt ?? now,\n updatedAt: now,\n };\n\n transaction.set(streamRef, updatedMetadata);\n\n // 7. Update global position counter\n transaction.set(counterRef, {\n value: globalPosition,\n updatedAt: now,\n });\n\n safeLog(this.logger, 'debug', 'Events written to transaction', {\n streamName,\n count: events.length,\n newVersion,\n });\n\n // 8. Return result\n return {\n nextExpectedStreamVersion: BigInt(newVersion),\n createdNewStream: !streamExists,\n };\n }\n}\n\n/**\n * Factory function to create a Firestore event store\n *\n * @param firestore - Firestore instance\n * @param options - Optional configuration\n * @returns Firestore event store instance\n *\n * @example\n * ```typescript\n * import { Firestore } from '@google-cloud/firestore';\n * import { getFirestoreEventStore } from '@emmett-community/emmett-google-firestore';\n *\n * const firestore = new Firestore({ projectId: 'my-project' });\n * const eventStore = getFirestoreEventStore(firestore);\n * ```\n */\nexport function getFirestoreEventStore(\n firestore: Firestore,\n options?: FirestoreEventStoreOptions,\n): FirestoreEventStore {\n return new FirestoreEventStoreImpl(firestore, options);\n}\n"]}
1
+ {"version":3,"sources":["../src/eventStore/types.ts","../src/eventStore/utils.ts","../src/eventStore/firestoreEventStore.ts"],"names":["NO_CONCURRENCY_CHECK","STREAM_DOES_NOT_EXIST","STREAM_EXISTS","trace","SpanStatusCode"],"mappings":"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","file":"index.js","sourcesContent":["import type { Firestore, Timestamp } from '@google-cloud/firestore';\nimport type { Event, ReadEvent, ReadEventMetadataWithGlobalPosition } from '@event-driven-io/emmett';\nimport {\n STREAM_DOES_NOT_EXIST,\n STREAM_EXISTS,\n NO_CONCURRENCY_CHECK,\n type ExpectedStreamVersion as EmmettExpectedStreamVersion,\n} from '@event-driven-io/emmett';\n\n/**\n * Canonical Logger contract for the Emmett ecosystem.\n *\n * DO NOT MODIFY this interface without updating ALL packages in the ecosystem.\n *\n * This package defines the canonical Logger interface.\n * Implementations (Pino, Winston, etc.) MUST adapt to this contract.\n * This contract MUST NOT adapt to any specific implementation.\n *\n * Semantic Rules:\n * - context (first parameter): ALWAYS structured data as Record<string, unknown>\n * - message (second parameter): ALWAYS the human-readable log message\n * - The order is NEVER inverted\n * - The (message, data) form is NOT valid for this contract\n * - Error objects MUST use the 'err' key (frozen semantic)\n *\n * @example\n * ```typescript\n * // Pino - native compatibility\n * import pino from 'pino';\n * const logger = pino();\n * // logger.info({ orderId }, 'Order created') matches our contract\n * ```\n */\nexport interface Logger {\n /**\n * Log debug-level message with structured context.\n * @param context - Structured data to include in the log entry\n * @param message - Optional human-readable message\n */\n debug(context: Record<string, unknown>, message?: string): void;\n\n /**\n * Log info-level message with structured context.\n * @param context - Structured data to include in the log entry\n * @param message - Optional human-readable message\n */\n info(context: Record<string, unknown>, message?: string): void;\n\n /**\n * Log warn-level message with structured context.\n * @param context - Structured data to include in the log entry\n * @param message - Optional human-readable message\n */\n warn(context: Record<string, unknown>, message?: string): void;\n\n /**\n * Log error-level message with structured context.\n * @param context - Structured data to include in the log entry (MUST use 'err' key for Error objects)\n * @param message - Optional human-readable message\n */\n error(context: Record<string, unknown>, message?: string): void;\n}\n\n/**\n * Observability configuration options\n */\nexport interface ObservabilityOptions {\n /** Optional logger instance. If not provided, no logging occurs. */\n logger?: Logger;\n}\n\n/**\n * Expected version for stream operations\n * Uses Emmett's standard version constants for full compatibility\n * - number | bigint: Expect specific version\n * - STREAM_DOES_NOT_EXIST: Stream must not exist\n * - STREAM_EXISTS: Stream must exist (any version)\n * - NO_CONCURRENCY_CHECK: No version check\n */\nexport type ExpectedStreamVersion = EmmettExpectedStreamVersion<bigint>;\n\n// Re-export Emmett constants for convenience\nexport { STREAM_DOES_NOT_EXIST, STREAM_EXISTS, NO_CONCURRENCY_CHECK };\n\n/**\n * Options for appending events to a stream\n */\nexport interface AppendToStreamOptions {\n expectedStreamVersion?: ExpectedStreamVersion;\n}\n\n/**\n * Result of appending events to a stream\n */\nexport interface AppendToStreamResult {\n nextExpectedStreamVersion: bigint;\n createdNewStream: boolean;\n}\n\n/**\n * Options for reading events from a stream\n * Reuses the canonical `ReadStreamOptions` shape from the Emmett core types.\n */\nexport interface ReadStreamOptions {\n from?: bigint;\n to?: bigint;\n maxCount?: number;\n}\n\n/**\n * Metadata stored in Firestore stream document\n */\nexport interface StreamMetadata {\n version: number;\n createdAt: Timestamp;\n updatedAt: Timestamp;\n}\n\n/**\n * Event document structure in Firestore\n */\nexport interface EventDocument {\n type: string;\n data: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n timestamp: Timestamp;\n globalPosition: number;\n streamVersion: number;\n}\n\n/**\n * Firestore-specific read event metadata\n */\nexport interface FirestoreReadEventMetadata extends ReadEventMetadataWithGlobalPosition {\n streamName: string;\n streamVersion: bigint;\n timestamp: Date;\n}\n\n/**\n * Firestore read event\n */\nexport type FirestoreReadEvent<EventType extends Event = Event> = ReadEvent<\n EventType,\n FirestoreReadEventMetadata\n>;\n\n/**\n * Collection configuration for Firestore event store\n */\nexport interface CollectionConfig {\n streams: string;\n counters: string;\n}\n\n/**\n * Firestore event store options\n */\nexport interface FirestoreEventStoreOptions {\n collections?: Partial<CollectionConfig>;\n observability?: ObservabilityOptions;\n}\n\n/**\n * Firestore event store interface\n */\nexport interface FirestoreEventStore {\n /**\n * The underlying Firestore instance\n */\n readonly firestore: Firestore;\n\n /**\n * Collection names configuration\n */\n readonly collections: CollectionConfig;\n\n /**\n * Read events from a stream\n */\n readStream<EventType extends Event>(\n streamName: string,\n options?: ReadStreamOptions,\n ): Promise<FirestoreReadEvent<EventType>[]>;\n\n /**\n * Aggregate stream by applying events to state\n */\n aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: {\n evolve: (state: State, event: FirestoreReadEvent<EventType>) => State;\n initialState: () => State;\n read?: ReadStreamOptions;\n },\n ): Promise<{\n state: State;\n currentStreamVersion: bigint;\n streamExists: boolean;\n }>;\n\n /**\n * Append events to a stream\n */\n appendToStream<EventType extends Event>(\n streamName: string,\n events: EventType[],\n options?: AppendToStreamOptions,\n ): Promise<AppendToStreamResult>;\n}\n\n/**\n * Error thrown when expected version doesn't match current version\n */\nexport class ExpectedVersionConflictError extends Error {\n constructor(\n public readonly streamName: string,\n public readonly expected: ExpectedStreamVersion,\n public readonly actual: bigint | typeof STREAM_DOES_NOT_EXIST,\n ) {\n super(\n `Expected version conflict for stream '${streamName}': expected ${String(expected)}, actual ${String(actual)}`,\n );\n this.name = 'ExpectedVersionConflictError';\n Object.setPrototypeOf(this, ExpectedVersionConflictError.prototype);\n }\n}\n","import type { Timestamp } from '@google-cloud/firestore';\nimport type { ExpectedStreamVersion } from './types';\nimport {\n STREAM_DOES_NOT_EXIST,\n STREAM_EXISTS,\n NO_CONCURRENCY_CHECK,\n ExpectedVersionConflictError,\n} from './types';\n\n/**\n * Pad version number with leading zeros for Firestore document IDs\n * This ensures automatic ordering by version in Firestore\n *\n * @param version - The version number to pad\n * @returns Zero-padded string of length 10\n *\n * @example\n * padVersion(0) // \"0000000000\"\n * padVersion(42) // \"0000000042\"\n * padVersion(12345) // \"0000012345\"\n */\nexport function padVersion(version: number | bigint): string {\n return version.toString().padStart(10, '0');\n}\n\n/**\n * Parse a stream name into type and ID components\n *\n * @param streamName - Stream name in format \"Type-id\" or \"Type-with-dashes-id\"\n * @returns Object with streamType and streamId\n *\n * @example\n * parseStreamName(\"User-123\") // { streamType: \"User\", streamId: \"123\" }\n * parseStreamName(\"ShoppingCart-abc-def-123\") // { streamType: \"ShoppingCart\", streamId: \"abc-def-123\" }\n */\nexport function parseStreamName(streamName: string): {\n streamType: string;\n streamId: string;\n} {\n const firstDashIndex = streamName.indexOf('-');\n\n if (firstDashIndex === -1) {\n return {\n streamType: streamName,\n streamId: '',\n };\n }\n\n return {\n streamType: streamName.substring(0, firstDashIndex),\n streamId: streamName.substring(firstDashIndex + 1),\n };\n}\n\n/**\n * Convert Firestore Timestamp to JavaScript Date\n *\n * @param timestamp - Firestore Timestamp\n * @returns JavaScript Date object\n */\nexport function timestampToDate(timestamp: Timestamp): Date {\n return timestamp.toDate();\n}\n\n/**\n * Validate expected version against current version\n *\n * @param streamName - Stream name for error messages\n * @param expectedVersion - Expected version constraint\n * @param currentVersion - Current stream version (or STREAM_DOES_NOT_EXIST if stream doesn't exist)\n * @throws ExpectedVersionConflictError if versions don't match\n */\nexport function assertExpectedVersionMatchesCurrent(\n streamName: string,\n expectedVersion: ExpectedStreamVersion,\n currentVersion: bigint | typeof STREAM_DOES_NOT_EXIST,\n): void {\n // NO_CONCURRENCY_CHECK - no validation needed\n if (expectedVersion === NO_CONCURRENCY_CHECK) {\n return;\n }\n\n // STREAM_DOES_NOT_EXIST - stream must not exist\n if (expectedVersion === STREAM_DOES_NOT_EXIST) {\n if (currentVersion !== STREAM_DOES_NOT_EXIST) {\n throw new ExpectedVersionConflictError(\n streamName,\n expectedVersion,\n currentVersion,\n );\n }\n return;\n }\n\n // STREAM_EXISTS - stream must exist\n if (expectedVersion === STREAM_EXISTS) {\n if (currentVersion === STREAM_DOES_NOT_EXIST) {\n throw new ExpectedVersionConflictError(\n streamName,\n expectedVersion,\n currentVersion,\n );\n }\n return;\n }\n\n // Specific version number\n const expectedBigInt = BigInt(expectedVersion);\n if (currentVersion === STREAM_DOES_NOT_EXIST || currentVersion !== expectedBigInt) {\n throw new ExpectedVersionConflictError(\n streamName,\n expectedVersion,\n currentVersion,\n );\n }\n}\n\n/**\n * Get the current stream version from metadata\n *\n * @param streamExists - Whether the stream document exists\n * @param version - Version number from Firestore (if stream exists)\n * @returns Current version as bigint or STREAM_DOES_NOT_EXIST\n */\nexport function getCurrentStreamVersion(\n streamExists: boolean,\n version?: number,\n): bigint | typeof STREAM_DOES_NOT_EXIST {\n if (!streamExists) {\n return STREAM_DOES_NOT_EXIST;\n }\n return BigInt(version ?? -1);\n}\n\n/**\n * Calculate the next expected stream version after appending events\n *\n * @param currentVersion - Current stream version\n * @param eventCount - Number of events being appended\n * @returns Next expected version as bigint\n */\nexport function calculateNextVersion(\n currentVersion: bigint | typeof STREAM_DOES_NOT_EXIST,\n eventCount: number,\n): bigint {\n if (currentVersion === STREAM_DOES_NOT_EXIST) {\n return BigInt(eventCount - 1);\n }\n // Type assertion needed because TypeScript doesn't narrow ExpectedStreamVersionGeneral properly\n return (currentVersion as bigint) + BigInt(eventCount);\n}\n","import type { Firestore, Transaction, Timestamp } from '@google-cloud/firestore';\nimport type {\n AggregateStreamOptions,\n AggregateStreamResult,\n Event,\n EventStore,\n ReadStreamOptions as EmmettReadStreamOptions,\n ReadStreamResult,\n} from '@event-driven-io/emmett';\nimport { trace, SpanStatusCode } from '@opentelemetry/api';\nimport type {\n AppendToStreamOptions,\n AppendToStreamResult,\n CollectionConfig,\n EventDocument,\n ExpectedStreamVersion,\n FirestoreEventStore,\n FirestoreEventStoreOptions,\n FirestoreReadEvent,\n FirestoreReadEventMetadata,\n Logger,\n ReadStreamOptions,\n StreamMetadata,\n} from './types';\nimport { NO_CONCURRENCY_CHECK, STREAM_DOES_NOT_EXIST, ExpectedVersionConflictError } from './types';\nimport {\n assertExpectedVersionMatchesCurrent,\n getCurrentStreamVersion,\n padVersion,\n timestampToDate,\n} from './utils';\n\nconst tracer = trace.getTracer('@emmett-community/emmett-google-firestore');\n\n/**\n * @internal - NOT part of public API\n * Normalizes data to a context object for the Logger contract.\n */\nfunction normalizeContext(data: unknown): Record<string, unknown> {\n if (data === undefined || data === null) return {};\n if (typeof data === 'object' && !Array.isArray(data)) {\n return { ...(data as Record<string, unknown>) };\n }\n return { data };\n}\n\n/**\n * @internal - NOT part of public API\n * Normalizes error data to a context object for the Logger contract.\n * Uses 'err' key for Error instances (Pino compatibility).\n */\nfunction normalizeErrorContext(error: unknown): Record<string, unknown> {\n if (error === undefined || error === null) return {};\n if (error instanceof Error) {\n return { err: error };\n }\n if (typeof error === 'object' && !Array.isArray(error)) {\n return { ...(error as Record<string, unknown>) };\n }\n return { err: error };\n}\n\n/**\n * @internal - NOT part of public API\n *\n * Internal helper for ergonomic logging within this package.\n * Translates internal (msg, data) calls to canonical (context, message) contract.\n *\n * This is the ONLY point where translation from internal format to contract format occurs.\n */\nconst safeLog = {\n debug: (logger: Logger | undefined, msg: string, data?: unknown): void => {\n if (!logger) return;\n logger.debug(normalizeContext(data), msg);\n },\n info: (logger: Logger | undefined, msg: string, data?: unknown): void => {\n if (!logger) return;\n logger.info(normalizeContext(data), msg);\n },\n warn: (logger: Logger | undefined, msg: string, data?: unknown): void => {\n if (!logger) return;\n logger.warn(normalizeContext(data), msg);\n },\n error: (logger: Logger | undefined, msg: string, error?: unknown): void => {\n if (!logger) return;\n logger.error(normalizeErrorContext(error), msg);\n },\n};\n\nconst DEFAULT_COLLECTIONS: CollectionConfig = {\n streams: 'streams',\n counters: '_counters',\n};\n\n/**\n * Firestore Event Store Implementation\n *\n * Stores events in Firestore using a subcollection pattern:\n * - /streams/{streamName} - Stream metadata (version, timestamps)\n * - /streams/{streamName}/events/{version} - Individual events\n * - /_counters/global_position - Global event counter\n */\nexport class FirestoreEventStoreImpl implements FirestoreEventStore {\n public readonly collections: CollectionConfig;\n private readonly logger: Logger | undefined;\n\n constructor(\n public readonly firestore: Firestore,\n options: FirestoreEventStoreOptions = {},\n ) {\n this.collections = {\n ...DEFAULT_COLLECTIONS,\n ...options.collections,\n };\n this.logger = options.observability?.logger;\n\n safeLog.info(this.logger, 'FirestoreEventStore initialized');\n }\n\n /**\n * Read events from a stream\n */\n async readStream<EventType extends Event>(\n streamName: string,\n options?: ReadStreamOptions,\n ): Promise<FirestoreReadEvent<EventType>[]> {\n const span = tracer.startSpan('emmett.firestore.read_stream', {\n attributes: { 'emmett.stream_name': streamName },\n });\n\n try {\n const from = options && 'from' in options ? options.from : undefined;\n const to = options && 'to' in options ? options.to : undefined;\n const maxCountLimit =\n options && 'maxCount' in options && options.maxCount !== undefined\n ? Number(options.maxCount)\n : undefined;\n\n safeLog.debug(this.logger, 'Reading stream', {\n streamName,\n from: from?.toString(),\n to: to?.toString(),\n maxCount: maxCountLimit,\n });\n\n // Reference to events subcollection\n let query = this.firestore\n .collection(this.collections.streams)\n .doc(streamName)\n .collection('events')\n .orderBy('streamVersion', 'asc');\n\n // Apply range filters\n if (from !== undefined) {\n query = query.where('streamVersion', '>=', Number(from));\n }\n if (to !== undefined) {\n query = query.where('streamVersion', '<=', Number(to));\n }\n if (maxCountLimit !== undefined && maxCountLimit > 0) {\n query = query.limit(maxCountLimit);\n }\n\n // Execute query\n const snapshot = await query.get();\n\n // Transform Firestore documents to events\n const events = snapshot.docs.map((doc) => {\n const data = doc.data() as EventDocument;\n return {\n type: data.type,\n data: data.data,\n metadata: {\n ...data.metadata,\n streamName,\n streamVersion: BigInt(data.streamVersion),\n streamPosition: BigInt(data.streamVersion),\n globalPosition: BigInt(data.globalPosition),\n timestamp: timestampToDate(data.timestamp),\n },\n } as FirestoreReadEvent<EventType>;\n });\n\n span.setAttribute('emmett.event_count', events.length);\n span.setStatus({ code: SpanStatusCode.OK });\n\n safeLog.debug(this.logger, 'Stream read completed', {\n streamName,\n eventCount: events.length,\n });\n\n return events;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n\n safeLog.error(this.logger, 'Failed to read stream', {\n streamName,\n error,\n });\n\n throw error;\n } finally {\n span.end();\n }\n }\n\n /**\n * Aggregate stream by applying events to state\n */\n async aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: {\n evolve: (state: State, event: FirestoreReadEvent<EventType>) => State;\n initialState: () => State;\n read?: ReadStreamOptions;\n },\n ): Promise<{\n state: State;\n currentStreamVersion: bigint;\n streamExists: boolean;\n }> {\n const { evolve, initialState, read } = options;\n const events = await this.readStream<EventType>(streamName, read);\n\n const streamExists = events.length > 0;\n const state = events.reduce(evolve, initialState());\n const currentStreamVersion = streamExists\n ? events[events.length - 1].metadata.streamVersion\n : BigInt(0);\n\n return {\n state,\n currentStreamVersion,\n streamExists,\n };\n }\n\n /**\n * Append events to a stream with optimistic concurrency control\n */\n async appendToStream<EventType extends Event>(\n streamName: string,\n events: EventType[],\n options: AppendToStreamOptions = {},\n ): Promise<AppendToStreamResult> {\n const span = tracer.startSpan('emmett.firestore.append_to_stream', {\n attributes: {\n 'emmett.stream_name': streamName,\n 'emmett.event_count': events.length,\n },\n });\n\n try {\n if (events.length === 0) {\n throw new Error('Cannot append empty event array');\n }\n\n const { expectedStreamVersion = NO_CONCURRENCY_CHECK } = options;\n\n safeLog.debug(this.logger, 'Appending to stream', {\n streamName,\n eventCount: events.length,\n eventTypes: events.map((e) => e.type),\n expectedVersion: String(expectedStreamVersion),\n });\n\n // Execute in transaction for atomicity\n const result = await this.firestore.runTransaction(async (transaction) => {\n return await this.appendToStreamInTransaction(\n transaction,\n streamName,\n events,\n expectedStreamVersion,\n );\n });\n\n span.setAttribute('emmett.new_version', Number(result.nextExpectedStreamVersion));\n span.setAttribute('emmett.created_new_stream', result.createdNewStream);\n span.setStatus({ code: SpanStatusCode.OK });\n\n safeLog.debug(this.logger, 'Append completed', {\n streamName,\n newVersion: result.nextExpectedStreamVersion.toString(),\n createdNewStream: result.createdNewStream,\n });\n\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n\n if (error instanceof ExpectedVersionConflictError) {\n safeLog.warn(this.logger, 'Version conflict during append', {\n streamName,\n expected: String(error.expected),\n actual: String(error.actual),\n });\n } else {\n safeLog.error(this.logger, 'Failed to append to stream', {\n streamName,\n error,\n });\n }\n\n throw error;\n } finally {\n span.end();\n }\n }\n\n /**\n * Internal method to append events within a transaction\n *\n * Note: No separate span here - this method runs inside appendToStream's span,\n * and Firestore transaction operations are atomic. The parent span captures\n * the full transaction duration.\n */\n private async appendToStreamInTransaction<EventType extends Event>(\n transaction: Transaction,\n streamName: string,\n events: EventType[],\n expectedStreamVersion: ExpectedStreamVersion,\n ): Promise<AppendToStreamResult> {\n // 1. Get stream metadata reference\n const streamRef = this.firestore\n .collection(this.collections.streams)\n .doc(streamName);\n\n const streamDoc = await transaction.get(streamRef);\n const streamExists = streamDoc.exists;\n const streamData = streamDoc.data() as StreamMetadata | undefined;\n\n // 2. Get current version and validate expected version\n const currentVersion = getCurrentStreamVersion(\n streamExists,\n streamData?.version,\n );\n\n safeLog.debug(this.logger, 'Read stream metadata', {\n streamName,\n exists: streamExists,\n currentVersion: currentVersion === STREAM_DOES_NOT_EXIST ? 'none' : currentVersion.toString(),\n });\n\n assertExpectedVersionMatchesCurrent(\n streamName,\n expectedStreamVersion,\n currentVersion,\n );\n\n // 3. Get and increment global position counter\n const counterRef = this.firestore\n .collection(this.collections.counters)\n .doc('global_position');\n\n const counterDoc = await transaction.get(counterRef);\n let globalPosition = counterDoc.exists\n ? (counterDoc.data()?.value as number) ?? 0\n : 0;\n\n // 4. Calculate starting version for new events\n const baseVersion =\n currentVersion === STREAM_DOES_NOT_EXIST ? -1 : Number(currentVersion);\n\n // 5. Append events to subcollection\n const TimestampClass = this.firestore.constructor as unknown as { Timestamp: typeof Timestamp };\n const now = TimestampClass.Timestamp.now();\n\n events.forEach((event, index) => {\n const eventVersion = baseVersion + 1 + index;\n const eventRef = streamRef\n .collection('events')\n .doc(padVersion(eventVersion));\n\n const metadata = (event as { metadata?: Record<string, unknown> }).metadata;\n const eventDocument: EventDocument = {\n type: event.type,\n data: event.data,\n ...(metadata && { metadata }),\n timestamp: now,\n globalPosition: globalPosition++,\n streamVersion: eventVersion,\n };\n\n transaction.set(eventRef, eventDocument);\n });\n\n // 6. Update stream metadata\n const newVersion = baseVersion + events.length;\n const updatedMetadata: StreamMetadata = {\n version: newVersion,\n createdAt: streamData?.createdAt ?? now,\n updatedAt: now,\n };\n\n transaction.set(streamRef, updatedMetadata);\n\n // 7. Update global position counter\n transaction.set(counterRef, {\n value: globalPosition,\n updatedAt: now,\n });\n\n safeLog.debug(this.logger, 'Events written to transaction', {\n streamName,\n count: events.length,\n newVersion,\n });\n\n // 8. Return result\n return {\n nextExpectedStreamVersion: BigInt(newVersion),\n createdNewStream: !streamExists,\n };\n }\n}\n\n/**\n * Creates an Emmett-compatible `EventStore` wrapper around a Firestore event store.\n */\nfunction normalizeReadOptions(\n options?: EmmettReadStreamOptions<bigint>,\n): ReadStreamOptions | undefined {\n if (!options) return undefined;\n\n const result: ReadStreamOptions = {};\n\n if ('from' in options) {\n result.from = options.from;\n if ('maxCount' in options && options.maxCount !== undefined) {\n result.maxCount = Number(options.maxCount);\n }\n }\n\n if ('to' in options) {\n result.to = options.to;\n }\n\n return result;\n}\n\nexport function asEventStore(\n store: FirestoreEventStore,\n): EventStore<FirestoreReadEventMetadata> {\n async function readStream<EventType extends Event>(\n streamName: string,\n options?: EmmettReadStreamOptions<bigint>,\n ): Promise<ReadStreamResult<EventType, FirestoreReadEventMetadata>> {\n const events = await store.readStream<EventType>(streamName, normalizeReadOptions(options));\n const streamExists = events.length > 0;\n const currentStreamVersion = streamExists\n ? events[events.length - 1].metadata.streamVersion\n : BigInt(0);\n\n return {\n currentStreamVersion,\n events,\n streamExists,\n };\n }\n\n function aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: AggregateStreamOptions<State, EventType, FirestoreReadEventMetadata>,\n ): Promise<AggregateStreamResult<State, bigint>> {\n return store.aggregateStream(streamName, {\n evolve: options.evolve,\n initialState: options.initialState,\n read: normalizeReadOptions(options.read),\n });\n }\n\n function appendToStream<EventType extends Event>(\n streamName: string,\n events: EventType[],\n options?: AppendToStreamOptions,\n ): Promise<AppendToStreamResult> {\n return store.appendToStream(streamName, events, options);\n }\n\n return {\n readStream,\n aggregateStream,\n appendToStream,\n };\n}\n\n/**\n * Factory function to create a Firestore event store\n *\n * @param firestore - Firestore instance\n * @param options - Optional configuration\n * @returns Firestore event store instance\n *\n * @example\n * ```typescript\n * import { Firestore } from '@google-cloud/firestore';\n * import { getFirestoreEventStore } from '@emmett-community/emmett-google-firestore';\n *\n * const firestore = new Firestore({ projectId: 'my-project' });\n * const eventStore = getFirestoreEventStore(firestore);\n * ```\n */\nexport function getFirestoreEventStore(\n firestore: Firestore,\n options?: FirestoreEventStoreOptions,\n): FirestoreEventStore {\n return new FirestoreEventStoreImpl(firestore, options);\n}\n"]}
package/dist/index.mjs CHANGED
@@ -84,13 +84,41 @@ function calculateNextVersion(currentVersion, eventCount) {
84
84
 
85
85
  // src/eventStore/firestoreEventStore.ts
86
86
  var tracer = trace.getTracer("@emmett-community/emmett-google-firestore");
87
- function safeLog(logger, level, msg, data) {
88
- if (!logger) return;
89
- const logFn = logger[level];
90
- if (typeof logFn === "function") {
91
- logFn.call(logger, msg, data);
87
+ function normalizeContext(data) {
88
+ if (data === void 0 || data === null) return {};
89
+ if (typeof data === "object" && !Array.isArray(data)) {
90
+ return { ...data };
92
91
  }
92
+ return { data };
93
93
  }
94
+ function normalizeErrorContext(error) {
95
+ if (error === void 0 || error === null) return {};
96
+ if (error instanceof Error) {
97
+ return { err: error };
98
+ }
99
+ if (typeof error === "object" && !Array.isArray(error)) {
100
+ return { ...error };
101
+ }
102
+ return { err: error };
103
+ }
104
+ var safeLog = {
105
+ debug: (logger, msg, data) => {
106
+ if (!logger) return;
107
+ logger.debug(normalizeContext(data), msg);
108
+ },
109
+ info: (logger, msg, data) => {
110
+ if (!logger) return;
111
+ logger.info(normalizeContext(data), msg);
112
+ },
113
+ warn: (logger, msg, data) => {
114
+ if (!logger) return;
115
+ logger.warn(normalizeContext(data), msg);
116
+ },
117
+ error: (logger, msg, error) => {
118
+ if (!logger) return;
119
+ logger.error(normalizeErrorContext(error), msg);
120
+ }
121
+ };
94
122
  var DEFAULT_COLLECTIONS = {
95
123
  streams: "streams",
96
124
  counters: "_counters"
@@ -103,25 +131,27 @@ var FirestoreEventStoreImpl = class {
103
131
  ...options.collections
104
132
  };
105
133
  this.logger = options.observability?.logger;
106
- safeLog(this.logger, "info", "FirestoreEventStore initialized");
134
+ safeLog.info(this.logger, "FirestoreEventStore initialized");
107
135
  }
108
136
  collections;
109
137
  logger;
110
138
  /**
111
139
  * Read events from a stream
112
140
  */
113
- async readStream(streamName, options = {}) {
141
+ async readStream(streamName, options) {
114
142
  const span = tracer.startSpan("emmett.firestore.read_stream", {
115
143
  attributes: { "emmett.stream_name": streamName }
116
144
  });
117
145
  try {
118
- safeLog(this.logger, "debug", "Reading stream", {
146
+ const from = options && "from" in options ? options.from : void 0;
147
+ const to = options && "to" in options ? options.to : void 0;
148
+ const maxCountLimit = options && "maxCount" in options && options.maxCount !== void 0 ? Number(options.maxCount) : void 0;
149
+ safeLog.debug(this.logger, "Reading stream", {
119
150
  streamName,
120
- from: options.from?.toString(),
121
- to: options.to?.toString(),
122
- maxCount: options.maxCount
151
+ from: from?.toString(),
152
+ to: to?.toString(),
153
+ maxCount: maxCountLimit
123
154
  });
124
- const { from, to, maxCount } = options;
125
155
  let query = this.firestore.collection(this.collections.streams).doc(streamName).collection("events").orderBy("streamVersion", "asc");
126
156
  if (from !== void 0) {
127
157
  query = query.where("streamVersion", ">=", Number(from));
@@ -129,8 +159,8 @@ var FirestoreEventStoreImpl = class {
129
159
  if (to !== void 0) {
130
160
  query = query.where("streamVersion", "<=", Number(to));
131
161
  }
132
- if (maxCount !== void 0 && maxCount > 0) {
133
- query = query.limit(maxCount);
162
+ if (maxCountLimit !== void 0 && maxCountLimit > 0) {
163
+ query = query.limit(maxCountLimit);
134
164
  }
135
165
  const snapshot = await query.get();
136
166
  const events = snapshot.docs.map((doc) => {
@@ -150,7 +180,7 @@ var FirestoreEventStoreImpl = class {
150
180
  });
151
181
  span.setAttribute("emmett.event_count", events.length);
152
182
  span.setStatus({ code: SpanStatusCode.OK });
153
- safeLog(this.logger, "debug", "Stream read completed", {
183
+ safeLog.debug(this.logger, "Stream read completed", {
154
184
  streamName,
155
185
  eventCount: events.length
156
186
  });
@@ -158,7 +188,7 @@ var FirestoreEventStoreImpl = class {
158
188
  } catch (error) {
159
189
  span.recordException(error);
160
190
  span.setStatus({ code: SpanStatusCode.ERROR });
161
- safeLog(this.logger, "error", "Failed to read stream", {
191
+ safeLog.error(this.logger, "Failed to read stream", {
162
192
  streamName,
163
193
  error
164
194
  });
@@ -197,7 +227,7 @@ var FirestoreEventStoreImpl = class {
197
227
  throw new Error("Cannot append empty event array");
198
228
  }
199
229
  const { expectedStreamVersion = NO_CONCURRENCY_CHECK } = options;
200
- safeLog(this.logger, "debug", "Appending to stream", {
230
+ safeLog.debug(this.logger, "Appending to stream", {
201
231
  streamName,
202
232
  eventCount: events.length,
203
233
  eventTypes: events.map((e) => e.type),
@@ -214,7 +244,7 @@ var FirestoreEventStoreImpl = class {
214
244
  span.setAttribute("emmett.new_version", Number(result.nextExpectedStreamVersion));
215
245
  span.setAttribute("emmett.created_new_stream", result.createdNewStream);
216
246
  span.setStatus({ code: SpanStatusCode.OK });
217
- safeLog(this.logger, "debug", "Append completed", {
247
+ safeLog.debug(this.logger, "Append completed", {
218
248
  streamName,
219
249
  newVersion: result.nextExpectedStreamVersion.toString(),
220
250
  createdNewStream: result.createdNewStream
@@ -224,13 +254,13 @@ var FirestoreEventStoreImpl = class {
224
254
  span.recordException(error);
225
255
  span.setStatus({ code: SpanStatusCode.ERROR });
226
256
  if (error instanceof ExpectedVersionConflictError) {
227
- safeLog(this.logger, "warn", "Version conflict during append", {
257
+ safeLog.warn(this.logger, "Version conflict during append", {
228
258
  streamName,
229
259
  expected: String(error.expected),
230
260
  actual: String(error.actual)
231
261
  });
232
262
  } else {
233
- safeLog(this.logger, "error", "Failed to append to stream", {
263
+ safeLog.error(this.logger, "Failed to append to stream", {
234
264
  streamName,
235
265
  error
236
266
  });
@@ -256,7 +286,7 @@ var FirestoreEventStoreImpl = class {
256
286
  streamExists,
257
287
  streamData?.version
258
288
  );
259
- safeLog(this.logger, "debug", "Read stream metadata", {
289
+ safeLog.debug(this.logger, "Read stream metadata", {
260
290
  streamName,
261
291
  exists: streamExists,
262
292
  currentVersion: currentVersion === STREAM_DOES_NOT_EXIST ? "none" : currentVersion.toString()
@@ -297,7 +327,7 @@ var FirestoreEventStoreImpl = class {
297
327
  value: globalPosition,
298
328
  updatedAt: now
299
329
  });
300
- safeLog(this.logger, "debug", "Events written to transaction", {
330
+ safeLog.debug(this.logger, "Events written to transaction", {
301
331
  streamName,
302
332
  count: events.length,
303
333
  newVersion
@@ -308,10 +338,51 @@ var FirestoreEventStoreImpl = class {
308
338
  };
309
339
  }
310
340
  };
341
+ function normalizeReadOptions(options) {
342
+ if (!options) return void 0;
343
+ const result = {};
344
+ if ("from" in options) {
345
+ result.from = options.from;
346
+ if ("maxCount" in options && options.maxCount !== void 0) {
347
+ result.maxCount = Number(options.maxCount);
348
+ }
349
+ }
350
+ if ("to" in options) {
351
+ result.to = options.to;
352
+ }
353
+ return result;
354
+ }
355
+ function asEventStore(store) {
356
+ async function readStream(streamName, options) {
357
+ const events = await store.readStream(streamName, normalizeReadOptions(options));
358
+ const streamExists = events.length > 0;
359
+ const currentStreamVersion = streamExists ? events[events.length - 1].metadata.streamVersion : BigInt(0);
360
+ return {
361
+ currentStreamVersion,
362
+ events,
363
+ streamExists
364
+ };
365
+ }
366
+ function aggregateStream(streamName, options) {
367
+ return store.aggregateStream(streamName, {
368
+ evolve: options.evolve,
369
+ initialState: options.initialState,
370
+ read: normalizeReadOptions(options.read)
371
+ });
372
+ }
373
+ function appendToStream(streamName, events, options) {
374
+ return store.appendToStream(streamName, events, options);
375
+ }
376
+ return {
377
+ readStream,
378
+ aggregateStream,
379
+ appendToStream
380
+ };
381
+ }
311
382
  function getFirestoreEventStore(firestore, options) {
312
383
  return new FirestoreEventStoreImpl(firestore, options);
313
384
  }
314
385
 
315
- export { ExpectedVersionConflictError, assertExpectedVersionMatchesCurrent, calculateNextVersion, getCurrentStreamVersion, getFirestoreEventStore, padVersion, parseStreamName, timestampToDate };
386
+ export { ExpectedVersionConflictError, asEventStore, assertExpectedVersionMatchesCurrent, calculateNextVersion, getCurrentStreamVersion, getFirestoreEventStore, padVersion, parseStreamName, timestampToDate };
316
387
  //# sourceMappingURL=index.mjs.map
317
388
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/eventStore/types.ts","../src/eventStore/utils.ts","../src/eventStore/firestoreEventStore.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { Firestore, Timestamp } from '@google-cloud/firestore';\nimport type { Event, ReadEvent, ReadEventMetadataWithGlobalPosition } from '@event-driven-io/emmett';\nimport {\n STREAM_DOES_NOT_EXIST,\n STREAM_EXISTS,\n NO_CONCURRENCY_CHECK,\n type ExpectedStreamVersion as EmmettExpectedStreamVersion,\n} from '@event-driven-io/emmett';\n\n/**\n * Minimal logger interface compatible with Pino and other loggers.\n * All methods are optional to support varying logger implementations.\n */\nexport interface Logger {\n debug?(msg: string, data?: unknown): void;\n info?(msg: string, data?: unknown): void;\n warn?(msg: string, data?: unknown): void;\n error?(msg: string, err?: unknown): void;\n}\n\n/**\n * Observability configuration options\n */\nexport interface ObservabilityOptions {\n /** Optional logger instance. If not provided, no logging occurs. */\n logger?: Logger;\n}\n\n/**\n * Expected version for stream operations\n * Uses Emmett's standard version constants for full compatibility\n * - number | bigint: Expect specific version\n * - STREAM_DOES_NOT_EXIST: Stream must not exist\n * - STREAM_EXISTS: Stream must exist (any version)\n * - NO_CONCURRENCY_CHECK: No version check\n */\nexport type ExpectedStreamVersion = EmmettExpectedStreamVersion<bigint>;\n\n// Re-export Emmett constants for convenience\nexport { STREAM_DOES_NOT_EXIST, STREAM_EXISTS, NO_CONCURRENCY_CHECK };\n\n/**\n * Options for appending events to a stream\n */\nexport interface AppendToStreamOptions {\n expectedStreamVersion?: ExpectedStreamVersion;\n}\n\n/**\n * Result of appending events to a stream\n */\nexport interface AppendToStreamResult {\n nextExpectedStreamVersion: bigint;\n createdNewStream: boolean;\n}\n\n/**\n * Options for reading events from a stream\n */\nexport interface ReadStreamOptions {\n from?: bigint;\n to?: bigint;\n maxCount?: number;\n}\n\n/**\n * Metadata stored in Firestore stream document\n */\nexport interface StreamMetadata {\n version: number;\n createdAt: Timestamp;\n updatedAt: Timestamp;\n}\n\n/**\n * Event document structure in Firestore\n */\nexport interface EventDocument {\n type: string;\n data: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n timestamp: Timestamp;\n globalPosition: number;\n streamVersion: number;\n}\n\n/**\n * Firestore-specific read event metadata\n */\nexport interface FirestoreReadEventMetadata extends ReadEventMetadataWithGlobalPosition {\n streamName: string;\n streamVersion: bigint;\n timestamp: Date;\n}\n\n/**\n * Firestore read event\n */\nexport type FirestoreReadEvent<EventType extends Event = Event> = ReadEvent<\n EventType,\n FirestoreReadEventMetadata\n>;\n\n/**\n * Collection configuration for Firestore event store\n */\nexport interface CollectionConfig {\n streams: string;\n counters: string;\n}\n\n/**\n * Firestore event store options\n */\nexport interface FirestoreEventStoreOptions {\n collections?: Partial<CollectionConfig>;\n observability?: ObservabilityOptions;\n}\n\n/**\n * Firestore event store interface\n */\nexport interface FirestoreEventStore {\n /**\n * The underlying Firestore instance\n */\n readonly firestore: Firestore;\n\n /**\n * Collection names configuration\n */\n readonly collections: CollectionConfig;\n\n /**\n * Read events from a stream\n */\n readStream<EventType extends Event>(\n streamName: string,\n options?: ReadStreamOptions,\n ): Promise<FirestoreReadEvent<EventType>[]>;\n\n /**\n * Aggregate stream by applying events to state\n */\n aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: {\n evolve: (state: State, event: FirestoreReadEvent<EventType>) => State;\n initialState: () => State;\n read?: ReadStreamOptions;\n },\n ): Promise<{\n state: State;\n currentStreamVersion: bigint;\n streamExists: boolean;\n }>;\n\n /**\n * Append events to a stream\n */\n appendToStream<EventType extends Event>(\n streamName: string,\n events: EventType[],\n options?: AppendToStreamOptions,\n ): Promise<AppendToStreamResult>;\n}\n\n/**\n * Error thrown when expected version doesn't match current version\n */\nexport class ExpectedVersionConflictError extends Error {\n constructor(\n public readonly streamName: string,\n public readonly expected: ExpectedStreamVersion,\n public readonly actual: bigint | typeof STREAM_DOES_NOT_EXIST,\n ) {\n super(\n `Expected version conflict for stream '${streamName}': expected ${String(expected)}, actual ${String(actual)}`,\n );\n this.name = 'ExpectedVersionConflictError';\n Object.setPrototypeOf(this, ExpectedVersionConflictError.prototype);\n }\n}\n","import type { Timestamp } from '@google-cloud/firestore';\nimport type { ExpectedStreamVersion } from './types';\nimport {\n STREAM_DOES_NOT_EXIST,\n STREAM_EXISTS,\n NO_CONCURRENCY_CHECK,\n ExpectedVersionConflictError,\n} from './types';\n\n/**\n * Pad version number with leading zeros for Firestore document IDs\n * This ensures automatic ordering by version in Firestore\n *\n * @param version - The version number to pad\n * @returns Zero-padded string of length 10\n *\n * @example\n * padVersion(0) // \"0000000000\"\n * padVersion(42) // \"0000000042\"\n * padVersion(12345) // \"0000012345\"\n */\nexport function padVersion(version: number | bigint): string {\n return version.toString().padStart(10, '0');\n}\n\n/**\n * Parse a stream name into type and ID components\n *\n * @param streamName - Stream name in format \"Type-id\" or \"Type-with-dashes-id\"\n * @returns Object with streamType and streamId\n *\n * @example\n * parseStreamName(\"User-123\") // { streamType: \"User\", streamId: \"123\" }\n * parseStreamName(\"ShoppingCart-abc-def-123\") // { streamType: \"ShoppingCart\", streamId: \"abc-def-123\" }\n */\nexport function parseStreamName(streamName: string): {\n streamType: string;\n streamId: string;\n} {\n const firstDashIndex = streamName.indexOf('-');\n\n if (firstDashIndex === -1) {\n return {\n streamType: streamName,\n streamId: '',\n };\n }\n\n return {\n streamType: streamName.substring(0, firstDashIndex),\n streamId: streamName.substring(firstDashIndex + 1),\n };\n}\n\n/**\n * Convert Firestore Timestamp to JavaScript Date\n *\n * @param timestamp - Firestore Timestamp\n * @returns JavaScript Date object\n */\nexport function timestampToDate(timestamp: Timestamp): Date {\n return timestamp.toDate();\n}\n\n/**\n * Validate expected version against current version\n *\n * @param streamName - Stream name for error messages\n * @param expectedVersion - Expected version constraint\n * @param currentVersion - Current stream version (or STREAM_DOES_NOT_EXIST if stream doesn't exist)\n * @throws ExpectedVersionConflictError if versions don't match\n */\nexport function assertExpectedVersionMatchesCurrent(\n streamName: string,\n expectedVersion: ExpectedStreamVersion,\n currentVersion: bigint | typeof STREAM_DOES_NOT_EXIST,\n): void {\n // NO_CONCURRENCY_CHECK - no validation needed\n if (expectedVersion === NO_CONCURRENCY_CHECK) {\n return;\n }\n\n // STREAM_DOES_NOT_EXIST - stream must not exist\n if (expectedVersion === STREAM_DOES_NOT_EXIST) {\n if (currentVersion !== STREAM_DOES_NOT_EXIST) {\n throw new ExpectedVersionConflictError(\n streamName,\n expectedVersion,\n currentVersion,\n );\n }\n return;\n }\n\n // STREAM_EXISTS - stream must exist\n if (expectedVersion === STREAM_EXISTS) {\n if (currentVersion === STREAM_DOES_NOT_EXIST) {\n throw new ExpectedVersionConflictError(\n streamName,\n expectedVersion,\n currentVersion,\n );\n }\n return;\n }\n\n // Specific version number\n const expectedBigInt = BigInt(expectedVersion);\n if (currentVersion === STREAM_DOES_NOT_EXIST || currentVersion !== expectedBigInt) {\n throw new ExpectedVersionConflictError(\n streamName,\n expectedVersion,\n currentVersion,\n );\n }\n}\n\n/**\n * Get the current stream version from metadata\n *\n * @param streamExists - Whether the stream document exists\n * @param version - Version number from Firestore (if stream exists)\n * @returns Current version as bigint or STREAM_DOES_NOT_EXIST\n */\nexport function getCurrentStreamVersion(\n streamExists: boolean,\n version?: number,\n): bigint | typeof STREAM_DOES_NOT_EXIST {\n if (!streamExists) {\n return STREAM_DOES_NOT_EXIST;\n }\n return BigInt(version ?? -1);\n}\n\n/**\n * Calculate the next expected stream version after appending events\n *\n * @param currentVersion - Current stream version\n * @param eventCount - Number of events being appended\n * @returns Next expected version as bigint\n */\nexport function calculateNextVersion(\n currentVersion: bigint | typeof STREAM_DOES_NOT_EXIST,\n eventCount: number,\n): bigint {\n if (currentVersion === STREAM_DOES_NOT_EXIST) {\n return BigInt(eventCount - 1);\n }\n // Type assertion needed because TypeScript doesn't narrow ExpectedStreamVersionGeneral properly\n return (currentVersion as bigint) + BigInt(eventCount);\n}\n","import type { Firestore, Transaction, Timestamp } from '@google-cloud/firestore';\nimport type { Event } from '@event-driven-io/emmett';\nimport { trace, SpanStatusCode } from '@opentelemetry/api';\nimport type {\n AppendToStreamOptions,\n AppendToStreamResult,\n CollectionConfig,\n EventDocument,\n ExpectedStreamVersion,\n FirestoreEventStore,\n FirestoreEventStoreOptions,\n FirestoreReadEvent,\n Logger,\n ReadStreamOptions,\n StreamMetadata,\n} from './types';\nimport { NO_CONCURRENCY_CHECK, STREAM_DOES_NOT_EXIST, ExpectedVersionConflictError } from './types';\nimport {\n assertExpectedVersionMatchesCurrent,\n getCurrentStreamVersion,\n padVersion,\n timestampToDate,\n} from './utils';\n\nconst tracer = trace.getTracer('@emmett-community/emmett-google-firestore');\n\n/**\n * Safe logging helper that handles undefined logger methods\n */\nfunction safeLog(\n logger: Logger | undefined,\n level: keyof Logger,\n msg: string,\n data?: unknown,\n): void {\n if (!logger) return;\n const logFn = logger[level];\n if (typeof logFn === 'function') {\n logFn.call(logger, msg, data);\n }\n}\n\nconst DEFAULT_COLLECTIONS: CollectionConfig = {\n streams: 'streams',\n counters: '_counters',\n};\n\n/**\n * Firestore Event Store Implementation\n *\n * Stores events in Firestore using a subcollection pattern:\n * - /streams/{streamName} - Stream metadata (version, timestamps)\n * - /streams/{streamName}/events/{version} - Individual events\n * - /_counters/global_position - Global event counter\n */\nexport class FirestoreEventStoreImpl implements FirestoreEventStore {\n public readonly collections: CollectionConfig;\n private readonly logger: Logger | undefined;\n\n constructor(\n public readonly firestore: Firestore,\n options: FirestoreEventStoreOptions = {},\n ) {\n this.collections = {\n ...DEFAULT_COLLECTIONS,\n ...options.collections,\n };\n this.logger = options.observability?.logger;\n\n safeLog(this.logger, 'info', 'FirestoreEventStore initialized');\n }\n\n /**\n * Read events from a stream\n */\n async readStream<EventType extends Event>(\n streamName: string,\n options: ReadStreamOptions = {},\n ): Promise<FirestoreReadEvent<EventType>[]> {\n const span = tracer.startSpan('emmett.firestore.read_stream', {\n attributes: { 'emmett.stream_name': streamName },\n });\n\n try {\n safeLog(this.logger, 'debug', 'Reading stream', {\n streamName,\n from: options.from?.toString(),\n to: options.to?.toString(),\n maxCount: options.maxCount,\n });\n\n const { from, to, maxCount } = options;\n\n // Reference to events subcollection\n let query = this.firestore\n .collection(this.collections.streams)\n .doc(streamName)\n .collection('events')\n .orderBy('streamVersion', 'asc');\n\n // Apply range filters\n if (from !== undefined) {\n query = query.where('streamVersion', '>=', Number(from));\n }\n if (to !== undefined) {\n query = query.where('streamVersion', '<=', Number(to));\n }\n if (maxCount !== undefined && maxCount > 0) {\n query = query.limit(maxCount);\n }\n\n // Execute query\n const snapshot = await query.get();\n\n // Transform Firestore documents to events\n const events = snapshot.docs.map((doc) => {\n const data = doc.data() as EventDocument;\n return {\n type: data.type,\n data: data.data,\n metadata: {\n ...data.metadata,\n streamName,\n streamVersion: BigInt(data.streamVersion),\n streamPosition: BigInt(data.streamVersion),\n globalPosition: BigInt(data.globalPosition),\n timestamp: timestampToDate(data.timestamp),\n },\n } as FirestoreReadEvent<EventType>;\n });\n\n span.setAttribute('emmett.event_count', events.length);\n span.setStatus({ code: SpanStatusCode.OK });\n\n safeLog(this.logger, 'debug', 'Stream read completed', {\n streamName,\n eventCount: events.length,\n });\n\n return events;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n\n safeLog(this.logger, 'error', 'Failed to read stream', {\n streamName,\n error,\n });\n\n throw error;\n } finally {\n span.end();\n }\n }\n\n /**\n * Aggregate stream by applying events to state\n */\n async aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: {\n evolve: (state: State, event: FirestoreReadEvent<EventType>) => State;\n initialState: () => State;\n read?: ReadStreamOptions;\n },\n ): Promise<{\n state: State;\n currentStreamVersion: bigint;\n streamExists: boolean;\n }> {\n const { evolve, initialState, read } = options;\n const events = await this.readStream<EventType>(streamName, read);\n\n const streamExists = events.length > 0;\n const state = events.reduce(evolve, initialState());\n const currentStreamVersion = streamExists\n ? events[events.length - 1].metadata.streamVersion\n : BigInt(0);\n\n return {\n state,\n currentStreamVersion,\n streamExists,\n };\n }\n\n /**\n * Append events to a stream with optimistic concurrency control\n */\n async appendToStream<EventType extends Event>(\n streamName: string,\n events: EventType[],\n options: AppendToStreamOptions = {},\n ): Promise<AppendToStreamResult> {\n const span = tracer.startSpan('emmett.firestore.append_to_stream', {\n attributes: {\n 'emmett.stream_name': streamName,\n 'emmett.event_count': events.length,\n },\n });\n\n try {\n if (events.length === 0) {\n throw new Error('Cannot append empty event array');\n }\n\n const { expectedStreamVersion = NO_CONCURRENCY_CHECK } = options;\n\n safeLog(this.logger, 'debug', 'Appending to stream', {\n streamName,\n eventCount: events.length,\n eventTypes: events.map((e) => e.type),\n expectedVersion: String(expectedStreamVersion),\n });\n\n // Execute in transaction for atomicity\n const result = await this.firestore.runTransaction(async (transaction) => {\n return await this.appendToStreamInTransaction(\n transaction,\n streamName,\n events,\n expectedStreamVersion,\n );\n });\n\n span.setAttribute('emmett.new_version', Number(result.nextExpectedStreamVersion));\n span.setAttribute('emmett.created_new_stream', result.createdNewStream);\n span.setStatus({ code: SpanStatusCode.OK });\n\n safeLog(this.logger, 'debug', 'Append completed', {\n streamName,\n newVersion: result.nextExpectedStreamVersion.toString(),\n createdNewStream: result.createdNewStream,\n });\n\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n\n if (error instanceof ExpectedVersionConflictError) {\n safeLog(this.logger, 'warn', 'Version conflict during append', {\n streamName,\n expected: String(error.expected),\n actual: String(error.actual),\n });\n } else {\n safeLog(this.logger, 'error', 'Failed to append to stream', {\n streamName,\n error,\n });\n }\n\n throw error;\n } finally {\n span.end();\n }\n }\n\n /**\n * Internal method to append events within a transaction\n *\n * Note: No separate span here - this method runs inside appendToStream's span,\n * and Firestore transaction operations are atomic. The parent span captures\n * the full transaction duration.\n */\n private async appendToStreamInTransaction<EventType extends Event>(\n transaction: Transaction,\n streamName: string,\n events: EventType[],\n expectedStreamVersion: ExpectedStreamVersion,\n ): Promise<AppendToStreamResult> {\n // 1. Get stream metadata reference\n const streamRef = this.firestore\n .collection(this.collections.streams)\n .doc(streamName);\n\n const streamDoc = await transaction.get(streamRef);\n const streamExists = streamDoc.exists;\n const streamData = streamDoc.data() as StreamMetadata | undefined;\n\n // 2. Get current version and validate expected version\n const currentVersion = getCurrentStreamVersion(\n streamExists,\n streamData?.version,\n );\n\n safeLog(this.logger, 'debug', 'Read stream metadata', {\n streamName,\n exists: streamExists,\n currentVersion: currentVersion === STREAM_DOES_NOT_EXIST ? 'none' : currentVersion.toString(),\n });\n\n assertExpectedVersionMatchesCurrent(\n streamName,\n expectedStreamVersion,\n currentVersion,\n );\n\n // 3. Get and increment global position counter\n const counterRef = this.firestore\n .collection(this.collections.counters)\n .doc('global_position');\n\n const counterDoc = await transaction.get(counterRef);\n let globalPosition = counterDoc.exists\n ? (counterDoc.data()?.value as number) ?? 0\n : 0;\n\n // 4. Calculate starting version for new events\n const baseVersion =\n currentVersion === STREAM_DOES_NOT_EXIST ? -1 : Number(currentVersion);\n\n // 5. Append events to subcollection\n const TimestampClass = this.firestore.constructor as unknown as { Timestamp: typeof Timestamp };\n const now = TimestampClass.Timestamp.now();\n\n events.forEach((event, index) => {\n const eventVersion = baseVersion + 1 + index;\n const eventRef = streamRef\n .collection('events')\n .doc(padVersion(eventVersion));\n\n const metadata = (event as { metadata?: Record<string, unknown> }).metadata;\n const eventDocument: EventDocument = {\n type: event.type,\n data: event.data,\n ...(metadata && { metadata }),\n timestamp: now,\n globalPosition: globalPosition++,\n streamVersion: eventVersion,\n };\n\n transaction.set(eventRef, eventDocument);\n });\n\n // 6. Update stream metadata\n const newVersion = baseVersion + events.length;\n const updatedMetadata: StreamMetadata = {\n version: newVersion,\n createdAt: streamData?.createdAt ?? now,\n updatedAt: now,\n };\n\n transaction.set(streamRef, updatedMetadata);\n\n // 7. Update global position counter\n transaction.set(counterRef, {\n value: globalPosition,\n updatedAt: now,\n });\n\n safeLog(this.logger, 'debug', 'Events written to transaction', {\n streamName,\n count: events.length,\n newVersion,\n });\n\n // 8. Return result\n return {\n nextExpectedStreamVersion: BigInt(newVersion),\n createdNewStream: !streamExists,\n };\n }\n}\n\n/**\n * Factory function to create a Firestore event store\n *\n * @param firestore - Firestore instance\n * @param options - Optional configuration\n * @returns Firestore event store instance\n *\n * @example\n * ```typescript\n * import { Firestore } from '@google-cloud/firestore';\n * import { getFirestoreEventStore } from '@emmett-community/emmett-google-firestore';\n *\n * const firestore = new Firestore({ projectId: 'my-project' });\n * const eventStore = getFirestoreEventStore(firestore);\n * ```\n */\nexport function getFirestoreEventStore(\n firestore: Firestore,\n options?: FirestoreEventStoreOptions,\n): FirestoreEventStore {\n return new FirestoreEventStoreImpl(firestore, options);\n}\n"]}
1
+ {"version":3,"sources":["../src/eventStore/types.ts","../src/eventStore/utils.ts","../src/eventStore/firestoreEventStore.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { Firestore, Timestamp } from '@google-cloud/firestore';\nimport type { Event, ReadEvent, ReadEventMetadataWithGlobalPosition } from '@event-driven-io/emmett';\nimport {\n STREAM_DOES_NOT_EXIST,\n STREAM_EXISTS,\n NO_CONCURRENCY_CHECK,\n type ExpectedStreamVersion as EmmettExpectedStreamVersion,\n} from '@event-driven-io/emmett';\n\n/**\n * Canonical Logger contract for the Emmett ecosystem.\n *\n * DO NOT MODIFY this interface without updating ALL packages in the ecosystem.\n *\n * This package defines the canonical Logger interface.\n * Implementations (Pino, Winston, etc.) MUST adapt to this contract.\n * This contract MUST NOT adapt to any specific implementation.\n *\n * Semantic Rules:\n * - context (first parameter): ALWAYS structured data as Record<string, unknown>\n * - message (second parameter): ALWAYS the human-readable log message\n * - The order is NEVER inverted\n * - The (message, data) form is NOT valid for this contract\n * - Error objects MUST use the 'err' key (frozen semantic)\n *\n * @example\n * ```typescript\n * // Pino - native compatibility\n * import pino from 'pino';\n * const logger = pino();\n * // logger.info({ orderId }, 'Order created') matches our contract\n * ```\n */\nexport interface Logger {\n /**\n * Log debug-level message with structured context.\n * @param context - Structured data to include in the log entry\n * @param message - Optional human-readable message\n */\n debug(context: Record<string, unknown>, message?: string): void;\n\n /**\n * Log info-level message with structured context.\n * @param context - Structured data to include in the log entry\n * @param message - Optional human-readable message\n */\n info(context: Record<string, unknown>, message?: string): void;\n\n /**\n * Log warn-level message with structured context.\n * @param context - Structured data to include in the log entry\n * @param message - Optional human-readable message\n */\n warn(context: Record<string, unknown>, message?: string): void;\n\n /**\n * Log error-level message with structured context.\n * @param context - Structured data to include in the log entry (MUST use 'err' key for Error objects)\n * @param message - Optional human-readable message\n */\n error(context: Record<string, unknown>, message?: string): void;\n}\n\n/**\n * Observability configuration options\n */\nexport interface ObservabilityOptions {\n /** Optional logger instance. If not provided, no logging occurs. */\n logger?: Logger;\n}\n\n/**\n * Expected version for stream operations\n * Uses Emmett's standard version constants for full compatibility\n * - number | bigint: Expect specific version\n * - STREAM_DOES_NOT_EXIST: Stream must not exist\n * - STREAM_EXISTS: Stream must exist (any version)\n * - NO_CONCURRENCY_CHECK: No version check\n */\nexport type ExpectedStreamVersion = EmmettExpectedStreamVersion<bigint>;\n\n// Re-export Emmett constants for convenience\nexport { STREAM_DOES_NOT_EXIST, STREAM_EXISTS, NO_CONCURRENCY_CHECK };\n\n/**\n * Options for appending events to a stream\n */\nexport interface AppendToStreamOptions {\n expectedStreamVersion?: ExpectedStreamVersion;\n}\n\n/**\n * Result of appending events to a stream\n */\nexport interface AppendToStreamResult {\n nextExpectedStreamVersion: bigint;\n createdNewStream: boolean;\n}\n\n/**\n * Options for reading events from a stream\n * Reuses the canonical `ReadStreamOptions` shape from the Emmett core types.\n */\nexport interface ReadStreamOptions {\n from?: bigint;\n to?: bigint;\n maxCount?: number;\n}\n\n/**\n * Metadata stored in Firestore stream document\n */\nexport interface StreamMetadata {\n version: number;\n createdAt: Timestamp;\n updatedAt: Timestamp;\n}\n\n/**\n * Event document structure in Firestore\n */\nexport interface EventDocument {\n type: string;\n data: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n timestamp: Timestamp;\n globalPosition: number;\n streamVersion: number;\n}\n\n/**\n * Firestore-specific read event metadata\n */\nexport interface FirestoreReadEventMetadata extends ReadEventMetadataWithGlobalPosition {\n streamName: string;\n streamVersion: bigint;\n timestamp: Date;\n}\n\n/**\n * Firestore read event\n */\nexport type FirestoreReadEvent<EventType extends Event = Event> = ReadEvent<\n EventType,\n FirestoreReadEventMetadata\n>;\n\n/**\n * Collection configuration for Firestore event store\n */\nexport interface CollectionConfig {\n streams: string;\n counters: string;\n}\n\n/**\n * Firestore event store options\n */\nexport interface FirestoreEventStoreOptions {\n collections?: Partial<CollectionConfig>;\n observability?: ObservabilityOptions;\n}\n\n/**\n * Firestore event store interface\n */\nexport interface FirestoreEventStore {\n /**\n * The underlying Firestore instance\n */\n readonly firestore: Firestore;\n\n /**\n * Collection names configuration\n */\n readonly collections: CollectionConfig;\n\n /**\n * Read events from a stream\n */\n readStream<EventType extends Event>(\n streamName: string,\n options?: ReadStreamOptions,\n ): Promise<FirestoreReadEvent<EventType>[]>;\n\n /**\n * Aggregate stream by applying events to state\n */\n aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: {\n evolve: (state: State, event: FirestoreReadEvent<EventType>) => State;\n initialState: () => State;\n read?: ReadStreamOptions;\n },\n ): Promise<{\n state: State;\n currentStreamVersion: bigint;\n streamExists: boolean;\n }>;\n\n /**\n * Append events to a stream\n */\n appendToStream<EventType extends Event>(\n streamName: string,\n events: EventType[],\n options?: AppendToStreamOptions,\n ): Promise<AppendToStreamResult>;\n}\n\n/**\n * Error thrown when expected version doesn't match current version\n */\nexport class ExpectedVersionConflictError extends Error {\n constructor(\n public readonly streamName: string,\n public readonly expected: ExpectedStreamVersion,\n public readonly actual: bigint | typeof STREAM_DOES_NOT_EXIST,\n ) {\n super(\n `Expected version conflict for stream '${streamName}': expected ${String(expected)}, actual ${String(actual)}`,\n );\n this.name = 'ExpectedVersionConflictError';\n Object.setPrototypeOf(this, ExpectedVersionConflictError.prototype);\n }\n}\n","import type { Timestamp } from '@google-cloud/firestore';\nimport type { ExpectedStreamVersion } from './types';\nimport {\n STREAM_DOES_NOT_EXIST,\n STREAM_EXISTS,\n NO_CONCURRENCY_CHECK,\n ExpectedVersionConflictError,\n} from './types';\n\n/**\n * Pad version number with leading zeros for Firestore document IDs\n * This ensures automatic ordering by version in Firestore\n *\n * @param version - The version number to pad\n * @returns Zero-padded string of length 10\n *\n * @example\n * padVersion(0) // \"0000000000\"\n * padVersion(42) // \"0000000042\"\n * padVersion(12345) // \"0000012345\"\n */\nexport function padVersion(version: number | bigint): string {\n return version.toString().padStart(10, '0');\n}\n\n/**\n * Parse a stream name into type and ID components\n *\n * @param streamName - Stream name in format \"Type-id\" or \"Type-with-dashes-id\"\n * @returns Object with streamType and streamId\n *\n * @example\n * parseStreamName(\"User-123\") // { streamType: \"User\", streamId: \"123\" }\n * parseStreamName(\"ShoppingCart-abc-def-123\") // { streamType: \"ShoppingCart\", streamId: \"abc-def-123\" }\n */\nexport function parseStreamName(streamName: string): {\n streamType: string;\n streamId: string;\n} {\n const firstDashIndex = streamName.indexOf('-');\n\n if (firstDashIndex === -1) {\n return {\n streamType: streamName,\n streamId: '',\n };\n }\n\n return {\n streamType: streamName.substring(0, firstDashIndex),\n streamId: streamName.substring(firstDashIndex + 1),\n };\n}\n\n/**\n * Convert Firestore Timestamp to JavaScript Date\n *\n * @param timestamp - Firestore Timestamp\n * @returns JavaScript Date object\n */\nexport function timestampToDate(timestamp: Timestamp): Date {\n return timestamp.toDate();\n}\n\n/**\n * Validate expected version against current version\n *\n * @param streamName - Stream name for error messages\n * @param expectedVersion - Expected version constraint\n * @param currentVersion - Current stream version (or STREAM_DOES_NOT_EXIST if stream doesn't exist)\n * @throws ExpectedVersionConflictError if versions don't match\n */\nexport function assertExpectedVersionMatchesCurrent(\n streamName: string,\n expectedVersion: ExpectedStreamVersion,\n currentVersion: bigint | typeof STREAM_DOES_NOT_EXIST,\n): void {\n // NO_CONCURRENCY_CHECK - no validation needed\n if (expectedVersion === NO_CONCURRENCY_CHECK) {\n return;\n }\n\n // STREAM_DOES_NOT_EXIST - stream must not exist\n if (expectedVersion === STREAM_DOES_NOT_EXIST) {\n if (currentVersion !== STREAM_DOES_NOT_EXIST) {\n throw new ExpectedVersionConflictError(\n streamName,\n expectedVersion,\n currentVersion,\n );\n }\n return;\n }\n\n // STREAM_EXISTS - stream must exist\n if (expectedVersion === STREAM_EXISTS) {\n if (currentVersion === STREAM_DOES_NOT_EXIST) {\n throw new ExpectedVersionConflictError(\n streamName,\n expectedVersion,\n currentVersion,\n );\n }\n return;\n }\n\n // Specific version number\n const expectedBigInt = BigInt(expectedVersion);\n if (currentVersion === STREAM_DOES_NOT_EXIST || currentVersion !== expectedBigInt) {\n throw new ExpectedVersionConflictError(\n streamName,\n expectedVersion,\n currentVersion,\n );\n }\n}\n\n/**\n * Get the current stream version from metadata\n *\n * @param streamExists - Whether the stream document exists\n * @param version - Version number from Firestore (if stream exists)\n * @returns Current version as bigint or STREAM_DOES_NOT_EXIST\n */\nexport function getCurrentStreamVersion(\n streamExists: boolean,\n version?: number,\n): bigint | typeof STREAM_DOES_NOT_EXIST {\n if (!streamExists) {\n return STREAM_DOES_NOT_EXIST;\n }\n return BigInt(version ?? -1);\n}\n\n/**\n * Calculate the next expected stream version after appending events\n *\n * @param currentVersion - Current stream version\n * @param eventCount - Number of events being appended\n * @returns Next expected version as bigint\n */\nexport function calculateNextVersion(\n currentVersion: bigint | typeof STREAM_DOES_NOT_EXIST,\n eventCount: number,\n): bigint {\n if (currentVersion === STREAM_DOES_NOT_EXIST) {\n return BigInt(eventCount - 1);\n }\n // Type assertion needed because TypeScript doesn't narrow ExpectedStreamVersionGeneral properly\n return (currentVersion as bigint) + BigInt(eventCount);\n}\n","import type { Firestore, Transaction, Timestamp } from '@google-cloud/firestore';\nimport type {\n AggregateStreamOptions,\n AggregateStreamResult,\n Event,\n EventStore,\n ReadStreamOptions as EmmettReadStreamOptions,\n ReadStreamResult,\n} from '@event-driven-io/emmett';\nimport { trace, SpanStatusCode } from '@opentelemetry/api';\nimport type {\n AppendToStreamOptions,\n AppendToStreamResult,\n CollectionConfig,\n EventDocument,\n ExpectedStreamVersion,\n FirestoreEventStore,\n FirestoreEventStoreOptions,\n FirestoreReadEvent,\n FirestoreReadEventMetadata,\n Logger,\n ReadStreamOptions,\n StreamMetadata,\n} from './types';\nimport { NO_CONCURRENCY_CHECK, STREAM_DOES_NOT_EXIST, ExpectedVersionConflictError } from './types';\nimport {\n assertExpectedVersionMatchesCurrent,\n getCurrentStreamVersion,\n padVersion,\n timestampToDate,\n} from './utils';\n\nconst tracer = trace.getTracer('@emmett-community/emmett-google-firestore');\n\n/**\n * @internal - NOT part of public API\n * Normalizes data to a context object for the Logger contract.\n */\nfunction normalizeContext(data: unknown): Record<string, unknown> {\n if (data === undefined || data === null) return {};\n if (typeof data === 'object' && !Array.isArray(data)) {\n return { ...(data as Record<string, unknown>) };\n }\n return { data };\n}\n\n/**\n * @internal - NOT part of public API\n * Normalizes error data to a context object for the Logger contract.\n * Uses 'err' key for Error instances (Pino compatibility).\n */\nfunction normalizeErrorContext(error: unknown): Record<string, unknown> {\n if (error === undefined || error === null) return {};\n if (error instanceof Error) {\n return { err: error };\n }\n if (typeof error === 'object' && !Array.isArray(error)) {\n return { ...(error as Record<string, unknown>) };\n }\n return { err: error };\n}\n\n/**\n * @internal - NOT part of public API\n *\n * Internal helper for ergonomic logging within this package.\n * Translates internal (msg, data) calls to canonical (context, message) contract.\n *\n * This is the ONLY point where translation from internal format to contract format occurs.\n */\nconst safeLog = {\n debug: (logger: Logger | undefined, msg: string, data?: unknown): void => {\n if (!logger) return;\n logger.debug(normalizeContext(data), msg);\n },\n info: (logger: Logger | undefined, msg: string, data?: unknown): void => {\n if (!logger) return;\n logger.info(normalizeContext(data), msg);\n },\n warn: (logger: Logger | undefined, msg: string, data?: unknown): void => {\n if (!logger) return;\n logger.warn(normalizeContext(data), msg);\n },\n error: (logger: Logger | undefined, msg: string, error?: unknown): void => {\n if (!logger) return;\n logger.error(normalizeErrorContext(error), msg);\n },\n};\n\nconst DEFAULT_COLLECTIONS: CollectionConfig = {\n streams: 'streams',\n counters: '_counters',\n};\n\n/**\n * Firestore Event Store Implementation\n *\n * Stores events in Firestore using a subcollection pattern:\n * - /streams/{streamName} - Stream metadata (version, timestamps)\n * - /streams/{streamName}/events/{version} - Individual events\n * - /_counters/global_position - Global event counter\n */\nexport class FirestoreEventStoreImpl implements FirestoreEventStore {\n public readonly collections: CollectionConfig;\n private readonly logger: Logger | undefined;\n\n constructor(\n public readonly firestore: Firestore,\n options: FirestoreEventStoreOptions = {},\n ) {\n this.collections = {\n ...DEFAULT_COLLECTIONS,\n ...options.collections,\n };\n this.logger = options.observability?.logger;\n\n safeLog.info(this.logger, 'FirestoreEventStore initialized');\n }\n\n /**\n * Read events from a stream\n */\n async readStream<EventType extends Event>(\n streamName: string,\n options?: ReadStreamOptions,\n ): Promise<FirestoreReadEvent<EventType>[]> {\n const span = tracer.startSpan('emmett.firestore.read_stream', {\n attributes: { 'emmett.stream_name': streamName },\n });\n\n try {\n const from = options && 'from' in options ? options.from : undefined;\n const to = options && 'to' in options ? options.to : undefined;\n const maxCountLimit =\n options && 'maxCount' in options && options.maxCount !== undefined\n ? Number(options.maxCount)\n : undefined;\n\n safeLog.debug(this.logger, 'Reading stream', {\n streamName,\n from: from?.toString(),\n to: to?.toString(),\n maxCount: maxCountLimit,\n });\n\n // Reference to events subcollection\n let query = this.firestore\n .collection(this.collections.streams)\n .doc(streamName)\n .collection('events')\n .orderBy('streamVersion', 'asc');\n\n // Apply range filters\n if (from !== undefined) {\n query = query.where('streamVersion', '>=', Number(from));\n }\n if (to !== undefined) {\n query = query.where('streamVersion', '<=', Number(to));\n }\n if (maxCountLimit !== undefined && maxCountLimit > 0) {\n query = query.limit(maxCountLimit);\n }\n\n // Execute query\n const snapshot = await query.get();\n\n // Transform Firestore documents to events\n const events = snapshot.docs.map((doc) => {\n const data = doc.data() as EventDocument;\n return {\n type: data.type,\n data: data.data,\n metadata: {\n ...data.metadata,\n streamName,\n streamVersion: BigInt(data.streamVersion),\n streamPosition: BigInt(data.streamVersion),\n globalPosition: BigInt(data.globalPosition),\n timestamp: timestampToDate(data.timestamp),\n },\n } as FirestoreReadEvent<EventType>;\n });\n\n span.setAttribute('emmett.event_count', events.length);\n span.setStatus({ code: SpanStatusCode.OK });\n\n safeLog.debug(this.logger, 'Stream read completed', {\n streamName,\n eventCount: events.length,\n });\n\n return events;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n\n safeLog.error(this.logger, 'Failed to read stream', {\n streamName,\n error,\n });\n\n throw error;\n } finally {\n span.end();\n }\n }\n\n /**\n * Aggregate stream by applying events to state\n */\n async aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: {\n evolve: (state: State, event: FirestoreReadEvent<EventType>) => State;\n initialState: () => State;\n read?: ReadStreamOptions;\n },\n ): Promise<{\n state: State;\n currentStreamVersion: bigint;\n streamExists: boolean;\n }> {\n const { evolve, initialState, read } = options;\n const events = await this.readStream<EventType>(streamName, read);\n\n const streamExists = events.length > 0;\n const state = events.reduce(evolve, initialState());\n const currentStreamVersion = streamExists\n ? events[events.length - 1].metadata.streamVersion\n : BigInt(0);\n\n return {\n state,\n currentStreamVersion,\n streamExists,\n };\n }\n\n /**\n * Append events to a stream with optimistic concurrency control\n */\n async appendToStream<EventType extends Event>(\n streamName: string,\n events: EventType[],\n options: AppendToStreamOptions = {},\n ): Promise<AppendToStreamResult> {\n const span = tracer.startSpan('emmett.firestore.append_to_stream', {\n attributes: {\n 'emmett.stream_name': streamName,\n 'emmett.event_count': events.length,\n },\n });\n\n try {\n if (events.length === 0) {\n throw new Error('Cannot append empty event array');\n }\n\n const { expectedStreamVersion = NO_CONCURRENCY_CHECK } = options;\n\n safeLog.debug(this.logger, 'Appending to stream', {\n streamName,\n eventCount: events.length,\n eventTypes: events.map((e) => e.type),\n expectedVersion: String(expectedStreamVersion),\n });\n\n // Execute in transaction for atomicity\n const result = await this.firestore.runTransaction(async (transaction) => {\n return await this.appendToStreamInTransaction(\n transaction,\n streamName,\n events,\n expectedStreamVersion,\n );\n });\n\n span.setAttribute('emmett.new_version', Number(result.nextExpectedStreamVersion));\n span.setAttribute('emmett.created_new_stream', result.createdNewStream);\n span.setStatus({ code: SpanStatusCode.OK });\n\n safeLog.debug(this.logger, 'Append completed', {\n streamName,\n newVersion: result.nextExpectedStreamVersion.toString(),\n createdNewStream: result.createdNewStream,\n });\n\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n\n if (error instanceof ExpectedVersionConflictError) {\n safeLog.warn(this.logger, 'Version conflict during append', {\n streamName,\n expected: String(error.expected),\n actual: String(error.actual),\n });\n } else {\n safeLog.error(this.logger, 'Failed to append to stream', {\n streamName,\n error,\n });\n }\n\n throw error;\n } finally {\n span.end();\n }\n }\n\n /**\n * Internal method to append events within a transaction\n *\n * Note: No separate span here - this method runs inside appendToStream's span,\n * and Firestore transaction operations are atomic. The parent span captures\n * the full transaction duration.\n */\n private async appendToStreamInTransaction<EventType extends Event>(\n transaction: Transaction,\n streamName: string,\n events: EventType[],\n expectedStreamVersion: ExpectedStreamVersion,\n ): Promise<AppendToStreamResult> {\n // 1. Get stream metadata reference\n const streamRef = this.firestore\n .collection(this.collections.streams)\n .doc(streamName);\n\n const streamDoc = await transaction.get(streamRef);\n const streamExists = streamDoc.exists;\n const streamData = streamDoc.data() as StreamMetadata | undefined;\n\n // 2. Get current version and validate expected version\n const currentVersion = getCurrentStreamVersion(\n streamExists,\n streamData?.version,\n );\n\n safeLog.debug(this.logger, 'Read stream metadata', {\n streamName,\n exists: streamExists,\n currentVersion: currentVersion === STREAM_DOES_NOT_EXIST ? 'none' : currentVersion.toString(),\n });\n\n assertExpectedVersionMatchesCurrent(\n streamName,\n expectedStreamVersion,\n currentVersion,\n );\n\n // 3. Get and increment global position counter\n const counterRef = this.firestore\n .collection(this.collections.counters)\n .doc('global_position');\n\n const counterDoc = await transaction.get(counterRef);\n let globalPosition = counterDoc.exists\n ? (counterDoc.data()?.value as number) ?? 0\n : 0;\n\n // 4. Calculate starting version for new events\n const baseVersion =\n currentVersion === STREAM_DOES_NOT_EXIST ? -1 : Number(currentVersion);\n\n // 5. Append events to subcollection\n const TimestampClass = this.firestore.constructor as unknown as { Timestamp: typeof Timestamp };\n const now = TimestampClass.Timestamp.now();\n\n events.forEach((event, index) => {\n const eventVersion = baseVersion + 1 + index;\n const eventRef = streamRef\n .collection('events')\n .doc(padVersion(eventVersion));\n\n const metadata = (event as { metadata?: Record<string, unknown> }).metadata;\n const eventDocument: EventDocument = {\n type: event.type,\n data: event.data,\n ...(metadata && { metadata }),\n timestamp: now,\n globalPosition: globalPosition++,\n streamVersion: eventVersion,\n };\n\n transaction.set(eventRef, eventDocument);\n });\n\n // 6. Update stream metadata\n const newVersion = baseVersion + events.length;\n const updatedMetadata: StreamMetadata = {\n version: newVersion,\n createdAt: streamData?.createdAt ?? now,\n updatedAt: now,\n };\n\n transaction.set(streamRef, updatedMetadata);\n\n // 7. Update global position counter\n transaction.set(counterRef, {\n value: globalPosition,\n updatedAt: now,\n });\n\n safeLog.debug(this.logger, 'Events written to transaction', {\n streamName,\n count: events.length,\n newVersion,\n });\n\n // 8. Return result\n return {\n nextExpectedStreamVersion: BigInt(newVersion),\n createdNewStream: !streamExists,\n };\n }\n}\n\n/**\n * Creates an Emmett-compatible `EventStore` wrapper around a Firestore event store.\n */\nfunction normalizeReadOptions(\n options?: EmmettReadStreamOptions<bigint>,\n): ReadStreamOptions | undefined {\n if (!options) return undefined;\n\n const result: ReadStreamOptions = {};\n\n if ('from' in options) {\n result.from = options.from;\n if ('maxCount' in options && options.maxCount !== undefined) {\n result.maxCount = Number(options.maxCount);\n }\n }\n\n if ('to' in options) {\n result.to = options.to;\n }\n\n return result;\n}\n\nexport function asEventStore(\n store: FirestoreEventStore,\n): EventStore<FirestoreReadEventMetadata> {\n async function readStream<EventType extends Event>(\n streamName: string,\n options?: EmmettReadStreamOptions<bigint>,\n ): Promise<ReadStreamResult<EventType, FirestoreReadEventMetadata>> {\n const events = await store.readStream<EventType>(streamName, normalizeReadOptions(options));\n const streamExists = events.length > 0;\n const currentStreamVersion = streamExists\n ? events[events.length - 1].metadata.streamVersion\n : BigInt(0);\n\n return {\n currentStreamVersion,\n events,\n streamExists,\n };\n }\n\n function aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: AggregateStreamOptions<State, EventType, FirestoreReadEventMetadata>,\n ): Promise<AggregateStreamResult<State, bigint>> {\n return store.aggregateStream(streamName, {\n evolve: options.evolve,\n initialState: options.initialState,\n read: normalizeReadOptions(options.read),\n });\n }\n\n function appendToStream<EventType extends Event>(\n streamName: string,\n events: EventType[],\n options?: AppendToStreamOptions,\n ): Promise<AppendToStreamResult> {\n return store.appendToStream(streamName, events, options);\n }\n\n return {\n readStream,\n aggregateStream,\n appendToStream,\n };\n}\n\n/**\n * Factory function to create a Firestore event store\n *\n * @param firestore - Firestore instance\n * @param options - Optional configuration\n * @returns Firestore event store instance\n *\n * @example\n * ```typescript\n * import { Firestore } from '@google-cloud/firestore';\n * import { getFirestoreEventStore } from '@emmett-community/emmett-google-firestore';\n *\n * const firestore = new Firestore({ projectId: 'my-project' });\n * const eventStore = getFirestoreEventStore(firestore);\n * ```\n */\nexport function getFirestoreEventStore(\n firestore: Firestore,\n options?: FirestoreEventStoreOptions,\n): FirestoreEventStore {\n return new FirestoreEventStoreImpl(firestore, options);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emmett-community/emmett-google-firestore",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Google Firestore event store implementation for Emmett",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",