Skip to content

Commit 80619aa

Browse files
authored
Merge pull request #18 from wataruoguchi/refactor_primary_keys
fix(emmett-event-store-kysely): deprecate primary_keys
2 parents 3420a49 + 296a845 commit 80619aa

11 files changed

Lines changed: 38 additions & 34 deletions

File tree

docs/emmett-event-store-kysely.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,6 @@ const registry = createSnapshotProjectionRegistry(
185185
["CartCreated", "ItemAdded", "CartCheckedOut"],
186186
{
187187
tableName: "carts",
188-
primaryKeys: ["tenant_id", "cart_id", "partition"],
189188
extractKeys: (event, partition) => ({
190189
tenant_id: event.data.eventMeta.tenantId,
191190
cart_id: event.data.eventMeta.cartId,
@@ -201,6 +200,16 @@ const registry = createSnapshotProjectionRegistry(
201200
);
202201
```
203202

203+
**Arguments:**
204+
205+
- **First argument**: Array of event types to handle
206+
- **Second argument**: Configuration object
207+
- `tableName`: Database table name for the projection
208+
- `extractKeys`: Function that returns primary key values from the event (keys are inferred automatically)
209+
- `evolve`: Your domain's evolve function (reuse from write model)
210+
- `initialState`: Function that returns the initial aggregate state
211+
- `mapToColumns` _(optional)_: Function to denormalize state fields into table columns for querying
212+
204213
### Process Events (On-Demand)
205214

206215
For on-demand processing (tests, backfills, or scheduled jobs), use the projection runner:
@@ -339,7 +348,6 @@ const registry = createSnapshotProjectionRegistry(
339348
["CartCreated", "ItemAdded"],
340349
{
341350
tableName: "carts",
342-
primaryKeys: ["tenant_id", "cart_id"],
343351
extractKeys: (event, partition) => ({ /* ... */ }),
344352
evolve: domainEvolve,
345353
initialState: () => ({ /* ... */ }),

example/src/docs/PROJECTIONS_ARCHITECTURE.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ export function cartsSnapshotProjection() {
4242
["CartCreated", "ItemAdded", "CartCheckedOut"],
4343
{
4444
tableName: "carts",
45-
primaryKeys: ["tenant_id", "cart_id", "partition"],
4645
extractKeys: (event, partition) => ({
4746
tenant_id: event.data.eventMeta.tenantId,
4847
cart_id: event.data.eventMeta.cartId,

example/src/docs/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ const registry = createSnapshotProjectionRegistry(
5353
["CartCreated", "ItemAdded", "CartCheckedOut"],
5454
{
5555
tableName: "carts",
56-
primaryKeys: ["tenant_id", "cart_id", "partition"],
5756
extractKeys: (event, partition) => ({...}),
5857
evolve: domainEvolve, // Same logic as write model
5958
initialState,

example/src/modules/cart/application/event-sourcing/cart.read-model.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ export function cartsSnapshotProjection(): ProjectionRegistry<DatabaseExecutor>
6969
],
7070
{
7171
tableName: "carts",
72-
primaryKeys: ["tenant_id", "cart_id", "partition"],
7372
extractKeys: (
7473
event: ProjectionEvent<CartDomainEvent>,
7574
partition: string,

example/src/modules/generator/application/event-sourcing/generator.read-model.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ export function generatorsSnapshotProjection(): ProjectionRegistry<DatabaseExecu
6060
GeneratorDomainEvent
6161
>(["GeneratorCreated", "GeneratorUpdated", "GeneratorDeleted"], {
6262
tableName: "generators",
63-
primaryKeys: ["tenant_id", "generator_id", "partition"],
6463
extractKeys: (
6564
event: ProjectionEvent<GeneratorDomainEvent>,
6665
partition: string,
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
{
22
"extends": ["semantic-release-monorepo", "../../.releaserc.json"]
33
}
4-

packages/emmett-event-store-kysely/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ const registry = createSnapshotProjectionRegistry(
8181
["CartCreated", "ItemAdded", "CartCheckedOut"],
8282
{
8383
tableName: "carts",
84-
primaryKeys: ["tenant_id", "cart_id", "partition"],
8584
extractKeys: (event, partition) => ({
8685
tenant_id: event.data.eventMeta.tenantId,
8786
cart_id: event.data.eventMeta.cartId,

packages/emmett-event-store-kysely/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type {
1010
ProjectionReadStreamOptions,
1111
} from "./event-store/kysely-event-store.js";
1212
export { createProjectionRunner } from "./projections/runner.js";
13+
export type { ProjectEvents } from "./projections/runner.js";
1314
export {
1415
createSnapshotProjection,
1516
createSnapshotProjectionRegistry,

packages/emmett-event-store-kysely/src/projections/runner.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,17 @@ export type ProjectionRunnerDeps = {
2020
registry: ProjectionRegistry;
2121
};
2222

23+
export type ProjectEvents = (
24+
subscriptionId: string,
25+
streamId: string,
26+
opts?: { partition?: string; batchSize?: number },
27+
) => Promise<{ processed: number; currentStreamVersion: bigint }>;
28+
2329
export function createProjectionRunner({
2430
db,
2531
readStream,
2632
registry,
27-
}: ProjectionRunnerDeps) {
33+
}: ProjectionRunnerDeps): { projectEvents: ProjectEvents } {
2834
type EventWithMetadata = Event & {
2935
metadata: {
3036
streamId: string;

packages/emmett-event-store-kysely/src/projections/snapshot-projection.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@ export type SnapshotProjectionConfig<
2525
tableName: TTable;
2626

2727
/**
28-
* The primary key columns that uniquely identify a row
28+
* @deprecated The primary key columns are now automatically inferred from the keys returned by extractKeys.
29+
* This field is optional and will be removed in a future version.
30+
*
31+
* If provided, it will be validated against the keys returned by extractKeys.
2932
* e.g., ['tenant_id', 'cart_id', 'partition']
3033
*/
31-
primaryKeys: string[];
34+
primaryKeys?: string[];
3235

3336
/**
34-
* Extract primary key values from the event data
37+
* Extract primary key values from the event data.
38+
* The keys of the returned object will be used as the primary key columns for upsert operations.
3539
*/
3640
extractKeys: (
3741
event: ProjectionEvent<E>,
@@ -78,7 +82,6 @@ export type SnapshotProjectionConfig<
7882
* ```typescript
7983
* const cartProjection = createSnapshotProjection({
8084
* tableName: 'carts',
81-
* primaryKeys: ['tenant_id', 'cart_id', 'partition'],
8285
* extractKeys: (event, partition) => ({
8386
* tenant_id: event.data.eventMeta.tenantId,
8487
* cart_id: event.data.eventMeta.cartId,
@@ -103,21 +106,24 @@ export function createSnapshotProjection<
103106
>(
104107
config: SnapshotProjectionConfig<TState, TTable, E>,
105108
): ProjectionHandler<DatabaseExecutor, E> {
106-
const {
107-
tableName,
108-
primaryKeys,
109-
extractKeys,
110-
evolve,
111-
initialState,
112-
mapToColumns,
113-
} = config;
109+
const { tableName, extractKeys, evolve, initialState, mapToColumns } = config;
110+
111+
// Cache the inferred primary keys after the first call
112+
let inferredPrimaryKeys: string[] | undefined;
114113

115114
return async (
116115
{ db, partition }: ProjectionContext<DatabaseExecutor>,
117116
event: ProjectionEvent<E>,
118117
) => {
119118
const keys = extractKeys(event, partition);
120119

120+
// Infer primary keys from extractKeys on first call
121+
if (!inferredPrimaryKeys) {
122+
inferredPrimaryKeys = Object.keys(keys);
123+
}
124+
125+
const primaryKeys = inferredPrimaryKeys;
126+
121127
// Check if event is newer than what we've already processed
122128
// Note: Casting to `any` is necessary because Kysely cannot infer types for dynamic table names.
123129
// The table name is provided at runtime, so TypeScript cannot verify the table structure at compile time.
@@ -212,7 +218,6 @@ export function createSnapshotProjection<
212218
* ['CartCreated', 'ItemAddedToCart', 'ItemRemovedFromCart'],
213219
* {
214220
* tableName: 'carts',
215-
* primaryKeys: ['tenant_id', 'cart_id', 'partition'],
216221
* extractKeys: (event, partition) => ({
217222
* tenant_id: event.data.eventMeta.tenantId,
218223
* cart_id: event.data.eventMeta.cartId,

0 commit comments

Comments
 (0)