Skip to content

Commit 662d40b

Browse files
Merge pull request #162 from castore-dev/enable-passing-options-to-pushEventGroup
2 parents c2b5286 + c8b724c commit 662d40b

13 files changed

Lines changed: 147 additions & 30 deletions

File tree

.github/workflows/label-pr.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ defaults:
1616
run:
1717
shell: bash
1818

19+
permissions:
20+
contents: read
21+
pull-requests: write
22+
1923
jobs:
2024
label-pr:
2125
name: 🏷 Label PR

docs/docs/2-event-sourcing/6-joining-data.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ await EventStore.pushEventGroup(
2929
...
3030
}),
3131
);
32+
33+
// You can also pass options as a first argument
34+
await EventStore.pushEventGroup(
35+
{ force: true },
36+
pokemonsEventStore.groupEvent({
37+
...
38+
}),
39+
...
40+
);
3241
```
3342

3443
:::note

docs/docs/3-reacting-to-events/4-connected-event-store.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ await connectedPokemonsEventStore.pushEvent(
4747
{ prevAggregate: pokemonAggregate },
4848
// Removes the need to re-fetch 🙌
4949
);
50+
51+
await EventStore.pushEventGroup(
52+
connectedPokemonsEventStore.groupEvent(
53+
{ ... },
54+
// Will also work on event groups 🙌
55+
{ prevAggregate: pokemonAggregate },
56+
),
57+
);
5058
```
5159

5260
Compared to data streams, connected event stores have the advantage of simplicity, performances and costs. However, they **strongly decouple your storage and messaging solutions**: Make sure to anticipate any issue that might arise (consistency, non-caught errors etc.).

packages/core/src/eventStorageAdapter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export interface EventStorageAdapter {
3838
options: PushEventOptions,
3939
) => Promise<{ event: EventDetail }>;
4040
pushEventGroup: (
41+
options: { force?: boolean },
4142
...groupedEvents: [GroupedEvent, ...GroupedEvent[]]
4243
) => Promise<{ eventGroup: { event: EventDetail }[] }>;
4344
groupEvent: (eventDetail: OptionalTimestamp<EventDetail>) => GroupedEvent;

packages/core/src/eventStore/eventStore.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import type { Aggregate } from '~/aggregate';
33
import type { EventDetail } from '~/event/eventDetail';
44
import type { EventType, EventTypeDetails } from '~/event/eventType';
5-
import type { GroupedEvent } from '~/event/groupedEvent';
5+
import { GroupedEvent } from '~/event/groupedEvent';
66
import type { EventStorageAdapter } from '~/eventStorageAdapter';
77
import type { $Contravariant } from '~/utils';
88

@@ -41,15 +41,29 @@ export class EventStore<
4141
> {
4242
static pushEventGroup: EventGroupPusher = async <
4343
GROUPED_EVENTS extends [GroupedEvent, ...GroupedEvent[]],
44+
OPTIONS_OR_GROUPED_EVENTS_HEAD extends
45+
| GroupedEvent
46+
| { force?: boolean } = GroupedEvent,
4447
>(
45-
...groupedEvents: GROUPED_EVENTS
48+
optionsOrGroupedEvent: OPTIONS_OR_GROUPED_EVENTS_HEAD,
49+
..._groupedEvents: GROUPED_EVENTS
4650
) => {
47-
const [groupedEventsHead, ...groupedEventsTail] = groupedEvents;
51+
const groupedEvents = (
52+
optionsOrGroupedEvent instanceof GroupedEvent
53+
? [optionsOrGroupedEvent, ..._groupedEvents]
54+
: _groupedEvents
55+
) as [GroupedEvent, ...GroupedEvent[]];
56+
57+
const options = (
58+
optionsOrGroupedEvent instanceof GroupedEvent ? {} : optionsOrGroupedEvent
59+
) as { force?: boolean };
60+
61+
const [groupedEventsHead] = groupedEvents;
4862

4963
const { eventGroup: eventGroupWithoutAggregates } =
5064
await groupedEventsHead.eventStorageAdapter.pushEventGroup(
51-
groupedEventsHead,
52-
...groupedEventsTail,
65+
options,
66+
...groupedEvents,
5367
);
5468

5569
const eventGroupWithAggregates = eventGroupWithoutAggregates.map(
@@ -68,7 +82,7 @@ export class EventStore<
6882

6983
return { event, ...(nextAggregate ? { nextAggregate } : {}) };
7084
},
71-
) as EventGroupPusherResponse<GROUPED_EVENTS>;
85+
);
7286

