event-store-adapter-js 1.1.99-snapshot.1 → 2.0.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.ja.md CHANGED
@@ -41,7 +41,6 @@ class UserAccountRepository {
41
41
  async findById(id: UserAccountId): Promise<UserAccount | undefined> {
42
42
  const snapshot = await this.eventStore.getLatestSnapshotById(
43
43
  id,
44
- convertJSONToUserAccount,
45
44
  );
46
45
  if (snapshot === undefined) {
47
46
  return undefined;
@@ -49,7 +48,6 @@ class UserAccountRepository {
49
48
  const events = await this.eventStore.getEventsByIdSinceSequenceNumber(
50
49
  id,
51
50
  snapshot.sequenceNumber + 1,
52
- convertJSONtoUserAccountEvent,
53
51
  );
54
52
  return UserAccount.replay(events, snapshot);
55
53
  }
@@ -71,6 +69,8 @@ const eventStore = EventStoreFactory.ofDynamoDB<
71
69
  JOURNAL_AID_INDEX_NAME,
72
70
  SNAPSHOTS_AID_INDEX_NAME,
73
71
  32,
72
+ convertJSONtoUserAccountEvent,
73
+ convertJSONToUserAccount,
74
74
  );
75
75
  const userAccountRepository = new UserAccountRepository(eventStore);
76
76
 
package/README.md CHANGED
@@ -71,6 +71,8 @@ const eventStore = EventStoreFactory.ofDynamoDB<
71
71
  JOURNAL_AID_INDEX_NAME,
72
72
  SNAPSHOTS_AID_INDEX_NAME,
73
73
  32,
74
+ convertJSONtoUserAccountEvent,
75
+ convertJSONToUserAccount,
74
76
  );
75
77
  const userAccountRepository = new UserAccountRepository(eventStore);
76
78
 
@@ -0,0 +1,6 @@
1
+ import { Aggregate, AggregateId, Event } from "./types";
2
+ import { EventStoreOptions } from "./event-store-options";
3
+ import { EventStore } from "./event-store";
4
+ interface EventStoreWithOptions<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>> extends EventStore<AID, A, E>, EventStoreOptions<EventStoreWithOptions<AID, A, E>, AID, A, E> {
5
+ }
6
+ export { EventStoreWithOptions };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,15 +1,15 @@
1
1
  import { Aggregate, AggregateId, Event, EventSerializer, KeyResolver, SnapshotSerializer } from "./types";
2
- import { EventStoreOptions } from "./event-store-options";
3
- import { EventStoreForDynamoDB } from "./internal/event-store-for-dynamodb";
4
2
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
5
3
  import moment from "moment";
6
- interface EventStore<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>> extends EventStoreOptions<EventStore<AID, A, E>, AID, A, E> {
4
+ import { EventStoreWithOptions } from "./event-store-with-options";
5
+ interface EventStore<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>> {
7
6
  persistEvent(event: E, version: number): Promise<void>;
8
7
  persistEventAndSnapshot(event: E, aggregate: A): Promise<void>;
9
- getEventsByIdSinceSequenceNumber(id: AID, sequenceNumber: number, converter: (json: string) => E): Promise<E[]>;
10
- getLatestSnapshotById(id: AID, converter: (json: string) => A): Promise<A | undefined>;
8
+ getEventsByIdSinceSequenceNumber(id: AID, sequenceNumber: number): Promise<E[]>;
9
+ getLatestSnapshotById(id: AID): Promise<A | undefined>;
11
10
  }
12
11
  declare class EventStoreFactory {
13
- static ofDynamoDB<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>>(dynamodbClient: DynamoDBClient, journalTableName: string, snapshotTableName: string, journalAidIndexName: string, snapshotAidIndexName: string, shardCount: number, keepSnapshotCount?: number | undefined, deleteTtl?: moment.Duration | undefined, keyResolver?: KeyResolver<AID>, eventSerializer?: EventSerializer<AID, E>, snapshotSerializer?: SnapshotSerializer<AID, A>): EventStoreForDynamoDB<AID, A, E>;
12
+ static ofDynamoDB<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>>(dynamodbClient: DynamoDBClient, journalTableName: string, snapshotTableName: string, journalAidIndexName: string, snapshotAidIndexName: string, shardCount: number, eventConverter: (json: string) => E, snapshotConverter: (json: string) => A, keepSnapshotCount?: number | undefined, deleteTtl?: moment.Duration | undefined, keyResolver?: KeyResolver<AID>, eventSerializer?: EventSerializer<AID, E>, snapshotSerializer?: SnapshotSerializer<AID, A>): EventStoreWithOptions<AID, A, E>;
13
+ static ofMemory<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>>(events: Map<AID, E[]>, snapshots: Map<AID, A>): EventStore<AID, A, E>;
14
14
  }
15
15
  export { EventStore, EventStoreFactory };
@@ -4,9 +4,13 @@ exports.EventStoreFactory = void 0;
4
4
  const event_store_for_dynamodb_1 = require("./internal/event-store-for-dynamodb");
5
5
  const default_key_resolver_1 = require("./internal/default-key-resolver");
6
6
  const default_serializer_1 = require("./internal/default-serializer");
7
+ const event_store_for_memory_1 = require("./internal/event-store-for-memory");
7
8
  class EventStoreFactory {
8
- static ofDynamoDB(dynamodbClient, journalTableName, snapshotTableName, journalAidIndexName, snapshotAidIndexName, shardCount, keepSnapshotCount = undefined, deleteTtl = undefined, keyResolver = new default_key_resolver_1.DefaultKeyResolver(), eventSerializer = new default_serializer_1.JsonEventSerializer(), snapshotSerializer = new default_serializer_1.JsonSnapshotSerializer()) {
9
- return new event_store_for_dynamodb_1.EventStoreForDynamoDB(dynamodbClient, journalTableName, snapshotTableName, journalAidIndexName, snapshotAidIndexName, shardCount, keepSnapshotCount, deleteTtl, keyResolver, eventSerializer, snapshotSerializer);
9
+ static ofDynamoDB(dynamodbClient, journalTableName, snapshotTableName, journalAidIndexName, snapshotAidIndexName, shardCount, eventConverter, snapshotConverter, keepSnapshotCount = undefined, deleteTtl = undefined, keyResolver = new default_key_resolver_1.DefaultKeyResolver(), eventSerializer = new default_serializer_1.JsonEventSerializer(), snapshotSerializer = new default_serializer_1.JsonSnapshotSerializer()) {
10
+ return new event_store_for_dynamodb_1.EventStoreForDynamoDB(dynamodbClient, journalTableName, snapshotTableName, journalAidIndexName, snapshotAidIndexName, shardCount, eventConverter, snapshotConverter, keepSnapshotCount, deleteTtl, keyResolver, eventSerializer, snapshotSerializer);
11
+ }
12
+ static ofMemory(events, snapshots) {
13
+ return new event_store_for_memory_1.EventStoreForMemory(events, snapshots);
10
14
  }
11
15
  }
12
16
  exports.EventStoreFactory = EventStoreFactory;
@@ -1,30 +1,32 @@
1
1
  import { Aggregate, AggregateId, Event, EventSerializer, KeyResolver, Logger, SnapshotSerializer } from "../types";
2
- import { EventStore } from "../event-store";
3
2
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
4
3
  import moment from "moment/moment";
5
- declare class EventStoreForDynamoDB<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>> implements EventStore<AID, A, E> {
4
+ import { EventStoreWithOptions } from "../event-store-with-options";
5
+ declare class EventStoreForDynamoDB<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>> implements EventStoreWithOptions<AID, A, E> {
6
6
  private dynamodbClient;
7
7
  private journalTableName;
8
8
  private snapshotTableName;
9
9
  private journalAidIndexName;
10
10
  private snapshotAidIndexName;
11
11
  private shardCount;
12
+ private eventConverter;
13
+ private snapshotConverter;
12
14
  private keepSnapshotCount;
13
15
  private deleteTtl;
14
16
  private keyResolver;
15
17
  private eventSerializer;
16
18
  private snapshotSerializer;
17
19
  private logger;
18
- constructor(dynamodbClient: DynamoDBClient, journalTableName: string, snapshotTableName: string, journalAidIndexName: string, snapshotAidIndexName: string, shardCount: number, keepSnapshotCount?: number | undefined, deleteTtl?: moment.Duration | undefined, keyResolver?: KeyResolver<AID>, eventSerializer?: EventSerializer<AID, E>, snapshotSerializer?: SnapshotSerializer<AID, A>, logger?: Logger | undefined);
19
- getEventsByIdSinceSequenceNumber(id: AID, sequenceNumber: number, converter: (json: string) => E): Promise<E[]>;
20
- getLatestSnapshotById(id: AID, converter: (json: string) => A): Promise<A | undefined>;
20
+ constructor(dynamodbClient: DynamoDBClient, journalTableName: string, snapshotTableName: string, journalAidIndexName: string, snapshotAidIndexName: string, shardCount: number, eventConverter: (json: string) => E, snapshotConverter: (json: string) => A, keepSnapshotCount?: number | undefined, deleteTtl?: moment.Duration | undefined, keyResolver?: KeyResolver<AID>, eventSerializer?: EventSerializer<AID, E>, snapshotSerializer?: SnapshotSerializer<AID, A>, logger?: Logger | undefined);
21
+ getEventsByIdSinceSequenceNumber(id: AID, sequenceNumber: number): Promise<E[]>;
22
+ getLatestSnapshotById(id: AID): Promise<A | undefined>;
21
23
  persistEvent(event: E, version: number): Promise<void>;
22
24
  persistEventAndSnapshot(event: E, aggregate: A): Promise<void>;
23
- withDeleteTtl(deleteTtl: moment.Duration): EventStore<AID, A, E>;
24
- withEventSerializer(eventSerializer: EventSerializer<AID, E>): EventStore<AID, A, E>;
25
- withKeepSnapshotCount(keepSnapshotCount: number): EventStore<AID, A, E>;
26
- withKeyResolver(keyResolver: KeyResolver<AID>): EventStore<AID, A, E>;
27
- withSnapshotSerializer(snapshotSerializer: SnapshotSerializer<AID, A>): EventStore<AID, A, E>;
25
+ withDeleteTtl(deleteTtl: moment.Duration): EventStoreWithOptions<AID, A, E>;
26
+ withEventSerializer(eventSerializer: EventSerializer<AID, E>): EventStoreWithOptions<AID, A, E>;
27
+ withKeepSnapshotCount(keepSnapshotCount: number): EventStoreWithOptions<AID, A, E>;
28
+ withKeyResolver(keyResolver: KeyResolver<AID>): EventStoreWithOptions<AID, A, E>;
29
+ withSnapshotSerializer(snapshotSerializer: SnapshotSerializer<AID, A>): EventStoreWithOptions<AID, A, E>;
28
30
  private createEventAndSnapshot;
29
31
  private updateEventAndSnapshotOpt;
30
32
  private putJournal;
@@ -18,13 +18,15 @@ const moment_1 = __importDefault(require("moment/moment"));
18
18
  const default_key_resolver_1 = require("./default-key-resolver");
19
19
  const default_serializer_1 = require("./default-serializer");
20
20
  class EventStoreForDynamoDB {
21
- constructor(dynamodbClient, journalTableName, snapshotTableName, journalAidIndexName, snapshotAidIndexName, shardCount, keepSnapshotCount = undefined, deleteTtl = undefined, keyResolver = new default_key_resolver_1.DefaultKeyResolver(), eventSerializer = new default_serializer_1.JsonEventSerializer(), snapshotSerializer = new default_serializer_1.JsonSnapshotSerializer(), logger = undefined) {
21
+ constructor(dynamodbClient, journalTableName, snapshotTableName, journalAidIndexName, snapshotAidIndexName, shardCount, eventConverter, snapshotConverter, keepSnapshotCount = undefined, deleteTtl = undefined, keyResolver = new default_key_resolver_1.DefaultKeyResolver(), eventSerializer = new default_serializer_1.JsonEventSerializer(), snapshotSerializer = new default_serializer_1.JsonSnapshotSerializer(), logger = undefined) {
22
22
  this.dynamodbClient = dynamodbClient;
23
23
  this.journalTableName = journalTableName;
24
24
  this.snapshotTableName = snapshotTableName;
25
25
  this.journalAidIndexName = journalAidIndexName;
26
26
  this.snapshotAidIndexName = snapshotAidIndexName;
27
27
  this.shardCount = shardCount;
28
+ this.eventConverter = eventConverter;
29
+ this.snapshotConverter = snapshotConverter;
28
30
  this.keepSnapshotCount = keepSnapshotCount;
29
31
  this.deleteTtl = deleteTtl;
30
32
  this.keyResolver = keyResolver;
@@ -32,7 +34,7 @@ class EventStoreForDynamoDB {
32
34
  this.snapshotSerializer = snapshotSerializer;
33
35
  this.logger = logger;
34
36
  }
35
- getEventsByIdSinceSequenceNumber(id, sequenceNumber, converter) {
37
+ getEventsByIdSinceSequenceNumber(id, sequenceNumber) {
36
38
  var _a, _b;
37
39
  return __awaiter(this, void 0, void 0, function* () {
38
40
  (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug(`getEventsByIdSinceSequenceNumber(${JSON.stringify(id)}, ${sequenceNumber}, ...): start`);
@@ -60,14 +62,14 @@ class EventStoreForDynamoDB {
60
62
  if (payload === undefined) {
61
63
  throw new Error("Payload is undefined");
62
64
  }
63
- return this.eventSerializer.deserialize(payload, converter);
65
+ return this.eventSerializer.deserialize(payload, this.eventConverter);
64
66
  });
65
67
  }
66
68
  (_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug(`getEventsByIdSinceSequenceNumber(${JSON.stringify(id)}, ${sequenceNumber}, ...): finished`);
67
69
  return result;
68
70
  });
69
71
  }
70
- getLatestSnapshotById(id, converter) {
72
+ getLatestSnapshotById(id) {
71
73
  var _a, _b;
72
74
  return __awaiter(this, void 0, void 0, function* () {
73
75
  (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug(`getLatestSnapshotById(${JSON.stringify(id)}, ...): start`);
@@ -99,7 +101,7 @@ class EventStoreForDynamoDB {
99
101
  if (payload === undefined) {
100
102
  throw new Error("Payload is undefined");
101
103
  }
102
- const result = this.snapshotSerializer.deserialize(payload, converter);
104
+ const result = this.snapshotSerializer.deserialize(payload, this.snapshotConverter);
103
105
  (_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug(`getLatestSnapshotById(${JSON.stringify(id)}, ...): finished`);
104
106
  return result.withVersion(Number(version));
105
107
  }
@@ -120,6 +122,9 @@ class EventStoreForDynamoDB {
120
122
  persistEventAndSnapshot(event, aggregate) {
121
123
  var _a, _b;
122
124
  return __awaiter(this, void 0, void 0, function* () {
125
+ if (event.aggregateId.asString !== aggregate.id.asString) {
126
+ throw new Error(`aggregateId mismatch: expected ${event.aggregateId.asString}, got ${aggregate.id.asString}`);
127
+ }
123
128
  (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug(`persistEventAndSnapshot(${JSON.stringify(event)}, ${JSON.stringify(aggregate)}): start`);
124
129
  if (event.isCreated) {
125
130
  yield this.createEventAndSnapshot(event, aggregate);
@@ -132,19 +137,19 @@ class EventStoreForDynamoDB {
132
137
  });
133
138
  }
134
139
  withDeleteTtl(deleteTtl) {
135
- return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.keepSnapshotCount, deleteTtl, this.keyResolver, this.eventSerializer, this.snapshotSerializer);
140
+ return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.eventConverter, this.snapshotConverter, this.keepSnapshotCount, deleteTtl, this.keyResolver, this.eventSerializer, this.snapshotSerializer);
136
141
  }
137
142
  withEventSerializer(eventSerializer) {
138
- return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.keepSnapshotCount, this.deleteTtl, this.keyResolver, eventSerializer, this.snapshotSerializer);
143
+ return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.eventConverter, this.snapshotConverter, this.keepSnapshotCount, this.deleteTtl, this.keyResolver, eventSerializer, this.snapshotSerializer);
139
144
  }
140
145
  withKeepSnapshotCount(keepSnapshotCount) {
141
- return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, keepSnapshotCount, this.deleteTtl, this.keyResolver, this.eventSerializer, this.snapshotSerializer);
146
+ return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.eventConverter, this.snapshotConverter, keepSnapshotCount, this.deleteTtl, this.keyResolver, this.eventSerializer, this.snapshotSerializer);
142
147
  }
143
148
  withKeyResolver(keyResolver) {
144
- return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.keepSnapshotCount, this.deleteTtl, keyResolver, this.eventSerializer, this.snapshotSerializer);
149
+ return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.eventConverter, this.snapshotConverter, this.keepSnapshotCount, this.deleteTtl, keyResolver, this.eventSerializer, this.snapshotSerializer);
145
150
  }
146
151
  withSnapshotSerializer(snapshotSerializer) {
147
- return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.keepSnapshotCount, this.deleteTtl, this.keyResolver, this.eventSerializer, snapshotSerializer);
152
+ return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.eventConverter, this.snapshotConverter, this.keepSnapshotCount, this.deleteTtl, this.keyResolver, this.eventSerializer, snapshotSerializer);
148
153
  }
149
154
  createEventAndSnapshot(event, aggregate) {
150
155
  var _a, _b;
@@ -379,7 +384,9 @@ class EventStoreForDynamoDB {
379
384
  ":ttl": { N: ttl.seconds().toString() },
380
385
  },
381
386
  };
382
- return this.dynamodbClient.send(new client_dynamodb_1.UpdateItemCommand(request));
387
+ return this.dynamodbClient
388
+ .send(new client_dynamodb_1.UpdateItemCommand(request))
389
+ .then((_) => { });
383
390
  });
384
391
  return yield Promise.all(result);
385
392
  }
@@ -401,7 +408,7 @@ class EventStoreForDynamoDB {
401
408
  if (keys === undefined) {
402
409
  return undefined;
403
410
  }
404
- const result = keys.map((key) => {
411
+ const request = keys.map((key) => {
405
412
  const request = {
406
413
  DeleteRequest: {
407
414
  Key: {
@@ -410,13 +417,15 @@ class EventStoreForDynamoDB {
410
417
  },
411
418
  },
412
419
  };
413
- return this.dynamodbClient.send(new client_dynamodb_1.BatchWriteItemCommand({
414
- RequestItems: {
415
- [this.snapshotTableName]: [request],
416
- },
417
- }));
420
+ return request;
418
421
  });
419
- return yield Promise.all(result);
422
+ return this.dynamodbClient
423
+ .send(new client_dynamodb_1.BatchWriteItemCommand({
424
+ RequestItems: {
425
+ [this.snapshotTableName]: request,
426
+ },
427
+ }))
428
+ .then((_) => { });
420
429
  }
421
430
  }
422
431
  return undefined;
@@ -0,0 +1,12 @@
1
+ import { Aggregate, AggregateId, Event } from "../types";
2
+ import { EventStore } from "../event-store";
3
+ declare class EventStoreForMemory<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>> implements EventStore<AID, A, E> {
4
+ private readonly events;
5
+ private readonly snapshots;
6
+ constructor(events: Map<AID, E[]>, snapshots: Map<AID, A>);
7
+ persistEvent(event: E, version: number): Promise<void>;
8
+ persistEventAndSnapshot(event: E, aggregate: A): Promise<void>;
9
+ getEventsByIdSinceSequenceNumber(id: AID, sequenceNumber: number): Promise<E[]>;
10
+ getLatestSnapshotById(id: AID): Promise<A | undefined>;
11
+ }
12
+ export { EventStoreForMemory };
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.EventStoreForMemory = void 0;
13
+ class EventStoreForMemory {
14
+ constructor(events, snapshots) {
15
+ this.events = new Map(Array.from(events).map(([key, values]) => {
16
+ return [key.asString, values];
17
+ }));
18
+ this.snapshots = new Map(Array.from(snapshots).map(([key, value]) => {
19
+ return [key.asString, value];
20
+ }));
21
+ }
22
+ persistEvent(event, version) {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ if (event.isCreated) {
25
+ throw new Error("event is created");
26
+ }
27
+ const aggregateIdString = event.aggregateId.asString;
28
+ const snapshot = this.snapshots.get(aggregateIdString);
29
+ if (snapshot === undefined) {
30
+ throw new Error("snapshot is undefined");
31
+ }
32
+ if (snapshot.id.asString !== event.aggregateId.asString) {
33
+ throw new Error("aggregateId mismatch: snapshot.id = " +
34
+ snapshot.id.asString +
35
+ ", event.aggregateId = " +
36
+ event.aggregateId.asString);
37
+ }
38
+ if (snapshot.version !== version) {
39
+ throw new Error("version mismatch");
40
+ }
41
+ const events = this.events.get(aggregateIdString);
42
+ if (events === undefined) {
43
+ throw new Error("events is undefined");
44
+ }
45
+ events.push(event);
46
+ this.events.set(aggregateIdString, events);
47
+ const newVersion = snapshot.version + 1;
48
+ const newSnapshot = snapshot.withVersion(newVersion);
49
+ this.snapshots.set(aggregateIdString, newSnapshot);
50
+ });
51
+ }
52
+ persistEventAndSnapshot(event, aggregate) {
53
+ var _a, _b;
54
+ return __awaiter(this, void 0, void 0, function* () {
55
+ if (event.aggregateId.asString !== aggregate.id.asString) {
56
+ throw new Error(`aggregateId mismatch: expected ${event.aggregateId.asString}, got ${aggregate.id.asString}`);
57
+ }
58
+ const aggregateIdString = event.aggregateId.asString;
59
+ const events = (_a = this.events.get(aggregateIdString)) !== null && _a !== void 0 ? _a : [];
60
+ const snapshot = (_b = this.snapshots.get(aggregateIdString)) !== null && _b !== void 0 ? _b : aggregate;
61
+ let newVersion = 1;
62
+ if (!event.isCreated) {
63
+ const version = snapshot.version;
64
+ if (version !== aggregate.version) {
65
+ throw new Error("version mismatch");
66
+ }
67
+ newVersion = snapshot.version + 1;
68
+ }
69
+ events.push(event);
70
+ this.events.set(aggregateIdString, events);
71
+ const newSnapshot = snapshot.withVersion(newVersion);
72
+ this.snapshots.set(aggregateIdString, newSnapshot);
73
+ });
74
+ }
75
+ getEventsByIdSinceSequenceNumber(id, sequenceNumber) {
76
+ return __awaiter(this, void 0, void 0, function* () {
77
+ const aggregateIdString = id.asString;
78
+ const events = this.events.get(aggregateIdString);
79
+ if (events === undefined) {
80
+ throw new Error("events is undefined");
81
+ }
82
+ return events.filter((event) => event.sequenceNumber >= sequenceNumber);
83
+ });
84
+ }
85
+ getLatestSnapshotById(id) {
86
+ return __awaiter(this, void 0, void 0, function* () {
87
+ const aggregateIdString = id.asString;
88
+ return this.snapshots.get(aggregateIdString);
89
+ });
90
+ }
91
+ }
92
+ exports.EventStoreForMemory = EventStoreForMemory;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "event-store-adapter-js",
3
- "version": "1.1.99-snapshot.1",
3
+ "version": "2.0.0",
4
4
  "description": "This library is designed to turn DynamoDB into an Event Store for Event Sourcing.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,12 @@
1
+ import { Aggregate, AggregateId, Event } from "./types";
2
+ import { EventStoreOptions } from "./event-store-options";
3
+ import { EventStore } from "./event-store";
4
+
5
+ interface EventStoreWithOptions<
6
+ AID extends AggregateId,
7
+ A extends Aggregate<A, AID>,
8
+ E extends Event<AID>,
9
+ > extends EventStore<AID, A, E>,
10
+ EventStoreOptions<EventStoreWithOptions<AID, A, E>, AID, A, E> {}
11
+
12
+ export { EventStoreWithOptions };
@@ -6,7 +6,6 @@ import {
6
6
  KeyResolver,
7
7
  SnapshotSerializer,
8
8
  } from "./types";
9
- import { EventStoreOptions } from "./event-store-options";
10
9
  import { EventStoreForDynamoDB } from "./internal/event-store-for-dynamodb";
11
10
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
12
11
  import moment from "moment";
@@ -15,23 +14,21 @@ import {
15
14
  JsonEventSerializer,
16
15
  JsonSnapshotSerializer,
17
16
  } from "./internal/default-serializer";
17
+ import { EventStoreForMemory } from "./internal/event-store-for-memory";
18
+ import { EventStoreWithOptions } from "./event-store-with-options";
18
19
 
19
20
  interface EventStore<
20
21
  AID extends AggregateId,
21
22
  A extends Aggregate<A, AID>,
22
23
  E extends Event<AID>,
23
- > extends EventStoreOptions<EventStore<AID, A, E>, AID, A, E> {
24
+ > {
24
25
  persistEvent(event: E, version: number): Promise<void>;
25
26
  persistEventAndSnapshot(event: E, aggregate: A): Promise<void>;
26
27
  getEventsByIdSinceSequenceNumber(
27
28
  id: AID,
28
29
  sequenceNumber: number,
29
- converter: (json: string) => E,
30
30
  ): Promise<E[]>;
31
- getLatestSnapshotById(
32
- id: AID,
33
- converter: (json: string) => A,
34
- ): Promise<A | undefined>;
31
+ getLatestSnapshotById(id: AID): Promise<A | undefined>;
35
32
  }
36
33
 
37
34
  class EventStoreFactory {
@@ -46,6 +43,8 @@ class EventStoreFactory {
46
43
  journalAidIndexName: string,
47
44
  snapshotAidIndexName: string,
48
45
  shardCount: number,
46
+ eventConverter: (json: string) => E,
47
+ snapshotConverter: (json: string) => A,
49
48
  keepSnapshotCount: number | undefined = undefined,
50
49
  deleteTtl: moment.Duration | undefined = undefined,
51
50
  keyResolver: KeyResolver<AID> = new DefaultKeyResolver(),
@@ -57,7 +56,7 @@ class EventStoreFactory {
57
56
  AID,
58
57
  A
59
58
  >(),
60
- ) {
59
+ ): EventStoreWithOptions<AID, A, E> {
61
60
  return new EventStoreForDynamoDB<AID, A, E>(
62
61
  dynamodbClient,
63
62
  journalTableName,
@@ -65,6 +64,8 @@ class EventStoreFactory {
65
64
  journalAidIndexName,
66
65
  snapshotAidIndexName,
67
66
  shardCount,
67
+ eventConverter,
68
+ snapshotConverter,
68
69
  keepSnapshotCount,
69
70
  deleteTtl,
70
71
  keyResolver,
@@ -72,6 +73,14 @@ class EventStoreFactory {
72
73
  snapshotSerializer,
73
74
  );
74
75
  }
76
+
77
+ static ofMemory<
78
+ AID extends AggregateId,
79
+ A extends Aggregate<A, AID>,
80
+ E extends Event<AID>,
81
+ >(events: Map<AID, E[]>, snapshots: Map<AID, A>): EventStore<AID, A, E> {
82
+ return new EventStoreForMemory(events, snapshots);
83
+ }
75
84
  }
76
85
 
77
86
  export { EventStore, EventStoreFactory };
@@ -10,7 +10,10 @@ import { EventStoreForDynamoDB } from "./event-store-for-dynamodb";
10
10
  import { ulid } from "ulid";
11
11
  import { UserAccountId } from "./test/user-account-id";
12
12
  import { convertJSONToUserAccount, UserAccount } from "./test/user-account";
13
- import { UserAccountEvent } from "./test/user-account-event";
13
+ import {
14
+ convertJSONtoUserAccountEvent,
15
+ UserAccountEvent,
16
+ } from "./test/user-account-event";
14
17
  import {
15
18
  createDynamoDBClient,
16
19
  createJournalTable,
@@ -52,6 +55,8 @@ describe("EventStoreForDynamoDB", () => {
52
55
  JOURNAL_AID_INDEX_NAME,
53
56
  SNAPSHOTS_AID_INDEX_NAME,
54
57
  32,
58
+ convertJSONtoUserAccountEvent,
59
+ convertJSONToUserAccount,
55
60
  );
56
61
  }
57
62
 
@@ -96,10 +101,7 @@ describe("EventStoreForDynamoDB", () => {
96
101
 
97
102
  await eventStore.persistEventAndSnapshot(created, userAccount1);
98
103
 
99
- const userAccount2 = await eventStore.getLatestSnapshotById(
100
- id,
101
- convertJSONToUserAccount,
102
- );
104
+ const userAccount2 = await eventStore.getLatestSnapshotById(id);
103
105
  if (userAccount2 === undefined) {
104
106
  throw new Error("userAccount2 is undefined");
105
107
  }
@@ -123,17 +125,23 @@ describe("EventStoreForDynamoDB", () => {
123
125
 
124
126
  await eventStore.persistEvent(renamed, userAccount2.version);
125
127
 
126
- const userAccount3 = await eventStore.getLatestSnapshotById(
127
- id,
128
- convertJSONToUserAccount,
129
- );
130
- if (userAccount3 === undefined) {
131
- throw new Error("userAccount2 is undefined");
128
+ const latestSnapshot = await eventStore.getLatestSnapshotById(id);
129
+ if (latestSnapshot === undefined) {
130
+ throw new Error("latestSnapshot is undefined");
132
131
  }
132
+ const eventsAfterSnapshot =
133
+ await eventStore.getEventsByIdSinceSequenceNumber(
134
+ id,
135
+ latestSnapshot.sequenceNumber + 1,
136
+ );
137
+ const userAccount3 = UserAccount.replay(
138
+ eventsAfterSnapshot,
139
+ latestSnapshot,
140
+ );
133
141
 
134
142
  expect(userAccount3.id).toEqual(id);
135
- expect(userAccount3.name).toEqual(name);
136
- expect(userAccount3.sequenceNumber).toEqual(1);
143
+ expect(userAccount3.name).toEqual("Bob");
144
+ expect(userAccount3.sequenceNumber).toEqual(2);
137
145
  expect(userAccount3.version).toEqual(2);
138
146
  },
139
147
  TIMEOUT,
@@ -7,7 +7,6 @@ import {
7
7
  Logger,
8
8
  SnapshotSerializer,
9
9
  } from "../types";
10
- import { EventStore } from "../event-store";
11
10
  import {
12
11
  BatchWriteItemCommand,
13
12
  DynamoDBClient,
@@ -27,12 +26,13 @@ import {
27
26
  JsonEventSerializer,
28
27
  JsonSnapshotSerializer,
29
28
  } from "./default-serializer";
29
+ import { EventStoreWithOptions } from "../event-store-with-options";
30
30
 
31
31
  class EventStoreForDynamoDB<
32
32
  AID extends AggregateId,
33
33
  A extends Aggregate<A, AID>,
34
34
  E extends Event<AID>,
35
- > implements EventStore<AID, A, E>
35
+ > implements EventStoreWithOptions<AID, A, E>
36
36
  {
37
37
  constructor(
38
38
  private dynamodbClient: DynamoDBClient,
@@ -41,6 +41,8 @@ class EventStoreForDynamoDB<
41
41
  private journalAidIndexName: string,
42
42
  private snapshotAidIndexName: string,
43
43
  private shardCount: number,
44
+ private eventConverter: (json: string) => E,
45
+ private snapshotConverter: (json: string) => A,
44
46
  private keepSnapshotCount: number | undefined = undefined,
45
47
  private deleteTtl: moment.Duration | undefined = undefined,
46
48
  private keyResolver: KeyResolver<AID> = new DefaultKeyResolver(),
@@ -58,7 +60,7 @@ class EventStoreForDynamoDB<
58
60
  async getEventsByIdSinceSequenceNumber(
59
61
  id: AID,
60
62
  sequenceNumber: number,
61
- converter: (json: string) => E,
63
+ // converter: (json: string) => E,
62
64
  ): Promise<E[]> {
63
65
  this.logger?.debug(
64
66
  `getEventsByIdSinceSequenceNumber(${JSON.stringify(
@@ -90,7 +92,7 @@ class EventStoreForDynamoDB<
90
92
  if (payload === undefined) {
91
93
  throw new Error("Payload is undefined");
92
94
  }
93
- return this.eventSerializer.deserialize(payload, converter);
95
+ return this.eventSerializer.deserialize(payload, this.eventConverter);
94
96
  });
95
97
  }
96
98
  this.logger?.debug(
@@ -103,7 +105,7 @@ class EventStoreForDynamoDB<
103
105
 
104
106
  async getLatestSnapshotById(
105
107
  id: AID,
106
- converter: (json: string) => A,
108
+ // converter: (json: string) => A,
107
109
  ): Promise<A | undefined> {
108
110
  this.logger?.debug(
109
111
  `getLatestSnapshotById(${JSON.stringify(id)}, ...): start`,
@@ -137,7 +139,10 @@ class EventStoreForDynamoDB<
137
139
  if (payload === undefined) {
138
140
  throw new Error("Payload is undefined");
139
141
  }
140
- const result = this.snapshotSerializer.deserialize(payload, converter);
142
+ const result = this.snapshotSerializer.deserialize(
143
+ payload,
144
+ this.snapshotConverter,
145
+ );
141
146
  this.logger?.debug(
142
147
  `getLatestSnapshotById(${JSON.stringify(id)}, ...): finished`,
143
148
  );
@@ -160,6 +165,11 @@ class EventStoreForDynamoDB<
160
165
  }
161
166
 
162
167
  async persistEventAndSnapshot(event: E, aggregate: A): Promise<void> {
168
+ if (event.aggregateId.asString !== aggregate.id.asString) {
169
+ throw new Error(
170
+ `aggregateId mismatch: expected ${event.aggregateId.asString}, got ${aggregate.id.asString}`,
171
+ );
172
+ }
163
173
  this.logger?.debug(
164
174
  `persistEventAndSnapshot(${JSON.stringify(event)}, ${JSON.stringify(
165
175
  aggregate,
@@ -182,7 +192,7 @@ class EventStoreForDynamoDB<
182
192
  );
183
193
  }
184
194
 
185
- withDeleteTtl(deleteTtl: moment.Duration): EventStore<AID, A, E> {
195
+ withDeleteTtl(deleteTtl: moment.Duration): EventStoreWithOptions<AID, A, E> {
186
196
  return new EventStoreForDynamoDB(
187
197
  this.dynamodbClient,
188
198
  this.journalTableName,
@@ -190,6 +200,8 @@ class EventStoreForDynamoDB<
190
200
  this.journalAidIndexName,
191
201
  this.snapshotAidIndexName,
192
202
  this.shardCount,
203
+ this.eventConverter,
204
+ this.snapshotConverter,
193
205
  this.keepSnapshotCount,
194
206
  deleteTtl,
195
207
  this.keyResolver,
@@ -200,7 +212,7 @@ class EventStoreForDynamoDB<
200
212
 
201
213
  withEventSerializer(
202
214
  eventSerializer: EventSerializer<AID, E>,
203
- ): EventStore<AID, A, E> {
215
+ ): EventStoreWithOptions<AID, A, E> {
204
216
  return new EventStoreForDynamoDB(
205
217
  this.dynamodbClient,
206
218
  this.journalTableName,
@@ -208,6 +220,8 @@ class EventStoreForDynamoDB<
208
220
  this.journalAidIndexName,
209
221
  this.snapshotAidIndexName,
210
222
  this.shardCount,
223
+ this.eventConverter,
224
+ this.snapshotConverter,
211
225
  this.keepSnapshotCount,
212
226
  this.deleteTtl,
213
227
  this.keyResolver,
@@ -216,7 +230,9 @@ class EventStoreForDynamoDB<
216
230
  );
217
231
  }
218
232
 
219
- withKeepSnapshotCount(keepSnapshotCount: number): EventStore<AID, A, E> {
233
+ withKeepSnapshotCount(
234
+ keepSnapshotCount: number,
235
+ ): EventStoreWithOptions<AID, A, E> {
220
236
  return new EventStoreForDynamoDB(
221
237
  this.dynamodbClient,
222
238
  this.journalTableName,
@@ -224,6 +240,8 @@ class EventStoreForDynamoDB<
224
240
  this.journalAidIndexName,
225
241
  this.snapshotAidIndexName,
226
242
  this.shardCount,
243
+ this.eventConverter,
244
+ this.snapshotConverter,
227
245
  keepSnapshotCount,
228
246
  this.deleteTtl,
229
247
  this.keyResolver,
@@ -232,7 +250,9 @@ class EventStoreForDynamoDB<
232
250
  );
233
251
  }
234
252
 
235
- withKeyResolver(keyResolver: KeyResolver<AID>): EventStore<AID, A, E> {
253
+ withKeyResolver(
254
+ keyResolver: KeyResolver<AID>,
255
+ ): EventStoreWithOptions<AID, A, E> {
236
256
  return new EventStoreForDynamoDB(
237
257
  this.dynamodbClient,
238
258
  this.journalTableName,
@@ -240,6 +260,8 @@ class EventStoreForDynamoDB<
240
260
  this.journalAidIndexName,
241
261
  this.snapshotAidIndexName,
242
262
  this.shardCount,
263
+ this.eventConverter,
264
+ this.snapshotConverter,
243
265
  this.keepSnapshotCount,
244
266
  this.deleteTtl,
245
267
  keyResolver,
@@ -250,7 +272,7 @@ class EventStoreForDynamoDB<
250
272
 
251
273
  withSnapshotSerializer(
252
274
  snapshotSerializer: SnapshotSerializer<AID, A>,
253
- ): EventStore<AID, A, E> {
275
+ ): EventStoreWithOptions<AID, A, E> {
254
276
  return new EventStoreForDynamoDB(
255
277
  this.dynamodbClient,
256
278
  this.journalTableName,
@@ -258,6 +280,8 @@ class EventStoreForDynamoDB<
258
280
  this.journalAidIndexName,
259
281
  this.snapshotAidIndexName,
260
282
  this.shardCount,
283
+ this.eventConverter,
284
+ this.snapshotConverter,
261
285
  this.keepSnapshotCount,
262
286
  this.deleteTtl,
263
287
  this.keyResolver,
@@ -571,7 +595,9 @@ class EventStoreForDynamoDB<
571
595
  ":ttl": { N: ttl.seconds().toString() },
572
596
  },
573
597
  };
574
- return this.dynamodbClient.send(new UpdateItemCommand(request));
598
+ return this.dynamodbClient
599
+ .send(new UpdateItemCommand(request))
600
+ .then((_) => {});
575
601
  });
576
602
  return await Promise.all(result);
577
603
  }
@@ -592,7 +618,7 @@ class EventStoreForDynamoDB<
592
618
  if (keys === undefined) {
593
619
  return undefined;
594
620
  }
595
- const result = keys.map((key) => {
621
+ const request = keys.map((key) => {
596
622
  const request: WriteRequest = {
597
623
  DeleteRequest: {
598
624
  Key: {
@@ -601,15 +627,17 @@ class EventStoreForDynamoDB<
601
627
  },
602
628
  },
603
629
  };
604
- return this.dynamodbClient.send(
630
+ return request;
631
+ });
632
+ return this.dynamodbClient
633
+ .send(
605
634
  new BatchWriteItemCommand({
606
635
  RequestItems: {
607
- [this.snapshotTableName]: [request],
636
+ [this.snapshotTableName]: request,
608
637
  },
609
638
  }),
610
- );
611
- });
612
- return await Promise.all(result);
639
+ )
640
+ .then((_) => {});
613
641
  }
614
642
  }
615
643
  return undefined;
@@ -0,0 +1,89 @@
1
+ import { describe } from "node:test";
2
+ import { UserAccountId } from "./test/user-account-id";
3
+ // import {ulid} from "ulid";
4
+ import { UserAccount } from "./test/user-account";
5
+ import { UserAccountEvent } from "./test/user-account-event";
6
+ import { EventStore } from "../event-store";
7
+ import { EventStoreForMemory } from "./event-store-for-memory";
8
+
9
+ afterEach(() => {
10
+ jest.useRealTimers();
11
+ });
12
+
13
+ describe("EventStoreForDynamoDB", () => {
14
+ const TEST_TIME_FACTOR = parseFloat(process.env.TEST_TIME_FACTOR ?? "1.0");
15
+ const TIMEOUT: number = 10 * 1000 * TEST_TIME_FACTOR;
16
+
17
+ let eventStore: EventStore<UserAccountId, UserAccount, UserAccountEvent>;
18
+
19
+ function createEventStore(): EventStore<
20
+ UserAccountId,
21
+ UserAccount,
22
+ UserAccountEvent
23
+ > {
24
+ return new EventStoreForMemory<
25
+ UserAccountId,
26
+ UserAccount,
27
+ UserAccountEvent
28
+ >(new Map(), new Map());
29
+ }
30
+
31
+ beforeAll(async () => {
32
+ eventStore = createEventStore();
33
+ });
34
+
35
+ test(
36
+ "persistAndSnapshot",
37
+ async () => {
38
+ const id = new UserAccountId("1");
39
+ const name = "Alice";
40
+ const [userAccount1, created] = UserAccount.create(id, name);
41
+
42
+ await eventStore.persistEventAndSnapshot(created, userAccount1);
43
+
44
+ const userAccount2 = await eventStore.getLatestSnapshotById(id);
45
+ if (userAccount2 === undefined) {
46
+ throw new Error("userAccount2 is undefined");
47
+ }
48
+ expect(userAccount2.id).toEqual(id);
49
+ expect(userAccount2.name).toEqual(name);
50
+ expect(userAccount2.version).toEqual(1);
51
+ },
52
+ TIMEOUT,
53
+ );
54
+
55
+ test(
56
+ "persistAndSnapshot2",
57
+ async () => {
58
+ const id = new UserAccountId("2");
59
+ const name = "Alice";
60
+ const [userAccount1, created] = UserAccount.create(id, name);
61
+
62
+ await eventStore.persistEventAndSnapshot(created, userAccount1);
63
+
64
+ const [userAccount2, renamed] = userAccount1.rename("Bob");
65
+
66
+ await eventStore.persistEvent(renamed, userAccount2.version);
67
+
68
+ const latestSnapshot = await eventStore.getLatestSnapshotById(id);
69
+ if (latestSnapshot === undefined) {
70
+ throw new Error("latestSnapshot is undefined");
71
+ }
72
+ const eventsAfterSnapshot =
73
+ await eventStore.getEventsByIdSinceSequenceNumber(
74
+ id,
75
+ latestSnapshot.sequenceNumber + 1,
76
+ );
77
+ const userAccount3 = UserAccount.replay(
78
+ eventsAfterSnapshot,
79
+ latestSnapshot,
80
+ );
81
+
82
+ expect(userAccount3.id).toEqual(id);
83
+ expect(userAccount3.name).toEqual("Bob");
84
+ expect(userAccount3.sequenceNumber).toEqual(2);
85
+ expect(userAccount3.version).toEqual(2);
86
+ },
87
+ TIMEOUT,
88
+ );
89
+ });
@@ -0,0 +1,99 @@
1
+ import { Aggregate, AggregateId, Event } from "../types";
2
+ import { EventStore } from "../event-store";
3
+
4
+ class EventStoreForMemory<
5
+ AID extends AggregateId,
6
+ A extends Aggregate<A, AID>,
7
+ E extends Event<AID>,
8
+ > implements EventStore<AID, A, E>
9
+ {
10
+ private readonly events: Map<string, E[]>;
11
+ private readonly snapshots: Map<string, A>;
12
+
13
+ constructor(events: Map<AID, E[]>, snapshots: Map<AID, A>) {
14
+ this.events = new Map(
15
+ Array.from(events).map(([key, values]) => {
16
+ return [key.asString, values];
17
+ }),
18
+ );
19
+ this.snapshots = new Map(
20
+ Array.from(snapshots).map(([key, value]) => {
21
+ return [key.asString, value];
22
+ }),
23
+ );
24
+ }
25
+
26
+ async persistEvent(event: E, version: number): Promise<void> {
27
+ if (event.isCreated) {
28
+ throw new Error("event is created");
29
+ }
30
+ const aggregateIdString = event.aggregateId.asString;
31
+ const snapshot = this.snapshots.get(aggregateIdString);
32
+ if (snapshot === undefined) {
33
+ throw new Error("snapshot is undefined");
34
+ }
35
+ if (snapshot.id.asString !== event.aggregateId.asString) {
36
+ throw new Error(
37
+ "aggregateId mismatch: snapshot.id = " +
38
+ snapshot.id.asString +
39
+ ", event.aggregateId = " +
40
+ event.aggregateId.asString,
41
+ );
42
+ }
43
+ if (snapshot.version !== version) {
44
+ throw new Error("version mismatch");
45
+ }
46
+ const events = this.events.get(aggregateIdString);
47
+ if (events === undefined) {
48
+ throw new Error("events is undefined");
49
+ }
50
+ events.push(event);
51
+ this.events.set(aggregateIdString, events);
52
+ const newVersion = snapshot.version + 1;
53
+ const newSnapshot = snapshot.withVersion(newVersion);
54
+ this.snapshots.set(aggregateIdString, newSnapshot);
55
+ }
56
+
57
+ async persistEventAndSnapshot(event: E, aggregate: A): Promise<void> {
58
+ if (event.aggregateId.asString !== aggregate.id.asString) {
59
+ throw new Error(
60
+ `aggregateId mismatch: expected ${event.aggregateId.asString}, got ${aggregate.id.asString}`,
61
+ );
62
+ }
63
+ const aggregateIdString = event.aggregateId.asString;
64
+ const events = this.events.get(aggregateIdString) ?? [];
65
+ const snapshot = this.snapshots.get(aggregateIdString) ?? aggregate;
66
+
67
+ let newVersion = 1;
68
+ if (!event.isCreated) {
69
+ const version = snapshot.version;
70
+ if (version !== aggregate.version) {
71
+ throw new Error("version mismatch");
72
+ }
73
+ newVersion = snapshot.version + 1;
74
+ }
75
+ events.push(event);
76
+ this.events.set(aggregateIdString, events);
77
+ const newSnapshot = snapshot.withVersion(newVersion);
78
+ this.snapshots.set(aggregateIdString, newSnapshot);
79
+ }
80
+
81
+ async getEventsByIdSinceSequenceNumber(
82
+ id: AID,
83
+ sequenceNumber: number,
84
+ ): Promise<E[]> {
85
+ const aggregateIdString = id.asString;
86
+ const events = this.events.get(aggregateIdString);
87
+ if (events === undefined) {
88
+ throw new Error("events is undefined");
89
+ }
90
+ return events.filter((event) => event.sequenceNumber >= sequenceNumber);
91
+ }
92
+
93
+ async getLatestSnapshotById(id: AID): Promise<A | undefined> {
94
+ const aggregateIdString = id.asString;
95
+ return this.snapshots.get(aggregateIdString);
96
+ }
97
+ }
98
+
99
+ export { EventStoreForMemory };
@@ -6,8 +6,11 @@ import {
6
6
  Wait,
7
7
  } from "testcontainers";
8
8
  import { UserAccountId } from "./user-account-id";
9
- import { UserAccount } from "./user-account";
10
- import { UserAccountEvent } from "./user-account-event";
9
+ import { convertJSONToUserAccount, UserAccount } from "./user-account";
10
+ import {
11
+ convertJSONtoUserAccountEvent,
12
+ UserAccountEvent,
13
+ } from "./user-account-event";
11
14
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
12
15
  import {
13
16
  createDynamoDBClient,
@@ -49,6 +52,8 @@ describe("UserAccountRepository", () => {
49
52
  JOURNAL_AID_INDEX_NAME,
50
53
  SNAPSHOTS_AID_INDEX_NAME,
51
54
  32,
55
+ convertJSONtoUserAccountEvent,
56
+ convertJSONToUserAccount,
52
57
  );
53
58
  }
54
59
 
@@ -1,9 +1,6 @@
1
- import {
2
- UserAccountEvent,
3
- convertJSONtoUserAccountEvent,
4
- } from "./user-account-event";
1
+ import { UserAccountEvent } from "./user-account-event";
5
2
  import { UserAccountId } from "./user-account-id";
6
- import { convertJSONToUserAccount, UserAccount } from "./user-account";
3
+ import { UserAccount } from "./user-account";
7
4
  import { EventStore } from "../../event-store";
8
5
 
9
6
  class UserAccountRepository {
@@ -24,17 +21,13 @@ class UserAccountRepository {
24
21
  }
25
22
 
26
23
  async findById(id: UserAccountId): Promise<UserAccount | undefined> {
27
- const snapshot = await this.eventStore.getLatestSnapshotById(
28
- id,
29
- convertJSONToUserAccount,
30
- );
24
+ const snapshot = await this.eventStore.getLatestSnapshotById(id);
31
25
  if (snapshot === undefined) {
32
26
  return undefined;
33
27
  } else {
34
28
  const events = await this.eventStore.getEventsByIdSinceSequenceNumber(
35
29
  id,
36
30
  snapshot.sequenceNumber + 1,
37
- convertJSONtoUserAccountEvent,
38
31
  );
39
32
  return UserAccount.replay(events, snapshot);
40
33
  }