event-store-adapter-js 1.0.9 → 1.0.10-snapshot.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Aggregate, AggregateId, Event, EventSerializer, KeyResolver, Logger, SnapshotSerializer } from "../types";
|
|
2
2
|
import { EventStore } from "../event-store";
|
|
3
3
|
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
4
|
-
import
|
|
4
|
+
import moment from "moment/moment";
|
|
5
5
|
declare class EventStoreForDynamoDB<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>> implements EventStore<AID, A, E> {
|
|
6
6
|
private dynamodbClient;
|
|
7
7
|
private journalTableName;
|
|
@@ -30,5 +30,10 @@ declare class EventStoreForDynamoDB<AID extends AggregateId, A extends Aggregate
|
|
|
30
30
|
private putJournal;
|
|
31
31
|
private putSnapshot;
|
|
32
32
|
private updateSnapshot;
|
|
33
|
+
private tryPurgeExcessSnapshots;
|
|
34
|
+
private getSnapshotCount;
|
|
35
|
+
private getLastSnapshotKeys;
|
|
36
|
+
private updateTtlOfExcessSnapshots;
|
|
37
|
+
private deleteExcessSnapshots;
|
|
33
38
|
}
|
|
34
39
|
export { EventStoreForDynamoDB };
|
|
@@ -8,9 +8,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
11
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
15
|
exports.EventStoreForDynamoDB = void 0;
|
|
13
16
|
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
17
|
+
const moment_1 = __importDefault(require("moment/moment"));
|
|
14
18
|
const default_key_resolver_1 = require("./default-key-resolver");
|
|
15
19
|
const default_serializer_1 = require("./default-serializer");
|
|
16
20
|
class EventStoreForDynamoDB {
|
|
@@ -109,21 +113,22 @@ class EventStoreForDynamoDB {
|
|
|
109
113
|
throw new Error("Cannot persist created event");
|
|
110
114
|
}
|
|
111
115
|
yield this.updateEventAndSnapshotOpt(event, version, undefined);
|
|
116
|
+
yield this.tryPurgeExcessSnapshots(event);
|
|
112
117
|
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug(`persistEvent(${JSON.stringify(event)}, ${version}): finished`);
|
|
113
118
|
});
|
|
114
119
|
}
|
|
115
120
|
persistEventAndSnapshot(event, aggregate) {
|
|
116
|
-
var _a, _b
|
|
121
|
+
var _a, _b;
|
|
117
122
|
return __awaiter(this, void 0, void 0, function* () {
|
|
118
123
|
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug(`persistEventAndSnapshot(${JSON.stringify(event)}, ${JSON.stringify(aggregate)}): start`);
|
|
119
124
|
if (event.isCreated) {
|
|
120
125
|
yield this.createEventAndSnapshot(event, aggregate);
|
|
121
126
|
}
|
|
122
127
|
else {
|
|
123
|
-
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug("update!!!");
|
|
124
128
|
yield this.updateEventAndSnapshotOpt(event, aggregate.sequenceNumber, aggregate);
|
|
129
|
+
yield this.tryPurgeExcessSnapshots(event);
|
|
125
130
|
}
|
|
126
|
-
(
|
|
131
|
+
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug(`persistEventAndSnapshot(${JSON.stringify(event)}, ${JSON.stringify(aggregate)}): finished`);
|
|
127
132
|
});
|
|
128
133
|
}
|
|
129
134
|
withDeleteTtl(deleteTtl) {
|
|
@@ -274,5 +279,148 @@ class EventStoreForDynamoDB {
|
|
|
274
279
|
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug(`updateSnapshot(${JSON.stringify(event)}, ${sequenceNumber}, ${version}, ${JSON.stringify(aggregate)}): finished`);
|
|
275
280
|
return result;
|
|
276
281
|
}
|
|
282
|
+
tryPurgeExcessSnapshots(event) {
|
|
283
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
284
|
+
if (this.keepSnapshotCount !== undefined) {
|
|
285
|
+
if (this.deleteTtl !== undefined) {
|
|
286
|
+
yield this.updateTtlOfExcessSnapshots(event.aggregateId);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
yield this.deleteExcessSnapshots(event.aggregateId);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
getSnapshotCount(aggregateId) {
|
|
295
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
296
|
+
const request = {
|
|
297
|
+
TableName: this.snapshotTableName,
|
|
298
|
+
IndexName: this.snapshotAidIndexName,
|
|
299
|
+
KeyConditionExpression: "#aid = :aid",
|
|
300
|
+
ExpressionAttributeNames: {
|
|
301
|
+
"#aid": "aid",
|
|
302
|
+
},
|
|
303
|
+
ExpressionAttributeValues: {
|
|
304
|
+
":aid": { S: aggregateId.asString },
|
|
305
|
+
},
|
|
306
|
+
Select: "COUNT",
|
|
307
|
+
};
|
|
308
|
+
const queryResult = yield this.dynamodbClient.send(new client_dynamodb_1.QueryCommand(request));
|
|
309
|
+
return queryResult.Count;
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
getLastSnapshotKeys(aggregateId, limit) {
|
|
313
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
314
|
+
const names = {
|
|
315
|
+
"#aid": "aid",
|
|
316
|
+
"#seq_nr": "seq_nr",
|
|
317
|
+
};
|
|
318
|
+
const values = {
|
|
319
|
+
":aid": { S: aggregateId.asString },
|
|
320
|
+
":seq_nr": { N: "0" },
|
|
321
|
+
};
|
|
322
|
+
const request = {
|
|
323
|
+
TableName: this.snapshotTableName,
|
|
324
|
+
IndexName: this.snapshotAidIndexName,
|
|
325
|
+
KeyConditionExpression: "#aid = :aid AND #seq_nr > :seq_nr",
|
|
326
|
+
ExpressionAttributeNames: Object.assign({}, names),
|
|
327
|
+
ExpressionAttributeValues: Object.assign({}, values),
|
|
328
|
+
ScanIndexForward: false,
|
|
329
|
+
Limit: limit,
|
|
330
|
+
};
|
|
331
|
+
if (this.deleteTtl !== undefined) {
|
|
332
|
+
request.FilterExpression = "#ttl = :ttl";
|
|
333
|
+
request.ExpressionAttributeNames = Object.assign(Object.assign({}, names), { "#ttl": "ttl" });
|
|
334
|
+
request.ExpressionAttributeValues = Object.assign(Object.assign({}, values), { ":ttl": { N: "0" } });
|
|
335
|
+
}
|
|
336
|
+
const queryResult = yield this.dynamodbClient.send(new client_dynamodb_1.QueryCommand(request));
|
|
337
|
+
if (queryResult.Items === undefined || queryResult.Items.length === 0) {
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
return queryResult.Items.map((item) => {
|
|
342
|
+
const pkey = item.pkey.S;
|
|
343
|
+
const skey = item.skey.S;
|
|
344
|
+
if (pkey === undefined || skey === undefined) {
|
|
345
|
+
throw new Error("pkey or skey is undefined");
|
|
346
|
+
}
|
|
347
|
+
return { pkey, skey };
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
updateTtlOfExcessSnapshots(aggregateId) {
|
|
353
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
354
|
+
if (this.keepSnapshotCount !== undefined && this.deleteTtl !== undefined) {
|
|
355
|
+
let snapshotCount = yield this.getSnapshotCount(aggregateId);
|
|
356
|
+
if (snapshotCount === undefined) {
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
snapshotCount -= 1;
|
|
360
|
+
const excessCount = snapshotCount - this.keepSnapshotCount;
|
|
361
|
+
if (excessCount > 0) {
|
|
362
|
+
const keys = yield this.getLastSnapshotKeys(aggregateId, excessCount);
|
|
363
|
+
if (keys === undefined) {
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
const ttl = (0, moment_1.default)().add(this.deleteTtl);
|
|
367
|
+
const result = keys.map((key) => {
|
|
368
|
+
const request = {
|
|
369
|
+
TableName: this.snapshotTableName,
|
|
370
|
+
Key: {
|
|
371
|
+
pkey: { S: key.pkey },
|
|
372
|
+
skey: { S: key.skey },
|
|
373
|
+
},
|
|
374
|
+
UpdateExpression: "SET #ttl = :ttl",
|
|
375
|
+
ExpressionAttributeNames: {
|
|
376
|
+
"#ttl": "ttl",
|
|
377
|
+
},
|
|
378
|
+
ExpressionAttributeValues: {
|
|
379
|
+
":ttl": { N: ttl.seconds().toString() },
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
return this.dynamodbClient.send(new client_dynamodb_1.UpdateItemCommand(request));
|
|
383
|
+
});
|
|
384
|
+
return yield Promise.all(result);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return undefined;
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
deleteExcessSnapshots(aggregateId) {
|
|
391
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
392
|
+
if (this.keepSnapshotCount !== undefined && this.deleteTtl !== undefined) {
|
|
393
|
+
let snapshotCount = yield this.getSnapshotCount(aggregateId);
|
|
394
|
+
if (snapshotCount === undefined) {
|
|
395
|
+
return undefined;
|
|
396
|
+
}
|
|
397
|
+
snapshotCount -= 1;
|
|
398
|
+
const excessCount = snapshotCount - this.keepSnapshotCount;
|
|
399
|
+
if (excessCount > 0) {
|
|
400
|
+
const keys = yield this.getLastSnapshotKeys(aggregateId, excessCount);
|
|
401
|
+
if (keys === undefined) {
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
const result = keys.map((key) => {
|
|
405
|
+
const request = {
|
|
406
|
+
DeleteRequest: {
|
|
407
|
+
Key: {
|
|
408
|
+
pkey: { S: key.pkey },
|
|
409
|
+
skey: { S: key.skey },
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
return this.dynamodbClient.send(new client_dynamodb_1.BatchWriteItemCommand({
|
|
414
|
+
RequestItems: {
|
|
415
|
+
[this.snapshotTableName]: [request],
|
|
416
|
+
},
|
|
417
|
+
}));
|
|
418
|
+
});
|
|
419
|
+
return yield Promise.all(result);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return undefined;
|
|
423
|
+
});
|
|
424
|
+
}
|
|
277
425
|
}
|
|
278
426
|
exports.EventStoreForDynamoDB = EventStoreForDynamoDB;
|
package/package.json
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "../types";
|
|
10
10
|
import { EventStore } from "../event-store";
|
|
11
11
|
import {
|
|
12
|
+
BatchWriteItemCommand,
|
|
12
13
|
DynamoDBClient,
|
|
13
14
|
Put,
|
|
14
15
|
QueryCommand,
|
|
@@ -16,8 +17,11 @@ import {
|
|
|
16
17
|
TransactWriteItemsCommand,
|
|
17
18
|
TransactWriteItemsInput,
|
|
18
19
|
Update,
|
|
20
|
+
UpdateItemCommand,
|
|
21
|
+
UpdateItemInput,
|
|
22
|
+
WriteRequest,
|
|
19
23
|
} from "@aws-sdk/client-dynamodb";
|
|
20
|
-
import
|
|
24
|
+
import moment from "moment/moment";
|
|
21
25
|
import { DefaultKeyResolver } from "./default-key-resolver";
|
|
22
26
|
import {
|
|
23
27
|
JsonEventSerializer,
|
|
@@ -149,6 +153,7 @@ class EventStoreForDynamoDB<
|
|
|
149
153
|
throw new Error("Cannot persist created event");
|
|
150
154
|
}
|
|
151
155
|
await this.updateEventAndSnapshotOpt(event, version, undefined);
|
|
156
|
+
await this.tryPurgeExcessSnapshots(event);
|
|
152
157
|
this.logger?.debug(
|
|
153
158
|
`persistEvent(${JSON.stringify(event)}, ${version}): finished`,
|
|
154
159
|
);
|
|
@@ -163,12 +168,12 @@ class EventStoreForDynamoDB<
|
|
|
163
168
|
if (event.isCreated) {
|
|
164
169
|
await this.createEventAndSnapshot(event, aggregate);
|
|
165
170
|
} else {
|
|
166
|
-
this.logger?.debug("update!!!");
|
|
167
171
|
await this.updateEventAndSnapshotOpt(
|
|
168
172
|
event,
|
|
169
173
|
aggregate.sequenceNumber,
|
|
170
174
|
aggregate,
|
|
171
175
|
);
|
|
176
|
+
await this.tryPurgeExcessSnapshots(event);
|
|
172
177
|
}
|
|
173
178
|
this.logger?.debug(
|
|
174
179
|
`persistEventAndSnapshot(${JSON.stringify(event)}, ${JSON.stringify(
|
|
@@ -461,6 +466,154 @@ class EventStoreForDynamoDB<
|
|
|
461
466
|
);
|
|
462
467
|
return result;
|
|
463
468
|
}
|
|
469
|
+
|
|
470
|
+
private async tryPurgeExcessSnapshots(event: E) {
|
|
471
|
+
if (this.keepSnapshotCount !== undefined) {
|
|
472
|
+
if (this.deleteTtl !== undefined) {
|
|
473
|
+
await this.updateTtlOfExcessSnapshots(event.aggregateId);
|
|
474
|
+
} else {
|
|
475
|
+
await this.deleteExcessSnapshots(event.aggregateId);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private async getSnapshotCount(aggregateId: AID) {
|
|
481
|
+
const request: QueryCommandInput = {
|
|
482
|
+
TableName: this.snapshotTableName,
|
|
483
|
+
IndexName: this.snapshotAidIndexName,
|
|
484
|
+
KeyConditionExpression: "#aid = :aid",
|
|
485
|
+
ExpressionAttributeNames: {
|
|
486
|
+
"#aid": "aid",
|
|
487
|
+
},
|
|
488
|
+
ExpressionAttributeValues: {
|
|
489
|
+
":aid": { S: aggregateId.asString },
|
|
490
|
+
},
|
|
491
|
+
Select: "COUNT",
|
|
492
|
+
};
|
|
493
|
+
const queryResult = await this.dynamodbClient.send(
|
|
494
|
+
new QueryCommand(request),
|
|
495
|
+
);
|
|
496
|
+
return queryResult.Count;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
private async getLastSnapshotKeys(aggregateId: AID, limit: number) {
|
|
500
|
+
const names = {
|
|
501
|
+
"#aid": "aid",
|
|
502
|
+
"#seq_nr": "seq_nr",
|
|
503
|
+
};
|
|
504
|
+
const values = {
|
|
505
|
+
":aid": { S: aggregateId.asString },
|
|
506
|
+
":seq_nr": { N: "0" },
|
|
507
|
+
};
|
|
508
|
+
const request: QueryCommandInput = {
|
|
509
|
+
TableName: this.snapshotTableName,
|
|
510
|
+
IndexName: this.snapshotAidIndexName,
|
|
511
|
+
KeyConditionExpression: "#aid = :aid AND #seq_nr > :seq_nr",
|
|
512
|
+
ExpressionAttributeNames: { ...names },
|
|
513
|
+
ExpressionAttributeValues: { ...values },
|
|
514
|
+
ScanIndexForward: false,
|
|
515
|
+
Limit: limit,
|
|
516
|
+
};
|
|
517
|
+
if (this.deleteTtl !== undefined) {
|
|
518
|
+
request.FilterExpression = "#ttl = :ttl";
|
|
519
|
+
request.ExpressionAttributeNames = {
|
|
520
|
+
...names,
|
|
521
|
+
"#ttl": "ttl",
|
|
522
|
+
};
|
|
523
|
+
request.ExpressionAttributeValues = {
|
|
524
|
+
...values,
|
|
525
|
+
":ttl": { N: "0" },
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
const queryResult = await this.dynamodbClient.send(
|
|
529
|
+
new QueryCommand(request),
|
|
530
|
+
);
|
|
531
|
+
if (queryResult.Items === undefined || queryResult.Items.length === 0) {
|
|
532
|
+
return undefined;
|
|
533
|
+
} else {
|
|
534
|
+
return queryResult.Items.map((item) => {
|
|
535
|
+
const pkey = item.pkey.S;
|
|
536
|
+
const skey = item.skey.S;
|
|
537
|
+
if (pkey === undefined || skey === undefined) {
|
|
538
|
+
throw new Error("pkey or skey is undefined");
|
|
539
|
+
}
|
|
540
|
+
return { pkey, skey };
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
private async updateTtlOfExcessSnapshots(aggregateId: AID) {
|
|
546
|
+
if (this.keepSnapshotCount !== undefined && this.deleteTtl !== undefined) {
|
|
547
|
+
let snapshotCount = await this.getSnapshotCount(aggregateId);
|
|
548
|
+
if (snapshotCount === undefined) {
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
snapshotCount -= 1;
|
|
552
|
+
const excessCount = snapshotCount - this.keepSnapshotCount;
|
|
553
|
+
if (excessCount > 0) {
|
|
554
|
+
const keys = await this.getLastSnapshotKeys(aggregateId, excessCount);
|
|
555
|
+
if (keys === undefined) {
|
|
556
|
+
return undefined;
|
|
557
|
+
}
|
|
558
|
+
const ttl = moment().add(this.deleteTtl);
|
|
559
|
+
const result = keys.map((key) => {
|
|
560
|
+
const request: UpdateItemInput = {
|
|
561
|
+
TableName: this.snapshotTableName,
|
|
562
|
+
Key: {
|
|
563
|
+
pkey: { S: key.pkey },
|
|
564
|
+
skey: { S: key.skey },
|
|
565
|
+
},
|
|
566
|
+
UpdateExpression: "SET #ttl = :ttl",
|
|
567
|
+
ExpressionAttributeNames: {
|
|
568
|
+
"#ttl": "ttl",
|
|
569
|
+
},
|
|
570
|
+
ExpressionAttributeValues: {
|
|
571
|
+
":ttl": { N: ttl.seconds().toString() },
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
return this.dynamodbClient.send(new UpdateItemCommand(request));
|
|
575
|
+
});
|
|
576
|
+
return await Promise.all(result);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return undefined;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
private async deleteExcessSnapshots(aggregateId: AID) {
|
|
583
|
+
if (this.keepSnapshotCount !== undefined && this.deleteTtl !== undefined) {
|
|
584
|
+
let snapshotCount = await this.getSnapshotCount(aggregateId);
|
|
585
|
+
if (snapshotCount === undefined) {
|
|
586
|
+
return undefined;
|
|
587
|
+
}
|
|
588
|
+
snapshotCount -= 1;
|
|
589
|
+
const excessCount = snapshotCount - this.keepSnapshotCount;
|
|
590
|
+
if (excessCount > 0) {
|
|
591
|
+
const keys = await this.getLastSnapshotKeys(aggregateId, excessCount);
|
|
592
|
+
if (keys === undefined) {
|
|
593
|
+
return undefined;
|
|
594
|
+
}
|
|
595
|
+
const result = keys.map((key) => {
|
|
596
|
+
const request: WriteRequest = {
|
|
597
|
+
DeleteRequest: {
|
|
598
|
+
Key: {
|
|
599
|
+
pkey: { S: key.pkey },
|
|
600
|
+
skey: { S: key.skey },
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
};
|
|
604
|
+
return this.dynamodbClient.send(
|
|
605
|
+
new BatchWriteItemCommand({
|
|
606
|
+
RequestItems: {
|
|
607
|
+
[this.snapshotTableName]: [request],
|
|
608
|
+
},
|
|
609
|
+
}),
|
|
610
|
+
);
|
|
611
|
+
});
|
|
612
|
+
return await Promise.all(result);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return undefined;
|
|
616
|
+
}
|
|
464
617
|
}
|
|
465
618
|
|
|
466
619
|
export { EventStoreForDynamoDB };
|