7387
await Promise.all(
7488
groupedEvents.map((groupedEvent, eventIndex) => {
@@ -82,7 +96,13 @@ export class EventStore<
8296
}),
8397
);
8498

85-
return { eventGroup: eventGroupWithAggregates };
99+
return { eventGroup: eventGroupWithAggregates } as {
100+
eventGroup: OPTIONS_OR_GROUPED_EVENTS_HEAD extends GroupedEvent
101+
? EventGroupPusherResponse<
102+
[OPTIONS_OR_GROUPED_EVENTS_HEAD, ...GROUPED_EVENTS]
103+
>
104+
: EventGroupPusherResponse<GROUPED_EVENTS>;
105+
};
86106
};
87107

88108
_types?: {

packages/core/src/eventStore/eventStore.type.test.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable max-lines */
12
import type { A } from 'ts-toolbelt';
23

34
import type { Aggregate } from '~/aggregate';
@@ -147,17 +148,27 @@ assertGroupEventOutput;
147148

148149
const assertGenericPushEventGroupInput: A.Equals<
149150
Parameters<typeof EventStore.pushEventGroup>,
150-
[GroupedEvent, ...GroupedEvent[]]
151+
[
152+
GroupedEvent | { force?: boolean | undefined },
153+
GroupedEvent,
154+
...GroupedEvent[],
155+
]
151156
> = 1;
152157
assertGenericPushEventGroupInput;
153158

154159
const assertGenericPushEventGroupOutput: A.Equals<
155160
ReturnType<typeof EventStore.pushEventGroup>,
156161
Promise<{
157-
eventGroup: {
158-
event: EventDetail;
159-
nextAggregate?: Aggregate | undefined;
160-
}[];
162+
eventGroup:
163+
| {
164+
event: EventDetail;
165+
nextAggregate?: Aggregate | undefined;
166+
}[]
167+
// Weird TS bug
168+
| {
169+
event: EventDetail;
170+
nextAggregate?: Aggregate | undefined;
171+
}[];
161172
}>
162173
> = 1;
163174
assertGenericPushEventGroupOutput;
@@ -178,3 +189,21 @@ const assertPushEventGroupOutput: A.Equals<
178189
}
179190
> = 1;
180191
assertPushEventGroupOutput;
192+
193+
const pushTwoPokemonsEventGroupWithOptions = () =>
194+
EventStore.pushEventGroup(
195+
{ force: true },
196+
pokemonsEventStore.groupEvent(pikachuAppearedEvent),
197+
pokemonsEventStore.groupEvent(pikachuCaughtEvent),
198+
);
199+
200+
const assertPushTwoPokemonsEventGroupWithOptions: A.Equals<
201+
Awaited<ReturnType<typeof pushTwoPokemonsEventGroupWithOptions>>,
202+
{
203+
eventGroup: [
204+
{ event: PokemonEventDetails; nextAggregate?: PokemonAggregate },
205+
{ event: PokemonEventDetails; nextAggregate?: PokemonAggregate },
206+
];
207+
}
208+
> = 1;
209+
assertPushTwoPokemonsEventGroupWithOptions;

