Skip to content

Commit 1cdac4a

Browse files
committed
Added optional getDocumentId selector for single stream projection
Thanks to that, one can still use custom id based on the event data
1 parent 7bf96d4 commit 1cdac4a

File tree

2 files changed

+121
-1
lines changed

2 files changed

+121
-1
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ export type PongoSingleStreamProjectionOptions<
167167
PostgresReadEventMetadata = PostgresReadEventMetadata,
168168
> = {
169169
canHandle: CanHandle<EventType>;
170+
getDocumentId?: (event: ReadEvent<EventType>) => string;
170171

171172
collectionName: string;
172173
} & (
@@ -201,6 +202,7 @@ export const pongoSingleStreamProjection = <
201202
): PostgreSQLProjectionDefinition => {
202203
return pongoMultiStreamProjection<Document, EventType, EventMetaDataType>({
203204
...options,
204-
getDocumentId: (event) => event.metadata.streamName,
205+
getDocumentId:
206+
options.getDocumentId ?? ((event) => event.metadata.streamName),
205207
});
206208
};
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import type { Event } from '@event-driven-io/emmett';
2+
import {
3+
PostgreSqlContainer,
4+
StartedPostgreSqlContainer,
5+
} from '@testcontainers/postgresql';
6+
import { after, before, beforeEach, describe, it } from 'node:test';
7+
import { v4 as uuid } from 'uuid';
8+
import {
9+
expectPongoDocuments,
10+
pongoSingleStreamProjection,
11+
PostgreSQLProjectionSpec,
12+
} from '.';
13+
import type {
14+
DiscountApplied,
15+
PricedProductItem,
16+
} from '../../testing/shoppingCart.domain';
17+
18+
export type ProductItemAdded = Event<
19+
'ProductItemAdded',
20+
{ productItem: PricedProductItem; shoppingCartId: string }
21+
>;
22+
23+
void describe('Postgres Projections', () => {
24+
let postgres: StartedPostgreSqlContainer;
25+
let connectionString: string;
26+
let given: PostgreSQLProjectionSpec<ProductItemAdded | DiscountApplied>;
27+
let shoppingCartId: string;
28+
let streamName: string;
29+
30+
before(async () => {
31+
postgres = await new PostgreSqlContainer().start();
32+
connectionString = postgres.getConnectionUri();
33+
34+
given = PostgreSQLProjectionSpec.for({
35+
projection: shoppingCartShortInfoProjection,
36+
connectionString,
37+
});
38+
});
39+
40+
beforeEach(() => {
41+
shoppingCartId = uuid();
42+
streamName = `shoppingCart:${shoppingCartId}`;
43+
});
44+
45+
after(async () => {
46+
try {
47+
await postgres.stop();
48+
} catch (error) {
49+
console.log(error);
50+
}
51+
});
52+
53+
void it('uses custom document id instead of stream name assigned in projection evolve', () =>
54+
given([])
55+
.when([
56+
{
57+
type: 'ProductItemAdded',
58+
data: {
59+
productItem: { price: 100, productId: 'shoes', quantity: 100 },
60+
shoppingCartId,
61+
},
62+
metadata: {
63+
streamName,
64+
},
65+
},
66+
])
67+
.then(
68+
expectPongoDocuments
69+
.fromCollection<ShoppingCartShortInfo>(
70+
shoppingCartShortInfoCollectionName,
71+
)
72+
.withId(shoppingCartId)
73+
.toBeEqual({
74+
_id: shoppingCartId,
75+
productItemsCount: 100,
76+
totalAmount: 10000,
77+
}),
78+
));
79+
});
80+
81+
type ShoppingCartShortInfo = {
82+
_id?: string;
83+
productItemsCount: number;
84+
totalAmount: number;
85+
};
86+
87+
const shoppingCartShortInfoCollectionName = 'shoppingCartShortInfo';
88+
89+
const evolve = (
90+
document: ShoppingCartShortInfo,
91+
{ type, data: event }: ProductItemAdded,
92+
): ShoppingCartShortInfo => {
93+
switch (type) {
94+
case 'ProductItemAdded':
95+
return {
96+
...document,
97+
_id: event.shoppingCartId,
98+
totalAmount:
99+
document.totalAmount +
100+
event.productItem.price * event.productItem.quantity,
101+
productItemsCount:
102+
document.productItemsCount + event.productItem.quantity,
103+
};
104+
default:
105+
return document;
106+
}
107+
};
108+
109+
const shoppingCartShortInfoProjection = pongoSingleStreamProjection({
110+
collectionName: shoppingCartShortInfoCollectionName,
111+
evolve,
112+
getDocumentId: (event) => event.data.shoppingCartId,
113+
canHandle: ['ProductItemAdded'],
114+
initialState: () => ({
115+
productItemsCount: 0,
116+
totalAmount: 0,
117+
}),
118+
});

0 commit comments

Comments
 (0)