Skip to content

Commit 70dc842

Browse files
committed
Added SQLite append to stream
1 parent 1552a73 commit 70dc842

File tree

5 files changed

+487
-3
lines changed

5 files changed

+487
-3
lines changed
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import {
2+
assertEqual,
3+
assertFalse,
4+
assertIsNotNull,
5+
assertTrue,
6+
type Event,
7+
} from '@event-driven-io/emmett';
8+
import { after, before, describe, it } from 'node:test';
9+
import sqlite3 from 'sqlite3';
10+
import { v4 as uuid } from 'uuid';
11+
import { createEventStoreSchema } from '.';
12+
import { dbConn, type SQLiteConnection } from '../../sqliteConnection';
13+
import { appendToStream } from './appendToStream';
14+
15+
export type PricedProductItem = {
16+
productId: string;
17+
quantity: number;
18+
price: number;
19+
};
20+
21+
export type ShoppingCart = {
22+
productItems: PricedProductItem[];
23+
totalAmount: number;
24+
};
25+
26+
export type ProductItemAdded = Event<
27+
'ProductItemAdded',
28+
{ productItem: PricedProductItem }
29+
>;
30+
export type DiscountApplied = Event<'DiscountApplied', { percent: number }>;
31+
32+
export type ShoppingCartEvent = ProductItemAdded | DiscountApplied;
33+
34+
void describe('appendEvent', () => {
35+
let db: SQLiteConnection;
36+
let conn: sqlite3.Database;
37+
38+
before(async () => {
39+
conn = new sqlite3.Database(':memory:');
40+
41+
db = dbConn(conn);
42+
await createEventStoreSchema(db);
43+
});
44+
45+
after(() => {
46+
conn.close();
47+
});
48+
49+
const events: ShoppingCartEvent[] = [
50+
{
51+
type: 'ProductItemAdded',
52+
data: { productItem: { productId: '1', quantity: 2, price: 30 } },
53+
metadata: { meta: 'data1' },
54+
},
55+
{
56+
type: 'DiscountApplied',
57+
data: { percent: 10 },
58+
metadata: { meta: 'data2' },
59+
},
60+
];
61+
62+
void it('should append events correctly', async () => {
63+
const result = await appendToStream(db, uuid(), 'shopping_cart', events, {
64+
expectedStreamVersion: 0n,
65+
});
66+
67+
assertTrue(result.success);
68+
assertEqual(result.nextStreamPosition, 2n);
69+
assertIsNotNull(result.lastGlobalPosition);
70+
assertTrue(result.lastGlobalPosition > 0n);
71+
});
72+
73+
void it('should append events correctly without expected stream position', async () => {
74+
const result = await appendToStream(
75+
db,
76+
uuid(),
77+
'shopping_cart',
78+
events,
79+
{},
80+
);
81+
82+
assertTrue(result.success);
83+
assertEqual(result.nextStreamPosition, 2n);
84+
assertIsNotNull(result.lastGlobalPosition);
85+
assertTrue(result.lastGlobalPosition > 0n);
86+
});
87+
88+
void it('should append events correctly without optimistic concurrency', async () => {
89+
const streamId = uuid();
90+
await appendToStream(db, streamId, 'shopping_cart', events);
91+
const result = await appendToStream(db, streamId, 'shopping_cart', events);
92+
const resultEvents = await db.query(
93+
'SELECT * FROM emt_events WHERE stream_id = $1',
94+
[streamId],
95+
);
96+
97+
assertEqual(4, resultEvents.length);
98+
assertTrue(result.success);
99+
});
100+
101+
void it('should handle stream position conflict correctly when two streams are created', async () => {
102+
// Given
103+
const streamId = uuid();
104+
105+
const firstResult = await appendToStream(
106+
db,
107+
streamId,
108+
'shopping_cart',
109+
events,
110+
{
111+
expectedStreamVersion: 0n,
112+
},
113+
);
114+
assertTrue(firstResult.success);
115+
116+
// When
117+
const secondResult = await appendToStream(
118+
db,
119+
streamId,
120+
'shopping_cart',
121+
events,
122+
{
123+
expectedStreamVersion: 0n,
124+
},
125+
);
126+
127+
// Then
128+
assertFalse(secondResult.success);
129+
130+
const resultEvents = await db.query(
131+
'SELECT * FROM emt_events WHERE stream_id = $1',
132+
[streamId],
133+
);
134+
135+
assertEqual(events.length, resultEvents.length);
136+
});
137+
138+
void it('should handle stream position conflict correctly when version mismatches', async () => {
139+
// Given
140+
const streamId = uuid();
141+
142+
const creationResult = await appendToStream(
143+
db,
144+
streamId,
145+
'shopping_cart',
146+
events,
147+
);
148+
assertTrue(creationResult.success);
149+
const expectedStreamVersion = creationResult.nextStreamPosition;
150+
151+
const firstResult = await appendToStream(
152+
db,
153+
streamId,
154+
'shopping_cart',
155+
events,
156+
{
157+
expectedStreamVersion,
158+
},
159+
);
160+
161+
assertTrue(firstResult.success);
162+
163+
// When
164+
const secondResult = await appendToStream(
165+
db,
166+
streamId,
167+
'shopping_cart',
168+
events,
169+
{
170+
expectedStreamVersion,
171+
},
172+
);
173+
174+
// Then
175+
assertFalse(secondResult.success);
176+
177+
const resultEvents = await db.query(
178+
'SELECT * FROM emt_events WHERE stream_id = $1',
179+
[streamId],
180+
);
181+
182+
assertEqual(events.length * 2, resultEvents.length);
183+
});
184+
185+
void it('should not have stream position conflict when version matches', async () => {
186+
// Given
187+
const streamId = uuid();
188+
const expectedStreamVersion = 0n;
189+
190+
const firstResult = await appendToStream(
191+
db,
192+
streamId,
193+
'shopping_cart',
194+
events,
195+
{
196+
expectedStreamVersion,
197+
},
198+
);
199+
assertTrue(firstResult.success);
200+
201+
// When
202+
const secondResult = await appendToStream(
203+
db,
204+
streamId,
205+
'shopping_cart',
206+
events,
207+
{
208+
expectedStreamVersion: firstResult.nextStreamPosition,
209+
},
210+
);
211+
212+
// Then
213+
assertTrue(secondResult.success);
214+
215+
const resultEvents = await db.query(
216+
'SELECT * FROM emt_events WHERE stream_id = $1',
217+
[streamId],
218+
);
219+
220+
assertEqual(events.length * 2, resultEvents.length);
221+
});
222+
223+
void it('should handle appending an empty events array gracefully', async () => {
224+
const result = await appendToStream(db, uuid(), 'shopping_cart', []);
225+
226+
assertFalse(result.success);
227+
});
228+
});

0 commit comments

Comments
 (0)