Skip to content

Commit bb5f6e5

Browse files
committed
Adjusted projection definition
1 parent 16d434b commit bb5f6e5

File tree

6 files changed

+111
-74
lines changed

6 files changed

+111
-74
lines changed

src/packages/emmett-postgresql/src/eventStore/consumers/postgreSQLEventStoreConsumer.projections.int.spec.ts

Lines changed: 84 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
import { assertThatArray, type Event } from '@event-driven-io/emmett';
1+
import { assertThatArray, type ReadEvent } from '@event-driven-io/emmett';
22
import {
33
PostgreSqlContainer,
44
StartedPostgreSqlContainer,
55
} from '@testcontainers/postgresql';
66
import { after, before, describe, it } from 'node:test';
77
import { v4 as uuid } from 'uuid';
8+
import type { ShoppingCartConfirmed } from '../../testing/shoppingCart.domain';
89
import {
910
getPostgreSQLEventStore,
1011
type PostgresEventStore,
1112
} from '../postgreSQLEventStore';
13+
import { pongoMultiStreamProjection } from '../projections';
14+
import type { ProductItemAdded } from '../projections/postgresProjection.customid.int.spec';
1215
import { postgreSQLEventStoreConsumer } from './postgreSQLEventStoreConsumer';
1316
import type { PostgreSQLProcessorOptions } from './postgreSQLProcessor';
1417

