@cosmicdrift/kumiko-framework 0.154.0 → 0.154.1

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.
@@ -144,13 +144,10 @@ export type AddDefineEventArgs = {
144
144
  readonly name: string;
145
145
  readonly schemaSource: string;
146
146
  readonly version?: number;
147
- };
148
-
149
- export type AddEventMigrationArgs = {
150
- readonly event: string;
151
- readonly fromVersion: number;
152
- readonly toVersion: number;
153
- readonly transformSource: string;
147
+ // Keyed by fromVersion (as a string) → the transform source text for the
148
+ // fromVersion -> fromVersion+1 step. Folded in from the former
149
+ // addEventMigration (#1082 step 8).
150
+ readonly migrations?: Readonly<Record<string, string>>;
154
151
  };
155
152
 
156
153
  export type AddProjectionArgs = {
@@ -249,7 +246,6 @@ export type FeaturePatcher = {
249
246
  readonly addProjection: (args: AddProjectionArgs) => void;
250
247
  readonly addMultiStreamProjection: (args: AddMultiStreamProjectionArgs) => void;
251
248
  readonly addDefineEvent: (args: AddDefineEventArgs) => void;
252
- readonly addEventMigration: (args: AddEventMigrationArgs) => void;
253
249
  // --- Symmetric ops (id-driven) ---
254
250
  readonly replace: (id: PatternId, pattern: FeaturePattern) => void;
255
251
  readonly remove: (id: PatternId) => void;
@@ -495,24 +491,22 @@ export function createFeaturePatcher(sourceFile: SourceFile): FeaturePatcher {
495
491
  });
496
492
  },
497
493
 
