@lunora/replica 0.0.0 → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +231 -0
- package/README.md +192 -29
- package/dist/adapters/better-sqlite3.d.mts +27 -0
- package/dist/adapters/better-sqlite3.d.ts +27 -0
- package/dist/adapters/better-sqlite3.mjs +29 -0
- package/dist/adapters/sqlite-wasm.d.mts +25 -0
- package/dist/adapters/sqlite-wasm.d.ts +25 -0
- package/dist/adapters/sqlite-wasm.mjs +56 -0
- package/dist/adapters/sqljs.d.mts +19 -0
- package/dist/adapters/sqljs.d.ts +19 -0
- package/dist/adapters/sqljs.mjs +55 -0
- package/dist/index.d.mts +865 -0
- package/dist/index.d.ts +865 -0
- package/dist/index.mjs +20 -0
- package/dist/packem_shared/EventEmitter-CMZfct03.mjs +92 -0
- package/dist/packem_shared/EventLog-zMy7AYP4.mjs +162 -0
- package/dist/packem_shared/EventLogDO-CZYUvvSr.mjs +235 -0
- package/dist/packem_shared/EventLogDOClient-DGiEdi96.mjs +86 -0
- package/dist/packem_shared/EventSource-DfV4VoRD.mjs +195 -0
- package/dist/packem_shared/EventsSync-DkVbU0WV.mjs +91 -0
- package/dist/packem_shared/InMemorySnapshotStore-BHVAD-Bp.mjs +24 -0
- package/dist/packem_shared/LocalMirror-GeJ26eNe.mjs +188 -0
- package/dist/packem_shared/MaterializerRuntime-HqNXqJxp.mjs +204 -0
- package/dist/packem_shared/SubscriptionManager-C5xbw0pg.mjs +75 -0
- package/dist/packem_shared/applyDiff-BtbIl1D3.mjs +40 -0
- package/dist/packem_shared/applyDiffToDb-DQ1xZp5J.mjs +58 -0
- package/dist/packem_shared/classifyChanges-aZmkxgVI.mjs +38 -0
- package/dist/packem_shared/defineEvents-DiBkPTh_.mjs +28 -0
- package/dist/packem_shared/eventsContext-Bk_p48hj.mjs +6 -0
- package/dist/packem_shared/isClientSeq-C46BkzqJ.mjs +5 -0
- package/dist/packem_shared/local-mirror.d-Bp19ueGy.d.mts +412 -0
- package/dist/packem_shared/local-mirror.d-GhuZAKgm.d.ts +412 -0
- package/dist/packem_shared/subscribeToMirror-CiaM-nQ7.mjs +45 -0
- package/dist/packem_shared/types.d-VfJ76cK4.d.mts +23 -0
- package/dist/packem_shared/types.d-VfJ76cK4.d.ts +23 -0
- package/dist/react.d.mts +65 -0
- package/dist/react.d.ts +65 -0
- package/dist/react.mjs +15 -0
- package/package.json +88 -7
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
export { createBetterSqlite3Adapter } from "./adapters/better-sqlite3.mjs";
|
|
2
|
+
export { createSqliteWasmAdapter } from "./adapters/sqlite-wasm.mjs";
|
|
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
|
+
*/
|
|
28
|
+
declare const applyDiff: (current: ReadonlyMap<string, Record<string, unknown>>, diff: TableDiff) => Map<string, Record<string, unknown>>;
|
|
29
|
+
/**
|
|
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
|
+
*/
|
|
35
|
+
declare const applyDiffs: (current: ReadonlyMap<string, Record<string, unknown>>, diffs: ReadonlyArray<TableDiff>) => Map<string, Record<string, unknown>>;
|
|
36
|
+
/**
|
|
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<id, row>, posts: Map<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
|
+
*/
|
|
43
|
+
declare const applyDiffToSnapshot: (snapshot: ReadonlyMap<string, ReadonlyMap<string, Record<string, unknown>>>, diff: TableDiff) => Map<string, Map<string, Record<string, unknown>>>;
|
|
44
|
+
/** Map a namespace-and-name pair to a qualified event type string. */
|
|
45
|
+
type QualifiedType<Ns extends string, Name extends string> = `${Ns}.${Name}`;
|
|
46
|
+
/**
|
|
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
|
+
*/
|
|
56
|
+
type PayloadOf<T> = T extends {
|
|
57
|
+
readonly __type: infer P;
|
|
58
|
+
} ? P : T extends ((payload: infer P) => unknown) ? P : T extends Record<string, unknown> ? T : never;
|
|
59
|
+
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
60
|
+
/**
|
|
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]>;
|
|
64
|
+
/**
|
|
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
|
+
*/
|
|
70
|
+
interface EventFactory<Type extends string, Payload> {
|
|
71
|
+
(payload: Payload): InputEvent<Type, Payload>;
|
|
72
|
+
/** The fully qualified event type string (e.g. `"chat.messageSent"`). */
|
|
73
|
+
readonly type: Type;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
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]>> };
|
|
79
|
+
/**
|
|
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]> } & {
|
|
83
|
+
/** Type-level map of event type → payload shape. Useful for generic code. */
|
|
84
|
+
readonly _types: EventTypeMap<TDefinition>;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
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
|
+
*/
|
|
98
|
+
interface DefineEventsOptions {
|
|
99
|
+
/**
|
|
100
|
+
* Optional version prefix for all event types.
|
|
101
|
+
*
|
|
102
|
+
* When set, every qualified event type is prefixed with `"v<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
|
+
*/
|
|
108
|
+
readonly version?: string;
|
|
109
|
+
}
|
|
110
|
+
declare const defineEvents: <TDefinition extends Record<string, Record<string, unknown>>>(definition: TDefinition, options?: DefineEventsOptions) => EventsDefinition<TDefinition>;
|
|
111
|
+
/**
|
|
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
|
+
*/
|
|
117
|
+
interface AppendEventInput {
|
|
118
|
+
/** Globally-unique client identifier (for offline/optimistic support). */
|
|
119
|
+
readonly clientId?: string;
|
|
120
|
+
/** Causal parent sequence number (ClientSeq for optimistic, GlobalSeq for confirmed). */
|
|
121
|
+
readonly parentSeqNum?: Seq;
|
|
122
|
+
/** Arbitrary JSON-serialisable payload. */
|
|
123
|
+
readonly payload: unknown;
|
|
124
|
+
/** Session identifier within the client. */
|
|
125
|
+
readonly sessionId?: string;
|
|
126
|
+
/** Millisecond timestamp (epoch) — omit to let the server assign it. */
|
|
127
|
+
readonly timestamp?: number;
|
|
128
|
+
/** Event type discriminator. */
|
|
129
|
+
readonly type: string;
|
|
130
|
+
}
|
|
131
|
+
/** Options for constructing an {@link EventLogDOClient}. */
|
|
132
|
+
interface EventLogDOClientOptions {
|
|
133
|
+
/**
|
|
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
|
+
*/
|
|
141
|
+
fetch: (request: Request) => Promise<Response>;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
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
|
+
*/
|
|
149
|
+
declare class EventLogDOClient {
|
|
150
|
+
#private;
|
|
151
|
+
constructor(options: EventLogDOClientOptions);
|
|
152
|
+
/**
|
|
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
|
+
*/
|
|
162
|
+
getSince(sinceSeq: number): Promise<EventLogEntry[]>;
|
|
163
|
+
/**
|
|
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
|
+
*/
|
|
168
|
+
getRange(fromSeq: number, limit?: number): Promise<{
|
|
169
|
+
entries: EventLogEntry[];
|
|
170
|
+
hasMore: boolean;
|
|
171
|
+
}>;
|
|
172
|
+
/**
|
|
173
|
+
* Return the total number of entries currently in the log.
|
|
174
|
+
*/
|
|
175
|
+
getSize(): Promise<number>;
|
|
176
|
+
/**
|
|
177
|
+
* Return the full log state — all entries plus the next seq number.
|
|
178
|
+
*/
|
|
179
|
+
getState(): Promise<{
|
|
180
|
+
entries: EventLogEntry[];
|
|
181
|
+
nextSeq: number;
|
|
182
|
+
}>;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
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
|
+
*/
|
|
195
|
+
declare class EventEmitter<EventMap extends Record<string, unknown>> {
|
|
196
|
+
#private;
|
|
197
|
+
/**
|
|
198
|
+
* Register a handler for a specific event type.
|
|
199
|
+
* @returns An unsubscribe function (equivalent to calling {@link off}).
|
|
200
|
+
*/
|
|
201
|
+
on<K extends keyof EventMap>(event: K, handler: (payload: EventMap[K]) => void): () => void;
|
|
202
|
+
/**
|
|
203
|
+
* Remove a previously registered handler.
|
|
204
|
+
*/
|
|
205
|
+
off<K extends keyof EventMap>(event: K, handler: (payload: EventMap[K]) => void): void;
|
|
206
|
+
/**
|
|
207
|
+
* Register a wildcard handler that fires for **every** event type.
|
|
208
|
+
* @returns An unsubscribe function.
|
|
209
|
+
*/
|
|
210
|
+
onAny(handler: (event: keyof EventMap, payload: unknown) => void): () => void;
|
|
211
|
+
/**
|
|
212
|
+
* Remove a wildcard handler.
|
|
213
|
+
*/
|
|
214
|
+
offAny(handler: (event: keyof EventMap, payload: unknown) => void): void;
|
|
215
|
+
/**
|
|
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
|
+
*/
|
|
221
|
+
emit<K extends keyof EventMap>(event: K, payload: EventMap[K]): boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Return `true` when at least one listener is registered for `event`.
|
|
224
|
+
*/
|
|
225
|
+
hasListeners(event: keyof EventMap): boolean;
|
|
226
|
+
/**
|
|
227
|
+
* Return the number of typed listeners for a specific event.
|
|
228
|
+
*/
|
|
229
|
+
listenerCount(event: keyof EventMap): number;
|
|
230
|
+
/**
|
|
231
|
+
* Remove all listeners.
|
|
232
|
+
*/
|
|
233
|
+
clear(): void;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
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
|
+
*/
|
|
244
|
+
type UnknownEventHandling = "warn" | "ignore" | "fail" | ((entry: EventLogEntry) => boolean);
|
|
245
|
+
/**
|
|
246
|
+
* Events emitted by the {@link EventSource} runtime.
|
|
247
|
+
*
|
|
248
|
+
* A `type` (not `interface`) so it satisfies `EventEmitter`'s
|
|
249
|
+
* `Record<string, unknown>` constraint — interfaces have no implicit index
|
|
250
|
+
* signature and aren't assignable to `Record<string, unknown>`.
|
|
251
|
+
*/
|
|
252
|
+
type EventSourceEvents = {
|
|
253
|
+
/** Fired (once) after the initial replay completes. */
|
|
254
|
+
ready: {
|
|
255
|
+
entryCount: number;
|
|
256
|
+
};
|
|
257
|
+
/** Fired when a replay error occurs — the runtime will skip the bad entry. */
|
|
258
|
+
"replay-error": {
|
|
259
|
+
entry: EventLogEntry;
|
|
260
|
+
error: Error;
|
|
261
|
+
};
|
|
262
|
+
/** Fired after an event has been applied and the state updated. */
|
|
263
|
+
"state-changed": {
|
|
264
|
+
entry: EventLogEntry;
|
|
265
|
+
state: Record<string, unknown>;
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
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
|
+
*/
|
|
278
|
+
interface EventSourceOptions {
|
|
279
|
+
/**
|
|
280
|
+
* How to handle events whose `type` is not recognised by the reducer.
|
|
281
|
+
* @default "warn"
|
|
282
|
+
*/
|
|
283
|
+
unknownEventHandling?: UnknownEventHandling;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
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
|
+
*/
|
|
299
|
+
declare class EventSource<S extends Record<string, unknown> = Record<string, unknown>> {
|
|
300
|
+
#private;
|
|
301
|
+
readonly emitter: EventEmitter<EventSourceEvents>;
|
|
302
|
+
readonly log: EventLog;
|
|
303
|
+
constructor(initialState: S, reducer: EventReducer<S>, options?: EventSourceOptions);
|
|
304
|
+
/**
|
|
305
|
+
* The current derived state. Read-only snapshot; mutate through events.
|
|
306
|
+
*/
|
|
307
|
+
get state(): Readonly<S>;
|
|
308
|
+
/**
|
|
309
|
+
* Whether the initial replay from an existing log has completed.
|
|
310
|
+
*/
|
|
311
|
+
get replayed(): boolean;
|
|
312
|
+
/**
|
|
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
|
+
*/
|
|
319
|
+
applyEvent(event: InputEvent, options?: AppendOptions): EventLogEntry;
|
|
320
|
+
applyEvent(type: string, payload: unknown, options?: AppendOptions): EventLogEntry;
|
|
321
|
+
/**
|
|
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
|
+
*/
|
|
329
|
+
replayFromLog(log: EventLog): void;
|
|
330
|
+
/**
|
|
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
|
+
*/
|
|
344
|
+
reset(initialState: S, resumeFromSeq?: number): void;
|
|
345
|
+
/**
|
|
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
|
+
*/
|
|
359
|
+
events(signal?: AbortSignal): AsyncGenerator<EventLogEntry>;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
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
|
+
*/
|
|
370
|
+
interface SnapshotStore {
|
|
371
|
+
/** Delete all snapshots. */
|
|
372
|
+
clear: () => Promise<void>;
|
|
373
|
+
/** Delete a single snapshot. */
|
|
374
|
+
delete: (key: string) => Promise<void>;
|
|
375
|
+
/** List all snapshot keys. */
|
|
376
|
+
list: () => Promise<string[]>;
|
|
377
|
+
/** Load a previously saved snapshot, or `null` when not found. */
|
|
378
|
+
load: (key: string) => Promise<unknown>;
|
|
379
|
+
/** Persist a snapshot under `key`. */
|
|
380
|
+
save: (key: string, snapshot: unknown) => Promise<void>;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
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
|
+
*/
|
|
387
|
+
declare class InMemorySnapshotStore implements SnapshotStore {
|
|
388
|
+
#private;
|
|
389
|
+
save(key: string, snapshot: unknown): Promise<void>;
|
|
390
|
+
load(key: string): Promise<unknown>;
|
|
391
|
+
list(): Promise<string[]>;
|
|
392
|
+
delete(key: string): Promise<void>;
|
|
393
|
+
clear(): Promise<void>;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
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
|
+
*/
|
|
401
|
+
type MaterializerReducer<S> = (state: S, entry: EventLogEntry) => S;
|
|
402
|
+
/**
|
|
403
|
+
* Options for defining a single materializer.
|
|
404
|
+
*/
|
|
405
|
+
interface MaterializerDef<S> {
|
|
406
|
+
/**
|
|
407
|
+
* Reducer invoked for every event in the log.
|
|
408
|
+
*
|
|
409
|
+
* Return the current state unchanged to skip the event.
|
|
410
|
+
*/
|
|
411
|
+
handle: MaterializerReducer<S>;
|
|
412
|
+
/** Factory for the initial (empty) state. */
|
|
413
|
+
initial: () => S;
|
|
414
|
+
/** Unique name (used as the snapshot storage key). */
|
|
415
|
+
readonly name: string;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* A constructed materializer ready to be used with a {@link MaterializerRuntime}.
|
|
419
|
+
*/
|
|
420
|
+
interface Materializer<S> {
|
|
421
|
+
/** Apply a single event entry through the reducer. */
|
|
422
|
+
apply: (entry: EventLogEntry) => void;
|
|
423
|
+
readonly def: MaterializerDef<S>;
|
|
424
|
+
/** Reset to the initial state. */
|
|
425
|
+
reset: () => void;
|
|
426
|
+
/** Replace the runtime state (used on snapshot restore / replay). */
|
|
427
|
+
setState: (state: S) => void;
|
|
428
|
+
/** Current (runtime) derived state. */
|
|
429
|
+
readonly state: Readonly<S>;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
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
|
+
*/
|
|
437
|
+
declare const defineMaterializer: <S>(definition: MaterializerDef<S>) => Materializer<S>;
|
|
438
|
+
/**
|
|
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<unknown>` won't do: `setState(state: S)` makes
|
|
443
|
+
* `Materializer<S>` invariant in `S`, so `Materializer<number>` isn't assignable
|
|
444
|
+
* to `Materializer<unknown>`. Erasing the type param is the idiomatic fix.
|
|
445
|
+
*/
|
|
446
|
+
type AnyMaterializer = Materializer<any>;
|
|
447
|
+
/**
|
|
448
|
+
* Options for constructing a {@link MaterializerRuntime}.
|
|
449
|
+
*/
|
|
450
|
+
interface MaterializerRuntimeOptions {
|
|
451
|
+
/**
|
|
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
|
+
*/
|
|
458
|
+
doClient?: EventLogDOClient;
|
|
459
|
+
/** Optional snapshot store for persisting/recovering materialized state. */
|
|
460
|
+
snapshotStore?: SnapshotStore;
|
|
461
|
+
/**
|
|
462
|
+
* How to handle events whose type no materializer handles.
|
|
463
|
+
* @default "warn"
|
|
464
|
+
*/
|
|
465
|
+
unknownEventHandling?: UnknownEventHandling;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
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
|
+
*/
|
|
476
|
+
declare class MaterializerRuntime {
|
|
477
|
+
#private;
|
|
478
|
+
constructor(materializers: AnyMaterializer[], options?: MaterializerRuntimeOptions);
|
|
479
|
+
/**
|
|
480
|
+
* The sequence number of the last event applied to all materializers.
|
|
481
|
+
*/
|
|
482
|
+
get appliedSeq(): number;
|
|
483
|
+
/**
|
|
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
|
+
*/
|
|
489
|
+
applyEntries(entries: ReadonlyArray<EventLogEntry>): number;
|
|
490
|
+
/**
|
|
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
|
+
*/
|
|
498
|
+
recoverFromSnapshots(): Promise<number>;
|
|
499
|
+
/**
|
|
500
|
+
* Persist the current state of all materializers as snapshots.
|
|
501
|
+
*/
|
|
502
|
+
persistSnapshots(): Promise<void>;
|
|
503
|
+
/**
|
|
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
|
+
*/
|
|
514
|
+
initialize(): Promise<number>;
|
|
515
|
+
/**
|
|
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
|
+
*/
|
|
524
|
+
appendEvent(input: AppendEventInput): Promise<EventLogEntry>;
|
|
525
|
+
/**
|
|
526
|
+
* Reset all materializers to their initial state and clear snapshots.
|
|
527
|
+
*/
|
|
528
|
+
reset(): void;
|
|
529
|
+
/**
|
|
530
|
+
* The list of registered materializers.
|
|
531
|
+
*/
|
|
532
|
+
get materializers(): ReadonlyArray<Materializer<unknown>>;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
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
|
+
*/
|
|
543
|
+
declare const applyDiffToDatabase: (database: SqliteAdapter, diff: TableDiff, pkColumn?: string) => void;
|
|
544
|
+
/**
|
|
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
|
+
*/
|
|
550
|
+
declare const applyDiffsToDatabase: (database: SqliteAdapter, diffs: ReadonlyArray<TableDiff>) => void;
|
|
551
|
+
interface EventLogDOState {
|
|
552
|
+
storage: {
|
|
553
|
+
sql: {
|
|
554
|
+
exec: (query: string, ...params: unknown[]) => unknown;
|
|
555
|
+
};
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
declare class EventLogDO {
|
|
559
|
+
#private;
|
|
560
|
+
protected state: EventLogDOState;
|
|
561
|
+
protected env: unknown;
|
|
562
|
+
constructor(state: EventLogDOState, env: unknown);
|
|
563
|
+
fetch(request: Request): Promise<Response>;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
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
|
+
*/
|
|
570
|
+
interface MiddlewareNext<ContextIn> {
|
|
571
|
+
(): Promise<ContextIn>;
|
|
572
|
+
<Extension extends Record<string, unknown>>(options: {
|
|
573
|
+
ctx: Extension;
|
|
574
|
+
}): Promise<ContextIn & Extension>;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
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
|
+
*/
|
|
581
|
+
type Middleware<ContextIn, ContextOut> = (options: {
|
|
582
|
+
ctx: ContextIn;
|
|
583
|
+
next: MiddlewareNext<ContextIn>;
|
|
584
|
+
}) => ContextOut | Promise<ContextOut>;
|
|
585
|
+
/** Options accepted by `initLunora.dataModel<DM>().create(...)`. Reserved for transformer/error-formatter wiring. */
|
|
586
|
+
/**
|
|
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
|
+
*/
|
|
592
|
+
interface EventsFacade {
|
|
593
|
+
/**
|
|
594
|
+
* Append one or more events to the log.
|
|
595
|
+
* @returns The persisted entries with their assigned `seq` numbers.
|
|
596
|
+
*/
|
|
597
|
+
append: (events: {
|
|
598
|
+
payload: unknown;
|
|
599
|
+
timestamp?: number;
|
|
600
|
+
type: string;
|
|
601
|
+
}[]) => Promise<EventLogEntry[]>;
|
|
602
|
+
/**
|
|
603
|
+
* Fetch a paginated range of entries.
|
|
604
|
+
* @returns `{ entries, hasMore }` — `hasMore` is `true` when another
|
|
605
|
+
* page exists.
|
|
606
|
+
*/
|
|
607
|
+
getRange: (fromSeq: number, limit?: number) => Promise<{
|
|
608
|
+
entries: EventLogEntry[];
|
|
609
|
+
hasMore: boolean;
|
|
610
|
+
}>;
|
|
611
|
+
/**
|
|
612
|
+
* Fetch all entries with `seq >= sinceSeq`.
|
|
613
|
+
*
|
|
614
|
+
* Pass `sinceSeq = 0` to fetch the entire log.
|
|
615
|
+
*/
|
|
616
|
+
getSince: (sinceSeq: number) => Promise<EventLogEntry[]>;
|
|
617
|
+
/** Return the total number of entries currently in the log. */
|
|
618
|
+
getSize: () => Promise<number>;
|
|
619
|
+
/** Return the full log state — all entries plus the next seq number. */
|
|
620
|
+
getState: () => Promise<{
|
|
621
|
+
entries: EventLogEntry[];
|
|
622
|
+
nextSeq: number;
|
|
623
|
+
}>;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* The context shape produced by {@link eventsContext}.
|
|
627
|
+
*/
|
|
628
|
+
interface EventsContextOutput {
|
|
629
|
+
/** Typed event log facade backed by an {@link EventLogDOClient}. */
|
|
630
|
+
readonly events: EventsFacade;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
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
|
+
*/
|
|
658
|
+
declare const eventsContext: <Context>(client: EventLogDOClient) => Middleware<Context, Context & EventsContextOutput>;
|
|
659
|
+
/**
|
|
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
|
+
*/
|
|
664
|
+
interface SubscriptionClient {
|
|
665
|
+
subscribe: (functionRef: {
|
|
666
|
+
__lunoraRef: string;
|
|
667
|
+
}, args: Record<string, unknown>, callback: (data: unknown) => void, options?: {
|
|
668
|
+
shardKey?: string;
|
|
669
|
+
}) => () => void;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
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
|
+
*/
|
|
697
|
+
declare const subscribeToMirror: (client: SubscriptionClient, mirror: LocalMirror, functionRef: {
|
|
698
|
+
__lunoraRef: string;
|
|
699
|
+
}, args: Record<string, unknown>, shardKey?: string) => (() => void);
|
|
700
|
+
/**
|
|
701
|
+
* Callback signature for state-change subscriptions.
|
|
702
|
+
*/
|
|
703
|
+
type StateChangeCallback = (state: Readonly<Record<string, unknown>>) => void;
|
|
704
|
+
/**
|
|
705
|
+
* Callback signature for event-type subscriptions.
|
|
706
|
+
*/
|
|
707
|
+
type EventCallback = (entry: EventLogEntry) => void;
|
|
708
|
+
/**
|
|
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
|
+
*/
|
|
734
|
+
declare class SubscriptionManager {
|
|
735
|
+
#private;
|
|
736
|
+
/**
|
|
737
|
+
* Subscribe to every state change emitted by the event source.
|
|
738
|
+
* @returns Unsubscribe function.
|
|
739
|
+
*/
|
|
740
|
+
onStateChange(callback: StateChangeCallback): () => void;
|
|
741
|
+
/**
|
|
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
|
+
*/
|
|
747
|
+
onEvent(eventType: string, callback: EventCallback): () => void;
|
|
748
|
+
/**
|
|
749
|
+
* Notify all state-change subscribers with the current state.
|
|
750
|
+
*/
|
|
751
|
+
notifyState(state: Readonly<Record<string, unknown>>): void;
|
|
752
|
+
/**
|
|
753
|
+
* Notify event-type subscribers whose `eventType` matches.
|
|
754
|
+
*/
|
|
755
|
+
notifyEvent(entry: EventLogEntry): void;
|
|
756
|
+
/**
|
|
757
|
+
* Return the total number of active subscriptions.
|
|
758
|
+
*/
|
|
759
|
+
get size(): number;
|
|
760
|
+
/**
|
|
761
|
+
* Remove all subscriptions.
|
|
762
|
+
*/
|
|
763
|
+
clear(): void;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Options for constructing an {@link EventsSync}.
|
|
767
|
+
*/
|
|
768
|
+
interface EventsSyncOptions {
|
|
769
|
+
/**
|
|
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
|
+
*/
|
|
777
|
+
applyEvents: (events: ReadonlyArray<EventLogEntry>) => void;
|
|
778
|
+
/**
|
|
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
|
+
*/
|
|
789
|
+
fetchEventsSince: (sinceSeq: number) => Promise<ReadonlyArray<EventLogEntry>>;
|
|
790
|
+
/**
|
|
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
|
+
*/
|
|
799
|
+
getTableDiffs: () => TableDiff[];
|
|
800
|
+
/**
|
|
801
|
+
* The local SQLite mirror to apply diffs to.
|
|
802
|
+
*/
|
|
803
|
+
mirror: LocalMirror;
|
|
804
|
+
/**
|
|
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
|
+
*/
|
|
809
|
+
onError?: (error: unknown) => void;
|
|
810
|
+
/**
|
|
811
|
+
* How often to poll for new events (in milliseconds).
|
|
812
|
+
* @default 5000
|
|
813
|
+
*/
|
|
814
|
+
pollInterval?: number;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
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
|
+
*/
|
|
834
|
+
declare class EventsSync {
|
|
835
|
+
#private;
|
|
836
|
+
constructor(options: EventsSyncOptions);
|
|
837
|
+
/**
|
|
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
|
+
*/
|
|
843
|
+
get watermark(): number;
|
|
844
|
+
/**
|
|
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
|
+
*/
|
|
851
|
+
start(): void;
|
|
852
|
+
/**
|
|
853
|
+
* Stop polling for new events.
|
|
854
|
+
*
|
|
855
|
+
* Safe to call when not started.
|
|
856
|
+
*/
|
|
857
|
+
stop(): void;
|
|
858
|
+
/**
|
|
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
|
+
*/
|
|
863
|
+
sync(): Promise<number>;
|
|
864
|
+
}
|
|
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 };
|