Skip to content

Commit d604f0a

Browse files
Merge pull request #163 from castore-dev/return-initialEventTimestamp-in-listAggregateIds-method
feature: return initialEventTimestamp in listAggregateIds method
2 parents 662d40b + 43e51eb commit d604f0a

24 files changed

Lines changed: 248 additions & 133 deletions

packages/core/src/eventStorageAdapter.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ export type ListAggregateIdsOptions = {
2323
};
2424

2525
export type ListAggregateIdsOutput = {
26-
aggregateIds: string[];
26+
aggregateIds: {
27+
aggregateId: string;
28+
initialEventTimestamp: string;
29+
}[];
2730
nextPageToken?: string;
2831
};
2932

packages/core/src/messaging/message.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { EventDetail } from '~/event/eventDetail';
44
export type AggregateExistsMessage<EVENT_STORE_ID extends string = string> = {
55
eventStoreId: EVENT_STORE_ID;
66
aggregateId: string;
7+
initialEventTimestamp: string;
78
};
89

910
export type NotificationMessage<

packages/event-storage-adapter-dynamodb/src/legacyAdapter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ export class LegacyDynamoDBEventStorageAdapter implements EventStorageAdapter {
399399
}
400400

401401
const {
402-
Items: unmarshalledInitialEvents = [],
402+
Items: marshalledInitialEvents = [],
403403
LastEvaluatedKey: lastEvaluatedKey,
404404
} = await this.dynamoDBClient.send(
405405
new QueryCommand(aggregateIdsQueryCommandInput),
@@ -414,12 +414,12 @@ export class LegacyDynamoDBEventStorageAdapter implements EventStorageAdapter {
414414
};
415415

416416
return {
417-
aggregateIds: unmarshalledInitialEvents
417+
aggregateIds: marshalledInitialEvents
418418
.map(item => unmarshall(item))
419419
.map(item => {
420-
const { aggregateId } = item as Pick<EventDetail, 'aggregateId'>;
420+
const { aggregateId, timestamp } = item as EventDetail;
421421

422-
return aggregateId;
422+
return { aggregateId, initialEventTimestamp: timestamp };
423423
}),
424424
...(lastEvaluatedKey !== undefined
425425
? { nextPageToken: JSON.stringify(parsedNextPageToken) }

packages/event-storage-adapter-dynamodb/src/legacyAdapter.unit.test.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,11 @@ describe('LegacyDynamoDBEventStorageAdapter', () => {
302302
const secondAggregateIdMock = 'my-second-aggregate-id';
303303
const queryCommandOutputMock: QueryCommandOutput = {
304304
Items: [
305-
marshall({ aggregateId }),
306-
marshall({ aggregateId: secondAggregateIdMock }),
305+
marshall({ aggregateId, timestamp: timestampA }),
306+
marshall({
307+
aggregateId: secondAggregateIdMock,
308+
timestamp: timestampB,
309+
}),
307310
],
308311
$metadata: {},
309312
};
@@ -326,7 +329,13 @@ describe('LegacyDynamoDBEventStorageAdapter', () => {
326329
});
327330

328331
// We have to serialize / deserialize because DynamoDB numbers are not regular numbers
329-
expect(aggregateIds).toMatchObject([aggregateId, secondAggregateIdMock]);
332+
expect(aggregateIds).toMatchObject([
333+
{ aggregateId, initialEventTimestamp: timestampA },
334+
{
335+
aggregateId: secondAggregateIdMock,
336+
initialEventTimestamp: timestampB,
337+
},
338+
]);
330339
});
331340

332341
it('adds limit option', async () => {

packages/event-storage-adapter-dynamodb/src/singleTableAdapter.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ export class DynamoDBSingleTableEventStorageAdapter
414414
}
415415

416416
const {
417-
Items: unmarshalledInitialEvents = [],
417+
Items: marshalledInitialEvents = [],
418418
LastEvaluatedKey: lastEvaluatedKey,
419419
} = await this.dynamoDBClient.send(
420420
new QueryCommand(aggregateIdsQueryCommandInput),
@@ -429,12 +429,15 @@ export class DynamoDBSingleTableEventStorageAdapter
429429
};
430430

431431
return {
432-
aggregateIds: unmarshalledInitialEvents
432+
aggregateIds: marshalledInitialEvents
433433
.map(item => unmarshall(item))
434434
.map(item => {
435-
const { aggregateId } = item as Pick<EventDetail, 'aggregateId'>;
435+
const { aggregateId, timestamp } = item as EventDetail;
436436

437-
return unprefixAggregateId(eventStoreId, aggregateId);
437+
return {
438+
aggregateId: unprefixAggregateId(eventStoreId, aggregateId),
439+
initialEventTimestamp: timestamp,
440+
};
438441
}),
439442
...(lastEvaluatedKey !== undefined
440443
? { nextPageToken: JSON.stringify(parsedNextPageToken) }

packages/event-storage-adapter-dynamodb/src/singleTableAdapter.unit.test.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,9 +294,10 @@ describe('DynamoDBEventStorageAdapter', () => {
294294
const secondAggregateIdMock = 'my-second-aggregate-id';
295295
const queryCommandOutputMock: QueryCommandOutput = {
296296
Items: [
297-
marshall({ aggregateId: prefixedAggregateId }),
297+
marshall({ aggregateId: prefixedAggregateId, timestamp: timestampA }),
298298
marshall({
299299
aggregateId: prefixAggregateId(eventStoreId, secondAggregateIdMock),
300+
timestamp: timestampB,
300301
}),
301302
],
302303
$metadata: {},
@@ -323,7 +324,13 @@ describe('DynamoDBEventStorageAdapter', () => {
323324
});
324325

325326
// We have to serialize / deserialize because DynamoDB numbers are not regular numbers
326-
expect(aggregateIds).toMatchObject([aggregateId, secondAggregateIdMock]);
327+
expect(aggregateIds).toMatchObject([
328+
{ aggregateId, initialEventTimestamp: timestampA },
329+
{
330+
aggregateId: secondAggregateIdMock,
331+
initialEventTimestamp: timestampB,
332+
},
333+
]);
327334
});
328335

329336
it('adds limit option', async () => {

packages/event-storage-adapter-in-memory/src/adapter.ts

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -277,40 +277,41 @@ export class InMemoryEventStorageAdapter implements EventStorageAdapter {
277277
inputOptions,
278278
});
279279

280-
let aggregateEntries = Object.entries(this.eventStore).sort(
281-
(entryA, entryB) => {
282-
const initialEventATimestamp = getInitialEventTimestamp(...entryA);
283-
const initialEventBTimestamp = getInitialEventTimestamp(...entryB);
284-
285-
return initialEventATimestamp > initialEventBTimestamp ? 1 : -1;
286-
},
287-
);
280+
let aggregateIds = Object.entries(this.eventStore)
281+
.map(([aggregateId, aggregateEvents]) => ({
282+
aggregateId,
283+
initialEventTimestamp: getInitialEventTimestamp(
284+
aggregateId,
285+
aggregateEvents,
286+
),
287+
}))
288+
.sort((aggregateA, aggregateB) =>
289+
aggregateA.initialEventTimestamp > aggregateB.initialEventTimestamp
290+
? 1
291+
: -1,
292+
);
288293

289294
if (initialEventAfter !== undefined) {
290-
aggregateEntries = aggregateEntries.filter(entry => {
291-
const initialEventTimestamp = getInitialEventTimestamp(...entry);
292-
293-
return initialEventTimestamp >= initialEventAfter;
294-
});
295+
aggregateIds = aggregateIds.filter(
296+
({ initialEventTimestamp }) =>
297+
initialEventTimestamp >= initialEventAfter,
298+
);
295299
}
296300

297301
if (initialEventBefore !== undefined) {
298-
aggregateEntries = aggregateEntries.filter(entry => {
299-
const initialEventTimestamp = getInitialEventTimestamp(...entry);
300-
301-
return initialEventTimestamp <= initialEventBefore;
302-
});
302+
aggregateIds = aggregateIds.filter(
303+
({ initialEventTimestamp }) =>
304+
initialEventTimestamp <= initialEventBefore,
305+
);
303306
}
304307

305-
let aggregateIds = aggregateEntries.map(([aggregateId]) => aggregateId);
306-
307308
if (reverse === true) {
308309
aggregateIds = aggregateIds.reverse();
309310
}
310311

311312
if (exclusiveStartKey !== undefined) {
312313
const exclusiveStartKeyIndex = aggregateIds.findIndex(
313-
aggregateId => aggregateId === exclusiveStartKey,
314+
({ aggregateId }) => aggregateId === exclusiveStartKey.aggregateId,
314315
);
315316

316317
aggregateIds = aggregateIds.slice(exclusiveStartKeyIndex + 1);

packages/event-storage-adapter-in-memory/src/adapter.unit.test.ts

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,24 @@ import { InMemoryEventAlreadyExistsError } from './error';
1111
const eventStoreId = 'eventStoreId';
1212

1313
const aggregateIdMock1 = randomUUID();
14+
const aggregate1InitialEventTimestamp = '2021-01-01T00:00:00.000Z';
1415
const aggregateIdMock2 = randomUUID();
16+
const aggregate2InitialEventTimestamp = '2022-01-01T00:00:00.000Z';
1517
const aggregateIdMock3 = randomUUID();
18+
const aggregate3InitialEventTimestamp = '2023-01-01T00:00:00.000Z';
1619
const aggregateIdMock4 = randomUUID();
20+
const aggregate4InitialEventTimestamp = '2024-01-01T00:00:00.000Z';
1721
const eventMock1 = {
1822
aggregateId: aggregateIdMock1,
1923
version: 1,
2024
type: 'EVENT_TYPE',
21-
timestamp: '2021-01-01T00:00:00.000Z',
25+
timestamp: aggregate1InitialEventTimestamp,
2226
};
2327
const eventMock2 = {
2428
aggregateId: aggregateIdMock1,
2529
version: 2,
2630
type: 'EVENT_TYPE',
27-
timestamp: '2022-01-01T00:00:00.000Z',
31+
timestamp: aggregate2InitialEventTimestamp,
2832
};
2933

3034
describe('in-memory storage adapter', () => {
@@ -135,7 +139,7 @@ describe('in-memory storage adapter', () => {
135139
aggregateId: aggregateIdMock2,
136140
version: 1,
137141
type: 'EVENT_TYPE',
138-
timestamp: '2022-01-01T00:00:00.000Z',
142+
timestamp: aggregate2InitialEventTimestamp,
139143
},
140144
{ eventStoreId },
141145
);
@@ -145,7 +149,16 @@ describe('in-memory storage adapter', () => {
145149
});
146150

147151
expect(aggregateIds).toStrictEqual({
148-
aggregateIds: [aggregateIdMock1, aggregateIdMock2],
152+
aggregateIds: [
153+
{
154+
aggregateId: aggregateIdMock1,
155+
initialEventTimestamp: aggregate1InitialEventTimestamp,
156+
},
157+
{
158+
aggregateId: aggregateIdMock2,
159+
initialEventTimestamp: aggregate2InitialEventTimestamp,
160+
},
161+
],
149162
});
150163
});
151164

@@ -155,7 +168,7 @@ describe('in-memory storage adapter', () => {
155168
aggregateId: aggregateIdMock3,
156169
version: 1,
157170
type: 'EVENT_TYPE',
158-
timestamp: '2023-01-01T00:00:00.000Z',
171+
timestamp: aggregate3InitialEventTimestamp,
159172
},
160173
{ eventStoreId },
161174
);
@@ -165,7 +178,7 @@ describe('in-memory storage adapter', () => {
165178
aggregateId: aggregateIdMock4,
166179
version: 1,
167180
type: 'EVENT_TYPE',
168-
timestamp: '2024-01-01T00:00:00.000Z',
181+
timestamp: aggregate4InitialEventTimestamp,
169182
},
170183
{ eventStoreId },
171184
);
@@ -177,12 +190,22 @@ describe('in-memory storage adapter', () => {
177190
);
178191

179192
expect(aggregateIds).toStrictEqual([
180-
aggregateIdMock1,
181-
aggregateIdMock2,
193+
{
194+
aggregateId: aggregateIdMock1,
195+
initialEventTimestamp: aggregate1InitialEventTimestamp,
196+
},
197+
{
198+
aggregateId: aggregateIdMock2,
199+
initialEventTimestamp: aggregate2InitialEventTimestamp,
200+
},
182201
]);
202+
183203
expect(JSON.parse(nextPageToken as string)).toStrictEqual({
184204
limit: 2,
185-
lastEvaluatedKey: aggregateIdMock2,
205+
lastEvaluatedKey: {
206+
aggregateId: aggregateIdMock2,
207+
initialEventTimestamp: aggregate2InitialEventTimestamp,
208+
},
186209
});
187210

188211
const lastAggregateIds = await eventStorageAdapter.listAggregateIds(
@@ -191,7 +214,16 @@ describe('in-memory storage adapter', () => {
191214
);
192215

193216
expect(lastAggregateIds).toStrictEqual({
194-
aggregateIds: [aggregateIdMock3, aggregateIdMock4],
217+
aggregateIds: [
218+
{
219+
aggregateId: aggregateIdMock3,
220+
initialEventTimestamp: aggregate3InitialEventTimestamp,
221+
},
222+
{
223+
aggregateId: aggregateIdMock4,
224+
initialEventTimestamp: aggregate4InitialEventTimestamp,
225+
},
226+
],
195227
});
196228
});
197229

@@ -207,13 +239,21 @@ describe('in-memory storage adapter', () => {
207239
},
208240
);
209241

210-
expect(aggregateIds).toStrictEqual([aggregateIdMock3]);
242+
expect(aggregateIds).toStrictEqual([
243+
{
244+
aggregateId: aggregateIdMock3,
245+
initialEventTimestamp: aggregate3InitialEventTimestamp,
246+
},
247+
]);
211248
expect(JSON.parse(nextPageToken as string)).toStrictEqual({
212249
limit: 1,
213250
initialEventAfter: '2021-02-01T00:00:00.000Z',
214251
initialEventBefore: '2023-02-01T00:00:00.000Z',
215252
reverse: true,
216-
lastEvaluatedKey: aggregateIdMock3,
253+
lastEvaluatedKey: {
254+
aggregateId: aggregateIdMock3,
255+
initialEventTimestamp: aggregate3InitialEventTimestamp,
256+
},
217257
});
218258

219259
const { aggregateIds: lastAggregateIds, nextPageToken: noPageToken } =
@@ -223,7 +263,12 @@ describe('in-memory storage adapter', () => {
223263
);
224264

225265
expect(noPageToken).toBeUndefined();
226-
expect(lastAggregateIds).toStrictEqual([aggregateIdMock2]);
266+
expect(lastAggregateIds).toStrictEqual([
267+
{
268+
aggregateId: aggregateIdMock2,
269+
initialEventTimestamp: aggregate2InitialEventTimestamp,
270+
},
271+
]);
227272
});
228273
});
229274

packages/event-storage-adapter-in-memory/src/utils/parseAppliedListAggregateIdsOptions.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ export type ParsedPageToken = {
33
initialEventAfter?: string | undefined;
44
initialEventBefore?: string | undefined;
55
reverse?: boolean | undefined;
6-
lastEvaluatedKey?: string | undefined;
6+
lastEvaluatedKey?:
7+
| {
8+
aggregateId: string;
9+
initialEventTimestamp: string;
10+
}
11+
| undefined;
712
};
813

914
export const parseAppliedListAggregateIdsOptions = ({

0 commit comments

Comments
 (0)