@@ -43,7 +46,7 @@ void describe('PostgreSQL event store started consumer', () => {
4346
// Given
4447
const guestId = uuid();
4548
const streamName = `guestStay-${guestId}`;
46-
const events: GuestStayEvent[] = [
49+
const events: ShoppingCartSummaryEvent[] = [
4750
{ type: 'GuestCheckedIn', data: { guestId } },
4851
{ type: 'GuestCheckedOut', data: { guestId } },
4952
];
@@ -52,20 +55,18 @@ void describe('PostgreSQL event store started consumer', () => {
5255
events,
5356
);
5457

55-
const result: GuestStayEvent[] = [];
58+
const result: ShoppingCartSummaryEvent[] = [];
5659

5760
// When
5861
const consumer = postgreSQLEventStoreConsumer({
5962
connectionString,
6063
});
61-
consumer.processor<GuestStayEvent>({
64+
consumer.processor({
6265
processorId: uuid(),
66+
projection: shoppingCartsSummaryProjection,
6367
stopAfter: (event) =>
6468
event.metadata.globalPosition ===
6569
appendResult.lastEventGlobalPosition,
66-
eachMessage: (event) => {
67-
result.push(event);
68-
},
6970
});
7071

7172
try {
@@ -84,25 +85,23 @@ void describe('PostgreSQL event store started consumer', () => {
8485
async () => {
8586
// Given
8687

87-
const result: GuestStayEvent[] = [];
88+
const result: ShoppingCartSummaryEvent[] = [];
8889
let stopAfterPosition: bigint | undefined = undefined;
8990

9091
// When
9192
const consumer = postgreSQLEventStoreConsumer({
9293
connectionString,
9394
});
94-
consumer.processor<GuestStayEvent>({
95+
consumer.processor({
9596
processorId: uuid(),
97+
projection: shoppingCartsSummaryProjection,
9698
stopAfter: (event) =>
9799
event.metadata.globalPosition === stopAfterPosition,
98-
eachMessage: (event) => {
99-
result.push(event);
100-
},
101100
});
102101

103102
const guestId = uuid();
104103
const streamName = `guestStay-${guestId}`;
105-
const events: GuestStayEvent[] = [
104+
const events: ShoppingCartSummaryEvent[] = [
106105
{ type: 'GuestCheckedIn', data: { guestId } },
107106
{ type: 'GuestCheckedOut', data: { guestId } },
108107
];
@@ -134,33 +133,31 @@ void describe('PostgreSQL event store started consumer', () => {
134133
const otherGuestId = uuid();
135134
const streamName = `guestStay-${guestId}`;
136135

137-
const initialEvents: GuestStayEvent[] = [
136+
const initialEvents: ShoppingCartSummaryEvent[] = [
138137
{ type: 'GuestCheckedIn', data: { guestId } },
139138
{ type: 'GuestCheckedOut', data: { guestId } },
140139
];
141140
const { lastEventGlobalPosition: startPosition } =
142141
await eventStore.appendToStream(streamName, initialEvents);
143142

144-
const events: GuestStayEvent[] = [
143+
const events: ShoppingCartSummaryEvent[] = [
145144
{ type: 'GuestCheckedIn', data: { guestId: otherGuestId } },
146145
{ type: 'GuestCheckedOut', data: { guestId: otherGuestId } },
147146
];
148147

149-
const result: GuestStayEvent[] = [];
148+
const result: ShoppingCartSummaryEvent[] = [];
150149
let stopAfterPosition: bigint | undefined = undefined;
151150

152151
// When
153152
const consumer = postgreSQLEventStoreConsumer({
154153
connectionString,
155154
});
156-
consumer.processor<GuestStayEvent>({
155+
consumer.processor({
157156
processorId: uuid(),
157+
projection: shoppingCartsSummaryProjection,
158158
startFrom: { globalPosition: startPosition },
159159
stopAfter: (event) =>
160160
event.metadata.globalPosition === stopAfterPosition,
161-
eachMessage: (event) => {
162-
result.push(event);
163-
},
164161
});
165162

166163
try {
@@ -190,33 +187,31 @@ void describe('PostgreSQL event store started consumer', () => {
190187
const otherGuestId = uuid();
191188
const streamName = `guestStay-${guestId}`;
192189

193-
const initialEvents: GuestStayEvent[] = [
190+
const initialEvents: ShoppingCartSummaryEvent[] = [
194191
{ type: 'GuestCheckedIn', data: { guestId } },
195192
{ type: 'GuestCheckedOut', data: { guestId } },
196193
];
197194

198195
await eventStore.appendToStream(streamName, initialEvents);
199196

200-
const events: GuestStayEvent[] = [
197+
const events: ShoppingCartSummaryEvent[] = [
201198
{ type: 'GuestCheckedIn', data: { guestId: otherGuestId } },
202199
{ type: 'GuestCheckedOut', data: { guestId: otherGuestId } },
203200
];
204201

205-
const result: GuestStayEvent[] = [];
202+
const result: ShoppingCartSummaryEvent[] = [];
206203
let stopAfterPosition: bigint | undefined = undefined;
207204

208205
// When
209206
const consumer = postgreSQLEventStoreConsumer({
210207
connectionString,
211208
});
212-
consumer.processor<GuestStayEvent>({
209+
consumer.processor({
213210
processorId: uuid(),
211+
projection: shoppingCartsSummaryProjection,
214212
startFrom: 'CURRENT',
215213
stopAfter: (event) =>
216214
event.metadata.globalPosition === stopAfterPosition,
217-
eachMessage: (event) => {
218-
result.push(event);
219-
},
220215
});
221216

222217
try {
@@ -249,7 +244,7 @@ void describe('PostgreSQL event store started consumer', () => {
249244
const otherGuestId = uuid();
250245
const streamName = `guestStay-${guestId}`;
251246

252-
const initialEvents: GuestStayEvent[] = [
247+
const initialEvents: ShoppingCartSummaryEvent[] = [
253248
{ type: 'GuestCheckedIn', data: { guestId } },
254249
{ type: 'GuestCheckedOut', data: { guestId } },
255250
];
@@ -258,26 +253,24 @@ void describe('PostgreSQL event store started consumer', () => {
258253
initialEvents,
259254
);
260255

261-
const events: GuestStayEvent[] = [
256+
const events: ShoppingCartSummaryEvent[] = [
262257
{ type: 'GuestCheckedIn', data: { guestId: otherGuestId } },
263258
{ type: 'GuestCheckedOut', data: { guestId: otherGuestId } },
264259
];
265260

266-
let result: GuestStayEvent[] = [];
261+
let result: ShoppingCartSummaryEvent[] = [];
267262
let stopAfterPosition: bigint | undefined = lastEventGlobalPosition;
268263

269264
// When
270265
const consumer = postgreSQLEventStoreConsumer({
271266
connectionString,
272267
});
273-
consumer.processor<GuestStayEvent>({
268+
consumer.processor({
274269
processorId: uuid(),
270+
projection: shoppingCartsSummaryProjection,
275271
startFrom: 'CURRENT',
276272
stopAfter: (event) =>
277273
event.metadata.globalPosition === stopAfterPosition,
278-
eachMessage: (event) => {
279-
result.push(event);
280-
},
281274
});
282275

283276
await consumer.start();
@@ -314,7 +307,7 @@ void describe('PostgreSQL event store started consumer', () => {
314307
const otherGuestId = uuid();
315308
const streamName = `guestStay-${guestId}`;
316309

317-
const initialEvents: GuestStayEvent[] = [
310+
const initialEvents: ShoppingCartSummaryEvent[] = [
318311
{ type: 'GuestCheckedIn', data: { guestId } },
319312
{ type: 'GuestCheckedOut', data: { guestId } },
320313
];
@@ -323,30 +316,29 @@ void describe('PostgreSQL event store started consumer', () => {
323316
initialEvents,
324317
);
325318

326-
const events: GuestStayEvent[] = [
319+
const events: ShoppingCartSummaryEvent[] = [
327320
{ type: 'GuestCheckedIn', data: { guestId: otherGuestId } },
328321
{ type: 'GuestCheckedOut', data: { guestId: otherGuestId } },
329322
];
330323

331-
let result: GuestStayEvent[] = [];
324+
let result: ShoppingCartSummaryEvent[] = [];
332325
let stopAfterPosition: bigint | undefined = lastEventGlobalPosition;
333326

334-
const processorOptions: PostgreSQLProcessorOptions<GuestStayEvent> = {
335-
processorId: uuid(),
336-
startFrom: 'CURRENT',
337-
stopAfter: (event) =>
338-
event.metadata.globalPosition === stopAfterPosition,
339-
eachMessage: (event) => {
340-
result.push(event);
341-
},
342-
};
327+
const processorOptions: PostgreSQLProcessorOptions<ShoppingCartSummaryEvent> =
328+
{
329+
processorId: uuid(),
330+
projection: shoppingCartsSummaryProjection,
331+
startFrom: 'CURRENT',
332+
stopAfter: (event) =>
333+
event.metadata.globalPosition === stopAfterPosition,
334+
};
343335

344336
// When
345337
const consumer = postgreSQLEventStoreConsumer({
346338
connectionString,
347339
});
348340
try {
349-
consumer.processor<GuestStayEvent>(processorOptions);
341+
consumer.processor<ShoppingCartSummaryEvent>(processorOptions);
350342

351343
await consumer.start();
352344
} finally {
@@ -360,7 +352,7 @@ void describe('PostgreSQL event store started consumer', () => {
360352
const newConsumer = postgreSQLEventStoreConsumer({
361353
connectionString,
362354
});
363-
newConsumer.processor<GuestStayEvent>(processorOptions);
355+
newConsumer.processor<ShoppingCartSummaryEvent>(processorOptions);
364356

365357
try {
366358
const consumerPromise = newConsumer.start();
@@ -382,7 +374,48 @@ void describe('PostgreSQL event store started consumer', () => {
382374
});
383375
});
384376

385-
type GuestCheckedIn = Event<'GuestCheckedIn', { guestId: string }>;
386-
type GuestCheckedOut = Event<'GuestCheckedOut', { guestId: string }>;
377+
type ShoppingCartSummary = {
378+
_id?: string;
379+
activeCount: number;
380+
activeShopingCarts: string[];
381+
};
382+
383+
const shoppingCartsSummaryCollectionName = 'shoppingCartsSummary';
387384

388-
type GuestStayEvent = GuestCheckedIn | GuestCheckedOut;
385+
export type ShoppingCartSummaryEvent = ProductItemAdded | ShoppingCartConfirmed;
386+
387+
const evolve = (
388+
document: ShoppingCartSummary,
389+
{ type, metadata: { streamName } }: ReadEvent<ShoppingCartSummaryEvent>,
390+
): ShoppingCartSummary => {
391+
switch (type) {
392+
case 'ProductItemAdded': {
393+
if (!document.activeShopingCarts.includes(streamName)) {
394+
document.activeShopingCarts.push(streamName);
395+
document.activeCount++;
396+
}
397+
398+
return document;
399+
}
400+
case 'ShoppingCartConfirmed':
401+
document.activeShopingCarts = document.activeShopingCarts.filter(
402+
(item) => item !== streamName,
403+
);
404+
document.activeCount--;
405+
406+
return document;
407+
default:
408+
return document;
409+
}
410+
};
411+
412+
const shoppingCartsSummaryProjection = pongoMultiStreamProjection({
413+
getDocumentId: (event) => event.metadata.streamName.split(':')[1]!,
414+
collectionName: shoppingCartsSummaryCollectionName,
415+
evolve,
416+
canHandle: ['ProductItemAdded', 'ShoppingCartConfirmed'],
417+
initialState: () => ({
418+
activeCount: 0,
419+
activeShopingCarts: [],
420+
}),
421+
});

src/packages/emmett-postgresql/src/eventStore/consumers/postgreSQLProcessor.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,16 @@ export type GenericPostgreSQLProcessorOptions<EventType extends Event = Event> =
9090

9191
export type PostgreSQLProjectionProcessorOptions<
9292
EventType extends Event = Event,
93-
> = { type: 'projection' } & PostgreSQLProjectionDefinition<EventType> & {
94-
partition?: string;
95-
startFrom?: PostgreSQLProcessorStartFrom;
96-
stopAfter?: (
97-
message: ReadEvent<EventType, ReadEventMetadataWithGlobalPosition>,
98-
) => boolean;
99-
};
93+
> = {
94+
processorId?: string;
95+
version?: number;
96+
projection: PostgreSQLProjectionDefinition<EventType>;
97+
partition?: string;
98+
startFrom?: PostgreSQLProcessorStartFrom;
99+
stopAfter?: (
100+
message: ReadEvent<EventType, ReadEventMetadataWithGlobalPosition>,
101+
) => boolean;
102+
};
100103

101104
export type PostgreSQLProcessorOptions<EventType extends Event = Event> =
102105
| GenericPostgreSQLProcessorOptions<EventType>
@@ -202,12 +205,14 @@ const genericPostgreSQLProcessor = <EventType extends Event = Event>(
202205
export const postgreSQLProjectionProcessor = <EventType extends Event = Event>(
203206
options: PostgreSQLProjectionProcessorOptions<EventType>,
204207
): PostgreSQLProcessor => {
208+
const projection = options.projection;
209+
205210
return genericPostgreSQLProcessor<EventType>({
206-
processorId: `projection:${options.name}`,
211+
processorId: options.processorId ?? `projection:${projection.name}`,
207212
eachMessage: async (event, context) => {
208-
if (!options.canHandle.includes(event.type)) return;
213+
if (!projection.canHandle.includes(event.type)) return;
209214

210-
await options.handle([event], context);
215+
await projection.handle([event], context);
211216
},
212217
...options,
213218
});
@@ -216,7 +221,7 @@ export const postgreSQLProjectionProcessor = <EventType extends Event = Event>(
216221
export const postgreSQLProcessor = <EventType extends Event = Event>(
217222
options: PostgreSQLProcessorOptions<EventType>,
218223
): PostgreSQLProcessor => {
219-
if ('type' in options) {
224+
if ('projection' in options) {
220225
return postgreSQLProjectionProcessor(options);
221226
}
222227

src/packages/emmett-postgresql/src/eventStore/projections/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,21 +86,21 @@ export const handleProjections = async <EventType extends Event = Event>(
8686

8787
export const postgreSQLProjection = <EventType extends Event>(
8888
definition: PostgreSQLProjectionDefinition<EventType>,
89-
): PostgreSQLProjectionDefinition =>
89+
): PostgreSQLProjectionDefinition<EventType> =>
9090
projection<
9191
EventType,
9292
PostgresReadEventMetadata,
9393
PostgreSQLProjectionHandlerContext,
9494
PostgreSQLProjectionDefinition<EventType>
95-
>(definition) as PostgreSQLProjectionDefinition;
95+
>(definition);
9696

9797
export const postgreSQLRawBatchSQLProjection = <EventType extends Event>(
9898
handle: (
9999
events: EventType[],
100100
context: PostgreSQLProjectionHandlerContext,
101101
) => Promise<SQL[]> | SQL[],
102102
...canHandle: CanHandle<EventType>
103-
): PostgreSQLProjectionDefinition =>
103+
): PostgreSQLProjectionDefinition<EventType> =>
104104
postgreSQLProjection<EventType>({
105105
canHandle,
106106
handle: async (events, context) => {
@@ -116,7 +116,7 @@ export const postgreSQLRawSQLProjection = <EventType extends Event>(
116116
context: PostgreSQLProjectionHandlerContext,
117117
) => Promise<SQL> | SQL,
118118
...canHandle: CanHandle<EventType>
119-
): PostgreSQLProjectionDefinition =>
119+
): PostgreSQLProjectionDefinition<EventType> =>
120120
postgreSQLRawBatchSQLProjection<EventType>(
121121
async (events, context) => {
122122
const sqls: SQL[] = [];

0 commit comments

Comments
 (0)