498
- addDefineEvent({ name, schemaSource, version }) {
494
+ addDefineEvent({ name, schemaSource, version, migrations }) {
495
+ const migrationLocs = migrations
496
+ ? Object.fromEntries(
497
+ Object.entries(migrations).map(([fromVersion, source]) => [
498
+ fromVersion,
499
+ rawLoc(source),
500
+ ]),
501
+ )
502
+ : undefined;
499
503
  add({
500
504
  kind: "defineEvent",
501
505
  source: SYNTHETIC_LOC,
502
506
  eventName: name,
503
507
  schemaSource: rawLoc(schemaSource),
504
508
  ...(version !== undefined && { version }),
505
- });
506
- },
507
-
508
- addEventMigration({ event, fromVersion, toVersion, transformSource }) {
509
- add({
510
- kind: "eventMigration",
511
- source: SYNTHETIC_LOC,
512
- eventName: event,
513
- fromVersion,
514
- toVersion,
515
- transformBody: rawLoc(transformSource),
509
+ ...(migrationLocs !== undefined && { migrations: migrationLocs }),
516
510
  });
517
511
  },
518
512
 
@@ -501,29 +501,22 @@ export type MultiStreamProjectionPattern = {
501
501
  // `r.defineEvent(name, schema, options?)` — registers an event payload
502
502
  // shape and returns the qualified `EventDef`, so callers pass `.name` to
503
503
  // `ctx.appendEvent` instead of hand-building `<feature>:event:<short>`.
504
- // `options.version` declares the CURRENT schema generation (default 1);
505
- // bump it together with an `r.eventMigration` step the framework refuses
506
- // to boot if the chain from 1 to the current version has gaps.
504
+ // `options.version` declares the CURRENT schema generation (default 1).
505
+ // `options.migrations` is a step-wise upcast chainkeyed by fromVersion
506
+ // (toVersion is always fromVersion + 1, enforced at registration, so it is
507
+ // not stored separately); the framework refuses to boot if the chain from
508
+ // 1 to `version` has gaps. Formerly a separate `r.eventMigration()` call
509
+ // per step, folded in here (#1082 step 8) — an event and its schema
510
+ // evolution are one lifecycle, not two registrar concepts.
507
511
  export type DefineEventPattern = {
508
512
  readonly kind: "defineEvent";
509
513
  readonly source: SourceLocation;
510
514
  readonly eventName: string;
511
515
  readonly schemaSource: SourceLocation;
512
516
  readonly version?: number;
513
- };
514
-
515
- // `r.eventMigration(eventName, fromVersion, toVersion, transform)` —
516
- // registers a step-wise payload upcast for event-schema evolution.
517
- // `toVersion` must be `fromVersion + 1`; chain larger jumps step by step.
518
- // Transforms are pure old-payload-in/new-payload-out functions and run once
519
- // per READ, not once per event persisted — keep them cheap.
520
- export type EventMigrationPattern = {
521
- readonly kind: "eventMigration";
522
- readonly source: SourceLocation;
523
- readonly eventName: string;
524
- readonly fromVersion: number;
525
- readonly toVersion: number;
526
- readonly transformBody: SourceLocation;
517
+ // Map fromVersion (as string, e.g. "1") → SourceLocation of the transform
518
+ // closure for the fromVersion → fromVersion+1 step.
519
+ readonly migrations?: Readonly<Record<string, SourceLocation>>;
527
520
  };
528
521
 
529
522
  // `r.extendsRegistrar(extensionName, def)` — declares a named, globally
@@ -618,7 +611,6 @@ export type FeaturePattern =
618
611
  | ProjectionPattern
619
612
  | MultiStreamProjectionPattern
620
613
  | DefineEventPattern
621
- | EventMigrationPattern
622
614
  | ExtendsRegistrarPattern
623
615
  | EnvSchemaPattern
624
616
  // Catch-all
@@ -674,7 +666,6 @@ export function getEditability(pattern: FeaturePattern): Editability {
674
666
  case "projection":
675
667
  case "multiStreamProjection":
676
668
  case "defineEvent":
677
- case "eventMigration":
678
669
  return "mixed";
679
670
  case "authClaims":
680
671
  case "extendsRegistrar":
@@ -26,7 +26,6 @@ import type {
26
26
  EntityHookPattern,
27
27
  EntityPattern,
28
28
  EnvSchemaPattern,
29
- EventMigrationPattern,
30
29
  ExposesApiPattern,
31
30
  ExtendsRegistrarPattern,
32
31
  FeaturePattern,
@@ -129,8 +128,6 @@ export function renderPattern(pattern: FeaturePattern): string {
129
128
  return renderMultiStreamProjection(pattern);
130
129
  case "defineEvent":
131
130
  return renderDefineEvent(pattern);
132
- case "eventMigration":
133
- return renderEventMigration(pattern);
134
131
  case "extendsRegistrar":
135
132
  return renderExtendsRegistrar(pattern);
136
133
  case "usesApi":
@@ -505,16 +502,16 @@ function renderDefineEvent(p: DefineEventPattern): string {
505
502
  lines.push(` name: ${JSON.stringify(p.eventName)},`);
506
503
  lines.push(` schema: ${p.schemaSource.raw},`);
507
504
  if (p.version !== undefined) lines.push(` version: ${p.version},`);
508
- lines.push("});");
509
- return lines.join("\n");
510
- }
511
-
512
- function renderEventMigration(p: EventMigrationPattern): string {
513
- const lines: string[] = ["r.eventMigration({"];
514
- lines.push(` event: ${JSON.stringify(p.eventName)},`);
515
- lines.push(` fromVersion: ${p.fromVersion},`);
516
- lines.push(` toVersion: ${p.toVersion},`);
517
- lines.push(` transform: ${p.transformBody.raw},`);
505
+ if (p.migrations !== undefined) {
506
+ const entries = Object.entries(p.migrations);
507
+ if (entries.length > 0) {
508
+ lines.push(" migrations: {");
509
+ for (const [fromVersion, transformBody] of entries) {
510
+ lines.push(` "${fromVersion}": ${transformBody.raw},`);
511
+ }
512
+ lines.push(" },");
513
+ }
514
+ }
518
515
  lines.push("});");
519
516
  return lines.join("\n");
520
517
  }
@@ -93,6 +93,64 @@ export function buildConfigEventsJobsMethods<TName extends string>(
93
93
  return typeof arg1 === "string" ? handles[arg1] : handles; // @cast-boundary engine-bridge — overload impl signature widens to unknown, narrowed by the two public overloads above
94
94
  }
95
95
 
96
+ // piiFields misconfiguration is a boot-time error, not a silent
97
+ // plaintext leak: both the pii field and its subjectField must exist
98
+ // on the payload schema (checkable when the schema is a ZodObject).
99
+ function validateEventPiiFields(
100
+ eventName: string,
101
+ schema: ZodType,
102
+ piiFields: EventPiiFields,
103
+ ): void {
104
+ const shape = schema instanceof ZodObject ? schema.shape : undefined;
105
+ for (const [field, spec] of Object.entries(piiFields)) {
106
+ if (field === spec.subjectField) {
107
+ throw new Error(
108
+ `[Feature ${name}] defineEvent("${eventName}"): piiFields."${field}" cannot use itself as subjectField — the subject id is a plaintext pseudonymous fk, the pii field is the value it owns.`,
109
+ );
110
+ }
111
+ for (const required of [field, spec.subjectField]) {
112
+ if (shape && !(required in shape)) {
113
+ throw new Error(
114
+ `[Feature ${name}] defineEvent("${eventName}"): piiFields references "${required}" which is not a field of the payload schema.`,
115
+ );
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ // Shared by defineEvent's `migrations` option — each entry registers a
122
+ // single upcast step, same validation/dedup as the old standalone
123
+ // r.eventMigration() call (folded into defineEvent, #1082 step 8).
124
+ function registerEventMigration(
125
+ eventName: string,
126
+ fromVersion: number,
127
+ toVersion: number,
128
+ transform: EventUpcastFn | DeclarativeEventMigration,
129
+ ): void {
130
+ if (toVersion !== fromVersion + 1) {
131
+ throw new Error(
132
+ `[Feature ${name}] defineEvent("${eventName}") migrations: only single-step migrations are allowed — toVersion must be fromVersion + 1 (got ${fromVersion} -> ${toVersion}). ` +
133
+ `Chain larger jumps by declaring each step separately.`,
134
+ );
135
+ }
136
+ if (!Number.isInteger(fromVersion) || fromVersion < 1) {
137
+ throw new Error(
138
+ `[Feature ${name}] defineEvent("${eventName}") migrations: fromVersion must be >= 1, got ${String(fromVersion)}`,
139
+ );
140
+ }
141
+ const qualified = qn(toKebab(name), "event", toKebab(eventName));
142
+ const list = state.eventMigrations[eventName] ?? [];
143
+ if (list.some((m) => m.fromVersion === fromVersion)) {
144
+ throw new Error(
145
+ `[Feature ${name}] defineEvent("${eventName}") migrations: a migration from v${fromVersion} is already declared. Each step may only be declared once.`,
146
+ );
147
+ }
148
+ const transformFn =
149
+ typeof transform === "function" ? transform : compileEventMigration(transform);
150
+ list.push({ eventName: qualified, fromVersion, toVersion, transform: transformFn });
151
+ state.eventMigrations[eventName] = list;
152
+ }
153
+
96
154
  return {
97
155
  config,
98
156
  job(
@@ -136,7 +194,20 @@ export function buildConfigEventsJobsMethods<TName extends string>(
136
194
  defineEvent: <const TInner extends string, TPayload>(
137
195
  eventName: TInner,
138
196
  schema: ZodType<TPayload>,
139
- options?: { readonly version?: number; readonly piiFields?: EventPiiFields },
197
+ options?: {
198
+ readonly version?: number;
199
+ readonly piiFields?: EventPiiFields;
200
+ // Step-wise upcast chain for this event, folded in from the former
201
+ // standalone r.eventMigration() call (#1082 step 8) — an event and
202
+ // its schema evolution are one lifecycle, not two registrar
203
+ // concepts. Each entry's fromVersion must be unique and the chain
204
+ // from 1 to `version` must be gap-free (same validation as before).
205
+ readonly migrations?: readonly {
206
+ readonly fromVersion: number;
207
+ readonly toVersion: number;
208
+ readonly transform: EventUpcastFn | DeclarativeEventMigration;
209
+ }[];
210
+ },
140
211
  ): EventDef<TPayload, QualifiedEventName<TName, TInner>> => {
141
212
  // Return the fully-qualified event name so callers can pass it
142
213
  // straight to ctx.appendEvent without hand-building the
@@ -155,26 +226,9 @@ export function buildConfigEventsJobsMethods<TName extends string>(
155
226
  `[Feature ${name}] defineEvent("${eventName}"): version must be a positive integer, got ${String(version)}`,
156
227
  );
157
228
  }
158
- // piiFields misconfiguration is a boot-time error, not a silent
159
- // plaintext leak: both the pii field and its subjectField must exist
160
- // on the payload schema (checkable when the schema is a ZodObject).
161
229
  const piiFields = options?.piiFields;
162
230
  if (piiFields) {
163
- const shape = schema instanceof ZodObject ? schema.shape : undefined;
164
- for (const [field, spec] of Object.entries(piiFields)) {
165
- if (field === spec.subjectField) {
166
- throw new Error(
167
- `[Feature ${name}] defineEvent("${eventName}"): piiFields."${field}" cannot use itself as subjectField — the subject id is a plaintext pseudonymous fk, the pii field is the value it owns.`,
168
- );
169
- }
170
- for (const required of [field, spec.subjectField]) {
171
- if (shape && !(required in shape)) {
172
- throw new Error(
173
- `[Feature ${name}] defineEvent("${eventName}"): piiFields references "${required}" which is not a field of the payload schema.`,
174
- );
175
- }
176
- }
177
- }
231
+ validateEventPiiFields(eventName, schema, piiFields);
178
232
  }
179
233
  // @cast-boundary engine-bridge — runtime-string mirrors the
180
234
  // template-literal-type via QualifiedEventName + toKebab. Both
@@ -187,38 +241,10 @@ export function buildConfigEventsJobsMethods<TName extends string>(
187
241
  ...(piiFields !== undefined && { piiFields }),
188
242
  };
189
243
  state.events[eventName] = def;
190
- return def;
191
- },
192
- eventMigration(
193
- eventName: string,
194
- fromVersion: number,
195
- toVersion: number,
196
- transform: EventUpcastFn | DeclarativeEventMigration,
197
- ): void {
198
- if (toVersion !== fromVersion + 1) {
199
- throw new Error(
200
- `[Feature ${name}] eventMigration("${eventName}", ${fromVersion}, ${toVersion}): ` +
201
- `only single-step migrations are allowed — toVersion must be fromVersion + 1. ` +
202
- `Chain larger jumps by registering each step separately.`,
203
- );
244
+ for (const m of options?.migrations ?? []) {
245
+ registerEventMigration(eventName, m.fromVersion, m.toVersion, m.transform);
204
246
  }
205
- if (!Number.isInteger(fromVersion) || fromVersion < 1) {
206
- throw new Error(
207
- `[Feature ${name}] eventMigration("${eventName}", ...): fromVersion must be >= 1, got ${String(fromVersion)}`,
208
- );
209
- }
210
- const qualified = qn(toKebab(name), "event", toKebab(eventName));
211
- const list = state.eventMigrations[eventName] ?? [];
212
- if (list.some((m) => m.fromVersion === fromVersion)) {
213
- throw new Error(
214
- `[Feature ${name}] eventMigration("${eventName}", ${fromVersion}, ${toVersion}): ` +
215
- `a migration from v${fromVersion} is already registered. Each step may only be declared once.`,
216
- );
217
- }
218
- const transformFn =
219
- typeof transform === "function" ? transform : compileEventMigration(transform);
220
- list.push({ eventName: qualified, fromVersion, toVersion, transform: transformFn });
221
- state.eventMigrations[eventName] = list;
247
+ return def;
222
248
  },
223
249
  readsConfig(...qualifiedKeys: string[]): void {
224
250
  state.configReads.push(...qualifiedKeys);
@@ -296,7 +322,7 @@ function compileEventMigration(spec: DeclarativeEventMigration): EventUpcastFn {
296
322
  // Registration-time (not replay-time) check: two rename sources mapping to
297
323
  // the same target would silently drop one value on every future replay —
298
324
  // event migrations run against the full production event history, so a
299
- // typo here must fail loud at r.eventMigration() call time, not at replay.
325
+ // typo here must fail loud at defineEvent() registration time, not at replay.
300
326
  const renameTargets = new Map<string, string>();
301
327
  for (const [from, to] of Object.entries(spec.rename ?? {})) {
302
328
  const existing = renameTargets.get(to);
@@ -55,7 +55,6 @@ const ALL_KINDS: FeaturePatternKind[] = [
55
55
  "projection",
56
56
  "multiStreamProjection",
57
57
  "defineEvent",
58
- "eventMigration",
59
58
  "extendsRegistrar",
60
59
  "usesApi",
61
60
  "exposesApi",
@@ -332,15 +331,6 @@ function makePlaceholderPattern(kind: FeaturePatternKind): FeaturePattern {
332
331
  eventName: "x",
333
332
  schemaSource: PLACEHOLDER_BODY_LOC,
334
333
  };
335
- case "eventMigration":
336
- return {
337
- kind,
338
- source: PLACEHOLDER_LOC,
339
- eventName: "x",
340
- fromVersion: 1,
341
- toVersion: 2,
342
- transformBody: PLACEHOLDER_BODY_LOC,
343
- };
344
334
  case "extendsRegistrar":
345
335
  return {
346
336
  kind,
@@ -18,7 +18,6 @@ import {
18
18
  authClaimsSchema,
19
19
  defineEventSchema,
20
20
  entityHookSchema,
21
- eventMigrationSchema,
22
21
  hookSchema,
23
22
  httpRouteSchema,
24
23
  jobSchema,
@@ -90,7 +89,6 @@ export const PATTERN_LIBRARY: Readonly<Record<FeaturePatternKind, PatternFormSch
90
89
  projection: projectionSchema,
91
90
  multiStreamProjection: multiStreamProjectionSchema,
92
91
  defineEvent: defineEventSchema,
93
- eventMigration: eventMigrationSchema,
94
92
  extendsRegistrar: extendsRegistrarSchema,
95
93
  usesApi: usesApiSchema,
96
94
  exposesApi: exposesApiSchema,
@@ -443,42 +443,15 @@ export const defineEventSchema: PatternFormSchema = {
443
443
  input: "number",
444
444
  min: 1,
445
445
  },
446
- ],
447
- };
448
-
449
- export const eventMigrationSchema: PatternFormSchema = {
450
- kind: "eventMigration",
451
- label: { en: "Event migration", de: "Event-Migration" },
452
- summary: { en: "Step-wise transform between event versions." },
453
- category: "data",
454
- editability: "mixed",
455
- fields: [
456
446
  {
457
- path: "eventName",
458
- label: { en: "Event", de: "Event" },
459
- input: "text",
460
- required: true,
461
- },
462
- {
463
- path: "fromVersion",
464
- label: { en: "From version", de: "Von Version" },
465
- input: "number",
466
- min: 1,
467
- required: true,
468
- },
469
- {
470
- path: "toVersion",
471
- label: { en: "To version", de: "Auf Version" },
472
- input: "number",
473
- min: 2,
474
- required: true,
475
- },
476
- {
477
- path: "transformBody",
478
- label: { en: "Transform (source)", de: "Transform (Source)" },
479
- input: "code-block",
480
- language: "typescript",
481
- readOnly: true,
447
+ path: "migrations",
448
+ label: {
449
+ en: "Migrations (fromVersion → transform)",
450
+ de: "Migrationen (fromVersion → Transform)",
451
+ },
452
+ input: "key-value-map",
453
+ keyPlaceholder: "1",
454
+ valueInput: "code-block",
482
455
  },
483
456
  ],
484
457
  };
@@ -314,23 +314,23 @@ export function validateEventMigrationVersions(
314
314
  features: readonly FeatureDefinition[],
315
315
  ): void {
316
316
  // Build + validate event upcaster chains. Run AFTER all features are
317
- // ingested so r.eventMigration calls can reference events from any
318
- // feature (same feature in practice, but the check stays lax for future
319
- // cross-feature event packs).
317
+ // ingested so defineEvent's `migrations` option can reference events from
318
+ // any feature (same feature in practice, but the check stays lax for
319
+ // future cross-feature event packs).
320
320
  for (const feature of features) {
321
321
  for (const [shortName, migrations] of Object.entries(feature.eventMigrations ?? {})) {
322
322
  const qualified = qualify(feature.name, "event", shortName);
323
323
  const eventDef = state.eventMap.get(qualified);
324
324
  if (!eventDef) {
325
325
  throw new Error(
326
- `Feature "${feature.name}" registered r.eventMigration for "${shortName}" ` +
326
+ `Feature "${feature.name}" has migrations declared for event "${shortName}" ` +
327
327
  `but no r.defineEvent exists for that name. Register the event first.`,
328
328
  );
329
329
  }
330
330
  for (const m of migrations) {
331
331
  if (m.toVersion > eventDef.version) {
332
332
  throw new Error(
333
- `Feature "${feature.name}" has r.eventMigration("${shortName}", ${m.fromVersion}, ${m.toVersion}) ` +
333
+ `Feature "${feature.name}" declares a migration for event "${shortName}" from v${m.fromVersion} to v${m.toVersion} ` +
334
334
  `but r.defineEvent declares only version ${eventDef.version}. ` +
335
335
  `Bump the version in defineEvent to at least ${m.toVersion}, or remove the migration.`,
336
336
  );
@@ -364,7 +364,7 @@ export function buildEventUpcasterChains(
364
364
  if (!chainMap.has(v)) {
365
365
  throw new Error(
366
366
  `Event "${qualified}" declares version ${eventDef.version} but no migration ` +
367
- `covers the step v${v} → v${v + 1}. Register r.eventMigration("${qualified.split(":").pop() ?? qualified}", ${v}, ${v + 1}, transform) ` +
367
+ `covers the step v${v} → v${v + 1}. Add { fromVersion: ${v}, toVersion: ${v + 1}, transform } to r.defineEvent("${qualified.split(":").pop() ?? qualified}", ...)'s migrations array ` +
368
368
  `so stored v${v} payloads can be upcast on read.`,
369
369
  );
370
370
  }
@@ -298,7 +298,7 @@ export type FeatureDefinition = {
298
298
  readonly referenceData: readonly ReferenceDataDef[];
299
299
  readonly notifications: Readonly<Record<string, NotificationDefinition>>;
300
300
  readonly events: Readonly<Record<string, EventDef>>;
301
- // Event schema migrations declared via r.eventMigration(). Keyed by event
301
+ // Event schema migrations declared via defineEvent's `migrations` option. Keyed by event
302
302
  // short-name; each entry carries the step transforms (fromVersion →
303
303
  // toVersion). The registry stitches these with the defineEvent-declared
304
304
  // current version and exposes a per-qualified-name upcaster chain.
@@ -532,9 +532,14 @@ export type FeatureRegistrar<TFeature extends string = string> = {
532
532
  // "<feature>:event:<short>" string.
533
533
  //
534
534
  // `options.version` declares the CURRENT schema generation. Defaults to 1
535
- // on first registration. When you bump the payload shape, raise version
536
- // AND register r.eventMigration(shortName, N, N+1, transform) — the
537
- // framework refuses to boot if the chain from 1 version has gaps.
535
+ // on first registration. When you bump the payload shape, add a step to
536
+ // `options.migrations` covering N -> N+1 — the framework refuses to boot
537
+ // if the chain from 1 to `version` has gaps. Migrations were formerly a
538
+ // separate r.eventMigration() call; folded in here because an event and
539
+ // its schema evolution are one lifecycle, not two registrar concepts
540
+ // (#1082 step 8) — transforms are pure functions (old payload in, new
541
+ // payload out) and run once per read, not once per event persisted, so
542
+ // keep them cheap.
538
543
  //
539
544
  // `options.piiFields` declares PII payload fields encrypted under the DEK
540
545
  // of the user named by `subjectField` (crypto-shredding, #799). append()
@@ -542,21 +547,17 @@ export type FeatureRegistrar<TFeature extends string = string> = {
542
547
  defineEvent<const TInner extends string, TPayload>(
543
548
  name: TInner,
544
549
  schema: ZodType<TPayload>,
545
- options?: { readonly version?: number; readonly piiFields?: EventPiiFields },
550
+ options?: {
551
+ readonly version?: number;
552
+ readonly piiFields?: EventPiiFields;
553
+ readonly migrations?: readonly {
554
+ readonly fromVersion: number;
555
+ readonly toVersion: number;
556
+ readonly transform: EventUpcastFn | DeclarativeEventMigration;
557
+ }[];
558
+ },
546
559
  ): EventDef<TPayload, QualifiedEventName<TFeature, TInner>>;
547
560
 
548
- // Register a step-wise payload transform for event-schema evolution.
549
- // `eventName` is the SHORT name (same as defineEvent). `toVersion` must
550
- // be `fromVersion + 1` — chain larger jumps by registering each step.
551
- // Transforms are pure functions: old payload in, new payload out. They
552
- // run once per read (not once per event persisted), so keep them cheap.
553
- eventMigration(
554
- eventName: string,
555
- fromVersion: number,
556
- toVersion: number,
557
- transform: EventUpcastFn | DeclarativeEventMigration,
558
- ): void;
559
-
560
561
  readsConfig(...qualifiedKeys: string[]): void;
561
562
 
562
563
  referenceData(
@@ -736,9 +736,12 @@ export type AggregateStreamHandle = {
736
736
  // ctx-arg, run the lookup via ctx.db, return a Promise. The framework
737
737
  // awaits unconditionally — sync transforms return a plain value and pay
738
738
  // only the await-microtask overhead. Pattern-match Marten:
739
- // r.eventMigration("invoiceCreated", 1, 2, async (payload, ctx) => {
740
- // const customer = await ctx.db.select().from(customersTable)...;
741
- // return { ...payload, customerSegment: customer.segment };
739
+ // r.defineEvent("invoiceCreated", schema, {
740
+ // version: 2,
741
+ // migrations: [{ fromVersion: 1, toVersion: 2, transform: async (payload, ctx) => {
742
+ // const customer = await ctx.db.select().from(customersTable)...;
743
+ // return { ...payload, customerSegment: customer.segment };
744
+ // } }],
742
745
  // });
743
746
  export type EventUpcastCtx = {
744
747
  readonly db: import("../../db").DbRunner;