@wataruoguchi/emmett-event-store-kysely 1.0.0

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.
@@ -0,0 +1,82 @@
1
+ export function createProjectionRunner({ db, readStream, registry }) {
2
+ async function getOrCreateCheckpoint(subscriptionId, partition) {
3
+ const existing = await db
4
+ .selectFrom("subscriptions")
5
+ .select([
6
+ "subscription_id as subscriptionId",
7
+ "partition",
8
+ "last_processed_position as lastProcessedPosition",
9
+ ])
10
+ .where("subscription_id", "=", subscriptionId)
11
+ .where("partition", "=", partition)
12
+ .executeTakeFirst();
13
+ if (existing) {
14
+ const last = BigInt(String(existing
15
+ .lastProcessedPosition));
16
+ return {
17
+ subscriptionId,
18
+ partition,
19
+ lastProcessedPosition: last,
20
+ };
21
+ }
22
+ await db
23
+ .insertInto("subscriptions")
24
+ .values({
25
+ subscription_id: subscriptionId,
26
+ partition,
27
+ version: 1,
28
+ last_processed_position: 0n,
29
+ })
30
+ .onConflict((oc) => oc.columns(["subscription_id", "partition", "version"]).doUpdateSet({
31
+ last_processed_position: (eb) => eb.ref("excluded.last_processed_position"),
32
+ }))
33
+ .execute();
34
+ return {
35
+ subscriptionId,
36
+ partition,
37
+ lastProcessedPosition: 0n,
38
+ };
39
+ }
40
+ async function updateCheckpoint(subscriptionId, partition, lastProcessedPosition) {
41
+ await db
42
+ .updateTable("subscriptions")
43
+ .set({ last_processed_position: lastProcessedPosition })
44
+ .where("subscription_id", "=", subscriptionId)
45
+ .where("partition", "=", partition)
46
+ .execute();
47
+ }
48
+ async function projectEvents(subscriptionId, streamId, opts) {
49
+ const partition = opts?.partition ?? "default_partition";
50
+ const batchSize = BigInt(opts?.batchSize ?? 500);
51
+ const checkpoint = await getOrCreateCheckpoint(subscriptionId, partition);
52
+ const { events, currentStreamVersion } = await readStream(streamId, {
53
+ from: checkpoint.lastProcessedPosition + 1n,
54
+ to: checkpoint.lastProcessedPosition + batchSize,
55
+ partition,
56
+ });
57
+ for (const ev of events) {
58
+ if (!ev)
59
+ continue;
60
+ const handlers = registry[ev.type] ?? [];
61
+ if (handlers.length === 0) {
62
+ await updateCheckpoint(subscriptionId, partition, ev.metadata.streamPosition);
63
+ continue;
64
+ }
65
+ const projectionEvent = {
66
+ type: ev.type,
67
+ data: ev.data,
68
+ metadata: {
69
+ streamId: ev.metadata.streamId,
70
+ streamPosition: ev.metadata.streamPosition,
71
+ globalPosition: ev.metadata.globalPosition,
72
+ },
73
+ };
74
+ for (const handler of handlers) {
75
+ await handler({ db, partition }, projectionEvent);
76
+ }
77
+ await updateCheckpoint(subscriptionId, partition, projectionEvent.metadata.streamPosition);
78
+ }
79
+ return { processed: events.length, currentStreamVersion };
80
+ }
81
+ return { projectEvents };
82
+ }
@@ -0,0 +1,120 @@
1
+ import type { DatabaseExecutor, ProjectionEvent, ProjectionHandler, ProjectionRegistry } from "../types.js";
2
+ /**
3
+ * Configuration for snapshot-based projections.
4
+ *
5
+ * @template TState - The aggregate state type that will be stored in the snapshot
6
+ * @template TTable - The table name as a string literal type
7
+ * @template E - The event union type (must be a discriminated union with type and data properties)
8
+ */
9
+ export type SnapshotProjectionConfig<TState, TTable extends string, E extends {
10
+ type: string;
11
+ data: unknown;
12
+ } = {
13
+ type: string;
14
+ data: unknown;
15
+ }> = {
16
+ /**
17
+ * The name of the database table for this projection
18
+ */
19
+ tableName: TTable;
20
+ /**
21
+ * The primary key columns that uniquely identify a row
22
+ * e.g., ['tenant_id', 'cart_id', 'partition']
23
+ */
24
+ primaryKeys: string[];
25
+ /**
26
+ * Extract primary key values from the event data
27
+ */
28
+ extractKeys: (event: ProjectionEvent<E>, partition: string) => Record<string, string>;
29
+ /**
30
+ * The evolve function that takes current state and event, returns new state.
31
+ * This is the same evolve function used in the aggregate.
32
+ */
33
+ evolve: (state: TState, event: ProjectionEvent<E>) => TState;
34
+ /**
35
+ * Initial state for the aggregate when no snapshot exists
36
+ */
37
+ initialState: () => TState;
38
+ /**
39
+ * Optional: Map the snapshot state to individual columns for easier querying.
40
+ * This allows you to denormalize specific fields from the snapshot into table columns.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * mapToColumns: (state) => ({
45
+ * currency: state.currency,
46
+ * items_json: JSON.stringify(state.items),
47
+ * is_checked_out: state.status === 'checkedOut'
48
+ * })
49
+ * ```
50
+ */
51
+ mapToColumns?: (state: TState) => Record<string, unknown>;
52
+ };
53
+ /**
54
+ * Creates a projection handler that stores the aggregate state as a snapshot.
55
+ *
56
+ * This is a generic helper that works with any aggregate that follows the evolve pattern.
57
+ * Instead of manually mapping event fields to table columns, it:
58
+ * 1. Loads the current snapshot from the database (or starts with initial state)
59
+ * 2. Applies the event using the evolve function
60
+ * 3. Stores the new state back to the snapshot column
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * const cartProjection = createSnapshotProjection({
65
+ * tableName: 'carts',
66
+ * primaryKeys: ['tenant_id', 'cart_id', 'partition'],
67
+ * extractKeys: (event, partition) => ({
68
+ * tenant_id: event.data.eventMeta.tenantId,
69
+ * cart_id: event.data.eventMeta.cartId,
70
+ * partition
71
+ * }),
72
+ * evolve: cartEvolve,
73
+ * initialState: () => ({ status: 'init', items: [] })
74
+ * });
75
+ *
76
+ * // Use it in a projection registry
77
+ * const registry: ProjectionRegistry = {
78
+ * CartCreated: [cartProjection],
79
+ * ItemAddedToCart: [cartProjection],
80
+ * // ... other events
81
+ * };
82
+ * ```
83
+ */
84
+ export declare function createSnapshotProjection<TState, TTable extends string, E extends {
85
+ type: string;
86
+ data: unknown;
87
+ } = {
88
+ type: string;
89
+ data: unknown;
90
+ }>(config: SnapshotProjectionConfig<TState, TTable, E>): ProjectionHandler<DatabaseExecutor, E>;
91
+ /**
92
+ * Creates multiple projection handlers that all use the same snapshot projection logic.
93
+ * This is a convenience function to avoid repeating the same handler for multiple event types.
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const registry = createSnapshotProjectionRegistry(
98
+ * ['CartCreated', 'ItemAddedToCart', 'ItemRemovedFromCart'],
99
+ * {
100
+ * tableName: 'carts',
101
+ * primaryKeys: ['tenant_id', 'cart_id', 'partition'],
102
+ * extractKeys: (event, partition) => ({
103
+ * tenant_id: event.data.eventMeta.tenantId,
104
+ * cart_id: event.data.eventMeta.cartId,
105
+ * partition
106
+ * }),
107
+ * evolve: cartEvolve,
108
+ * initialState: () => ({ status: 'init', items: [] })
109
+ * }
110
+ * );
111
+ * ```
112
+ */
113
+ export declare function createSnapshotProjectionRegistry<TState, TTable extends string, E extends {
114
+ type: string;
115
+ data: unknown;
116
+ } = {
117
+ type: string;
118
+ data: unknown;
119
+ }>(eventTypes: E["type"][], config: SnapshotProjectionConfig<TState, TTable, E>): ProjectionRegistry<DatabaseExecutor>;
120
+ //# sourceMappingURL=snapshot-projection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-projection.d.ts","sourceRoot":"","sources":["../../src/projections/snapshot-projection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAEhB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,MAAM,MAAM,wBAAwB,CAClC,MAAM,EACN,MAAM,SAAS,MAAM,EACrB,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,IACzE;IACF;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,WAAW,EAAE,MAAM,EAAE,CAAC;IAEtB;;OAEG;IACH,WAAW,EAAE,CACX,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,EACzB,SAAS,EAAE,MAAM,KACd,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE5B;;;OAGG;IACH,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC;IAE7D;;OAEG;IACH,YAAY,EAAE,MAAM,MAAM,CAAC;IAE3B;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EACN,MAAM,SAAS,MAAM,EACrB,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,EAE3E,MAAM,EAAE,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,GAClD,iBAAiB,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAkGxC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gCAAgC,CAC9C,MAAM,EACN,MAAM,SAAS,MAAM,EACrB,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,EAE3E,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EACvB,MAAM,EAAE,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,GAClD,kBAAkB,CAAC,gBAAgB,CAAC,CAYtC"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Creates a projection handler that stores the aggregate state as a snapshot.
3
+ *
4
+ * This is a generic helper that works with any aggregate that follows the evolve pattern.
5
+ * Instead of manually mapping event fields to table columns, it:
6
+ * 1. Loads the current snapshot from the database (or starts with initial state)
7
+ * 2. Applies the event using the evolve function
8
+ * 3. Stores the new state back to the snapshot column
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const cartProjection = createSnapshotProjection({
13
+ * tableName: 'carts',
14
+ * primaryKeys: ['tenant_id', 'cart_id', 'partition'],
15
+ * extractKeys: (event, partition) => ({
16
+ * tenant_id: event.data.eventMeta.tenantId,
17
+ * cart_id: event.data.eventMeta.cartId,
18
+ * partition
19
+ * }),
20
+ * evolve: cartEvolve,
21
+ * initialState: () => ({ status: 'init', items: [] })
22
+ * });
23
+ *
24
+ * // Use it in a projection registry
25
+ * const registry: ProjectionRegistry = {
26
+ * CartCreated: [cartProjection],
27
+ * ItemAddedToCart: [cartProjection],
28
+ * // ... other events
29
+ * };
30
+ * ```
31
+ */
32
+ export function createSnapshotProjection(config) {
33
+ const { tableName, primaryKeys, extractKeys, evolve, initialState, mapToColumns, } = config;
34
+ return async ({ db, partition }, event) => {
35
+ const keys = extractKeys(event, partition);
36
+ // Check if event is newer than what we've already processed
37
+ // Note: Casting to `any` is necessary because Kysely cannot infer types for dynamic table names.
38
+ // The table name is provided at runtime, so TypeScript cannot verify the table structure at compile time.
39
+ // This is a known limitation when working with dynamic table names in Kysely.
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ const existing = await db
42
+ .selectFrom(tableName)
43
+ .select(["last_stream_position", "snapshot"])
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ .where((eb) => {
46
+ const conditions = Object.entries(keys).map(([key, value]) => eb(key, "=", value));
47
+ return eb.and(conditions);
48
+ })
49
+ .executeTakeFirst();
50
+ const lastPos = existing?.last_stream_position
51
+ ? BigInt(String(existing.last_stream_position))
52
+ : -1n;
53
+ // Skip if we've already processed a newer event
54
+ if (event.metadata.streamPosition <= lastPos) {
55
+ return;
56
+ }
57
+ // Load current state from snapshot or use initial state
58
+ // Note: snapshot is stored as JSONB and Kysely returns it as parsed JSON
59
+ const currentState = existing?.snapshot
60
+ ? existing.snapshot
61
+ : initialState();
62
+ // Apply the event to get new state
63
+ const newState = evolve(currentState, event);
64
+ // Prepare the row data with snapshot
65
+ const rowData = {
66
+ ...keys,
67
+ snapshot: JSON.stringify(newState),
68
+ stream_id: event.metadata.streamId,
69
+ last_stream_position: event.metadata.streamPosition.toString(),
70
+ last_global_position: event.metadata.globalPosition.toString(),
71
+ };
72
+ // If mapToColumns is provided, add the denormalized columns
73
+ if (mapToColumns) {
74
+ const columns = mapToColumns(newState);
75
+ Object.assign(rowData, columns);
76
+ }
77
+ // Upsert the snapshot
78
+ const insertQuery = db.insertInto(tableName).values(rowData);
79
+ const updateSet = {
80
+ snapshot: (eb) => eb.ref("excluded.snapshot"),
81
+ stream_id: (eb) => eb.ref("excluded.stream_id"),
82
+ last_stream_position: (eb) => eb.ref("excluded.last_stream_position"),
83
+ last_global_position: (eb) => eb.ref("excluded.last_global_position"),
84
+ };
85
+ // If mapToColumns is provided, also update the denormalized columns
86
+ if (mapToColumns) {
87
+ const columns = mapToColumns(newState);
88
+ for (const columnName of Object.keys(columns)) {
89
+ updateSet[columnName] = (eb) => eb.ref(`excluded.${columnName}`);
90
+ }
91
+ }
92
+ await insertQuery
93
+ // Note: `any` is used here because the conflict builder needs to work with any table schema.
94
+ // The actual schema is validated at runtime through Kysely's query builder.
95
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
96
+ .onConflict((oc) => {
97
+ const conflictBuilder = oc.columns(primaryKeys);
98
+ return conflictBuilder.doUpdateSet(updateSet);
99
+ })
100
+ .execute();
101
+ };
102
+ }
103
+ /**
104
+ * Creates multiple projection handlers that all use the same snapshot projection logic.
105
+ * This is a convenience function to avoid repeating the same handler for multiple event types.
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * const registry = createSnapshotProjectionRegistry(
110
+ * ['CartCreated', 'ItemAddedToCart', 'ItemRemovedFromCart'],
111
+ * {
112
+ * tableName: 'carts',
113
+ * primaryKeys: ['tenant_id', 'cart_id', 'partition'],
114
+ * extractKeys: (event, partition) => ({
115
+ * tenant_id: event.data.eventMeta.tenantId,
116
+ * cart_id: event.data.eventMeta.cartId,
117
+ * partition
118
+ * }),
119
+ * evolve: cartEvolve,
120
+ * initialState: () => ({ status: 'init', items: [] })
121
+ * }
122
+ * );
123
+ * ```
124
+ */
125
+ export function createSnapshotProjectionRegistry(eventTypes, config) {
126
+ const handler = createSnapshotProjection(config);
127
+ const registry = {};
128
+ for (const eventType of eventTypes) {
129
+ // Type cast is safe here because ProjectionHandler is contravariant in its event type parameter.
130
+ // A handler for a specific event type E can safely handle any event that matches E's structure.
131
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
132
+ registry[eventType] = [handler];
133
+ }
134
+ return registry;
135
+ }
@@ -0,0 +1,77 @@
1
+ import type { Kysely } from "kysely";
2
+ export type DatabaseExecutor<T = any> = Kysely<T>;
3
+ export type Logger = {
4
+ info: (obj: unknown, msg?: string) => void;
5
+ error: (obj: unknown, msg?: string) => void;
6
+ warn?: (obj: unknown, msg?: string) => void;
7
+ debug?: (obj: unknown, msg?: string) => void;
8
+ };
9
+ export type Dependencies<T = any> = {
10
+ db: DatabaseExecutor<T>;
11
+ logger: Logger;
12
+ /** If true, the provided db is already a transaction executor. */
13
+ inTransaction?: boolean;
14
+ };
15
+ export type ExtendedOptions = {
16
+ partition?: string;
17
+ streamType?: string;
18
+ };
19
+ export declare const PostgreSQLEventStoreDefaultStreamVersion = 0n;
20
+ export declare const DEFAULT_PARTITION: "default_partition";
21
+ export type ProjectionEventMetadata = {
22
+ streamId: string;
23
+ streamPosition: bigint;
24
+ globalPosition: bigint;
25
+ };
26
+ /**
27
+ * ProjectionEvent that preserves discriminated union relationships.
28
+ *
29
+ * Instead of independent EventType and EventData generics, this accepts a union type
30
+ * where each variant has a specific type-data pairing. This allows TypeScript to
31
+ * properly narrow the data type when you narrow the event type.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * type MyEvent =
36
+ * | { type: "Created"; data: { id: string } }
37
+ * | { type: "Updated"; data: { name: string } };
38
+ *
39
+ * type MyProjectionEvent = ProjectionEvent<MyEvent>;
40
+ *
41
+ * function handle(event: MyProjectionEvent) {
42
+ * if (event.type === "Created") {
43
+ * // TypeScript knows event.data is { id: string }
44
+ * console.log(event.data.id);
45
+ * }
46
+ * }
47
+ * ```
48
+ */
49
+ export type ProjectionEvent<E extends {
50
+ type: string;
51
+ data: unknown;
52
+ }> = E & {
53
+ metadata: ProjectionEventMetadata;
54
+ };
55
+ export type ProjectionContext<T = DatabaseExecutor<any>> = {
56
+ db: T;
57
+ partition: string;
58
+ };
59
+ export type ProjectionHandler<T = DatabaseExecutor<any>, E extends {
60
+ type: string;
61
+ data: unknown;
62
+ } = {
63
+ type: string;
64
+ data: unknown;
65
+ }> = (ctx: ProjectionContext<T>, event: ProjectionEvent<E>) => void | Promise<void>;
66
+ /**
67
+ * ProjectionRegistry maps event types to their handlers.
68
+ * The `any` in `ProjectionHandler<T, any>[]` is intentional - it allows handlers
69
+ * for different event types to be registered together, with type safety enforced
70
+ * at the handler level through the ProjectionHandler generic parameter.
71
+ */
72
+ export type ProjectionRegistry<T = DatabaseExecutor<any>> = Record<string, ProjectionHandler<T, {
73
+ type: string;
74
+ data: unknown;
75
+ }>[]>;
76
+ export declare function createProjectionRegistry<T = DatabaseExecutor<any>>(...registries: ProjectionRegistry<T>[]): ProjectionRegistry<T>;
77
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGrC,MAAM,MAAM,gBAAgB,CAAC,CAAC,GAAG,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;AAElD,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,GAAG,IAAI;IAClC,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,wCAAwC,KAAK,CAAC;AAC3D,eAAO,MAAM,iBAAiB,EAAG,mBAA4B,CAAC;AAG9D,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,IAAI,CAAC,GAAG;IAC3E,QAAQ,EAAE,uBAAuB,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,iBAAiB,CAAC,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,IAAI;IACzD,EAAE,EAAE,CAAC,CAAC;IACN,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,CAC3B,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,EACzB,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,IACzE,CACF,GAAG,EAAE,iBAAiB,CAAC,CAAC,CAAC,EACzB,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,KACtB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,CAAC,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,IAAI,MAAM,CAChE,MAAM,EACN,iBAAiB,CAAC,CAAC,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,EAAE,CACxD,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,EAChE,GAAG,UAAU,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAAE,GACrC,kBAAkB,CAAC,CAAC,CAAC,CAYvB"}
package/dist/types.js ADDED
@@ -0,0 +1,15 @@
1
+ export const PostgreSQLEventStoreDefaultStreamVersion = 0n;
2
+ export const DEFAULT_PARTITION = "default_partition";
3
+ export function createProjectionRegistry(...registries) {
4
+ const combined = {};
5
+ /**
6
+ * This is necessary because the projection runner can be used to project events from multiple partitions.
7
+ * e.g., the generators-read-model projection runner can be used to project events for partition A, partition B, and partition C.
8
+ */
9
+ for (const reg of registries) {
10
+ for (const [eventType, handlers] of Object.entries(reg)) {
11
+ combined[eventType] = [...(combined[eventType] ?? []), ...handlers];
12
+ }
13
+ }
14
+ return combined;
15
+ }
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@wataruoguchi/emmett-event-store-kysely",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "1.0.0",
7
+ "description": "Emmett Event Store with Kysely",
8
+ "author": "Wataru Oguchi",
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/wataruoguchi/emmett-libs.git"
13
+ },
14
+ "homepage": "https://github.com/wataruoguchi/emmett-libs",
15
+ "bugs": "https://github.com/wataruoguchi/emmett-libs/issues",
16
+ "type": "module",
17
+ "main": "dist/index.js",
18
+ "module": "dist/index.js",
19
+ "types": "dist/types.d.ts",
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js",
27
+ "require": "./dist/index.cjs"
28
+ }
29
+ },
30
+ "scripts": {
31
+ "build": "rm -rf dist && tsc -p tsconfig.build.json && tsup src/index.ts src/projections/index.ts",
32
+ "type-check": "tsc --noEmit",
33
+ "tc": "npm run type-check",
34
+ "test": "npm run type-check && vitest run",
35
+ "release": "semantic-release",
36
+ "release:dry-run": "semantic-release --dry-run"
37
+ },
38
+ "devDependencies": {
39
+ "@semantic-release/commit-analyzer": "^13.0.1",
40
+ "@semantic-release/github": "^11.0.6",
41
+ "@semantic-release/npm": "^12.0.2",
42
+ "@semantic-release/release-notes-generator": "^14.1.0",
43
+ "conventional-changelog-conventionalcommits": "^9.0.0",
44
+ "@types/node": "^24.9.2",
45
+ "@vitest/coverage-v8": "^3.2.4",
46
+ "semantic-release": "^24.2.9",
47
+ "semantic-release-monorepo": "^8.0.2",
48
+ "tsup": "^8.5.0",
49
+ "typescript": "^5.8.3",
50
+ "vitest": "^3.2.4"
51
+ },
52
+ "peerDependencies": {
53
+ "@event-driven-io/emmett": "0",
54
+ "kysely": "0"
55
+ },
56
+ "optionalDependencies": {
57
+ "@rollup/rollup-linux-x64-gnu": "4.9.5"
58
+ },
59
+ "keywords": [
60
+ "emmett",
61
+ "event-store",
62
+ "kysely",
63
+ "postgres",
64
+ "postgresql",
65
+ "Event Sourcing",
66
+ "event-sourcing",
67
+ "read-model",
68
+ "read-models",
69
+ "read-models-projection",
70
+ "read-models-projections"
71
+ ]
72
+ }