1- import { assertThatArray , type Event } from '@event-driven-io/emmett' ;
1+ import { assertThatArray , type ReadEvent } from '@event-driven-io/emmett' ;
22import {
33 PostgreSqlContainer ,
44 StartedPostgreSqlContainer ,
55} from '@testcontainers/postgresql' ;
66import { after , before , describe , it } from 'node:test' ;
77import { v4 as uuid } from 'uuid' ;
8+ import type { ShoppingCartConfirmed } from '../../testing/shoppingCart.domain' ;
89import {
910 getPostgreSQLEventStore ,
1011 type PostgresEventStore ,
1112} from '../postgreSQLEventStore' ;
13+ import { pongoMultiStreamProjection } from '../projections' ;
14+ import type { ProductItemAdded } from '../projections/postgresProjection.customid.int.spec' ;
1215import { postgreSQLEventStoreConsumer } from './postgreSQLEventStoreConsumer' ;
1316import 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+ } ) ;
0 commit comments