@arki/event-sourcing 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/store.ts ADDED
@@ -0,0 +1,367 @@
1
+ import { z } from '@arki/contracts';
2
+ import type { CanHandle, Event, ReadEvent } from '@event-driven-io/emmett';
3
+ import type {
4
+ PostgresEventStore,
5
+ PostgresEventStoreOptions,
6
+ PostgreSQLProjectionHandlerContext,
7
+ PostgresReadEventMetadata,
8
+ } from '@event-driven-io/emmett-postgresql';
9
+ import {
10
+ defaultPostgreSQLOptions,
11
+ getPostgreSQLEventStore,
12
+ postgreSQLProjection,
13
+ } from '@event-driven-io/emmett-postgresql';
14
+ import type { TablesRelationalConfig } from 'drizzle-orm';
15
+ import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
16
+
17
+ import { debugProjection, debugStore } from './debug.js';
18
+
19
+ export type PostgresReadEvent<EventType extends Event> = ReadEvent<EventType, PostgresReadEventMetadata>;
20
+
21
+ export {
22
+ defaultPostgreSQLOptions,
23
+ PostgreSQLEventStoreDefaultStreamVersion,
24
+ getPostgreSQLEventStore,
25
+ postgreSQLProjection,
26
+ } from '@event-driven-io/emmett-postgresql';
27
+
28
+ export const postgresEventStoreConfigSchema = z.object({
29
+ connectionString: z.string().min(1, 'PostgreSQL connection string is required'),
30
+ });
31
+
32
+ export type PostgresEventStoreConfig = z.infer<typeof postgresEventStoreConfigSchema>;
33
+
34
+ export const postgresEventStoreOptionsSchema = z
35
+ .object({
36
+ projections: z.array(z.unknown()).optional(),
37
+ schema: z
38
+ .object({
39
+ autoMigration: z.string().min(1).optional(),
40
+ })
41
+ .passthrough()
42
+ .optional(),
43
+ connectionOptions: z.record(z.string(), z.unknown()).optional(),
44
+ hooks: z.record(z.string(), z.unknown()).optional(),
45
+ })
46
+ .passthrough();
47
+
48
+ export type PostgresEventStoreOptionsInput = z.infer<typeof postgresEventStoreOptionsSchema>;
49
+
50
+ const mergeEventStoreOptions = (
51
+ options: PostgresEventStoreOptionsInput | undefined,
52
+ ): PostgresEventStoreOptions => {
53
+ if (!options) return { ...defaultPostgreSQLOptions };
54
+
55
+ return {
56
+ ...defaultPostgreSQLOptions,
57
+ ...options,
58
+ } as PostgresEventStoreOptions;
59
+ };
60
+
61
+ export type EventStoreWithCleanup = {
62
+ eventStore: PostgresEventStore;
63
+ close: () => Promise<void>;
64
+ };
65
+
66
+ export const getEventStore = (
67
+ connectionString: string,
68
+ options: PostgresEventStoreOptions = defaultPostgreSQLOptions,
69
+ ): EventStoreWithCleanup => {
70
+ debugStore('Creating event store with connection string and options');
71
+ const eventStore = getPostgreSQLEventStore(connectionString, options);
72
+ debugStore('Event store created successfully');
73
+
74
+ return {
75
+ eventStore,
76
+ close: async () => {
77
+ debugStore('Closing event store connection pool');
78
+ try {
79
+ // The emmett-postgresql event store has a close method that closes the underlying Pool
80
+ if (eventStore && typeof (eventStore as any).close === 'function') {
81
+ await (eventStore as any).close();
82
+ debugStore('Event store connection pool closed successfully');
83
+ } else {
84
+ debugStore('Event store does not have a close method, skipping');
85
+ }
86
+ } catch (error) {
87
+ debugStore('Error closing event store: %O', error);
88
+ throw error;
89
+ }
90
+ },
91
+ };
92
+ };
93
+
94
+ export const normalizePostgresEventStoreOptions = (options?: unknown): PostgresEventStoreOptions =>
95
+ mergeEventStoreOptions(
96
+ options === undefined ? undefined : postgresEventStoreOptionsSchema.parse(options),
97
+ );
98
+
99
+ export const createPostgresEventStore = (
100
+ config: unknown,
101
+ options?: unknown,
102
+ ): PostgresEventStore => {
103
+ debugStore('Creating Postgres event store from config');
104
+ const { connectionString } = postgresEventStoreConfigSchema.parse(config);
105
+ debugStore('Config validated successfully');
106
+
107
+ const parsedOptions = normalizePostgresEventStoreOptions(options);
108
+ debugStore('Options normalized: projections=%d', parsedOptions.projections?.length ?? 0);
109
+
110
+ const eventStore = getPostgreSQLEventStore(connectionString, parsedOptions);
111
+ debugStore('Postgres event store created successfully');
112
+ return eventStore;
113
+ };
114
+
115
+ export type DrizzleProjectionHandlerContext<TSchema extends TablesRelationalConfig> =
116
+ PostgreSQLProjectionHandlerContext & {
117
+ db: NodePgDatabase<TSchema>;
118
+ };
119
+
120
+ export { projections, type CanHandle } from '@event-driven-io/emmett';
121
+
122
+ export const drizzleProjection = <EventType extends Event, TSchema extends TablesRelationalConfig>(
123
+ db: NodePgDatabase<TSchema>,
124
+ {
125
+ handle,
126
+ canHandle,
127
+ }: {
128
+ handle: (
129
+ events: PostgresReadEvent<EventType>[],
130
+ context: DrizzleProjectionHandlerContext<TSchema>,
131
+ ) => Promise<void>;
132
+ canHandle: CanHandle<EventType>;
133
+ },
134
+ ) => {
135
+ debugProjection('Creating Drizzle projection with canHandle filter');
136
+ return postgreSQLProjection<EventType>({
137
+ canHandle,
138
+ handle: async (events, context) => {
139
+ debugProjection('Processing %d events in Drizzle projection', events.length);
140
+ await handle(events, {
141
+ ...context,
142
+ db,
143
+ });
144
+ debugProjection('Drizzle projection processed events successfully');
145
+ },
146
+ });
147
+ };
148
+
149
+ export type DrizzleProjectionHandlerContextWithRepo<TRepos> = PostgreSQLProjectionHandlerContext & {
150
+ repos: TRepos;
151
+ };
152
+
153
+ export const drizzleWithRepoProjection = <EventType extends Event, TRepos>(
154
+ repos: TRepos,
155
+ {
156
+ handle,
157
+ canHandle,
158
+ }: {
159
+ handle: (
160
+ events: PostgresReadEvent<EventType>[],
161
+ context: DrizzleProjectionHandlerContextWithRepo<TRepos>,
162
+ ) => Promise<void>;
163
+ canHandle: CanHandle<EventType>;
164
+ },
165
+ ) => {
166
+ debugProjection('Creating Drizzle projection with repository injection');
167
+ return postgreSQLProjection<EventType>({
168
+ canHandle,
169
+ handle: async (events, context) => {
170
+ debugProjection('Processing %d events in Drizzle repo projection', events.length);
171
+ await handle(events, {
172
+ ...context,
173
+ repos,
174
+ });
175
+ debugProjection('Drizzle repo projection processed events successfully');
176
+ },
177
+ });
178
+ };
179
+
180
+ // const handlePongo = async (
181
+ // id: string,
182
+ // handle: DocumentHandler<T>,
183
+ // options?: HandleOptions,
184
+ // ): Promise<PongoHandleResult<T>> => {
185
+ // const { expectedVersion: version, ...operationOptions } = options ?? {};
186
+ // await ensureCollectionCreated(options);
187
+
188
+ // const byId: PongoFilter<T> = { _id: id };
189
+
190
+ // const existing = (await collection.findOne(byId, options)) as WithVersion<T>;
191
+
192
+ // const expectedVersion = expectedVersionValue(version);
193
+
194
+ // if (
195
+ // (existing == undefined && version === 'DOCUMENT_EXISTS') ||
196
+ // (existing == undefined && expectedVersion != undefined) ||
197
+ // (existing != undefined && version === 'DOCUMENT_DOES_NOT_EXIST') ||
198
+ // (existing != undefined && expectedVersion !== null && existing._version !== expectedVersion)
199
+ // ) {
200
+ // return operationResult<PongoHandleResult<T>>(
201
+ // {
202
+ // successful: false,
203
+ // document: existing as T,
204
+ // },
205
+ // { operationName: 'handle', collectionName, errors },
206
+ // );
207
+ // }
208
+
209
+ // const result = await handle(existing as T);
210
+
211
+ // if (existing === result)
212
+ // return operationResult<PongoHandleResult<T>>(
213
+ // {
214
+ // successful: true,
215
+ // document: existing as T,
216
+ // },
217
+ // { operationName: 'handle', collectionName, errors },
218
+ // );
219
+
220
+ // if (!existing && result) {
221
+ // const newDoc = { ...result, _id: id };
222
+ // const insertResult = await collection.insertOne({ ...newDoc, _id: id } as OptionalUnlessRequiredIdAndVersion<T>, {
223
+ // ...operationOptions,
224
+ // expectedVersion: 'DOCUMENT_DOES_NOT_EXIST',
225
+ // });
226
+ // return {
227
+ // ...insertResult,
228
+ // document: {
229
+ // ...newDoc,
230
+ // _version: insertResult.nextExpectedVersion,
231
+ // } as T,
232
+ // };
233
+ // }
234
+
235
+ // if (existing && !result) {
236
+ // const deleteResult = await collection.deleteOne(byId, {
237
+ // ...operationOptions,
238
+ // expectedVersion: expectedVersion ?? 'DOCUMENT_EXISTS',
239
+ // });
240
+ // return { ...deleteResult, document: null };
241
+ // }
242
+
243
+ // if (existing && result) {
244
+ // const replaceResult = await collection.replaceOne(byId, result, {
245
+ // ...operationOptions,
246
+ // expectedVersion: expectedVersion ?? 'DOCUMENT_EXISTS',
247
+ // });
248
+ // return {
249
+ // ...replaceResult,
250
+ // document: {
251
+ // ...result,
252
+ // _version: replaceResult.nextExpectedVersion,
253
+ // } as T,
254
+ // };
255
+ // }
256
+
257
+ // return operationResult<PongoHandleResult<T>>(
258
+ // {
259
+ // successful: true,
260
+ // document: existing as T,
261
+ // },
262
+ // { operationName: 'handle', collectionName, errors },
263
+ // );
264
+ // };
265
+
266
+ // type PongoWithNotNullDocumentEvolve<
267
+ // Document extends PongoDocument,
268
+ // EventType extends Event,
269
+ // EventMetaDataType extends EventMetaDataOf<EventType> &
270
+ // ReadEventMetadataWithGlobalPosition = EventMetaDataOf<EventType> & ReadEventMetadataWithGlobalPosition,
271
+ // > =
272
+ // | ((document: Document, event: ReadEvent<EventType, EventMetaDataType>) => Document | null)
273
+ // | ((document: Document, event: ReadEvent<EventType>) => Promise<Document | null>);
274
+ // type PongoWithNullableDocumentEvolve<
275
+ // Document extends PongoDocument,
276
+ // EventType extends Event,
277
+ // EventMetaDataType extends EventMetaDataOf<EventType> &
278
+ // ReadEventMetadataWithGlobalPosition = EventMetaDataOf<EventType> & ReadEventMetadataWithGlobalPosition,
279
+ // > =
280
+ // | ((document: Document | null, event: ReadEvent<EventType, EventMetaDataType>) => Document | null)
281
+ // | ((document: Document | null, event: ReadEvent<EventType>) => Promise<Document | null>);
282
+
283
+ // export type PongoMultiStreamProjectionOptions<
284
+ // Document extends PongoDocument,
285
+ // EventType extends Event,
286
+ // EventMetaDataType extends EventMetaDataOf<EventType> &
287
+ // ReadEventMetadataWithGlobalPosition = EventMetaDataOf<EventType> & ReadEventMetadataWithGlobalPosition,
288
+ // > = {
289
+ // canHandle: CanHandle<EventType>;
290
+
291
+ // collectionName: string;
292
+ // getDocumentId: (event: ReadEvent<EventType>) => string;
293
+ // } & (
294
+ // | {
295
+ // evolve: PongoWithNullableDocumentEvolve<Document, EventType, EventMetaDataType>;
296
+ // }
297
+ // | {
298
+ // evolve: PongoWithNotNullDocumentEvolve<Document, EventType, EventMetaDataType>;
299
+ // initialState: () => Document;
300
+ // }
301
+ // );
302
+
303
+ // export const pongoMultiStreamProjection = <
304
+ // Document extends PongoDocument,
305
+ // EventType extends Event,
306
+ // EventMetaDataType extends EventMetaDataOf<EventType> &
307
+ // ReadEventMetadataWithGlobalPosition = EventMetaDataOf<EventType> & ReadEventMetadataWithGlobalPosition,
308
+ // >(
309
+ // options: PongoMultiStreamProjectionOptions<Document, EventType, EventMetaDataType>,
310
+ // ): PostgreSQLProjectionDefinition => {
311
+ // const { collectionName, getDocumentId, canHandle } = options;
312
+
313
+ // return pongoProjection({
314
+ // handle: async (events, { pongo }) => {
315
+ // const collection = pongo.db().collection<Document>(collectionName);
316
+
317
+ // for (const event of events) {
318
+ // await collection.handle(getDocumentId(event), async document => {
319
+ // return 'initialState' in options
320
+ // ? await options.evolve(document ?? options.initialState(), event as ReadEvent<EventType, EventMetaDataType>)
321
+ // : await options.evolve(document, event as ReadEvent<EventType, EventMetaDataType>);
322
+ // });
323
+ // }
324
+ // },
325
+ // canHandle,
326
+ // });
327
+ // };
328
+
329
+ // export type PongoSingleStreamProjectionOptions<
330
+ // Document extends PongoDocument,
331
+ // EventType extends Event,
332
+ // EventMetaDataType extends EventMetaDataOf<EventType> &
333
+ // ReadEventMetadataWithGlobalPosition = EventMetaDataOf<EventType> & ReadEventMetadataWithGlobalPosition,
334
+ // > = {
335
+ // canHandle: CanHandle<EventType>;
336
+
337
+ // collectionName: string;
338
+ // } & (
339
+ // | {
340
+ // evolve: PongoWithNullableDocumentEvolve<Document, EventType, EventMetaDataType>;
341
+ // }
342
+ // | {
343
+ // evolve: PongoWithNotNullDocumentEvolve<Document, EventType, EventMetaDataType>;
344
+ // initialState: () => Document;
345
+ // }
346
+ // );
347
+
348
+ // export const pongoSingleStreamProjection = <
349
+ // Document extends PongoDocument,
350
+ // EventType extends Event,
351
+ // EventMetaDataType extends EventMetaDataOf<EventType> &
352
+ // ReadEventMetadataWithGlobalPosition = EventMetaDataOf<EventType> & ReadEventMetadataWithGlobalPosition,
353
+ // >(
354
+ // options: PongoSingleStreamProjectionOptions<Document, EventType, EventMetaDataType>,
355
+ // ): PostgreSQLProjectionDefinition => {
356
+ // return pongoMultiStreamProjection<Document, EventType, EventMetaDataType>({
357
+ // ...options,
358
+ // getDocumentId: event => event.metadata.streamName,
359
+ // });
360
+ // };
361
+
362
+ export {
363
+ type PostgresEventStore,
364
+ type PostgresEventStoreConnectionOptions,
365
+ type PostgreSQLProjectionHandlerContext,
366
+ type PostgresReadEventMetadata
367
+ } from '@event-driven-io/emmett-postgresql';
package/src/types.ts ADDED
@@ -0,0 +1,9 @@
1
+ export type Brand<K, T> = K & {
2
+ readonly __brand: T;
3
+ };
4
+ export type Flavour<K, T> = K & {
5
+ readonly __brand?: T;
6
+ };
7
+ export type DefaultRecord = Record<string, unknown>;
8
+ export type AnyRecord = Record<string, any>;
9
+ export type NonNullable$1<T> = T extends null | undefined ? never : T;
package/dist/package.json DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "name": "dist",
3
- "version": "0.0.0",
4
- "private": true
5
- }