@ddd-ts/event-sourcing-firestore 0.0.38 → 0.0.39

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.
@@ -0,0 +1,16 @@
1
+ import type { IEsEvent, ISavedChange } from "@ddd-ts/core";
2
+ export declare class EventCoordinator {
3
+ private eventProcessing;
4
+ private currentEventId;
5
+ private lastEvent;
6
+ private isRunning;
7
+ private _onEmpty;
8
+ addEvent(event: ISavedChange<IEsEvent>): void;
9
+ start(event: ISavedChange<IEsEvent>): () => void;
10
+ waitCurrentEvent(): Promise<void>;
11
+ cleanEvent(event: ISavedChange<IEsEvent>): void;
12
+ canProceed(event: ISavedChange<IEsEvent>): boolean;
13
+ onEmpty(callback: () => void): void;
14
+ checkEmpty(): void;
15
+ }
16
+ //# sourceMappingURL=event-coordinator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-coordinator.d.ts","sourceRoot":"","sources":["../../src/projection/event-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG3D,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,eAAe,CAAkD;IACzE,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,SAAS,CAAuC;IACxD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAY;IAE5B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC,QAAQ,CAAC;IAYtC,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC,QAAQ,CAAC;IAU7B,gBAAgB;IAMtB,UAAU,CAAC,KAAK,EAAE,YAAY,CAAC,QAAQ,CAAC;IAYxC,UAAU,CAAC,KAAK,EAAE,YAAY,CAAC,QAAQ,CAAC;IAMxC,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI;IAI5B,UAAU;CAKX"}
@@ -0,0 +1,47 @@
1
+ const require_promise_with_resolvers = require('../utils/promise-with-resolvers.js');
2
+
3
+ //#region src/projection/event-coordinator.ts
4
+ var EventCoordinator = class {
5
+ eventProcessing = /* @__PURE__ */ new Map();
6
+ currentEventId = null;
7
+ lastEvent = null;
8
+ isRunning = false;
9
+ _onEmpty = () => {};
10
+ addEvent(event) {
11
+ const eventId = event.id.serialize();
12
+ this.eventProcessing.set(eventId, require_promise_with_resolvers.promiseWithResolvers());
13
+ if (this.lastEvent === null || this.lastEvent.revision < event.revision) this.lastEvent = event;
14
+ }
15
+ start(event) {
16
+ if (this.isRunning) throw new Error("Event processing already in progress");
17
+ this.currentEventId = event.id.serialize();
18
+ this.isRunning = true;
19
+ return () => this.cleanEvent(event);
20
+ }
21
+ async waitCurrentEvent() {
22
+ if (!this.currentEventId) return;
23
+ await this.eventProcessing.get(this.currentEventId)?.promise;
24
+ }
25
+ cleanEvent(event) {
26
+ const eventId = event.id.serialize();
27
+ this.eventProcessing.get(eventId)?.resolve();
28
+ this.eventProcessing.delete(eventId);
29
+ this.isRunning = false;
30
+ this.currentEventId = null;
31
+ this.checkEmpty();
32
+ }
33
+ canProceed(event) {
34
+ if (this.isRunning) return false;
35
+ const eventId = event.id.serialize();
36
+ return this.lastEvent?.id.serialize() === eventId;
37
+ }
38
+ onEmpty(callback) {
39
+ this._onEmpty = callback;
40
+ }
41
+ checkEmpty() {
42
+ if (this.eventProcessing.size === 0) this._onEmpty();
43
+ }
44
+ };
45
+
46
+ //#endregion
47
+ exports.EventCoordinator = EventCoordinator;
@@ -0,0 +1,47 @@
1
+ import { promiseWithResolvers } from "../utils/promise-with-resolvers.mjs";
2
+
3
+ //#region src/projection/event-coordinator.ts
4
+ var EventCoordinator = class {
5
+ eventProcessing = /* @__PURE__ */ new Map();
6
+ currentEventId = null;
7
+ lastEvent = null;
8
+ isRunning = false;
9
+ _onEmpty = () => {};
10
+ addEvent(event) {
11
+ const eventId = event.id.serialize();
12
+ this.eventProcessing.set(eventId, promiseWithResolvers());
13
+ if (this.lastEvent === null || this.lastEvent.revision < event.revision) this.lastEvent = event;
14
+ }
15
+ start(event) {
16
+ if (this.isRunning) throw new Error("Event processing already in progress");
17
+ this.currentEventId = event.id.serialize();
18
+ this.isRunning = true;
19
+ return () => this.cleanEvent(event);
20
+ }
21
+ async waitCurrentEvent() {
22
+ if (!this.currentEventId) return;
23
+ await this.eventProcessing.get(this.currentEventId)?.promise;
24
+ }
25
+ cleanEvent(event) {
26
+ const eventId = event.id.serialize();
27
+ this.eventProcessing.get(eventId)?.resolve();
28
+ this.eventProcessing.delete(eventId);
29
+ this.isRunning = false;
30
+ this.currentEventId = null;
31
+ this.checkEmpty();
32
+ }
33
+ canProceed(event) {
34
+ if (this.isRunning) return false;
35
+ const eventId = event.id.serialize();
36
+ return this.lastEvent?.id.serialize() === eventId;
37
+ }
38
+ onEmpty(callback) {
39
+ this._onEmpty = callback;
40
+ }
41
+ checkEmpty() {
42
+ if (this.eventProcessing.size === 0) this._onEmpty();
43
+ }
44
+ };
45
+
46
+ //#endregion
47
+ export { EventCoordinator };
@@ -1,39 +1,30 @@
1
1
  import { Firestore, WriteBatch } from "firebase-admin/firestore";
