@lunora/replica 1.0.0-alpha.2 → 1.0.0-alpha.21

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.
Files changed (58) hide show
  1. package/LICENSE.md +214 -0
  2. package/README.md +2 -0
  3. package/dist/adapters/better-sqlite3.d.mts +14 -13
  4. package/dist/adapters/better-sqlite3.d.ts +14 -13
  5. package/dist/adapters/better-sqlite3.mjs +1 -29
  6. package/dist/adapters/sqlite-wasm.d.mts +29 -18
  7. package/dist/adapters/sqlite-wasm.d.ts +29 -18
  8. package/dist/adapters/sqlite-wasm.mjs +1 -56
  9. package/dist/adapters/sqljs.d.mts +10 -9
  10. package/dist/adapters/sqljs.d.ts +10 -9
  11. package/dist/adapters/sqljs.mjs +1 -55
  12. package/dist/index.d.mts +646 -531
  13. package/dist/index.d.ts +646 -531
  14. package/dist/index.mjs +1 -20
  15. package/dist/packem_shared/EventEmitter-ovTsLeAj.mjs +1 -0
  16. package/dist/packem_shared/EventLog-DmlRY_4Z.mjs +1 -0
  17. package/dist/packem_shared/EventLogDO-Cb-7iN9w.mjs +1 -0
  18. package/dist/packem_shared/EventLogDOClient-C6gA3b3u.mjs +1 -0
  19. package/dist/packem_shared/EventSource-B5UTlkl9.mjs +1 -0
  20. package/dist/packem_shared/EventsSync-DWyGGZQZ.mjs +1 -0
  21. package/dist/packem_shared/InMemorySnapshotStore-C4taIG5K.mjs +1 -0
  22. package/dist/packem_shared/LocalMirror-Q7cPH_rX.mjs +4 -0
  23. package/dist/packem_shared/MaterializerRuntime-CqGGjSxl.mjs +1 -0
  24. package/dist/packem_shared/SubscriptionManager-CbSjG_GA.mjs +1 -0
  25. package/dist/packem_shared/applyDiff-BUzddc6r.mjs +1 -0
  26. package/dist/packem_shared/applyDiffToDb-1M2I3BHi.mjs +1 -0
  27. package/dist/packem_shared/classifyChanges-IoOGDVVx.mjs +1 -0
  28. package/dist/packem_shared/defineEvents-CJZV8Bgi.mjs +1 -0
  29. package/dist/packem_shared/eventsContext-Dxow9Y7S.mjs +1 -0
  30. package/dist/packem_shared/isClientSeq-DSXBJskD.mjs +1 -0
  31. package/dist/packem_shared/local-mirror.d-CyGOpUES.d.ts +530 -0
  32. package/dist/packem_shared/local-mirror.d-DTavX_y0.d.mts +530 -0
  33. package/dist/packem_shared/subscribeToMirror-Dru_AYBu.mjs +1 -0
  34. package/dist/packem_shared/{types.d-VfJ76cK4.d.mts → types.d-CkMkSwLJ.d.mts} +10 -9
  35. package/dist/packem_shared/{types.d-VfJ76cK4.d.ts → types.d-CkMkSwLJ.d.ts} +10 -9
  36. package/dist/react.d.mts +59 -57
  37. package/dist/react.d.ts +59 -57
  38. package/dist/react.mjs +1 -15
  39. package/package.json +2 -2
  40. package/dist/packem_shared/EventEmitter-CMZfct03.mjs +0 -92
  41. package/dist/packem_shared/EventLog-zMy7AYP4.mjs +0 -162
  42. package/dist/packem_shared/EventLogDO-CZYUvvSr.mjs +0 -235
  43. package/dist/packem_shared/EventLogDOClient-DGiEdi96.mjs +0 -86
  44. package/dist/packem_shared/EventSource-DfV4VoRD.mjs +0 -195
  45. package/dist/packem_shared/EventsSync-DkVbU0WV.mjs +0 -91
  46. package/dist/packem_shared/InMemorySnapshotStore-BHVAD-Bp.mjs +0 -24
  47. package/dist/packem_shared/LocalMirror-GeJ26eNe.mjs +0 -188
  48. package/dist/packem_shared/MaterializerRuntime-HqNXqJxp.mjs +0 -204
  49. package/dist/packem_shared/SubscriptionManager-C5xbw0pg.mjs +0 -75
  50. package/dist/packem_shared/applyDiff-BtbIl1D3.mjs +0 -40
  51. package/dist/packem_shared/applyDiffToDb-DQ1xZp5J.mjs +0 -58
  52. package/dist/packem_shared/classifyChanges-aZmkxgVI.mjs +0 -38
  53. package/dist/packem_shared/defineEvents-DiBkPTh_.mjs +0 -28
  54. package/dist/packem_shared/eventsContext-Bk_p48hj.mjs +0 -6
  55. package/dist/packem_shared/isClientSeq-C46BkzqJ.mjs +0 -5
  56. package/dist/packem_shared/local-mirror.d-Bp19ueGy.d.mts +0 -412
  57. package/dist/packem_shared/local-mirror.d-GhuZAKgm.d.ts +0 -412
  58. package/dist/packem_shared/subscribeToMirror-CiaM-nQ7.mjs +0 -45
package/dist/index.d.mts CHANGED
@@ -1,119 +1,131 @@
1
1
  export { createBetterSqlite3Adapter } from "./adapters/better-sqlite3.mjs";
2
2
  export { createSqliteWasmAdapter } from "./adapters/sqlite-wasm.mjs";
3
3
  export { createSqlJsAdapter } from "./adapters/sqljs.mjs";
4
- import { S as SqliteAdapter } from "./packem_shared/types.d-VfJ76cK4.mjs";
5
- import { T as TableDiff, I as InputEvent, S as Seq, E as EventLogEntry, a as EventLog, A as AppendOptions, L as LocalMirror } from "./packem_shared/local-mirror.d-Bp19ueGy.mjs";
6
- export { type C as ClientSeq, type b as EventLogSnapshot, type G as GlobalSeq, type c as LocalMirrorOptions, type M as MirrorTableDef, type R as RowChange, d as classifyChanges, e as createTableDiff, f as diffSize, i as isClientSeq, g as isDiffEmpty, h as isGlobalSeq, j as isInputEvent, m as mergeDiffs } from "./packem_shared/local-mirror.d-Bp19ueGy.mjs";
7
- /**
8
- * Apply a single {@link TableDiff} to an in-memory row map and return
9
- * the updated map.
10
- *
11
- * The function creates a **shallow copy** of the input map so the caller's
12
- * reference stays untouched unless they choose to replace it.
13
- * @example
14
- * ```ts
15
- * const rows = new Map<string, Record<string, unknown>>();
16
- * rows.set("id-1", { name: "alice" });
17
- *
18
- * const diff = createTableDiff("users", [
19
- * { type: "insert", data: { id: "id-2", name: "bob" } },
20
- * { type: "update", id: "id-1", data: { name: "alice-updated" } },
21
- * ]);
22
- *
23
- * const updated = applyDiff(rows, diff);
24
- * updated.get("id-1")?.name // "alice-updated"
25
- * updated.get("id-2")?.name // "bob"
26
- * ```
27
- */
4
+ import { S as SqliteAdapter } from "./packem_shared/types.d-CkMkSwLJ.mjs";
5
+ import { T as TableDiff, I as InputEvent, S as Seq, E as EventLogEntry, a as EventLog, A as AppendOptions, L as LocalMirror } from "./packem_shared/local-mirror.d-DTavX_y0.mjs";
6
+ export { type C as ClientSeq, type b as EventLogOptions, type c as EventLogSnapshot, type G as GlobalSeq, type d as LocalMirrorOptions, type M as MirrorTableDef, type R as RowChange, e as classifyChanges, f as createTableDiff, g as diffSize, i as isClientSeq, h as isDiffEmpty, j as isGlobalSeq, k as isInputEvent, m as mergeDiffs } from "./packem_shared/local-mirror.d-DTavX_y0.mjs";
7
+ /**
8
+ * Apply a single {@link TableDiff} to an in-memory row map and return
9
+ * the updated map.
10
+ *
11
+ * The function creates a **shallow copy** of the input map so the caller's
12
+ * reference stays untouched unless they choose to replace it.
13
+ * @example
14
+ * ```ts
15
+ * const rows = new Map<string, Record<string, unknown>>();
16
+ * rows.set("id-1", { name: "alice" });
17
+ *
18
+ * const diff = createTableDiff("users", [
19
+ * { type: "insert", data: { id: "id-2", name: "bob" } },
20
+ * { type: "update", id: "id-1", data: { name: "alice-updated" } },
21
+ * ]);
22
+ *
23
+ * const updated = applyDiff(rows, diff);
24
+ * updated.get("id-1")?.name // "alice-updated"
25
+ * updated.get("id-2")?.name // "bob"
26
+ * ```
27
+ * @experimental
28
+ */
28
29
  declare const applyDiff: (current: ReadonlyMap<string, Record<string, unknown>>, diff: TableDiff) => Map<string, Record<string, unknown>>;
29
30
  /**
30
- * Apply an array of diffs **in order**, returning the final row map.
31
- *
32
- * This is equivalent to calling {@link applyDiff} repeatedly but avoids
33
- * intermediate map copies.
34
- */
31
+ * Apply an array of diffs **in order**, returning the final row map.
32
+ *
33
+ * This is equivalent to calling {@link applyDiff} repeatedly but copies the
34
+ * input map exactly once rather than once per diff — catch-up replay of an
35
+ * N-diff backlog is a single copy, not N+1.
36
+ * @experimental
37
+ */
35
38
  declare const applyDiffs: (current: ReadonlyMap<string, Record<string, unknown>>, diffs: ReadonlyArray<TableDiff>) => Map<string, Record<string, unknown>>;