packages/core/src/eventStore/eventStore.unit.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ describe('event store', () => {
229229
timestamp: pikachuLeveledUpEvent.timestamp,
230230
};
231231

232+
beforeEach(() => {
233+
pushEventGroupMock.mockReset();
234+
});
235+
232236
it('pushes new event group correctly', async () => {
233237
pushEventGroupMock.mockResolvedValue({
234238
eventGroup: [
@@ -251,7 +255,41 @@ describe('event store', () => {
251255
const response = await EventStore.pushEventGroup(...eventGroup);
252256

253257
expect(pushEventGroupMock).toHaveBeenCalledTimes(1);
254-
expect(pushEventGroupMock).toHaveBeenCalledWith(...eventGroup);
258+
expect(pushEventGroupMock).toHaveBeenCalledWith({}, ...eventGroup);
259+
260+
expect(response).toStrictEqual({
261+
eventGroup: [
262+
{ event: pikachuLeveledUpEvent },
263+
{ event: charizardLeveledUpEvent },
264+
],
265+
});
266+
});
267+
268+
it('passes options through', async () => {
269+
const options = { force: true };
270+
271+
pushEventGroupMock.mockResolvedValue({
272+
eventGroup: [
273+
{ event: pikachuLeveledUpEvent },
274+
{ event: charizardLeveledUpEvent },
275+
],
276+
});
277+
278+
const eventGroup = [
279+
new GroupedEvent({
280+
event: pikachuLeveledUpEvent,
281+
eventStorageAdapter: eventStorageAdapterMock,
282+
}),
283+
new GroupedEvent({
284+
event: charizardLeveledUpEvent,
285+
eventStorageAdapter: eventStorageAdapterMock,
286+
}),
287+
] as const;
288+
289+
const response = await EventStore.pushEventGroup(options, ...eventGroup);
290+
291+
expect(pushEventGroupMock).toHaveBeenCalledTimes(1);
292+
expect(pushEventGroupMock).toHaveBeenCalledWith(options, ...eventGroup);
255293

256294
expect(response).toStrictEqual({
257295
eventGroup: [

packages/core/src/eventStore/types.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,19 @@ export type EventGroupPusher = <
6161
GroupedEvent,
6262
...GroupedEvent[],
6363
],
64+
OPTIONS_OR_GROUPED_EVENTS_HEAD extends
65+
| GroupedEvent
66+
| { force?: boolean } = GroupedEvent,
6467
>(
65-
/**
66-
* @debt v2 "use an array and enable options in 2nd arg (useful for 'force' opt for instance)"
67-
*/
68+
optionsOrGroupedEventsHead: OPTIONS_OR_GROUPED_EVENTS_HEAD,
6869
...groupedEvents: GROUPED_EVENTS
69-
) => Promise<{ eventGroup: EventGroupPusherResponse<GROUPED_EVENTS> }>;
70+
) => Promise<{
71+
eventGroup: OPTIONS_OR_GROUPED_EVENTS_HEAD extends GroupedEvent
72+
? EventGroupPusherResponse<
73+
[OPTIONS_OR_GROUPED_EVENTS_HEAD, ...GROUPED_EVENTS]
74+
>
75+
: EventGroupPusherResponse<GROUPED_EVENTS>;
76+
}>;
7077

7178
export type EventGroupPusherResponse<GROUPED_EVENTS extends GroupedEvent[]> =
7279
number extends GROUPED_EVENTS['length']

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ export class LegacyDynamoDBEventStorageAdapter implements EventStorageAdapter {
288288
/**
289289
* @debt test "Add unit test for pushEventGroup"
290290
*/
291-
this.pushEventGroup = async (...groupedEventsInput) => {
291+
this.pushEventGroup = async (options, ...groupedEventsInput) => {
292292
const { groupedEvents, timestamp = new Date().toISOString() } =
293293
parseGroupedEvents(...groupedEventsInput);
294294

@@ -302,7 +302,7 @@ export class LegacyDynamoDBEventStorageAdapter implements EventStorageAdapter {
302302
TransactItems: groupedEvents.map(groupedEvent => ({
303303
Put: groupedEvent.eventStorageAdapter.getPushEventInput(
304304
{ timestamp, ...groupedEvent.event },
305-
{ eventStoreId: groupedEvent.context.eventStoreId },
305+
{ ...options, eventStoreId: groupedEvent.context.eventStoreId },
306306
),
307307
})),
308308
}),

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ export class DynamoDBSingleTableEventStorageAdapter
300300
/**
301301
* @debt test "Add unit test for pushEventGroup"
302302
*/
303-
this.pushEventGroup = async (...groupedEventsInput) => {
303+
this.pushEventGroup = async (options, ...groupedEventsInput) => {
304304
const { groupedEvents, timestamp = new Date().toISOString() } =
305305
parseGroupedEvents(...groupedEventsInput);
306306

@@ -314,7 +314,7 @@ export class DynamoDBSingleTableEventStorageAdapter
314314
TransactItems: groupedEvents.map(groupedEvent => ({
315315
Put: groupedEvent.eventStorageAdapter.getPushEventInput(
316316
{ timestamp, ...groupedEvent.event },
317-
groupedEvent.context,
317+
{ ...options, ...groupedEvent.context },
318318
),
319319
})),
320320
}),

0 commit comments

Comments
 (0)