2
2
  import { type IEsEvent, type ISavedChange, EventId, ProjectedStreamReader, Cursor, CheckpointId, ESProjection, type IFact, type Serialized, Lock } from "@ddd-ts/core";
3
- import { MicrosecondTimestamp } from "@ddd-ts/shape";
3
+ import { Mapping, MicrosecondTimestamp } from "@ddd-ts/shape";
4
4
  import { DefaultConverter, FirestoreTransaction } from "@ddd-ts/store-firestore";
5
+ interface FirestoreProjectorConfig {
6
+ retry: {
7
+ attempts: number;
8
+ minDelay: number;
9
+ maxDelay: number;
10
+ backoff: number;
11
+ };
12
+ enqueue: {
13
+ batchSize: number;
14
+ };
15
+ onProcessError: (error: Error) => void;
16
+ onEnqueueError: (error: Error) => void;
17
+ }
5
18
  export declare class FirestoreProjector {
6
19
  readonly projection: ESProjection<IEsEvent>;
7
20
  readonly reader: ProjectedStreamReader<IEsEvent>;
8
21
  readonly queue: FirestoreQueueStore;
9
- config: {
10
- retry: {
11
- attempts: number;
12
- minDelay: number;
13
- maxDelay: number;
14
- backoff: number;
15
- };
16
- enqueue: {
17
- batchSize: number;
18
- };
19
- onProcessError: (error: Error) => void;
20
- onEnqueueError: (error: Error) => void;
21
- };
22
+ config: FirestoreProjectorConfig;
22
23
  _unclaim: boolean;
23
- constructor(projection: ESProjection<IEsEvent>, reader: ProjectedStreamReader<IEsEvent>, queue: FirestoreQueueStore, config?: {
24
- retry: {
25
- attempts: number;
26
- minDelay: number;
27
- maxDelay: number;
28
- backoff: number;
29
- };
30
- enqueue: {
31
- batchSize: number;
32
- };
33
- onProcessError: (error: Error) => void;
34
- onEnqueueError: (error: Error) => void;
35
- });
24
+ constructor(projection: ESProjection<IEsEvent>, reader: ProjectedStreamReader<IEsEvent>, queue: FirestoreQueueStore, config?: FirestoreProjectorConfig);
36
25
  breathe(): AsyncGenerator<readonly [number, () => void], void, unknown>;
26
+ private eventCoordinators;
27
+ private getEventCoordinator;
37
28
  handle(savedChange: ISavedChange<IEsEvent>): Promise<void>;
38
29
  private getCursor;
39
30
  private attempt;
@@ -45,6 +36,7 @@ export declare class FirestoreProjector {
45
36
  private getUnprocessed;
46
37
  private claimTasks;
47
38
  private processEvents;
39
+ private assertBeforeInsert;
48
40
  }
49
41
  export declare class AlreadyEnqueuedError extends Error {
50
42
  constructor();
@@ -72,7 +64,7 @@ export declare class FirestoreQueueStore {
72
64
  checkpoint(id: CheckpointId): FirebaseFirestore.CollectionReference<FirebaseFirestore.DocumentData, FirebaseFirestore.DocumentData>;
73
65
  queue(id: CheckpointId): FirebaseFirestore.CollectionReference<FirebaseFirestore.DocumentData, FirebaseFirestore.DocumentData>;
74
66
  queued(id: CheckpointId, eventId: EventId): FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData, FirebaseFirestore.DocumentData>;
75
- processed(id: CheckpointId, eventIds: EventId[], context?: {
67
+ processed(claimerId: ClaimerId, id: CheckpointId, eventIds: EventId[], context?: {
76
68
  transaction?: FirestoreTransaction;
77
69
  batchWriter?: WriteBatch;
78
70
  }): Promise<void>;
@@ -100,8 +92,13 @@ declare const Task_base: import("@ddd-ts/shape").IDict<{
100
92
  readonly revision: NumberConstructor;
101
93
  readonly attempts: NumberConstructor;
102
94
  readonly processed: BooleanConstructor;
103
- readonly claimer: import("@ddd-ts/shape").IOptional<StringConstructor, typeof import("@ddd-ts/shape").Empty>;
104
- readonly claimedAt: import("@ddd-ts/shape").IOptional<typeof MicrosecondTimestamp, typeof import("@ddd-ts/shape").Empty>;
95
+ /** @deprecated */ readonly claimer: import("@ddd-ts/shape").IOptional<StringConstructor, typeof import("@ddd-ts/shape").Empty>;
96
+ /** @deprecated */ readonly claimedAt: import("@ddd-ts/shape").IOptional<typeof MicrosecondTimestamp, typeof import("@ddd-ts/shape").Empty>;
97
+ readonly claimsMetadata: Mapping<[{
98
+ claimedAt: typeof MicrosecondTimestamp;
99
+ processedAt: import("@ddd-ts/shape").IOptional<typeof MicrosecondTimestamp, typeof import("@ddd-ts/shape").Empty>;
100
+ }], typeof import("@ddd-ts/shape").Empty>;
101
+ readonly claimIds: [StringConstructor];
105
102
  readonly lock: typeof Lock;
106
103
  readonly skipAfter: NumberConstructor;
107
104
  readonly remaining: NumberConstructor;
@@ -118,6 +115,7 @@ export declare class Task<Stored extends boolean> extends Task_base {
118
115
  skipAfter: number;
119
116
  isolateAfter: number;
120
117
  }): Task<false>;
118
+ get currentClaimId(): string | undefined;
121
119
  get isProcessing(): boolean;
122
120
  get isProcessed(): boolean;
123
121
  get shouldSkip(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"firestore.projector.d.ts","sourceRoot":"","sources":["../../src/projection/firestore.projector.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,SAAS,EAET,UAAU,EACX,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,OAAO,EACP,qBAAqB,EACrB,MAAM,EACN,YAAY,EACZ,YAAY,EAEZ,KAAK,KAAK,EACV,KAAK,UAAU,EACf,IAAI,EACL,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,oBAAoB,EAAmB,MAAM,eAAe,CAAC;AACtE,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,yBAAyB,CAAC;AAoBjC,qBAAa,kBAAkB;aAIX,UAAU,EAAE,YAAY,CAAC,QAAQ,CAAC;aAClC,MAAM,EAAE,qBAAqB,CAAC,QAAQ,CAAC;aACvC,KAAK,EAAE,mBAAmB;IACnC,MAAM;;;;;;;;;;gCAGa,KAAK;gCAGL,KAAK;;IAZjC,QAAQ,UAAQ;gBAGE,UAAU,EAAE,YAAY,CAAC,QAAQ,CAAC,EAClC,MAAM,EAAE,qBAAqB,CAAC,QAAQ,CAAC,EACvC,KAAK,EAAE,mBAAmB,EACnC,MAAM;;;;;;;;;;gCAGa,KAAK;gCAGL,KAAK;KAG9B;IAGI,OAAO;IA4BR,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,QAAQ,CAAC;YAwClC,SAAS;YAKT,OAAO;YAkEP,YAAY;YAKZ,gBAAgB;YAahB,OAAO;YAqBP,UAAU;YAYV,gBAAgB;YAKhB,cAAc;YAKd,UAAU;YAcV,aAAa;CA8C5B;AAED,qBAAa,oBAAqB,SAAQ,KAAK;;CAK9C;AAED,qBAAa,mBAAmB;IAIX,EAAE,EAAE,SAAS;IAHhC,SAAS,mDAA0B;IACnC,UAAU,EAAE,iBAAiB,CAAC,mBAAmB,CAAC;gBAE/B,EAAE,EAAE,SAAS;IAMhC,OAAO,CAAC,uBAAuB;IAO/B,OAAO,CAAC,uBAAuB;IAQzB,OAAO,CAAC,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE;IA0BxD,KAAK,CACT,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,SAAS,EAClB,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;IAqBf,IAAI,CAAC,YAAY,EAAE,YAAY;IAyB/B,WAAW,CAAC,YAAY,EAAE,YAAY;IA+CtC,OAAO,CACX,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,SAAS,GACjB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAgBlB,OAAO,CAAC,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;IAc7D;;;;;OAKG;IACG,WAAW,CAAC,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM;IAgB5D,UAAU,CAAC,EAAE,EAAE,YAAY;IAI3B,KAAK,CAAC,EAAE,EAAE,YAAY;IAItB,MAAM,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO;IAInC,SAAS,CACb,EAAE,EAAE,YAAY,EAChB,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,GAAE;QACP,WAAW,CAAC,EAAE,oBAAoB,CAAC;QACnC,WAAW,CAAC,EAAE,UAAU,CAAC;KACrB;IAuBF,aAAa,CAAC,EAAE,EAAE,YAAY;IA0B9B,OAAO,CAAC,EAAE,EAAE,YAAY;IA6BxB,KAAK,CAAC,EAAE,EAAE,YAAY;IAO5B;;;;;;;;;OASG;IACG,IAAI,CAAC,YAAY,EAAE,YAAY;CAoCtC;AAED,qBAAa,SAAU,SAAQ,OAAO;CAAG;;;;;;;;;;;;;;;;;AACzC,qBAAa,IAAI,CAAC,MAAM,SAAS,OAAO,CAAE,SAAQ,SAehD;IACQ,cAAc,EAAE,MAAM,SAAS,IAAI,GACvC,oBAAoB,GACpB,SAAS,CAAC;IAEd,IAAI,MAAM,WAOT;IAED,MAAM,CAAC,GAAG,CACR,IAAI,EAAE,KAAK,EACX,MAAM,EAAE;QACN,IAAI,EAAE,IAAI,CAAC;QACX,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;KACtB,GACA,IAAI,CAAC,KAAK,CAAC;IAmBd,IAAI,YAAY,YAEf;IAED,IAAI,WAAW,YAEd;IAED,IAAI,UAAU,YAEb;IAED,IAAI,aAAa,YAEhB;IAED,YAAY;IAaZ,MAAM,CAAC,6BAA6B,CAClC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,gBAAgB,CAAC,EACpD,SAAS,CAAC,EAAE,oBAAoB,GAC/B,IAAI,CAAC,IAAI,CAAC;IASb,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;CA0DjC"}
1
+ {"version":3,"file":"firestore.projector.d.ts","sourceRoot":"","sources":["../../src/projection/firestore.projector.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  const require_runtime = require('../_virtual/_rolldown/runtime.js');
2
+ const require_event_coordinator = require('./event-coordinator.js');
2
3
  let _ddd_ts_core = require("@ddd-ts/core");
3
4
  let _ddd_ts_store_firestore = require("@ddd-ts/store-firestore");
4
5
  let firebase_admin_firestore = require("firebase-admin/firestore");
@@ -51,10 +52,32 @@ var FirestoreProjector = class {
51
52
  await wait((backoff * i + 1) * minDelay + jitter);
52
53
  }
53
54
  }
55
+ eventCoordinators = /* @__PURE__ */ new Map();
56
+ getEventCoordinator(checkpointId) {
57
+ const key = checkpointId.serialize();
58
+ let coordinator = this.eventCoordinators.get(key);
59
+ if (!coordinator) {
60
+ coordinator = new require_event_coordinator.EventCoordinator();
61
+ coordinator.onEmpty(() => this.eventCoordinators.delete(key));
62
+ this.eventCoordinators.set(key, coordinator);
63
+ }
64
+ return coordinator;
65
+ }
54
66
  async handle(savedChange) {
55
67
  const checkpointId = this.projection.getCheckpointId(savedChange);
68
+ const eventCoordinator = this.getEventCoordinator(checkpointId);
69
+ eventCoordinator.addEvent(savedChange);
70
+ await eventCoordinator.waitCurrentEvent();
71
+ if (!eventCoordinator.canProceed(savedChange)) {
72
+ eventCoordinator.cleanEvent(savedChange);
73
+ return;
74
+ }
75
+ const disposeEventCoordinator = eventCoordinator.start(savedChange);
56
76
  const target = await this.getCursor(savedChange);
57
- if (!target) throw new Error(`Cursor not found for event ${savedChange.id.serialize()}`);
77
+ if (!target) {
78
+ disposeEventCoordinator();
79
+ throw new Error(`Cursor not found for event ${savedChange.id.serialize()}`);
80
+ }
58
81
  const errors = [];
59
82
  for await (const [attempt, reset] of this.breathe()) {
60
83
  const source = this.projection.getSource(savedChange);
@@ -65,10 +88,12 @@ var FirestoreProjector = class {
65
88
  }
66
89
  if (status === Status.SUCCESS) {
67
90
  await this.queue.cleanup(checkpointId);
91
+ disposeEventCoordinator();
68
92
  return;
69
93
  }
70
94
  errors.push(message);
71
95
  }
96
+ disposeEventCoordinator();
72
97
  throw new Error(`Failed to handle event ${savedChange.id.serialize()}: ${errors.join(", ")}`);
73
98
  }
74
99
  async getCursor(savedChange) {
@@ -140,8 +165,9 @@ var FirestoreProjector = class {
140
165
  const filtered = (await Promise.all(tasks.map((t) => this.reader.get(t.cursor)))).filter((t) => !!t);
141
166
  if (!filtered.length) return [Status.DEFERRED, "No events to process in claimed tasks"];
142
167
  const context = {
143
- onProcessed: this.queue.processed.bind(this.queue),
144
- checkpointId
168
+ onProcessed: this.queue.processed.bind(this.queue, claimer),
169
+ checkpointId,
170
+ assertBeforeInsert: this.assertBeforeInsert.bind(this, checkpointId, claimer, filtered)
145
171
  };
146
172
  const hasTarget = tasks.some((t) => t.id.equals(targetEventId));
147
173
  try {
@@ -154,6 +180,15 @@ var FirestoreProjector = class {
154
180
  return [Status.FAILURE, e];
155
181
  }
156
182
  }
183
+ async assertBeforeInsert(checkpointId, claimer, events) {
184
+ const claimedTasks = await this.queue.claimed(checkpointId, claimer);
185
+ const claimedTasksMap = new Map(claimedTasks.map((t) => [t.id.serialize(), t]));
186
+ for (const event of events) {
187
+ const task = claimedTasksMap.get(event.id.serialize());
188
+ if (!task) throw new Error(`Task not found for event ${event.id.serialize()} in claimer ${claimer.serialize()}`);
189
+ if (task.claimIds?.[0] !== claimer.serialize()) throw new Error(`Task ${task.id.serialize()} claimer mismatch: expected ${claimer.serialize()}, found ${task.claimIds?.[0]}`);
190
+ }
191
+ }
157
192
  };
158
193
  var AlreadyEnqueuedError = class extends Error {
159
194
  constructor() {
@@ -196,10 +231,13 @@ var FirestoreQueueStore = class {
196
231
  async claim(checkpointId, claimer, tasks) {
197
232
  const batch = this.collection.firestore.batch();
198
233
  for (const task of tasks) {
234
+ if (task.claimIds.length > 0) throw new Error(`Task ${task.id.serialize()} is already claimed by ${task.claimIds.join(", ")}`);
199
235
  const ref = this.queued(checkpointId, task.id);
200
236
  batch.update(ref, {
201
237
  claimer: claimer.serialize(),
202
238
  claimedAt: firebase_admin_firestore.FieldValue.serverTimestamp(),
239
+ [`claimsMetadata.${claimer.serialize()}`]: { claimedAt: firebase_admin_firestore.FieldValue.serverTimestamp() },
240
+ claimIds: firebase_admin_firestore.FieldValue.arrayUnion(claimer.serialize()),
203
241
  attempts: firebase_admin_firestore.FieldValue.increment(1),
204
242
  remaining: firebase_admin_firestore.FieldValue.increment(-1)
205
243
  }, { lastUpdateTime: this.microsecondsToTimestamp(task.lastUpdateTime) });
@@ -226,9 +264,9 @@ var FirestoreQueueStore = class {
226
264
  });
227
265
  const expiredTasks = [];
228
266
  for (const task of tasks) {
229
- const originalClaimer = task.claimer;
267
+ const originalClaimIds = task.claimIds;
230
268
  task.checkTimeout();
231
- if (originalClaimer && !task.claimer) expiredTasks.push(task);
269
+ if (originalClaimIds.length > task.claimIds.length) expiredTasks.push(task);
232
270
  }
233
271
  if (expiredTasks.length > 0) {
234
272
  const batch = this.collection.firestore.batch();
@@ -237,20 +275,19 @@ var FirestoreQueueStore = class {
237
275
  batch.update(ref, {
238
276
  claimer: firebase_admin_firestore.FieldValue.delete(),
239
277
  claimedAt: firebase_admin_firestore.FieldValue.delete(),
240
- attempts: task.attempts,
241
- remaining: task.remaining
242
- });
278
+ claimIds: task.claimIds
279
+ }, { lastUpdateTime: this.microsecondsToTimestamp(task.lastUpdateTime) });
243
280
  }
244
281
  await batch.commit();
245
282
  }
246
283
  return tasks;
247
284
  }
248
285
  async claimed(checkpointId, claimer) {
249
- return (await this.queue(checkpointId).where("claimer", "==", claimer.serialize()).orderBy("occurredAt", "asc").orderBy("revision", "asc").get()).docs.map((doc) => {
286
+ return (await this.queue(checkpointId).where("claimIds", "array-contains", claimer.serialize()).orderBy("occurredAt", "asc").orderBy("revision", "asc").get()).docs.map((doc) => {
250
287
  const data = this.converter.fromFirestoreSnapshot(doc);
251
288
  const timestamp = doc.updateTime ? this.timestampToMicroseconds(doc.updateTime) : void 0;
252
289
  return Task.deserializeWithLastUpdateTime(data, timestamp);
253
- });
290
+ }).filter((task) => task.claimIds[0] === claimer.serialize());
254
291
  }
255
292
  async unclaim(checkpointId, tasks) {
256
293
  const batch = this.collection.firestore.batch();
@@ -258,8 +295,9 @@ var FirestoreQueueStore = class {
258
295
  const ref = this.queued(checkpointId, task.id);
259
296
  batch.update(ref, {
260
297
  claimer: firebase_admin_firestore.FieldValue.delete(),
261
- claimedAt: firebase_admin_firestore.FieldValue.delete()
262
- });
298
+ claimedAt: firebase_admin_firestore.FieldValue.delete(),
299
+ claimIds: firebase_admin_firestore.FieldValue.arrayRemove(task.currentClaimId)
300
+ }, { lastUpdateTime: this.microsecondsToTimestamp(task.lastUpdateTime) });
263
301
  }
264
302
  await batch.commit();
265
303
  }
@@ -290,16 +328,22 @@ var FirestoreQueueStore = class {
290
328
  queued(id, eventId) {
291
329
  return this.queue(id).doc(eventId.serialize());
292
330
  }
293
- async processed(id, eventIds, context = {}) {
331
+ async processed(claimerId, id, eventIds, context = {}) {
294
332
  const { transaction: trx, batchWriter } = context;
295
333
  if (trx) {
296
334
  for (const eventId of eventIds) {
297
335
  const ref = this.queued(id, eventId);
298
- trx.transaction.update(ref, { processed: true });
336
+ trx.transaction.update(ref, {
337
+ processed: true,
338
+ [`claimsMetadata.${claimerId.serialize()}.processedAt`]: firebase_admin_firestore.FieldValue.serverTimestamp()
339
+ });
299
340
  }
300
341
  return;
301
342
  }
302
- await Promise.all(eventIds.map((eventId) => this.queued(id, eventId).update({ processed: true })));
343
+ await Promise.all(eventIds.map((eventId) => this.queued(id, eventId).update({
344
+ processed: true,
345
+ [`claimsMetadata.${claimerId.serialize()}.processedAt`]: firebase_admin_firestore.FieldValue.serverTimestamp()
346
+ })));
303
347
  }
304
348
  async getTailCursor(id) {
305
349
  const tailDoc = (await this.queue(id).where("remaining", ">", 0).orderBy("occurredAt", "asc").orderBy("revision", "asc").limit(1).get()).docs[0];
@@ -359,6 +403,8 @@ var FirestoreQueueStore = class {
359
403
  processed: true,
360
404
  claimer: void 0,
361
405
  claimedAt: void 0,
406
+ claimsMetadata: {},
407
+ claimIds: [],
362
408
  lock: new _ddd_ts_core.Lock({}),
363
409
  remaining: 1,
364
410
  claimTimeout: 0,
@@ -385,6 +431,11 @@ var Task = class Task extends (0, _ddd_ts_shape.Shape)({
385
431
  processed: Boolean,
386
432
  claimer: (0, _ddd_ts_shape.Optional)(String),
387
433
  claimedAt: (0, _ddd_ts_shape.Optional)(_ddd_ts_shape.MicrosecondTimestamp),
434
+ claimsMetadata: (0, _ddd_ts_shape.Mapping)([{
435
+ claimedAt: _ddd_ts_shape.MicrosecondTimestamp,
436
+ processedAt: (0, _ddd_ts_shape.Optional)(_ddd_ts_shape.MicrosecondTimestamp)
437
+ }]),
438
+ claimIds: [String],
388
439
  lock: _ddd_ts_core.Lock,
389
440
  skipAfter: Number,
390
441
  remaining: Number,
@@ -407,6 +458,8 @@ var Task = class Task extends (0, _ddd_ts_shape.Shape)({
407
458
  claimer: void 0,
408
459
  processed: false,
409
460
  claimedAt: void 0,
461
+ claimsMetadata: {},
462
+ claimIds: [],
410
463
  lock: config.lock,
411
464
  claimTimeout: config.claimTimeout,
412
465
  skipAfter: config.skipAfter,
@@ -418,8 +471,11 @@ var Task = class Task extends (0, _ddd_ts_shape.Shape)({
418
471
  lastUpdateTime: void 0
419
472
  });
420
473
  }
474
+ get currentClaimId() {
475
+ return this.claimIds.at(-1);
476
+ }
421
477
  get isProcessing() {
422
- return !!this.claimer;
478
+ return this.currentClaimId !== void 0;
423
479
  }
424
480
  get isProcessed() {
425
481
  return !!this.processed;
@@ -431,13 +487,11 @@ var Task = class Task extends (0, _ddd_ts_shape.Shape)({
431
487
  return this.attempts > this.isolateAfter;
432
488
  }
433
489
  checkTimeout() {
434
- if (!this.claimedAt) return;
435
- if (_ddd_ts_shape.MicrosecondTimestamp.now().micros - this.claimedAt.micros > BigInt(this.claimTimeout) * 1000n) {
436
- this.claimedAt = void 0;
437
- this.claimer = void 0;
438
- this.attempts += 1;
439
- this.remaining -= 1;
440
- }
490
+ const claimer = this.currentClaimId;
491
+ if (!claimer) return;
492
+ const claimInfo = this.claimsMetadata[claimer];
493
+ if (!claimInfo || !claimInfo.claimedAt) return;
494
+ if (_ddd_ts_shape.MicrosecondTimestamp.now().micros - claimInfo.claimedAt.micros > BigInt(this.claimTimeout) * 1000n) this.claimIds = this.claimIds.filter((id) => id !== claimer);
441
495
  }
442
496
  static deserializeWithLastUpdateTime(data, timestamp) {
443
497
  return Task.deserialize({
@@ -1,7 +1,8 @@
1
+ import { EventCoordinator } from "./event-coordinator.mjs";
1
2
  import { Cursor, EventId, Lock, ProjectedStreamReader } from "@ddd-ts/core";
2
3
  import { DefaultConverter } from "@ddd-ts/store-firestore";
3
4
  import { FieldValue, Timestamp } from "firebase-admin/firestore";
4
- import { MicrosecondTimestamp, Optional, Shape } from "@ddd-ts/shape";
5
+ import { Mapping, MicrosecondTimestamp, Optional, Shape } from "@ddd-ts/shape";
5
6
 
6
7
  //#region src/projection/firestore.projector.ts
7
8
  const Status = {
@@ -50,10 +51,32 @@ var FirestoreProjector = class {
50
51
  await wait((backoff * i + 1) * minDelay + jitter);
51
52
  }
52
53
  }
54
+ eventCoordinators = /* @__PURE__ */ new Map();
55
+ getEventCoordinator(checkpointId) {
56
+ const key = checkpointId.serialize();
57
+ let coordinator = this.eventCoordinators.get(key);
58
+ if (!coordinator) {
59
+ coordinator = new EventCoordinator();
60
+ coordinator.onEmpty(() => this.eventCoordinators.delete(key));
61
+ this.eventCoordinators.set(key, coordinator);
62
+ }
63
+ return coordinator;
64
+ }
53
65
  async handle(savedChange) {
54
66
  const checkpointId = this.projection.getCheckpointId(savedChange);
67
+ const eventCoordinator = this.getEventCoordinator(checkpointId);
68
+ eventCoordinator.addEvent(savedChange);
69
+ await eventCoordinator.waitCurrentEvent();
70
+ if (!eventCoordinator.canProceed(savedChange)) {
71
+ eventCoordinator.cleanEvent(savedChange);
72
+ return;
73
+ }
74
+ const disposeEventCoordinator = eventCoordinator.start(savedChange);
55
75
  const target = await this.getCursor(savedChange);
56
- if (!target) throw new Error(`Cursor not found for event ${savedChange.id.serialize()}`);
76
+ if (!target) {
77
+ disposeEventCoordinator();
78
+ throw new Error(`Cursor not found for event ${savedChange.id.serialize()}`);
79
+ }
57
80
  const errors = [];
58
81
  for await (const [attempt, reset] of this.breathe()) {
59
82
  const source = this.projection.getSource(savedChange);
@@ -64,10 +87,12 @@ var FirestoreProjector = class {
64
87
  }
65
88
  if (status === Status.SUCCESS) {
66
89
  await this.queue.cleanup(checkpointId);
90
+ disposeEventCoordinator();
67
91
  return;
68
92
  }
69
93
  errors.push(message);
70
94
  }
95
+ disposeEventCoordinator();
71
96
  throw new Error(`Failed to handle event ${savedChange.id.serialize()}: ${errors.join(", ")}`);
72
97
  }
73
98
  async getCursor(savedChange) {
@@ -139,8 +164,9 @@ var FirestoreProjector = class {
139
164
  const filtered = (await Promise.all(tasks.map((t) => this.reader.get(t.cursor)))).filter((t) => !!t);
140
165
  if (!filtered.length) return [Status.DEFERRED, "No events to process in claimed tasks"];
141
166
  const context = {
142
- onProcessed: this.queue.processed.bind(this.queue),
143
- checkpointId
167
+ onProcessed: this.queue.processed.bind(this.queue, claimer),
168
+ checkpointId,
169
+ assertBeforeInsert: this.assertBeforeInsert.bind(this, checkpointId, claimer, filtered)
144
170
  };
145
171
  const hasTarget = tasks.some((t) => t.id.equals(targetEventId));
146
172
  try {
@@ -153,6 +179,15 @@ var FirestoreProjector = class {
153
179
  return [Status.FAILURE, e];
154
180
  }
155
181
  }
182
+ async assertBeforeInsert(checkpointId, claimer, events) {
183
+ const claimedTasks = await this.queue.claimed(checkpointId, claimer);
184
+ const claimedTasksMap = new Map(claimedTasks.map((t) => [t.id.serialize(), t]));
185
+ for (const event of events) {
186
+ const task = claimedTasksMap.get(event.id.serialize());
187
+ if (!task) throw new Error(`Task not found for event ${event.id.serialize()} in claimer ${claimer.serialize()}`);
188
+ if (task.claimIds?.[0] !== claimer.serialize()) throw new Error(`Task ${task.id.serialize()} claimer mismatch: expected ${claimer.serialize()}, found ${task.claimIds?.[0]}`);
189
+ }
190
+ }
156
191
  };
157
192
  var AlreadyEnqueuedError = class extends Error {
158
193
  constructor() {
@@ -195,10 +230,13 @@ var FirestoreQueueStore = class {
195
230
  async claim(checkpointId, claimer, tasks) {
196
231
  const batch = this.collection.firestore.batch();
197
232
  for (const task of tasks) {
233
+ if (task.claimIds.length > 0) throw new Error(`Task ${task.id.serialize()} is already claimed by ${task.claimIds.join(", ")}`);
198
234
  const ref = this.queued(checkpointId, task.id);
199
235
  batch.update(ref, {
200
236
  claimer: claimer.serialize(),
201
237
  claimedAt: FieldValue.serverTimestamp(),
238
+ [`claimsMetadata.${claimer.serialize()}`]: { claimedAt: FieldValue.serverTimestamp() },
239
+ claimIds: FieldValue.arrayUnion(claimer.serialize()),
202
240
  attempts: FieldValue.increment(1),
203
241
  remaining: FieldValue.increment(-1)
204
242
  }, { lastUpdateTime: this.microsecondsToTimestamp(task.lastUpdateTime) });
@@ -225,9 +263,9 @@ var FirestoreQueueStore = class {
225
263
  });
226
264
  const expiredTasks = [];
227
265
  for (const task of tasks) {
228
- const originalClaimer = task.claimer;
266
+ const originalClaimIds = task.claimIds;
229
267
  task.checkTimeout();
230
- if (originalClaimer && !task.claimer) expiredTasks.push(task);
268
+ if (originalClaimIds.length > task.claimIds.length) expiredTasks.push(task);
231
269
  }
232
270
  if (expiredTasks.length > 0) {
233
271
  const batch = this.collection.firestore.batch();
@@ -236,20 +274,19 @@ var FirestoreQueueStore = class {
236
274
  batch.update(ref, {
237
275
  claimer: FieldValue.delete(),
238
276
  claimedAt: FieldValue.delete(),
239
- attempts: task.attempts,
240
- remaining: task.remaining
241
- });
277
+ claimIds: task.claimIds
278
+ }, { lastUpdateTime: this.microsecondsToTimestamp(task.lastUpdateTime) });
242
279
  }
243
280
  await batch.commit();
244
281
  }
245
282
  return tasks;
246
283
  }
247
284
  async claimed(checkpointId, claimer) {
248
- return (await this.queue(checkpointId).where("claimer", "==", claimer.serialize()).orderBy("occurredAt", "asc").orderBy("revision", "asc").get()).docs.map((doc) => {
285
+ return (await this.queue(checkpointId).where("claimIds", "array-contains", claimer.serialize()).orderBy("occurredAt", "asc").orderBy("revision", "asc").get()).docs.map((doc) => {
249
286
  const data = this.converter.fromFirestoreSnapshot(doc);
250
287
  const timestamp = doc.updateTime ? this.timestampToMicroseconds(doc.updateTime) : void 0;
251
288
  return Task.deserializeWithLastUpdateTime(data, timestamp);
252
- });
289
+ }).filter((task) => task.claimIds[0] === claimer.serialize());
253
290
  }
254
291
  async unclaim(checkpointId, tasks) {
255
292
  const batch = this.collection.firestore.batch();
@@ -257,8 +294,9 @@ var FirestoreQueueStore = class {
257
294
  const ref = this.queued(checkpointId, task.id);
258
295
  batch.update(ref, {
259
296
  claimer: FieldValue.delete(),
260
- claimedAt: FieldValue.delete()
261
- });
297
+ claimedAt: FieldValue.delete(),
298
+ claimIds: FieldValue.arrayRemove(task.currentClaimId)
299
+ }, { lastUpdateTime: this.microsecondsToTimestamp(task.lastUpdateTime) });
262
300
  }
263
301
  await batch.commit();
264
302
  }
@@ -289,16 +327,22 @@ var FirestoreQueueStore = class {
289
327
  queued(id, eventId) {
290
328
  return this.queue(id).doc(eventId.serialize());
291
329
  }
292
- async processed(id, eventIds, context = {}) {
330
+ async processed(claimerId, id, eventIds, context = {}) {
293
331
  const { transaction: trx, batchWriter } = context;
294
332
  if (trx) {
295
333
  for (const eventId of eventIds) {
296
334
  const ref = this.queued(id, eventId);
297
- trx.transaction.update(ref, { processed: true });
335
+ trx.transaction.update(ref, {
336
+ processed: true,
337
+ [`claimsMetadata.${claimerId.serialize()}.processedAt`]: FieldValue.serverTimestamp()
338
+ });
298
339
  }
299
340
  return;
300
341
  }
301
- await Promise.all(eventIds.map((eventId) => this.queued(id, eventId).update({ processed: true })));
342
+ await Promise.all(eventIds.map((eventId) => this.queued(id, eventId).update({
343
+ processed: true,
344
+ [`claimsMetadata.${claimerId.serialize()}.processedAt`]: FieldValue.serverTimestamp()
345
+ })));
302
346
  }
303
347
  async getTailCursor(id) {
304
348
  const tailDoc = (await this.queue(id).where("remaining", ">", 0).orderBy("occurredAt", "asc").orderBy("revision", "asc").limit(1).get()).docs[0];
@@ -358,6 +402,8 @@ var FirestoreQueueStore = class {
358
402
  processed: true,
359
403
  claimer: void 0,
360
404
  claimedAt: void 0,
405
+ claimsMetadata: {},
406
+ claimIds: [],
361
407
  lock: new Lock({}),
362
408
  remaining: 1,
363
409
  claimTimeout: 0,
@@ -384,6 +430,11 @@ var Task = class Task extends Shape({
384
430
  processed: Boolean,
385
431
  claimer: Optional(String),
386
432
  claimedAt: Optional(MicrosecondTimestamp),
433
+ claimsMetadata: Mapping([{
434
+ claimedAt: MicrosecondTimestamp,
435
+ processedAt: Optional(MicrosecondTimestamp)
436
+ }]),
437
+ claimIds: [String],
387
438
  lock: Lock,
388
439
  skipAfter: Number,
389
440
  remaining: Number,
@@ -406,6 +457,8 @@ var Task = class Task extends Shape({
406
457
  claimer: void 0,
407
458
  processed: false,
408
459
  claimedAt: void 0,
460
+ claimsMetadata: {},
461
+ claimIds: [],
409
462
  lock: config.lock,
410
463
  claimTimeout: config.claimTimeout,
411
464
  skipAfter: config.skipAfter,
@@ -417,8 +470,11 @@ var Task = class Task extends Shape({
417
470
  lastUpdateTime: void 0
418
471
  });
419
472
  }
473
+ get currentClaimId() {
474
+ return this.claimIds.at(-1);
475
+ }
420
476
  get isProcessing() {
421
- return !!this.claimer;
477
+ return this.currentClaimId !== void 0;
422
478
  }
423
479
  get isProcessed() {
424
480
  return !!this.processed;
@@ -430,13 +486,11 @@ var Task = class Task extends Shape({
430
486
  return this.attempts > this.isolateAfter;
431
487
  }
432
488
  checkTimeout() {
433
- if (!this.claimedAt) return;
434
- if (MicrosecondTimestamp.now().micros - this.claimedAt.micros > BigInt(this.claimTimeout) * 1000n) {
435
- this.claimedAt = void 0;
436
- this.claimer = void 0;
437
- this.attempts += 1;
438
- this.remaining -= 1;
439
- }
489
+ const claimer = this.currentClaimId;
490
+ if (!claimer) return;
491
+ const claimInfo = this.claimsMetadata[claimer];
492
+ if (!claimInfo || !claimInfo.claimedAt) return;
493
+ if (MicrosecondTimestamp.now().micros - claimInfo.claimedAt.micros > BigInt(this.claimTimeout) * 1000n) this.claimIds = this.claimIds.filter((id) => id !== claimer);
440
494
  }
441
495
  static deserializeWithLastUpdateTime(data, timestamp) {
442
496
  return Task.deserialize({
@@ -0,0 +1,7 @@
1
+ export interface PromiseResolvers<T> {
2
+ promise: Promise<T>;
3
+ resolve: (value: T) => void;
4
+ reject: (reason?: any) => void;
5
+ }
6
+ export declare const promiseWithResolvers: <T>() => PromiseResolvers<T>;
7
+ //# sourceMappingURL=promise-with-resolvers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promise-with-resolvers.d.ts","sourceRoot":"","sources":["../../src/utils/promise-with-resolvers.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;CAChC;AAED,eAAO,MAAM,oBAAoB,GAAI,CAAC,OAAK,gBAAgB,CAAC,CAAC,CAQ5D,CAAC"}
@@ -0,0 +1,17 @@
1
+
2
+ //#region src/utils/promise-with-resolvers.ts
3
+ const promiseWithResolvers = () => {
4
+ let resolve;
5
+ let reject;
6
+ return {
7
+ promise: new Promise((res, rej) => {
8
+ resolve = res;
9
+ reject = rej;
10
+ }),
11
+ resolve,
12
+ reject
13
+ };
14
+ };
15
+
16
+ //#endregion
17
+ exports.promiseWithResolvers = promiseWithResolvers;
@@ -0,0 +1,16 @@
1
+ //#region src/utils/promise-with-resolvers.ts
2
+ const promiseWithResolvers = () => {
3
+ let resolve;
4
+ let reject;
5
+ return {
6
+ promise: new Promise((res, rej) => {
7
+ resolve = res;
8
+ reject = rej;
9
+ }),
10
+ resolve,
11
+ reject
12
+ };
13
+ };
14
+
15
+ //#endregion
16
+ export { promiseWithResolvers };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ddd-ts/event-sourcing-firestore",
3
- "version": "0.0.38",
3
+ "version": "0.0.39",
4
4
  "types": "dist/index.d.ts",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -10,19 +10,19 @@
10
10
  "dist"
11
11
  ],
12
12
  "dependencies": {
13
- "@ddd-ts/core": "0.0.38",
14
- "@ddd-ts/shape": "0.0.38",
15
- "@ddd-ts/store-firestore": "0.0.38",
16
- "@ddd-ts/traits": "0.0.38",
17
- "@ddd-ts/types": "0.0.38",
13
+ "@ddd-ts/core": "0.0.39",
14
+ "@ddd-ts/shape": "0.0.39",
15
+ "@ddd-ts/store-firestore": "0.0.39",
16
+ "@ddd-ts/traits": "0.0.39",
17
+ "@ddd-ts/types": "0.0.39",
18
18
  "@opentelemetry/api": "1.6.0",
19
19
  "firebase-admin": "^13.2.0"
20
20
  },
21
21
  "devDependencies": {
22
- "@ddd-ts/shape": "0.0.38",
23
- "@ddd-ts/tests": "0.0.38",
24
- "@ddd-ts/tools": "0.0.38",
25
- "@ddd-ts/types": "0.0.38",
22
+ "@ddd-ts/shape": "0.0.39",
23
+ "@ddd-ts/tests": "0.0.39",
24
+ "@ddd-ts/tools": "0.0.39",
25
+ "@ddd-ts/types": "0.0.39",
26
26
  "@types/jest": "^29.5.1"
27
27
  },
28
28
  "exports": {