36
39
  /**
37
- * Merge the row-level effect of a {@link TableDiff} into plain JSON
38
- * state keyed by table name, returning a new snapshot.
39
- * @param snapshot Current snapshot, e.g. `{ users: Map&lt;id, row>, posts: Map&lt;id, row> }`.
40
- * @param diff Contains the target table name and the row-level changes to merge.
41
- * @returns A shallow copy of `snapshot` with `diff.table`'s map updated.
42
- */
40
+ * Merge the row-level effect of a {@link TableDiff} into plain JSON
41
+ * state keyed by table name, returning a new snapshot.
42
+ * @param snapshot Current snapshot, e.g. `{ users: Map&lt;id, row>, posts: Map&lt;id, row> }`.
43
+ * @param diff Contains the target table name and the row-level changes to merge.
44
+ * @returns A shallow copy of `snapshot` with `diff.table`'s map updated.
45
+ * @experimental
46
+ */
43
47
  declare const applyDiffToSnapshot: (snapshot: ReadonlyMap<string, ReadonlyMap<string, Record<string, unknown>>>, diff: TableDiff) => Map<string, Map<string, Record<string, unknown>>>;
44
48
  /** Map a namespace-and-name pair to a qualified event type string. */
45
49
  type QualifiedType<Ns extends string, Name extends string> = `${Ns}.${Name}`;
46
50
  /**
47
- * Extract the payload type from an event schema.
48
- *
49
- * A `@lunora/values` validator (e.g. `v.object(...)`) carries its output type on
50
- * the phantom `__type` field (the same hook `Infer` reads), so match that FIRST
51
- * — otherwise a validator, being object-shaped, would fall through to the
52
- * `Record` branch and resolve to the validator instance itself rather than its
53
- * validated `{ … }` output. A bare factory function or plain descriptor object
54
- * is still supported as a fallback.
55
- */
51
+ * Extract the payload type from an event schema.
52
+ *
53
+ * A `@lunora/values` validator (e.g. `v.object(...)`) carries its output type on
54
+ * the phantom `__type` field (the same hook `Infer` reads), so match that FIRST
55
+ * — otherwise a validator, being object-shaped, would fall through to the
56
+ * `Record` branch and resolve to the validator instance itself rather than its
57
+ * validated `{ … }` output. A bare factory function or plain descriptor object
58
+ * is still supported as a fallback.
59
+ */
56
60
  type PayloadOf<T> = T extends {
57
61
  readonly __type: infer P;
58
62
  } ? P : T extends ((payload: infer P) => unknown) ? P : T extends Record<string, unknown> ? T : never;
59
63
  type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
60
64
  /**
61
- * Produce `{ "ns.name": Payload }` for every event, merged.
62
- */
63
- type EventTypeMap<TDefinition extends Record<string, Record<string, unknown>>> = UnionToIntersection<{ [Ns in keyof TDefinition & string]: { [Name in keyof TDefinition[Ns] & string]: { [K in QualifiedType<Ns, Name>]: PayloadOf<TDefinition[Ns][Name]> } }[keyof TDefinition[Ns] & string] }[keyof TDefinition & string]>;
65
+ * Produce `{ "ns.name": Payload }` for every event, merged.
66
+ */
67
+ type EventTypeMap<TDefinition extends Record<string, Record<string, unknown>>> = UnionToIntersection<{ [Ns in keyof TDefinition & string]: { [Name in keyof TDefinition[Ns] & string]: { [K in QualifiedType<Ns, Name>]: PayloadOf<TDefinition[Ns][Name]>; }; }[keyof TDefinition[Ns] & string]; }[keyof TDefinition & string]>;
64
68
  /**
65
- * A factory function that creates an {@link InputEvent} for a specific event type.
66
- *
67
- * The returned event has no `seq` — it is an optimistic / command payload
68
- * that the event log will assign a sequence number to on append.
69
- */
69
+ * A factory function that creates an {@link InputEvent} for a specific event type.
70
+ *
71
+ * The returned event has no `seq` — it is an optimistic / command payload
72
+ * that the event log will assign a sequence number to on append.
73
+ * @experimental
74
+ */
70
75
  interface EventFactory<Type extends string, Payload> {
71
76
  (payload: Payload): InputEvent<Type, Payload>;
72
77
  /** The fully qualified event type string (e.g. `"chat.messageSent"`). */
73
78
  readonly type: Type;
74
79
  }
