@wataruoguchi/emmett-event-store-kysely 1.1.1 → 1.1.3

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/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # @wataruoguchi/emmett-event-store-kysely
2
+
3
+ A Kysely-based event store implementation for [Emmett](https://github.com/event-driven-io/emmett), providing event sourcing capabilities with PostgreSQL.
4
+
5
+ ## Features
6
+
7
+ - **Event Store**: Emmett event store implementation with Kysely
8
+ - **Projections**: Read model projections with automatic event processing
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @wataruoguchi/emmett-event-store-kysely @event-driven-io/emmett kysely
14
+ ```
15
+
16
+ ## Database Setup
17
+
18
+ First, you need to set up the event store tables in your PostgreSQL database. You can achieve this with [the Kysely migration file](https://github.com/wataruoguchi/poc-emmett/blob/main/package/1758758113676_event_sourcing_migration_example.ts)
19
+
20
+ ## Basic Usage
21
+
22
+ You can find [the complete working example](https://github.com/wataruoguchi/poc-emmett/tree/main/example). The following are some snippets.
23
+
24
+ ### 1. Setting up the Event Store
25
+
26
+ ```typescript
27
+ import { createEventStore } from "@wataruoguchi/emmett-event-store-kysely";
28
+ import { Kysely, PostgresDialect } from "kysely";
29
+ import { Pool } from "pg";
30
+
31
+ // Set up your Kysely database connection
32
+ const db = new Kysely<YourDatabaseSchema>({
33
+ dialect: new PostgresDialect({
34
+ pool: new Pool({
35
+ connectionString: process.env.DATABASE_URL,
36
+ }),
37
+ }),
38
+ });
39
+
40
+ // Create the event store
41
+ const eventStore = createEventStore({ db, logger });
42
+ ```
43
+
44
+ ### 2. Using the Event Store with Emmett
45
+
46
+ ```typescript
47
+ import { DeciderCommandHandler } from "@event-driven-io/emmett";
48
+ import type { EventStore } from "@wataruoguchi/emmett-event-store-kysely";
49
+
50
+ // Define your domain events and commands
51
+ type CreateCartCommand = {
52
+ type: "CreateCart";
53
+ data: { tenantId: string; cartId: string; currency: string };
54
+ };
55
+
56
+ type CartCreatedEvent = {
57
+ type: "CartCreated";
58
+ data: { tenantId: string; cartId: string; currency: string };
59
+ };
60
+
61
+ // Create your event handler
62
+ export function cartEventHandler({
63
+ eventStore,
64
+ getContext,
65
+ }: {
66
+ eventStore: EventStore;
67
+ getContext: () => AppContext;
68
+ }) {
69
+ const handler = DeciderCommandHandler({
70
+ decide: createDecide(getContext),
71
+ evolve: createEvolve(),
72
+ initialState,
73
+ });
74
+
75
+ return {
76
+ create: (cartId: string, data: CreateCartCommand["data"]) =>
77
+ handler(
78
+ eventStore,
79
+ cartId,
80
+ { type: "CreateCart", data },
81
+ { partition: data.tenantId, streamType: "cart" }
82
+ ),
83
+ };
84
+ }
85
+
86
+ // Use in your service
87
+ const cartService = createCartService({
88
+ eventStore,
89
+ getContext,
90
+ });
91
+ ```
92
+
93
+ ## Projections (Read Models)
94
+
95
+ ### 1. Creating a Projection
96
+
97
+ ```typescript
98
+ import type {
99
+ ProjectionEvent,
100
+ ProjectionRegistry,
101
+ } from "@wataruoguchi/emmett-event-store-kysely/projections";
102
+
103
+ export function cartsProjection(): ProjectionRegistry<DatabaseExecutor> {
104
+ return {
105
+ CartCreated: async (db, event) => {
106
+ await db
107
+ .insertInto("carts")
108
+ .values({
109
+ stream_id: event.metadata.streamId,
110
+ tenant_id: event.data.tenantId,
111
+ cart_id: event.data.cartId,
112
+ currency: event.data.currency,
113
+ items: JSON.stringify([]),
114
+ total: 0,
115
+ last_stream_position: event.metadata.streamPosition,
116
+ })
117
+ .execute();
118
+ },
119
+ ItemAddedToCart: async (db, event) => {
120
+ // Update cart with new item
121
+ await db
122
+ .updateTable("carts")
123
+ .set({
124
+ items: JSON.stringify([...existingItems, event.data.item]),
125
+ total: newTotal,
126
+ last_stream_position: event.metadata.streamPosition,
127
+ })
128
+ .where("stream_id", "=", event.metadata.streamId)
129
+ .execute();
130
+ },
131
+ };
132
+ }
133
+ ```
134
+
135
+ ### 2. Running Projections
136
+
137
+ ```typescript
138
+ import {
139
+ createProjectionRegistry,
140
+ createProjectionRunner,
141
+ } from "@wataruoguchi/emmett-event-store-kysely/projections";
142
+ import { createReadStream } from "@wataruoguchi/emmett-event-store-kysely";
143
+
144
+ // Set up projection runner
145
+ const readStream = createReadStream({ db, logger });
146
+ const registry = createProjectionRegistry(cartsProjection());
147
+ const runner = createProjectionRunner({
148
+ db,
149
+ readStream,
150
+ registry,
151
+ });
152
+
153
+ // Project events for a specific stream
154
+ await runner.projectEvents("carts-read-model", "cart-123", {
155
+ partition: "tenant-456",
156
+ batchSize: 100,
157
+ });
158
+ ```
159
+
160
+ ### 3. Projection Worker
161
+
162
+ Create a worker to continuously process projections:
163
+
164
+ ```typescript
165
+ #!/usr/bin/env node
166
+ import { createReadStream } from "@wataruoguchi/emmett-event-store-kysely";
167
+ import {
168
+ createProjectionRegistry,
169
+ createProjectionRunner,
170
+ } from "@wataruoguchi/emmett-event-store-kysely/projections";
171
+
172
+ async function main(partition: string) {
173
+ const db = getDb();
174
+ const readStream = createReadStream({ db, logger });
175
+ const registry = createProjectionRegistry(cartsProjection());
176
+ const runner = createProjectionRunner({
177
+ db,
178
+ readStream,
179
+ registry,
180
+ });
181
+
182
+ const subscriptionId = "carts-read-model";
183
+ const batchSize = 200;
184
+ const pollIntervalMs = 1000;
185
+
186
+ while (true) {
187
+ // Get streams for this partition
188
+ const streams = await db
189
+ .selectFrom("streams")
190
+ .select(["stream_id"])
191
+ .where("is_archived", "=", false)
192
+ .where("partition", "=", partition)
193
+ .where("stream_type", "=", "cart")
194
+ .execute();
195
+
196
+ // Process each stream
197
+ for (const stream of streams) {
198
+ await runner.projectEvents(subscriptionId, stream.stream_id, {
199
+ partition,
200
+ batchSize,
201
+ });
202
+ }
203
+
204
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
205
+ }
206
+ }
207
+
208
+ // Run with: node projection-worker.js tenant-123
209
+ main(process.argv[2]);
210
+ ```
211
+
212
+ ## License
213
+
214
+ MIT
215
+
216
+ ## Contributing
217
+
218
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -281,7 +281,10 @@ async function fetchStreamInfo2(executor, streamId, partition) {
281
281
  function createEventStore(deps) {
282
282
  const readStream = createReadStream(deps);
283
283
  const appendToStream = createAppendToStream(deps);
284
- const aggregateStream = createAggregateStream({ readStream }, deps);
284
+ const aggregateStream = createAggregateStream(
285
+ { readStream },
286
+ deps
287
+ );
285
288
  return { readStream, appendToStream, aggregateStream };
286
289
  }
287
290
  // Annotate the CommonJS export names for ESM import in node:
@@ -5,7 +5,7 @@ export type { AppendToStream } from "./append-to-stream.js";
5
5
  export type { ReadStream } from "./read-stream.js";
6
6
  export type EventStore = ReturnType<typeof createEventStore>;
7
7
  export { createReadStream } from "./read-stream.js";
8
- export declare function createEventStore(deps: Dependencies): {
8
+ export declare function createEventStore<T = any>(deps: Dependencies<T>): {
9
9
  readStream: import("./read-stream.js").ReadStream;
10
10
  appendToStream: import("./append-to-stream.js").AppendToStream;
11
11
  aggregateStream: import("./aggregate-stream.js").AggregateStream;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/event-store/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,YAAY;;;;EAKlD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/event-store/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC;;;;EAQ9D"}
package/dist/types.d.ts CHANGED
@@ -1,14 +1,13 @@
1
- import type { Kysely, Transaction } from "kysely";
2
- import type { EventStoreDBSchema } from "./db-schema.js";
3
- export type DatabaseExecutor = Kysely<EventStoreDBSchema> | Transaction<EventStoreDBSchema>;
1
+ import type { Kysely } from "kysely";
2
+ export type DatabaseExecutor<T = any> = Kysely<T>;
4
3
  export type Logger = {
5
4
  info: (obj: unknown, msg?: string) => void;
6
5
  error: (obj: unknown, msg?: string) => void;
7
6
  warn?: (obj: unknown, msg?: string) => void;
8
7
  debug?: (obj: unknown, msg?: string) => void;
9
8
  };
10
- export type Dependencies = {
11
- db: DatabaseExecutor;
9
+ export type Dependencies<T = any> = {
10
+ db: DatabaseExecutor<T>;
12
11
  logger: Logger;
13
12
  };
14
13
  export type ExtendedOptions = {
@@ -27,12 +26,12 @@ export type ProjectionEvent = {
27
26
  data: unknown;
28
27
  metadata: ProjectionEventMetadata;
29
28
  };
30
- export type ProjectionContext<T = DatabaseExecutor> = {
29
+ export type ProjectionContext<T = DatabaseExecutor<any>> = {
31
30
  db: T;
32
31
  partition: string;
33
32
  };
34
- export type ProjectionHandler<T = DatabaseExecutor> = (ctx: ProjectionContext<T>, event: ProjectionEvent) => void | Promise<void>;
35
- export type ProjectionRegistry<T = DatabaseExecutor> = Record<string, ProjectionHandler<T>[]>;
36
- export declare function createProjectionRegistry<T = DatabaseExecutor>(...registries: ProjectionRegistry<T>[]): ProjectionRegistry<T>;
33
+ export type ProjectionHandler<T = DatabaseExecutor<any>> = (ctx: ProjectionContext<T>, event: ProjectionEvent) => void | Promise<void>;
34
+ export type ProjectionRegistry<T = DatabaseExecutor<any>> = Record<string, ProjectionHandler<T>[]>;
35
+ export declare function createProjectionRegistry<T = DatabaseExecutor<any>>(...registries: ProjectionRegistry<T>[]): ProjectionRegistry<T>;
37
36
  export type { ReadStream } from "./event-store/read-stream.js";
38
37
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,MAAM,MAAM,gBAAgB,GACxB,MAAM,CAAC,kBAAkB,CAAC,GAC1B,WAAW,CAAC,kBAAkB,CAAC,CAAC;AAEpC,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,GAAG;IACzB,EAAE,EAAE,gBAAgB,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB,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,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,uBAAuB,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,iBAAiB,CAAC,CAAC,GAAG,gBAAgB,IAAI;IACpD,EAAE,EAAE,CAAC,CAAC;IACN,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,CAAC,CAAC,GAAG,gBAAgB,IAAI,CACpD,GAAG,EAAE,iBAAiB,CAAC,CAAC,CAAC,EACzB,KAAK,EAAE,eAAe,KACnB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B,MAAM,MAAM,kBAAkB,CAAC,CAAC,GAAG,gBAAgB,IAAI,MAAM,CAC3D,MAAM,EACN,iBAAiB,CAAC,CAAC,CAAC,EAAE,CACvB,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,CAAC,GAAG,gBAAgB,EAC3D,GAAG,UAAU,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAAE,GACrC,kBAAkB,CAAC,CAAC,CAAC,CAYvB;AAGD,YAAY,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC"}
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;CAChB,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,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,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,CAAC,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,IAAI,CACzD,GAAG,EAAE,iBAAiB,CAAC,CAAC,CAAC,EACzB,KAAK,EAAE,eAAe,KACnB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B,MAAM,MAAM,kBAAkB,CAAC,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,IAAI,MAAM,CAChE,MAAM,EACN,iBAAiB,CAAC,CAAC,CAAC,EAAE,CACvB,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;AAGD,YAAY,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.1.1",
6
+ "version": "1.1.3",
7
7
  "description": "Emmett Event Store with Kysely",
8
8
  "author": "Wataru Oguchi",
9
9
  "license": "MIT",