75
80
  /**
76
- * The namespace object returned for each group of events.
77
- */
78
- type EventNamespace<Ns extends string, TDefinition extends Record<string, unknown>> = { [Name in keyof TDefinition & string]: EventFactory<QualifiedType<Ns, Name>, PayloadOf<TDefinition[Name]>> };
81
+ * The namespace object returned for each group of events.
82
+ * @experimental
83
+ */
84
+ type EventNamespace<Ns extends string, TDefinition extends Record<string, unknown>> = { [Name in keyof TDefinition & string]: EventFactory<QualifiedType<Ns, Name>, PayloadOf<TDefinition[Name]>>; };
79
85
  /**
80
- * The full result of {@link defineEvents}.
81
- */
82
- type EventsDefinition<TDefinition extends Record<string, Record<string, unknown>>> = { [Ns in keyof TDefinition & string]: EventNamespace<Ns, TDefinition[Ns]> } & {
86
+ * The full result of {@link defineEvents}.
87
+ * @experimental
88
+ */
89
+ type EventsDefinition<TDefinition extends Record<string, Record<string, unknown>>> = { [Ns in keyof TDefinition & string]: EventNamespace<Ns, TDefinition[Ns]>; } & {
83
90
  /** Type-level map of event type → payload shape. Useful for generic code. */
84
91
  readonly _types: EventTypeMap<TDefinition>;
85
92
  };
86
93
  /**
87
- * Declare typed event types for event sourcing.
88
- *
89
- * Each key under a namespace becomes a factory function that produces
90
- * an {@link InputEvent} — an optimistic / command event that the event
91
- * log will assign a sequence number to on append.
92
- * @param definition A nested object where the outer keys are namespaces
93
- * and the inner keys are event names mapped to their
94
- * payload schemas (or simple type-descriptor objects).
95
- * @returns An object with the same nesting structure, where each leaf is
96
- * a factory function plus a `.type` property.
97
- */
94
+ * Declare typed event types for event sourcing.
95
+ *
96
+ * Each key under a namespace becomes a factory function that produces
97
+ * an {@link InputEvent} — an optimistic / command event that the event
98
+ * log will assign a sequence number to on append.
99
+ * @param definition A nested object where the outer keys are namespaces
100
+ * and the inner keys are event names mapped to their
101
+ * payload schemas (or simple type-descriptor objects).
102
+ * @returns An object with the same nesting structure, where each leaf is
103
+ * a factory function plus a `.type` property.
104
+ */
98
105
  interface DefineEventsOptions {
99
106
  /**
100
- * Optional version prefix for all event types.
101
- *
102
- * When set, every qualified event type is prefixed with `"v&lt;N>."`, enabling
103
- * versioned event naming like `"v1.chat.messageSent"` or `"v2.chat.messageSent"`.
104
- * This allows materializers to evolve their handling logic based on the event
105
- * version without breaking backward compatibility.
106
- * @example "v1" → event type becomes "v1.chat.messageSent"
107
- */
107
+ * Optional version prefix for all event types.
108
+ *
109
+ * When set, every qualified event type is prefixed with `"v&lt;N>."`, enabling
110
+ * versioned event naming like `"v1.chat.messageSent"` or `"v2.chat.messageSent"`.
111
+ * This allows materializers to evolve their handling logic based on the event
112
+ * version without breaking backward compatibility.
113
+ * @example "v1" → event type becomes "v1.chat.messageSent"
114
+ */
108
115
  readonly version?: string;
109
116
  }
117
+ /**
118
+ * `defineEvents` is part of the experimental `@lunora/replica` API and may change without a major version bump.
119
+ * @experimental
120
+ */
110
121
  declare const defineEvents: <TDefinition extends Record<string, Record<string, unknown>>>(definition: TDefinition, options?: DefineEventsOptions) => EventsDefinition<TDefinition>;
111
122
  /**
112
- * Shape of the `events[]` items sent in a POST `/append` body.
113
- *
114
- * Like {@link InputEvent} but with `timestamp` optional — omit it to
115
- * let the server assign the timestamp.
116
- */
123
+ * Shape of the `events[]` items sent in a POST `/append` body.
124
+ *
125
+ * Like {@link InputEvent} but with `timestamp` optional — omit it to
126
+ * let the server assign the timestamp.
127
+ * @experimental
128
+ */
117
129
  interface AppendEventInput {
118
130
  /** Globally-unique client identifier (for offline/optimistic support). */
119
131
  readonly clientId?: string;
@@ -128,127 +140,142 @@ interface AppendEventInput {
128
140
  /** Event type discriminator. */
129
141
  readonly type: string;
130
142
  }
131
- /** Options for constructing an {@link EventLogDOClient}. */
143
+ /**
144
+ * Options for constructing an {@link EventLogDOClient}.
145
+ * @experimental
146
+ */
132
147
  interface EventLogDOClientOptions {
133
148
  /**
134
- * A function that dispatches an HTTP request to the target EventLogDO
135
- * instance. In a Cloudflare Worker this is:
136
- *
137
- * ```ts
138
- * (req) => env.MY_DO_NAMESPACE.get(id).fetch(req)
139
- * ```
140
- */
149
+ * A function that dispatches an HTTP request to the target EventLogDO
150
+ * instance. In a Cloudflare Worker this is:
151
+ *
152
+ * ```ts
153
+ * (req) => env.MY_DO_NAMESPACE.get(id).fetch(req)
154
+ * ```
155
+ */
141
156
  fetch: (request: Request) => Promise<Response>;
142
157
  }
143
158
  /**
144
- * Lightweight HTTP client for EventLogDO's RPC surface.
145
- *
146
- * Each method maps to one of the DO's endpoints, throws on non-OK status,
147
- * and returns the parsed response body.
148
- */
159
+ * Lightweight HTTP client for EventLogDO's RPC surface.
160
+ *
161
+ * Each method maps to one of the DO's endpoints, throws on non-OK status,
162
+ * and returns the parsed response body.
163
+ * @experimental
164
+ */
149
165
  declare class EventLogDOClient {
150
166
  #private;
151
167
  constructor(options: EventLogDOClientOptions);
152
168
  /**
153
- * Append one or more events to the log.
154
- * @returns The persisted entries with their assigned `seq` numbers.
155
- */
156
- append(events: AppendEventInput[]): Promise<EventLogEntry[]>;
157
- /**
158
- * Fetch all entries with `seq >= sinceSeq`.
159
- *
160
- * Pass `sinceSeq = 0` to fetch the entire log.
161
- */
169
+ * Append one or more events to the log.
170
+ * @param events The events to append.
171
+ * @param options Idempotency controls for the batch.
172
+ * @param options.batchId Optional idempotency key for the whole batch — a
173
+ * retried `append` call with the same `batchId` (e.g. after a network
174
+ * timeout that hid a successful response) returns the originally-persisted
175
+ * entries instead of inserting duplicates.
176
+ * @returns The persisted entries with their assigned `seq` numbers.
177
+ */
178
+ append(events: AppendEventInput[], options?: {
179
+ batchId?: string;
180
+ }): Promise<EventLogEntry[]>;
181
+ /**
182
+ * Fetch all entries with `seq >= sinceSeq`.
183
+ *
184
+ * Pass `sinceSeq = 0` to fetch the entire log.
185
+ */
162
186
  getSince(sinceSeq: number): Promise<EventLogEntry[]>;
163
187
  /**
164
- * Fetch a paginated range of entries.
165
- * @returns `{ entries, hasMore }` — `hasMore` is `true` when another
166
- * page exists (i.e. the DO returned `limit + 1` rows).
167
- */
188
+ * Fetch a paginated range of entries.
189
+ * @returns `{ entries, hasMore }` — `hasMore` is `true` when another
190
+ * page exists (i.e. the DO returned `limit + 1` rows).
191
+ */
168
192
  getRange(fromSeq: number, limit?: number): Promise<{
169
193
  entries: EventLogEntry[];
170
194
  hasMore: boolean;
171
195
  }>;
172
196
  /**
173
- * Return the total number of entries currently in the log.
174
- */
197
+ * Return the total number of entries currently in the log.
198
+ */
175
199
  getSize(): Promise<number>;
176
200
  /**
177
- * Return the full log state — all entries plus the next seq number.
178
- */
201
+ * Return the full log state — all entries plus the next seq number.
202
+ */
179
203
  getState(): Promise<{
180
204
  entries: EventLogEntry[];
181
205
  nextSeq: number;
182
206
  }>;
183
207
  }
184
208
  /**
185
- * Type-safe event emitter that powers the event-sourcing runtime.
186
- * @example
187
- * ```ts
188
- * type MyEvents = { userCreated: { id: string; name: string }; error: { message: string } };
189
- *
190
- * const emitter = new EventEmitter<MyEvents>();
191
- * emitter.on("userCreated", (payload) => console.log(payload.name));
192
- * emitter.emit("userCreated", { id: "1", name: "alice" });
193
- * ```
194
- */
209
+ * Type-safe event emitter that powers the event-sourcing runtime.
210
+ * @example
211
+ * ```ts
212
+ * type MyEvents = { userCreated: { id: string; name: string }; error: { message: string } };
213
+ *
214
+ * const emitter = new EventEmitter<MyEvents>();
215
+ * emitter.on("userCreated", (payload) => console.log(payload.name));
216
+ * emitter.emit("userCreated", { id: "1", name: "alice" });
217
+ * ```
218
+ * @experimental
219
+ */
195
220
  declare class EventEmitter<EventMap extends Record<string, unknown>> {
196
221
  #private;
197
222
  /**
198
- * Register a handler for a specific event type.
199
- * @returns An unsubscribe function (equivalent to calling {@link off}).
200
- */
223
+ * Register a handler for a specific event type.
224
+ * @returns An unsubscribe function (equivalent to calling {@link off}).
225
+ */
201
226
  on<K extends keyof EventMap>(event: K, handler: (payload: EventMap[K]) => void): () => void;
202
227
  /**
203
- * Remove a previously registered handler.
204
- */
228
+ * Remove a previously registered handler.
229
+ */
205
230
  off<K extends keyof EventMap>(event: K, handler: (payload: EventMap[K]) => void): void;
206
231
  /**
207
- * Register a wildcard handler that fires for **every** event type.
208
- * @returns An unsubscribe function.
209
- */
232
+ * Register a wildcard handler that fires for **every** event type.
233
+ * @returns An unsubscribe function.
234
+ */
210
235
  onAny(handler: (event: keyof EventMap, payload: unknown) => void): () => void;
211
236
  /**
212
- * Remove a wildcard handler.
213
- */
237
+ * Remove a wildcard handler.
238
+ */
214
239
  offAny(handler: (event: keyof EventMap, payload: unknown) => void): void;
215
240
  /**
216
- * Emit an event. All registered handlers (typed + wildcard) are invoked
217
- * synchronously. Exceptions from handlers are caught and silently
218
- * swallowed — they **must not** break the emitter loop.
219
- * @returns `true` if at least one handler was called.
220
- */
241
+ * Emit an event. All registered handlers (typed + wildcard) are invoked
242
+ * synchronously. Exceptions from handlers are caught and silently
243
+ * swallowed — they **must not** break the emitter loop.
244
+ * @returns `true` if at least one handler was called.
245
+ */
221
246
  emit<K extends keyof EventMap>(event: K, payload: EventMap[K]): boolean;
222
247
  /**
223
- * Return `true` when at least one listener is registered for `event`.
224
- */
248
+ * Return `true` when at least one listener is registered for `event`.
249
+ */
225
250
  hasListeners(event: keyof EventMap): boolean;
226
251
  /**
227
- * Return the number of typed listeners for a specific event.
228
- */
252
+ * Return the number of typed listeners for a specific event.
253
+ */
229
254
  listenerCount(event: keyof EventMap): number;
230
255
  /**
231
- * Remove all listeners.
232
- */
256
+ * Remove all listeners.
257
+ */
233
258
  clear(): void;
234
259
  }
235
260
  /**
236
- * Strategy for handling events whose `type` the reducer does not recognise.
237
- *
238
- * - `"warn"` _(default)_ — log a warning and skip the event (state unchanged).
239
- * - `"ignore"` — skip silently (no warning, no error).
240
- * - `"fail"` — throw an error, halting the apply / replay cycle.
241
- * - A **callback** — invoked with the entry; return truthy to mark it as
242
- * handled (no warning), falsy to fall through to the configured fallback.
243
- */
261
+ * Strategy for handling events whose `type` the reducer does not recognise.
262
+ *
263
+ * - `"warn"` _(default)_ — log a warning and skip the event (state unchanged).
264
+ * - `"ignore"` — skip silently (no warning, no error).
265
+ * - `"fail"` — throw an error, halting the apply / replay cycle.
266
+ * - A **callback** — invoked with the entry; return truthy to mark it as
267
+ * handled (no warning), falsy to fall through to the configured fallback.
268
+ * @experimental
269
+ */
244
270
  type UnknownEventHandling = "warn" | "ignore" | "fail" | ((entry: EventLogEntry) => boolean);
245
271
  /**
246
- * Events emitted by the {@link EventSource} runtime.
247
- *
248
- * A `type` (not `interface`) so it satisfies `EventEmitter`'s
249
- * `Record&lt;string, unknown>` constraint — interfaces have no implicit index
250
- * signature and aren't assignable to `Record&lt;string, unknown>`.
251
- */
272
+ * Events emitted by the {@link EventSource} runtime.
273
+ *
274
+ * A `type` (not `interface`) so it satisfies `EventEmitter`'s
275
+ * `Record&lt;string, unknown>` constraint — interfaces have no implicit index
276
+ * signature and aren't assignable to `Record&lt;string, unknown>`.
277
+ * @experimental
278
+ */
252
279
  type EventSourceEvents = {
253
280
  /** Fired (once) after the initial replay completes. */
254
281
  ready: {
@@ -266,107 +293,143 @@ type EventSourceEvents = {
266
293
  };
267
294
  };
268
295
  /**
269
- * A function that reduces an event into a state mutation.
270
- *
271
- * Pure functions are strongly encouraged: given the same event payload
272
- * and state, they must produce the same next state.
273
- */
274
- type EventReducer<S> = (state: S, entry: EventLogEntry) => S;
275
- /**
276
- * Options for constructing an {@link EventSource}.
277
- */
296
+ * Sentinel a reducer can return to EXPLICITLY signal it does not handle a
297
+ * given event's `type` — as opposed to returning the current `state`
298
+ * reference unchanged to represent a legitimate, idempotent no-op for a type
299
+ * it DOES recognise.
300
+ *
301
+ * Reference equality alone can't tell these two cases apart (REPLICA-07): a
302
+ * reducer that intentionally returns `state` for a type it fully understands
303
+ * (e.g. "already applied this event, nothing to do") would otherwise be
304
+ * misclassified as "unhandled" and trigger {@link UnknownEventHandling} — a
305
+ * spurious warning, or worse, a thrown error under `"fail"`. Return `UNHANDLED`
306
+ * only for a `type` your reducer truly does not recognise; every other return
307
+ * (including a `state` returned by reference) is treated as handled.
308
+ *
309
+ * Reducers that always recognise every event they're given (a single
310
+ * always-matching type, or a catch-all) can ignore this entirely.
311
+ * @experimental
312
+ */
313
+ declare const UNHANDLED: unique symbol;
314
+ /**
315
+ * A function that reduces an event into a state mutation.
316
+ *
317
+ * Pure functions are strongly encouraged: given the same event payload
318
+ * and state, they must produce the same next state. Return {@link UNHANDLED}
319
+ * to explicitly mark an event `type` this reducer does not process — see
320
+ * {@link UNHANDLED} for why reference equality against the input `state`
321
+ * cannot be used for this instead.
322
+ * @experimental
323
+ */
324
+ type EventReducer<S> = (state: S, entry: EventLogEntry) => S | typeof UNHANDLED;
325
+ /**
326
+ * Options for constructing an {@link EventSource}.
327
+ * @experimental
328
+ */
278
329
  interface EventSourceOptions {
279
330
  /**
280
- * How to handle events whose `type` is not recognised by the reducer.
281
- * @default "warn"
282
- */
331
+ * Cap this runtime's internal `log` to this many entries (REPLICA-06).
332
+ * `replayFromLog` copies every entry it replays from the source log into
333
+ * `this.log` too — a second, uncapped copy of the same history — so a
334
+ * long-lived `EventSource` fed by repeated replay accumulates entries in
335
+ * both places forever without a cap.
336
+ *
337
+ * `undefined` (the default) preserves unbounded retention.
338
+ */
339
+ maxLogEntries?: number;
340
+ /**
341
+ * How to handle events whose `type` is not recognised by the reducer.
342
+ * @default "warn"
343
+ */
283
344
  unknownEventHandling?: UnknownEventHandling;
284
345
  }
285
346
  /**
286
- * Event-sourcing runtime that maintains a derived state by replaying an
287
- * append-only {@link EventLog}.
288
- *
289
- * Usage:
290
- * ```ts
291
- * const source = new EventSource(initialState, myReducer);
292
- * await source.replayFromLog(existingLog);
293
- *
294
- * // Later, when a new event arrives:
295
- * const entry = source.applyEvent("user-created", { id: "1", name: "alice" });
296
- * console.log(source.state); // updated state
297
- * ```
298
- */
347
+ * Event-sourcing runtime that maintains a derived state by replaying an
348
+ * append-only {@link EventLog}.
349
+ *
350
+ * Usage:
351
+ * ```ts
352
+ * const source = new EventSource(initialState, myReducer);
353
+ * await source.replayFromLog(existingLog);
354
+ *
355
+ * // Later, when a new event arrives:
356
+ * const entry = source.applyEvent("user-created", { id: "1", name: "alice" });
357
+ * console.log(source.state); // updated state
358
+ * ```
359
+ * @experimental
360
+ */
299
361
  declare class EventSource<S extends Record<string, unknown> = Record<string, unknown>> {
300
362
  #private;
301
363
  readonly emitter: EventEmitter<EventSourceEvents>;
302
364
  readonly log: EventLog;
303
365
  constructor(initialState: S, reducer: EventReducer<S>, options?: EventSourceOptions);
304
366
  /**
305
- * The current derived state. Read-only snapshot; mutate through events.
306
- */
367
+ * The current derived state. Read-only snapshot; mutate through events.
368
+ */
307
369
  get state(): Readonly<S>;
308
370
  /**
309
- * Whether the initial replay from an existing log has completed.
310
- */
371
+ * Whether the initial replay from an existing log has completed.
372
+ */
311
373
  get replayed(): boolean;
312
374
  /**
313
- * Append a new event to the log and apply it to the current state.
314
- *
315
- * Accepts either an {@link InputEvent} (e.g. from a `defineEvents` factory)
316
- * or the traditional `(type, payload)` pair.
317
- * @returns The newly created log entry (with its assigned `seq`).
318
- */
375
+ * Append a new event to the log and apply it to the current state.
376
+ *
377
+ * Accepts either an {@link InputEvent} (e.g. from a `defineEvents` factory)
378
+ * or the traditional `(type, payload)` pair.
379
+ * @returns The newly created log entry (with its assigned `seq`).
380
+ */
319
381
  applyEvent(event: InputEvent, options?: AppendOptions): EventLogEntry;
320
382
  applyEvent(type: string, payload: unknown, options?: AppendOptions): EventLogEntry;
321
383
  /**
322
- * Replay all entries from an existing {@link EventLog} to bootstrap
323
- * the current state.
324
- *
325
- * Idempotent across calls: only source entries past the `#lastAppliedSeq`
326
- * watermark are applied, so re-invoking picks up just the new entries.
327
- * @param log The external log to replay from.
328
- */
384
+ * Replay all entries from an existing {@link EventLog} to bootstrap
385
+ * the current state.
386
+ *
387
+ * Idempotent across calls: only source entries past the `#lastAppliedSeq`
388
+ * watermark are applied, so re-invoking picks up just the new entries.
389
+ * @param log The external log to replay from.
390
+ */
329
391
  replayFromLog(log: EventLog): void;
330
392
  /**
331
- * Reset the runtime to a base state, optionally resuming from a watermark.
332
- *
333
- * Useful after loading a snapshot from the DO: pass the snapshot's state as
334
- * `initialState` and its highest applied source `seq` as `resumeFromSeq`, so
335
- * the next {@link replayFromLog} applies ONLY the events after the snapshot
336
- * (`getSince(resumeFromSeq + 1)`) rather than replaying the whole log on top
337
- * of the snapshot — which would double-apply non-idempotent reducers.
338
- *
339
- * Omit `resumeFromSeq` (default `-1`) for a full reset that replays from the
340
- * beginning.
341
- * @param initialState The base state to reset to (e.g. a loaded snapshot).
342
- * @param resumeFromSeq Highest source `seq` already baked into `initialState`, or `-1` to replay all.
343
- */
393
+ * Reset the runtime to a base state, optionally resuming from a watermark.
394
+ *
395
+ * Useful after loading a snapshot from the DO: pass the snapshot's state as
396
+ * `initialState` and its highest applied source `seq` as `resumeFromSeq`, so
397
+ * the next {@link replayFromLog} applies ONLY the events after the snapshot
398
+ * (`getSince(resumeFromSeq + 1)`) rather than replaying the whole log on top
399
+ * of the snapshot — which would double-apply non-idempotent reducers.
400
+ *
401
+ * Omit `resumeFromSeq` (default `-1`) for a full reset that replays from the
402
+ * beginning.
403
+ * @param initialState The base state to reset to (e.g. a loaded snapshot).
404
+ * @param resumeFromSeq Highest source `seq` already baked into `initialState`, or `-1` to replay all.
405
+ */
344
406
  reset(initialState: S, resumeFromSeq?: number): void;
345
407
  /**
346
- * Return an async generator that yields every event as it is applied,
347
- * starting from the events currently in the log and continuing with
348
- * every future `applyEvent` / `replayFromLog` call.
349
- *
350
- * The generator runs indefinitely — it never returns. Callers should
351
- * break out of the `for await` loop or use an `AbortSignal` to stop.
352
- * @example
353
- * ```ts
354
- * for await (const entry of source.events()) {
355
- * console.log("event applied:", entry);
356
- * }
357
- * ```
358
- */
408
+ * Return an async generator that yields every event as it is applied,
409
+ * starting from the events currently in the log and continuing with
410
+ * every future `applyEvent` / `replayFromLog` call.
411
+ *
412
+ * The generator runs indefinitely — it never returns. Callers should
413
+ * break out of the `for await` loop or use an `AbortSignal` to stop.
414
+ * @example
415
+ * ```ts
416
+ * for await (const entry of source.events()) {
417
+ * console.log("event applied:", entry);
418
+ * }
419
+ * ```
420
+ */
359
421
  events(signal?: AbortSignal): AsyncGenerator<EventLogEntry>;
360
422
  }
361
423
  /**
362
- * Interface for persisting event-sourced state snapshots.
363
- *
364
- * In a Lunora app the primary implementation is backed by the
365
- * SnapshotDO (a Durable Object) on the server side. On the client
366
- * the {@link InMemorySnapshotStore} is used for the offline-first
367
- * local mirror, while a production client would implement this
368
- * over IndexedDB or OPFS.
369
- */
424
+ * Interface for persisting event-sourced state snapshots.
425
+ *
426
+ * In a Lunora app the primary implementation is backed by the
427
+ * SnapshotDO (a Durable Object) on the server side. On the client
428
+ * the {@link InMemorySnapshotStore} is used for the offline-first
429
+ * local mirror, while a production client would implement this
430
+ * over IndexedDB or OPFS.
431
+ * @experimental
432
+ */
370
433
  interface SnapshotStore {
371
434
  /** Delete all snapshots. */
372
435
  clear: () => Promise<void>;
@@ -380,10 +443,11 @@ interface SnapshotStore {
380
443
  save: (key: string, snapshot: unknown) => Promise<void>;
381
444
  }
382
445
  /**
383
- * In-memory snapshot store. Useful for testing and for the local
384
- * offline-first mirror where persistence is handled at a higher
385
- * layer (IndexedDB adapter).
386
- */
446
+ * In-memory snapshot store. Useful for testing and for the local
447
+ * offline-first mirror where persistence is handled at a higher
448
+ * layer (IndexedDB adapter).
449
+ * @experimental
450
+ */
387
451
  declare class InMemorySnapshotStore implements SnapshotStore {
388
452
  #private;
389
453
  save(key: string, snapshot: unknown): Promise<void>;
@@ -393,21 +457,23 @@ declare class InMemorySnapshotStore implements SnapshotStore {
393
457
  clear(): Promise<void>;
394
458
  }
395
459
  /**
396
- * A function that reduces an event entry into a state mutation.
397
- *
398
- * Pure functions are strongly encouraged: given the same event and state,
399
- * they must produce the same next state for deterministic replay.
400
- */
460
+ * A function that reduces an event entry into a state mutation.
461
+ *
462
+ * Pure functions are strongly encouraged: given the same event and state,
463
+ * they must produce the same next state for deterministic replay.
464
+ * @experimental
465
+ */
401
466
  type MaterializerReducer<S> = (state: S, entry: EventLogEntry) => S;
402
467
  /**
403
- * Options for defining a single materializer.
404
- */
468
+ * Options for defining a single materializer.
469
+ * @experimental
470
+ */
405
471
  interface MaterializerDef<S> {
406
472
  /**
407
- * Reducer invoked for every event in the log.
408
- *
409
- * Return the current state unchanged to skip the event.
410
- */
473
+ * Reducer invoked for every event in the log.
474
+ *
475
+ * Return the current state unchanged to skip the event.
476
+ */
411
477
  handle: MaterializerReducer<S>;
412
478
  /** Factory for the initial (empty) state. */
413
479
  initial: () => S;
@@ -415,8 +481,9 @@ interface MaterializerDef<S> {
415
481
  readonly name: string;
416
482
  }
417
483
  /**
418
- * A constructed materializer ready to be used with a {@link MaterializerRuntime}.
419
- */
484
+ * A constructed materializer ready to be used with a {@link MaterializerRuntime}.
485
+ * @experimental
486
+ */
420
487
  interface Materializer<S> {
421
488
  /** Apply a single event entry through the reducer. */
422
489
  apply: (entry: EventLogEntry) => void;
@@ -429,132 +496,162 @@ interface Materializer<S> {
429
496
  readonly state: Readonly<S>;
430
497
  }
431
498
  /**
432
- * Declare a materializer — a named reducer that derives state from events.
433
- *
434
- * The returned {@link Materializer} object can be used standalone or passed
435
- * to a {@link MaterializerRuntime} for automatic log subscription.
436
- */
499
+ * Declare a materializer — a named reducer that derives state from events.
500
+ *
501
+ * The returned {@link Materializer} object can be used standalone or passed
502
+ * to a {@link MaterializerRuntime} for automatic log subscription.
503
+ * @experimental
504
+ */
437
505
  declare const defineMaterializer: <S>(definition: MaterializerDef<S>) => Materializer<S>;
438
506
  /**
439
- * A materializer of any state shape. The {@link MaterializerRuntime} holds a
440
- * heterogeneous collection and only ever calls `apply(entry)` / `setState(...)`
441
- * (with cast values) / reads `def.name` — it never needs the concrete state
442
- * type. `Materializer&lt;unknown>` won't do: `setState(state: S)` makes
443
- * `Materializer&lt;S>` invariant in `S`, so `Materializer&lt;number>` isn't assignable
444
- * to `Materializer&lt;unknown>`. Erasing the type param is the idiomatic fix.
445
- */
507
+ * A materializer of any state shape. The {@link MaterializerRuntime} holds a
508
+ * heterogeneous collection and only ever calls `apply(entry)` / `setState(...)`
509
+ * (with cast values) / reads `def.name` — it never needs the concrete state
510
+ * type. `Materializer&lt;unknown>` won't do: `setState(state: S)` makes
511
+ * `Materializer&lt;S>` invariant in `S`, so `Materializer&lt;number>` isn't assignable
512
+ * to `Materializer&lt;unknown>`. Erasing the type param is the idiomatic fix.
513
+ */
446
514
  type AnyMaterializer = Materializer<any>;
447
515
  /**
448
- * Options for constructing a {@link MaterializerRuntime}.
449
- */
516
+ * Options for constructing a {@link MaterializerRuntime}.
517
+ * @experimental
518
+ */
450
519
  interface MaterializerRuntimeOptions {
451
520
  /**
452
- * Optional EventLogDO client for persistent event log integration.
453
- *
454
- * When provided, the runtime can bootstrap from the DO on startup
455
- * (recover from snapshots → catch up via `getSince`) and append
456
- * new events through the DO automatically.
457
- */
521
+ * Optional EventLogDO client for persistent event log integration.
522
+ *
523
+ * When provided, the runtime can bootstrap from the DO on startup
524
+ * (recover from snapshots → catch up via `getSince`) and append
525
+ * new events through the DO automatically.
526
+ */
458
527
  doClient?: EventLogDOClient;
459
528
  /** Optional snapshot store for persisting/recovering materialized state. */
460
529
  snapshotStore?: SnapshotStore;
461
530
  /**
462
- * How to handle events whose type no materializer handles.
463
- * @default "warn"
464
- */
531
+ * How to handle events whose type no materializer handles.
532
+ * @default "warn"
533
+ */
465
534
  unknownEventHandling?: UnknownEventHandling;
466
535
  }
467
536
  /**
468
- * Runtime that drives one or more materializers from an event log.
469
- *
470
- * Handles:
471
- * - Replaying the full log on startup
472
- * - Applying new events as they arrive
473
- * - Periodic snapshot persistence
474
- * - Recovery from snapshots (replay only what's missing)
475
- */
537
+ * Runtime that drives one or more materializers from an event log.
538
+ *
539
+ * Handles:
540
+ * - Replaying the full log on startup
541
+ * - Applying new events as they arrive
542
+ * - Periodic snapshot persistence
543
+ * - Recovery from snapshots (replay only what's missing)
544
+ * @experimental
545
+ */
476
546
  declare class MaterializerRuntime {
477
547
  #private;
478
548
  constructor(materializers: AnyMaterializer[], options?: MaterializerRuntimeOptions);
479
549
  /**
480
- * The sequence number of the last event applied to all materializers.
481
- */
550
+ * The lowest per-materializer watermark the seq of the next event that
551
+ * at least one materializer has not yet applied. `0` when there are no
552
+ * materializers.
553
+ */
482
554
  get appliedSeq(): number;
483
555
  /**
484
- * Replay a batch of entries through all materializers.
485
- *
486
- * Entries with `seq < this.appliedSeq` are silently skipped (idempotent).
487
- * @returns The number of entries actually applied.
488
- */
556
+ * Replay a batch of entries, applying each entry only to the
557
+ * materializers whose own watermark is behind it — a materializer at or
558
+ * past an entry's seq (e.g. recovered from a snapshot, or already caught
559
+ * up) skips it, so no materializer ever double-applies an event.
560
+ * @returns The number of entries applied to at least one materializer.
561
+ */
489
562
  applyEntries(entries: ReadonlyArray<EventLogEntry>): number;
490
563
  /**
491
- * Attempt to recover materialized state from a snapshot store.
492
- *
493
- * When a snapshot is found for a materializer, its state is restored
494
- * and the snapshot's watermark (`appliedSeq`) is returned so the caller
495
- * can skip replaying entries up to that point.
496
- * @returns The highest `appliedSeq` across all recovered snapshots, or `0`.
497
- */
564
+ * Attempt to recover materialized state from a snapshot store.
565
+ *
566
+ * When a snapshot is found for a materializer, its state AND its own
567
+ * watermark are restored from that snapshot. A materializer with no
568
+ * snapshot keeps its current watermark (`0` for a fresh runtime) — it
569
+ * does NOT inherit another materializer's watermark, so it still catches
570
+ * up from the very beginning (REPLICA-04: previously a shared watermark
571
+ * was bumped to the MAX across snapshots, permanently skipping events 0..N
572
+ * for any un-snapshotted or lagging materializer).
573
+ * @returns The highest snapshot `appliedSeq` across all materializers, or
574
+ * `0` — kept for backward compatibility; callers that need the fetch
575
+ * watermark for catch-up should use the per-materializer minimum instead
576
+ * (see `initialize`).
577
+ */
498
578
  recoverFromSnapshots(): Promise<number>;
499
579
  /**
500
- * Persist the current state of all materializers as snapshots.
501
- */
580
+ * Persist the current state of all materializers as snapshots, each
581
+ * tagged with ITS OWN watermark (not a shared one).
582
+ */
502
583
  persistSnapshots(): Promise<void>;
503
584
  /**
504
- * Bootstrap the runtime from the EventLogDO.
505
- *
506
- * 1. Recover materialized state from snapshots (if a snapshotStore is
507
- * configured).
508
- * 2. Fetch all entries since the recovered watermark from the DO.
509
- * 3. Apply them through the materializers.
510
- *
511
- * Call this once on startup / after the DO binding is available.
512
- * @returns The number of entries applied during catch-up.
513
- */
585
+ * Bootstrap the runtime from the EventLogDO.
586
+ *
587
+ * 1. Recover materialized state from snapshots (if a snapshotStore is
588
+ * configured).
589
+ * 2. Fetch all entries since the MINIMUM per-materializer watermark from
590
+ * the DO not the maximum — so a materializer with no snapshot (or a
591
+ * lower one) still receives every event it hasn't seen (REPLICA-04).
592
+ * 3. Apply them through the materializers; `applyEntries` skips each
593
+ * entry for any materializer already past it, so nothing is double-applied.
594
+ *
595
+ * Call this once on startup / after the DO binding is available.
596
+ * @returns The number of entries applied during catch-up.
597
+ */
514
598
  initialize(): Promise<number>;
515
599
  /**
516
- * Append an event to the EventLogDO and apply it through all
517
- * materializers.
518
- *
519
- * This is a convenience over calling `doClient.append(...)` +
520
- * `runtime.applyEntries(...)` yourself — it persists the event
521
- * **then** applies the returned entry (with its assigned seq).
522
- * @returns The persisted entry with its DO-assigned `seq`.
523
- */
600
+ * Append an event to the EventLogDO and apply it through all
601
+ * materializers.
602
+ *
603
+ * This is a convenience over calling `doClient.append(...)` +
604
+ * `runtime.applyEntries(...)` yourself — it persists the event
605
+ * **then** applies the returned entry (with its assigned seq).
606
+ * @returns The persisted entry with its DO-assigned `seq`.
607
+ */
524
608
  appendEvent(input: AppendEventInput): Promise<EventLogEntry>;
525
609
  /**
526
- * Reset all materializers to their initial state and clear snapshots.
527
- */
610
+ * Reset all materializers to their initial state and clear snapshots.
611
+ */
528
612
  reset(): void;
529
613
  /**
530
- * The list of registered materializers.
531
- */
614
+ * The list of registered materializers.
615
+ */
532
616
  get materializers(): ReadonlyArray<Materializer<unknown>>;
533
617
  }
534
618
  /**
535
- * Apply a {@link TableDiff} to the given SQLite database by translating
536
- * each row change into an INSERT, UPDATE, or DELETE statement.
537
- *
538
- * All statements are wrapped in a single transaction.
539
- * @param database SQLite adapter the statements run against.
540
- * @param diff The table diff to apply.
541
- * @param pkColumn Primary key column for DELETE/UPDATE (default `"id"`).
542
- */
619
+ * Apply a {@link TableDiff} to the given SQLite database by translating
620
+ * each row change into an INSERT, UPDATE, or DELETE statement.
621
+ *
622
+ * All statements are wrapped in a single transaction.
623
+ * @param database SQLite adapter the statements run against.
624
+ * @param diff The table diff to apply.
625
+ * @param pkColumn Primary key column for DELETE/UPDATE (default `"id"`).
626
+ * @experimental
627
+ */
543
628
  declare const applyDiffToDatabase: (database: SqliteAdapter, diff: TableDiff, pkColumn?: string) => void;
544
629
  /**
545
- * Apply multiple diffs **in order** within a single transaction.
546
- *
547
- * Each diff uses `"id"` as the primary key column. For tables with a custom
548
- * PK, use {@link applyDiffToDatabase} per-diff and pass the PK explicitly.
549
- */
630
+ * Apply multiple diffs **in order** within a single transaction.
631
+ *
632
+ * Each diff uses `"id"` as the primary key column. For tables with a custom
633
+ * PK, use {@link applyDiffToDatabase} per-diff and pass the PK explicitly.
634
+ * @experimental
635
+ */
550
636
  declare const applyDiffsToDatabase: (database: SqliteAdapter, diffs: ReadonlyArray<TableDiff>) => void;
551
637
  interface EventLogDOState {
552
638
  storage: {
553
639
  sql: {
554
640
  exec: (query: string, ...params: unknown[]) => unknown;
555
641
  };
642
+ /**
643
+ * The DO platform's native atomic-transaction primitive (async;
644
+ * commits on resolve, rolls back on throw/reject). Test doubles that
645
+ * omit it fall back to a bare (non-transactional) call — see
646
+ * `#handleAppend`.
647
+ */
648
+ transaction?: <T>(closure: () => Promise<T> | T) => Promise<T>;
556
649
  };
557
650
  }
651
+ /**
652
+ * `EventLogDO` is part of the experimental `@lunora/replica` API and may change without a major version bump.
653
+ * @experimental
654
+ */
558
655
  declare class EventLogDO {
559
656
  #private;
560
657
  protected state: EventLogDOState;
@@ -563,10 +660,10 @@ declare class EventLogDO {
563
660
  fetch(request: Request): Promise<Response>;
564
661
  }
565
662
  /**
566
- * `next()` advances the middleware chain. Called with no argument it forwards
567
- * the current context unchanged; called with `{ ctx }` it shallow-merges the
568
- * extension, and the result type reflects the widened context.
569
- */
663
+ * `next()` advances the middleware chain. Called with no argument it forwards
664
+ * the current context unchanged; called with `{ ctx }` it shallow-merges the
665
+ * extension, and the result type reflects the widened context.
666
+ */
570
667
  interface MiddlewareNext<ContextIn> {
571
668
  (): Promise<ContextIn>;
572
669
  <Extension extends Record<string, unknown>>(options: {
@@ -574,45 +671,45 @@ interface MiddlewareNext<ContextIn> {
574
671
  }): Promise<ContextIn & Extension>;
575
672
  }
576
673
  /**
577
- * A middleware receives the current context and a `next` continuation. Its
578
- * return type becomes the builder's new context, so `return next({ ctx })`
579
- * propagates the extension into every downstream `.use()` and the handler.
580
- */
674
+ * A middleware receives the current context and a `next` continuation. Its
675
+ * return type becomes the builder's new context, so `return next({ ctx })`
676
+ * propagates the extension into every downstream `.use()` and the handler.
677
+ */
581
678
  type Middleware<ContextIn, ContextOut> = (options: {
582
679
  ctx: ContextIn;
583
680
  next: MiddlewareNext<ContextIn>;
584
681
  }) => ContextOut | Promise<ContextOut>;
585
- /** Options accepted by `initLunora.dataModel&lt;DM>().create(...)`. Reserved for transformer/error-formatter wiring. */
586
682
  /**
587
- * The per-request `ctx.events` facade that {@link eventsContext} attaches.
588
- *
589
- * Each method delegates to the corresponding {@link EventLogDOClient} method,
590
- * so handlers never need to import or reference the DO client directly.
591
- */
683
+ * The per-request `ctx.events` facade that {@link eventsContext} attaches.
684
+ *
685
+ * Each method delegates to the corresponding {@link EventLogDOClient} method,
686
+ * so handlers never need to import or reference the DO client directly.
687
+ * @experimental
688
+ */
592
689
  interface EventsFacade {
593
690
  /**
594
- * Append one or more events to the log.
595
- * @returns The persisted entries with their assigned `seq` numbers.
596
- */
691
+ * Append one or more events to the log.
692
+ * @returns The persisted entries with their assigned `seq` numbers.
693
+ */
597
694
  append: (events: {
598
695
  payload: unknown;
599
696
  timestamp?: number;
600
697
  type: string;
601
698
  }[]) => Promise<EventLogEntry[]>;
602
699
  /**
603
- * Fetch a paginated range of entries.
604
- * @returns `{ entries, hasMore }` — `hasMore` is `true` when another
605
- * page exists.
606
- */
700
+ * Fetch a paginated range of entries.
701
+ * @returns `{ entries, hasMore }` — `hasMore` is `true` when another
702
+ * page exists.
703
+ */
607
704
  getRange: (fromSeq: number, limit?: number) => Promise<{
608
705
  entries: EventLogEntry[];
609
706
  hasMore: boolean;
610
707
  }>;
611
708
  /**
612
- * Fetch all entries with `seq >= sinceSeq`.
613
- *
614
- * Pass `sinceSeq = 0` to fetch the entire log.
615
- */
709
+ * Fetch all entries with `seq >= sinceSeq`.
710
+ *
711
+ * Pass `sinceSeq = 0` to fetch the entire log.
712
+ */
616
713
  getSince: (sinceSeq: number) => Promise<EventLogEntry[]>;
617
714
  /** Return the total number of entries currently in the log. */
618
715
  getSize: () => Promise<number>;
@@ -623,44 +720,47 @@ interface EventsFacade {
623
720
  }>;
624
721
  }
625
722
  /**
626
- * The context shape produced by {@link eventsContext}.
627
- */
723
+ * The context shape produced by {@link eventsContext}.
724
+ * @experimental
725
+ */
628
726
  interface EventsContextOutput {
629
727
  /** Typed event log facade backed by an {@link EventLogDOClient}. */
630
728
  readonly events: EventsFacade;
631
729
  }
632
730
  /**
633
- * Create a middleware that attaches a typed `ctx.events` facade backed by
634
- * the given {@link EventLogDOClient}.
635
- *
636
- * The facade surfaces `append`, `getSince`, `getRange`, `getSize`, and
637
- * `getState` — every method the DO client exposes — so handlers can read
638
- * and write the event log without reaching for the DO stub directly.
639
- *
640
- * The middleware is unopinionated about which context it extends — it works
641
- * with `MutationCtx`, `ActionCtx`, or `QueryCtx` equally.
642
- * @param client A configured {@link EventLogDOClient} instance.
643
- * @returns A Lunora middleware that injects `ctx.events`.
644
- *
645
- * ```ts
646
- * const client = new EventLogDOClient({
647
- * fetch: (req) => env.EVENTS.get(id).fetch(req),
648
- * });
649
- *
650
- * export const logEvent = mutation
651
- * .use(eventsContext(client))
652
- * .mutation(async ({ ctx, args }) => {
653
- * const [entry] = await ctx.events.append([{ type: "order.placed", payload: args }]);
654
- * return entry;
655
- * });
656
- * ```
657
- */
731
+ * Create a middleware that attaches a typed `ctx.events` facade backed by
732
+ * the given {@link EventLogDOClient}.
733
+ *
734
+ * The facade surfaces `append`, `getSince`, `getRange`, `getSize`, and
735
+ * `getState` — every method the DO client exposes — so handlers can read
736
+ * and write the event log without reaching for the DO stub directly.
737
+ *
738
+ * The middleware is unopinionated about which context it extends — it works
739
+ * with `MutationCtx`, `ActionCtx`, or `QueryCtx` equally.
740
+ * @param client A configured {@link EventLogDOClient} instance.
741
+ * @returns A Lunora middleware that injects `ctx.events`.
742
+ *
743
+ * ```ts
744
+ * const client = new EventLogDOClient({
745
+ * fetch: (req) => env.EVENTS.get(id).fetch(req),
746
+ * });
747
+ *
748
+ * export const logEvent = mutation
749
+ * .use(eventsContext(client))
750
+ * .mutation(async ({ ctx, args }) => {
751
+ * const [entry] = await ctx.events.append([{ type: "order.placed", payload: args }]);
752
+ * return entry;
753
+ * });
754
+ * ```
755
+ * @experimental
756
+ */
658
757
  declare const eventsContext: <Context>(client: EventLogDOClient) => Middleware<Context, Context & EventsContextOutput>;
659
758
  /**
660
- * A dependency-light subscription sink interface that mirrors what
661
- * `LunoraClient.subscribe` expects, so the mirror helper doesn't
662
- * need to import `@lunora/client`.
663
- */
759
+ * A dependency-light subscription sink interface that mirrors what
760
+ * `LunoraClient.subscribe` expects, so the mirror helper doesn't
761
+ * need to import `@lunora/client`.
762
+ * @experimental
763
+ */
664
764
  interface SubscriptionClient {
665
765
  subscribe: (functionRef: {
666
766
  __lunoraRef: string;
@@ -669,197 +769,212 @@ interface SubscriptionClient {
669
769
  }) => () => void;
670
770
  }
671
771
  /**
672
- * Subscribe a Lunora-query to the local mirror so every server push
673
- * is applied to the local SQLite store.
674
- *
675
- * Each frame from a Lunora live query is the FULL current result set, so the
676
- * callback treats it as a snapshot: it upserts every row present and emits a
677
- * `delete` for any id that was mirrored on a previous frame but is absent now —
678
- * otherwise rows that drop out of the server result would linger stale in the
679
- * local mirror. Rows are keyed by their `id` field (the mirror's default primary
680
- * key); a row without an `id` can't be reconciled on removal, and — because the
681
- * mirror table's `id` column is `NOT NULL` — will fail the insert.
682
- *
683
- * The mirror table name is derived from the function ref alone (not `args`), so
684
- * do NOT mirror two subscriptions to the same function with different `args`
685
- * into the same mirror: they'd share one table and the snapshot-delete pass of
686
- * one could remove rows still live in the other.
687
- *
688
- * Call the returned unsubscribe function to tear down both the client
689
- * subscription and future mirror writes.
690
- * @example
691
- * ```ts
692
- * const unsub = subscribeToMirror(client, mirror, api.todos.list, { userId });
693
- * // Later:
694
- * unsub();
695
- * ```
696
- */
772
+ * Subscribe a Lunora-query to the local mirror so every server push
773
+ * is applied to the local SQLite store.
774
+ *
775
+ * Each frame from a Lunora live query is the FULL current result set, so the
776
+ * callback treats it as a snapshot: it upserts every row present and emits a
777
+ * `delete` for any id that was mirrored on a previous frame but is absent now —
778
+ * otherwise rows that drop out of the server result would linger stale in the
779
+ * local mirror. Rows are keyed by their `id` field (the mirror's default primary
780
+ * key); a row without an `id` can't be reconciled on removal, and — because the
781
+ * mirror table's `id` column is `NOT NULL` — will fail the insert.
782
+ *
783
+ * The mirror table name is derived from the function ref alone (not `args`), so
784
+ * do NOT mirror two subscriptions to the same function with different `args`
785
+ * into the same mirror: they'd share one table and the snapshot-delete pass of
786
+ * one could remove rows still live in the other.
787
+ *
788
+ * Call the returned unsubscribe function to tear down both the client
789
+ * subscription and future mirror writes.
790
+ * @example
791
+ * ```ts
792
+ * const unsub = subscribeToMirror(client, mirror, api.todos.list, { userId });
793
+ * // Later:
794
+ * unsub();
795
+ * ```
796
+ * @experimental
797
+ */
697
798
  declare const subscribeToMirror: (client: SubscriptionClient, mirror: LocalMirror, functionRef: {
698
799
  __lunoraRef: string;
699
800
  }, args: Record<string, unknown>, shardKey?: string) => (() => void);
700
801
  /**
701
- * Callback signature for state-change subscriptions.
702
- */
802
+ * Callback signature for state-change subscriptions.
803
+ * @experimental
804
+ */
703
805
  type StateChangeCallback = (state: Readonly<Record<string, unknown>>) => void;
704
806
  /**
705
- * Callback signature for event-type subscriptions.
706
- */
807
+ * Callback signature for event-type subscriptions.
808
+ * @experimental
809
+ */
707
810
  type EventCallback = (entry: EventLogEntry) => void;
708
811
  /**
709
- * Manages subscriptions to state changes and individual event types
710
- * for the event-sourcing runtime.
711
- *
712
- * Each subscription returns an unsubscribe function — the caller is
713
- * expected to call it during cleanup (e.g. in a React `useEffect`
714
- * return or a Svelte `onDestroy`).
715
- * @example
716
- * ```ts
717
- * const subs = new SubscriptionManager();
718
- *
719
- * // Subscribe to every state change
720
- * const unsub1 = subs.onStateChange((state) => console.log("new state", state));
721
- *
722
- * // Subscribe to a specific event type
723
- * const unsub2 = subs.onEvent("user-created", (entry) => console.log("user created", entry.payload));
724
- *
725
- * // Later, when state or events arrive:
726
- * subs.notifyState({ users: [] });
727
- * subs.notifyEvent({ seq: 1, type: "user-created", payload: { id: "1" }, timestamp: 100 });
728
- *
729
- * // Cleanup
730
- * unsub1();
731
- * unsub2();
732
- * ```
733
- */
812
+ * Manages subscriptions to state changes and individual event types
813
+ * for the event-sourcing runtime.
814
+ *
815
+ * Each subscription returns an unsubscribe function — the caller is
816
+ * expected to call it during cleanup (e.g. in a React `useEffect`
817
+ * return or a Svelte `onDestroy`).
818
+ * @example
819
+ * ```ts
820
+ * const subs = new SubscriptionManager();
821
+ *
822
+ * // Subscribe to every state change
823
+ * const unsub1 = subs.onStateChange((state) => console.log("new state", state));
824
+ *
825
+ * // Subscribe to a specific event type
826
+ * const unsub2 = subs.onEvent("user-created", (entry) => console.log("user created", entry.payload));
827
+ *
828
+ * // Later, when state or events arrive:
829
+ * subs.notifyState({ users: [] });
830
+ * subs.notifyEvent({ seq: 1, type: "user-created", payload: { id: "1" }, timestamp: 100 });
831
+ *
832
+ * // Cleanup
833
+ * unsub1();
834
+ * unsub2();
835
+ * ```
836
+ * @experimental
837
+ */
734
838
  declare class SubscriptionManager {
735
839
  #private;
736
840
  /**
737
- * Subscribe to every state change emitted by the event source.
738
- * @returns Unsubscribe function.
739
- */
841
+ * Subscribe to every state change emitted by the event source.
842
+ * @returns Unsubscribe function.
843
+ */
740
844
  onStateChange(callback: StateChangeCallback): () => void;
741
845
  /**
742
- * Subscribe to a specific event type.
743
- * @param eventType The event type to listen for (matches `entry.type`).
744
- * @param callback Invoked with each matching entry.
745
- * @returns Unsubscribe function.
746
- */
846
+ * Subscribe to a specific event type.
847
+ * @param eventType The event type to listen for (matches `entry.type`).
848
+ * @param callback Invoked with each matching entry.
849
+ * @returns Unsubscribe function.
850
+ */
747
851
  onEvent(eventType: string, callback: EventCallback): () => void;
748
852
  /**
749
- * Notify all state-change subscribers with the current state.
750
- */
853
+ * Notify all state-change subscribers with the current state.
854
+ */
751
855
  notifyState(state: Readonly<Record<string, unknown>>): void;
752
856
  /**
753
- * Notify event-type subscribers whose `eventType` matches.
754
- */
857
+ * Notify event-type subscribers whose `eventType` matches.
858
+ */
755
859
  notifyEvent(entry: EventLogEntry): void;
756
860
  /**
757
- * Return the total number of active subscriptions.
758
- */
861
+ * Return the total number of active subscriptions.
862
+ */
759
863
  get size(): number;
760
864
  /**
761
- * Remove all subscriptions.
762
- */
865
+ * Remove all subscriptions.
866
+ */
763
867
  clear(): void;
764
868
  }
765
869
  /**
766
- * Options for constructing an {@link EventsSync}.
767
- */
870
+ * Options for constructing an {@link EventsSync}.
871
+ * @experimental
872
+ */
768
873
  interface EventsSyncOptions {
769
874
  /**
770
- * Replay a batch of events through the derived-state machine.
771
- *
772
- * Called with every batch of new events fetched from the log. The
773
- * consumer should feed these events into their state machine
774
- * (e.g. an {@link import("@lunora/replica").EventSource | EventSource})
775
- * so that the machine's state reflects the latest log position.
776
- */
875
+ * Replay a batch of events through the derived-state machine.
876
+ *
877
+ * Called with every batch of new events fetched from the log. The
878
+ * consumer should feed these events into their state machine
879
+ * (e.g. an {@link import("@lunora/replica").EventSource | EventSource})
880
+ * so that the machine's state reflects the latest log position.
881
+ */
777
882
  applyEvents: (events: ReadonlyArray<EventLogEntry>) => void;
778
883
  /**
779
- * Fetch all events whose `seq >= sinceSeq`.
780
- *
781
- * In a server-side context, this typically wraps
782
- * {@link import("@lunora/replica").EventLogDOClient.getSince |
783
- * EventLogDOClient.getSince()}.
784
- * In a client context it could call a Lunora action that proxies to the
785
- * event log, or read from an IndexedDB cache.
786
- *
787
- * Return an empty array when there are no new events.
788
- */
884
+ * Fetch all events whose `seq >= sinceSeq`.
885
+ *
886
+ * In a server-side context, this typically wraps
887
+ * {@link import("@lunora/replica").EventLogDOClient.getSince |
888
+ * EventLogDOClient.getSince()}.
889
+ * In a client context it could call a Lunora action that proxies to the
890
+ * event log, or read from an IndexedDB cache.
891
+ *
892
+ * Return an empty array when there are no new events.
893
+ */
789
894
  fetchEventsSince: (sinceSeq: number) => Promise<ReadonlyArray<EventLogEntry>>;
790
895
  /**
791
- * Produce {@link TableDiff | TableDiffs} from the current derived state.
792
- *
793
- * Called after every batch of events has been applied. The consumer
794
- * compares the state _before_ and _after_ the batch and returns the
795
- * diffs needed to bring the LocalMirror up to date.
796
- *
797
- * Return an empty array when there are no changes to push to the mirror.
798
- */
896
+ * Produce {@link TableDiff | TableDiffs} from the current derived state.
897
+ *
898
+ * Called after every batch of events has been applied. The consumer
899
+ * **recomputes a full diff from the current mirror-vs-source state** and
900
+ * returns the diffs needed to bring the LocalMirror up to date.
901
+ *
902
+ * **MUST be idempotent** it must NOT advance a one-shot cursor as a side
903
+ * effect. A batch that fails partway (a `mirror.applyDiff` throws) is
904
+ * retried on the next poll from the same watermark; if this call consumed a
905
+ * cursor on the first attempt it would return `[]` on the retry and the
906
+ * un-mirrored diffs would be lost forever. Recompute-from-current-state has
907
+ * no such hazard: calling it again with no new events returns the same
908
+ * diffs, and calling it after a partial mirror write returns exactly the
909
+ * diffs still missing from the mirror.
910
+ *
911
+ * Return an empty array when there are no changes to push to the mirror.
912
+ */
799
913
  getTableDiffs: () => TableDiff[];
800
914
  /**
801
- * The local SQLite mirror to apply diffs to.
802
- */
915
+ * The local SQLite mirror to apply diffs to.
916
+ */
803
917
  mirror: LocalMirror;
804
918
  /**
805
- * Called when an error occurs during a poll cycle.
806
- *
807
- * Defaults to `console.error`. Set to a no-op to suppress error logging.
808
- */
919
+ * Called when an error occurs during a poll cycle.
920
+ *
921
+ * Defaults to `console.error`. Set to a no-op to suppress error logging.
922
+ */
809
923
  onError?: (error: unknown) => void;
810
924
  /**
811
- * How often to poll for new events (in milliseconds).
812
- * @default 5000
813
- */
925
+ * How often to poll for new events (in milliseconds).
926
+ * @default 5000
927
+ */
814
928
  pollInterval?: number;
815
929
  }
816
930
  /**
817
- * Periodically polls an event log, replays events through a state machine,
818
- * converts the resulting state into {@link TableDiff | TableDiffs}, and
819
- * applies them to a {@link LocalMirror}.
820
- *
821
- * The class is **transport-agnostic** — it accepts a generic
822
- * `fetchEventsSince` function rather than coupling to a specific source
823
- * (EventLogDO, WebSocket push, IndexedDB, etc.).
824
- *
825
- * ## Lifecycle
826
- *
827
- * 1. Call `start()` to begin periodic polling.
828
- * 2. Call `sync()` to perform an immediate one-shot sync.
829
- * 3. Call `stop()` to halt polling.
830
- *
831
- * The current watermark is exposed via `watermark` and advances
832
- * monotonically as events are applied.
833
- */
931
+ * Periodically polls an event log, replays events through a state machine,
932
+ * converts the resulting state into {@link TableDiff | TableDiffs}, and
933
+ * applies them to a {@link LocalMirror}.
934
+ *
935
+ * The class is **transport-agnostic** — it accepts a generic
936
+ * `fetchEventsSince` function rather than coupling to a specific source
937
+ * (EventLogDO, WebSocket push, IndexedDB, etc.).
938
+ *
939
+ * ## Lifecycle
940
+ *
941
+ * 1. Call `start()` to begin periodic polling.
942
+ * 2. Call `sync()` to perform an immediate one-shot sync.
943
+ * 3. Call `stop()` to halt polling.
944
+ *
945
+ * The current watermark is exposed via `watermark` and advances
946
+ * monotonically as events are applied.
947
+ * @experimental
948
+ */
834
949
  declare class EventsSync {
835
950
  #private;
836
951
  constructor(options: EventsSyncOptions);
837
952
  /**
838
- * The current watermark — the next `seq` the sync will fetch from.
839
- *
840
- * Starts at `0` (fetch everything). Advances to `max(seq) + 1` after
841
- * each successful poll cycle.
842
- */
953
+ * The current watermark — the next `seq` the sync will fetch from.
954
+ *
955
+ * Starts at `0` (fetch everything). Advances to `max(seq) + 1` after
956
+ * each successful poll cycle.
957
+ */
843
958
  get watermark(): number;
844
959
  /**
845
- * Start polling for new events on the configured interval.
846
- *
847
- * Does nothing if polling is already active.
848
- * Does **not** perform an initial sync — call {@link sync} once if you
849
- * need to catch up immediately.
850
- */
960
+ * Start polling for new events on the configured interval.
961
+ *
962
+ * Does nothing if polling is already active.
963
+ * Does **not** perform an initial sync — call {@link sync} once if you
964
+ * need to catch up immediately.
965
+ */
851
966
  start(): void;
852
967
  /**
853
- * Stop polling for new events.
854
- *
855
- * Safe to call when not started.
856
- */
968
+ * Stop polling for new events.
969
+ *
970
+ * Safe to call when not started.
971
+ */
857
972
  stop(): void;
858
973
  /**
859
- * Perform a one-shot sync: fetch events since the current watermark,
860
- * apply them through the state machine, and push diffs to the mirror.
861
- * @returns The number of events that were fetched and applied.
862
- */
974
+ * Perform a one-shot sync: fetch events since the current watermark,
975
+ * apply them through the state machine, and push diffs to the mirror.
976
+ * @returns The number of events that were fetched and applied.
977
+ */
863
978
  sync(): Promise<number>;
864
979
  }
865
- export { type AppendEventInput, type AppendOptions, type EventCallback, EventEmitter, type EventFactory, EventLog, EventLogDO, EventLogDOClient, type EventLogDOClientOptions, type EventLogEntry, type EventNamespace, type EventReducer, EventSource, type EventSourceEvents, type EventSourceOptions, type EventsContextOutput, type EventsDefinition, type EventsFacade, EventsSync, type EventsSyncOptions, InMemorySnapshotStore, type InputEvent, LocalMirror, type Materializer, type MaterializerDef, type MaterializerReducer, MaterializerRuntime, type MaterializerRuntimeOptions, type Seq, type SnapshotStore, type SqliteAdapter, type StateChangeCallback, type SubscriptionClient, SubscriptionManager, type TableDiff, type UnknownEventHandling, applyDiff, applyDiffToDatabase as applyDiffToDb, applyDiffToSnapshot, applyDiffs, applyDiffsToDatabase as applyDiffsToDb, defineEvents, defineMaterializer, eventsContext, subscribeToMirror };
980
+ export { type AppendEventInput, type AppendOptions, type EventCallback, EventEmitter, type EventFactory, EventLog, EventLogDO, EventLogDOClient, type EventLogDOClientOptions, type EventLogEntry, type EventNamespace, type EventReducer, EventSource, type EventSourceEvents, type EventSourceOptions, type EventsContextOutput, type EventsDefinition, type EventsFacade, EventsSync, type EventsSyncOptions, InMemorySnapshotStore, type InputEvent, LocalMirror, type Materializer, type MaterializerDef, type MaterializerReducer, MaterializerRuntime, type MaterializerRuntimeOptions, type Seq, type SnapshotStore, type SqliteAdapter, type StateChangeCallback, type SubscriptionClient, SubscriptionManager, type TableDiff, UNHANDLED, type UnknownEventHandling, applyDiff, applyDiffToDatabase as applyDiffToDb, applyDiffToSnapshot, applyDiffs, applyDiffsToDatabase as applyDiffsToDb, defineEvents, defineMaterializer, eventsContext, subscribeToMirror };