@lunora/replica 1.0.0-alpha.4 → 1.0.0-alpha.6

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 (31) hide show
  1. package/dist/adapters/better-sqlite3.d.mts +14 -14
  2. package/dist/adapters/better-sqlite3.d.ts +14 -14
  3. package/dist/adapters/sqlite-wasm.d.mts +29 -19
  4. package/dist/adapters/sqlite-wasm.d.ts +29 -19
  5. package/dist/adapters/sqlite-wasm.mjs +12 -24
  6. package/dist/adapters/sqljs.d.mts +10 -10
  7. package/dist/adapters/sqljs.d.ts +10 -10
  8. package/dist/index.d.mts +634 -574
  9. package/dist/index.d.ts +634 -574
  10. package/dist/index.mjs +9 -9
  11. package/dist/packem_shared/EventLog-CnK-3Wge.mjs +264 -0
  12. package/dist/packem_shared/{EventLogDO-CZYUvvSr.mjs → EventLogDO-DqlsVx0H.mjs} +155 -9
  13. package/dist/packem_shared/{EventLogDOClient-DGiEdi96.mjs → EventLogDOClient-F4FO8Si4.mjs} +8 -2
  14. package/dist/packem_shared/{EventSource-DfV4VoRD.mjs → EventSource-D5yO9_aI.mjs} +48 -22
  15. package/dist/packem_shared/{EventsSync-DkVbU0WV.mjs → EventsSync-BP36tC9O.mjs} +49 -17
  16. package/dist/packem_shared/{LocalMirror-GeJ26eNe.mjs → LocalMirror-a-5jEqFN.mjs} +34 -3
  17. package/dist/packem_shared/{MaterializerRuntime-HqNXqJxp.mjs → MaterializerRuntime-BoIrsMYB.mjs} +65 -45
  18. package/dist/packem_shared/applyDiff-98tKzmiW.mjs +67 -0
  19. package/dist/packem_shared/{classifyChanges-aZmkxgVI.mjs → classifyChanges-RcqLBpLs.mjs} +6 -3
  20. package/dist/packem_shared/local-mirror.d-CyGOpUES.d.ts +530 -0
  21. package/dist/packem_shared/local-mirror.d-DTavX_y0.d.mts +530 -0
  22. package/dist/packem_shared/{types.d-BuAWjEY5.d.mts → types.d-CkMkSwLJ.d.mts} +10 -10
  23. package/dist/packem_shared/{types.d-BuAWjEY5.d.ts → types.d-CkMkSwLJ.d.ts} +10 -10
  24. package/dist/react.d.mts +59 -59
  25. package/dist/react.d.ts +59 -59
  26. package/dist/react.mjs +2 -2
  27. package/package.json +1 -1
  28. package/dist/packem_shared/EventLog-zMy7AYP4.mjs +0 -162
  29. package/dist/packem_shared/applyDiff-BtbIl1D3.mjs +0 -40
  30. package/dist/packem_shared/local-mirror.d-BUeOe5KC.d.mts +0 -439
  31. package/dist/packem_shared/local-mirror.d-Cd8tAg-W.d.ts +0 -439
package/dist/react.d.mts CHANGED
@@ -1,67 +1,67 @@
1
- import { L as LocalMirror } from "./packem_shared/local-mirror.d-BUeOe5KC.mjs";
2
- import "./packem_shared/types.d-BuAWjEY5.mjs";
1
+ import { L as LocalMirror } from "./packem_shared/local-mirror.d-DTavX_y0.mjs";
2
+ import "./packem_shared/types.d-CkMkSwLJ.mjs";
3
3
  /**
4
- * Options for the {@link useLocalQuery} hook.
5
- * @experimental
6
- */
4
+ * Options for the {@link useLocalQuery} hook.
5
+ * @experimental
6
+ */
7
7
  interface UseLocalQueryOptions {
8
8
  /**
9
- * Optional shard key (reserved for future use; currently unused).
10
- *
11
- * Intended for multi-mirror setups where a single app maintains multiple
12
- * SQLite databases sharded by a key (e.g. user id, tenant id). Currently
13
- * has no effect — the hook always queries the mirror passed as the first
14
- * argument.
15
- */
9
+ * Optional shard key (reserved for future use; currently unused).
10
+ *
11
+ * Intended for multi-mirror setups where a single app maintains multiple
12
+ * SQLite databases sharded by a key (e.g. user id, tenant id). Currently
13
+ * has no effect — the hook always queries the mirror passed as the first
14
+ * argument.
15
+ */
16
16
  shardKey?: string;
17
17
  }
18
18
  /**
19
- * React hook that subscribes to a local SQLite query and returns
20
- * live-updating results whenever the mirror applies a diff.
21
- *
22
- * Uses `useSyncExternalStore` to subscribe to the mirror's `onChange`
23
- * callback — every diff triggers a re-query against the local SQLite.
24
- *
25
- * The hook works with React 18+ concurrent features, Suspense, and
26
- * server-side rendering. During SSR the same value is returned as on
27
- * the client (the mirror's current state at render time).
28
- * @param mirror The {@link LocalMirror} instance to query. Must have been
29
- * constructed with an {@link import("./adapters/types").SqliteAdapter}.
30
- * @param sql Parameterised SQL query string. Use `?` placeholders for
31
- * bound parameters (the adapter forwards them to the underlying SQLite
32
- * engine without rewriting).
33
- * @param params Optional positional bound parameters matching `?`
34
- * placeholders in `sql`.
35
- * @param _options Optional configuration (currently unused; reserved for
36
- * future features like shard key routing).
37
- * @returns An array of result rows typed via the generic parameter `T`, or
38
- * `undefined` if the query fails (e.g. the target table doesn't exist yet
39
- * because no matching diff has been applied). Treat `undefined` as a
40
- * "loading" or "no data yet" signal in your component.
41
- *
42
- * **Error handling**: The hook catches SQL errors internally and returns
43
- * `undefined`. Use a try-catch around `mirror.query(...)` directly if you
44
- * need finer-grained error diagnostics.
45
- * @example
46
- * ```tsx
47
- * import { useLocalQuery } from "@lunora/replica/react";
48
- * import { mirror } from "./mirror";
49
- *
50
- * function UserList() {
51
- * const users = useLocalQuery<{ id: string; name: string }>(
52
- * mirror,
53
- * "SELECT id, name FROM fn_todos_list WHERE name LIKE ?",
54
- * ["%alice%"],
55
- * );
56
- *
57
- * if (users === undefined) {
58
- * return <p>Waiting for data…</p>;
59
- * }
60
- *
61
- * return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
62
- * }
63
- * ```
64
- * @experimental
65
- */
19
+ * React hook that subscribes to a local SQLite query and returns
20
+ * live-updating results whenever the mirror applies a diff.
21
+ *
22
+ * Uses `useSyncExternalStore` to subscribe to the mirror's `onChange`
23
+ * callback — every diff triggers a re-query against the local SQLite.
24
+ *
25
+ * The hook works with React 18+ concurrent features, Suspense, and
26
+ * server-side rendering. During SSR the same value is returned as on
27
+ * the client (the mirror's current state at render time).
28
+ * @param mirror The {@link LocalMirror} instance to query. Must have been
29
+ * constructed with an {@link import("./adapters/types").SqliteAdapter}.
30
+ * @param sql Parameterised SQL query string. Use `?` placeholders for
31
+ * bound parameters (the adapter forwards them to the underlying SQLite
32
+ * engine without rewriting).
33
+ * @param params Optional positional bound parameters matching `?`
34
+ * placeholders in `sql`.
35
+ * @param _options Optional configuration (currently unused; reserved for
36
+ * future features like shard key routing).
37
+ * @returns An array of result rows typed via the generic parameter `T`, or
38
+ * `undefined` if the query fails (e.g. the target table doesn't exist yet
39
+ * because no matching diff has been applied). Treat `undefined` as a
40
+ * "loading" or "no data yet" signal in your component.
41
+ *
42
+ * **Error handling**: The hook catches SQL errors internally and returns
43
+ * `undefined`. Use a try-catch around `mirror.query(...)` directly if you
44
+ * need finer-grained error diagnostics.
45
+ * @example
46
+ * ```tsx
47
+ * import { useLocalQuery } from "@lunora/replica/react";
48
+ * import { mirror } from "./mirror";
49
+ *
50
+ * function UserList() {
51
+ * const users = useLocalQuery<{ id: string; name: string }>(
52
+ * mirror,
53
+ * "SELECT id, name FROM fn_todos_list WHERE name LIKE ?",
54
+ * ["%alice%"],
55
+ * );
56
+ *
57
+ * if (users === undefined) {
58
+ * return <p>Waiting for data…</p>;
59
+ * }
60
+ *
61
+ * return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
62
+ * }
63
+ * ```
64
+ * @experimental
65
+ */
66
66
  declare const useLocalQuery: <T = Record<string, unknown>>(mirror: LocalMirror, sql: string, params?: ReadonlyArray<unknown>, _options?: UseLocalQueryOptions) => T[] | undefined;
67
67
  export { UseLocalQueryOptions, useLocalQuery };
package/dist/react.d.ts CHANGED
@@ -1,67 +1,67 @@
1
- import { L as LocalMirror } from "./packem_shared/local-mirror.d-Cd8tAg-W.js";
2
- import "./packem_shared/types.d-BuAWjEY5.js";
1
+ import { L as LocalMirror } from "./packem_shared/local-mirror.d-CyGOpUES.js";
2
+ import "./packem_shared/types.d-CkMkSwLJ.js";
3
3
  /**
4
- * Options for the {@link useLocalQuery} hook.
5
- * @experimental
6
- */
4
+ * Options for the {@link useLocalQuery} hook.
5
+ * @experimental
6
+ */
7
7
  interface UseLocalQueryOptions {
8
8
  /**
9
- * Optional shard key (reserved for future use; currently unused).
10
- *
11
- * Intended for multi-mirror setups where a single app maintains multiple
12
- * SQLite databases sharded by a key (e.g. user id, tenant id). Currently
13
- * has no effect — the hook always queries the mirror passed as the first
14
- * argument.
15
- */
9
+ * Optional shard key (reserved for future use; currently unused).
10
+ *
11
+ * Intended for multi-mirror setups where a single app maintains multiple
12
+ * SQLite databases sharded by a key (e.g. user id, tenant id). Currently
13
+ * has no effect — the hook always queries the mirror passed as the first
14
+ * argument.
15
+ */
16
16
  shardKey?: string;
17
17
  }
18
18
  /**
19
- * React hook that subscribes to a local SQLite query and returns
20
- * live-updating results whenever the mirror applies a diff.
21
- *
22
- * Uses `useSyncExternalStore` to subscribe to the mirror's `onChange`
23
- * callback — every diff triggers a re-query against the local SQLite.
24
- *
25
- * The hook works with React 18+ concurrent features, Suspense, and
26
- * server-side rendering. During SSR the same value is returned as on
27
- * the client (the mirror's current state at render time).
28
- * @param mirror The {@link LocalMirror} instance to query. Must have been
29
- * constructed with an {@link import("./adapters/types").SqliteAdapter}.
30
- * @param sql Parameterised SQL query string. Use `?` placeholders for
31
- * bound parameters (the adapter forwards them to the underlying SQLite
32
- * engine without rewriting).
33
- * @param params Optional positional bound parameters matching `?`
34
- * placeholders in `sql`.
35
- * @param _options Optional configuration (currently unused; reserved for
36
- * future features like shard key routing).
37
- * @returns An array of result rows typed via the generic parameter `T`, or
38
- * `undefined` if the query fails (e.g. the target table doesn't exist yet
39
- * because no matching diff has been applied). Treat `undefined` as a
40
- * "loading" or "no data yet" signal in your component.
41
- *
42
- * **Error handling**: The hook catches SQL errors internally and returns
43
- * `undefined`. Use a try-catch around `mirror.query(...)` directly if you
44
- * need finer-grained error diagnostics.
45
- * @example
46
- * ```tsx
47
- * import { useLocalQuery } from "@lunora/replica/react";
48
- * import { mirror } from "./mirror";
49
- *
50
- * function UserList() {
51
- * const users = useLocalQuery<{ id: string; name: string }>(
52
- * mirror,
53
- * "SELECT id, name FROM fn_todos_list WHERE name LIKE ?",
54
- * ["%alice%"],
55
- * );
56
- *
57
- * if (users === undefined) {
58
- * return <p>Waiting for data…</p>;
59
- * }
60
- *
61
- * return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
62
- * }
63
- * ```
64
- * @experimental
65
- */
19
+ * React hook that subscribes to a local SQLite query and returns
20
+ * live-updating results whenever the mirror applies a diff.
21
+ *
22
+ * Uses `useSyncExternalStore` to subscribe to the mirror's `onChange`
23
+ * callback — every diff triggers a re-query against the local SQLite.
24
+ *
25
+ * The hook works with React 18+ concurrent features, Suspense, and
26
+ * server-side rendering. During SSR the same value is returned as on
27
+ * the client (the mirror's current state at render time).
28
+ * @param mirror The {@link LocalMirror} instance to query. Must have been
29
+ * constructed with an {@link import("./adapters/types").SqliteAdapter}.
30
+ * @param sql Parameterised SQL query string. Use `?` placeholders for
31
+ * bound parameters (the adapter forwards them to the underlying SQLite
32
+ * engine without rewriting).
33
+ * @param params Optional positional bound parameters matching `?`
34
+ * placeholders in `sql`.
35
+ * @param _options Optional configuration (currently unused; reserved for
36
+ * future features like shard key routing).
37
+ * @returns An array of result rows typed via the generic parameter `T`, or
38
+ * `undefined` if the query fails (e.g. the target table doesn't exist yet
39
+ * because no matching diff has been applied). Treat `undefined` as a
40
+ * "loading" or "no data yet" signal in your component.
41
+ *
42
+ * **Error handling**: The hook catches SQL errors internally and returns
43
+ * `undefined`. Use a try-catch around `mirror.query(...)` directly if you
44
+ * need finer-grained error diagnostics.
45
+ * @example
46
+ * ```tsx
47
+ * import { useLocalQuery } from "@lunora/replica/react";
48
+ * import { mirror } from "./mirror";
49
+ *
50
+ * function UserList() {
51
+ * const users = useLocalQuery<{ id: string; name: string }>(
52
+ * mirror,
53
+ * "SELECT id, name FROM fn_todos_list WHERE name LIKE ?",
54
+ * ["%alice%"],
55
+ * );
56
+ *
57
+ * if (users === undefined) {
58
+ * return <p>Waiting for data…</p>;
59
+ * }
60
+ *
61
+ * return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
62
+ * }
63
+ * ```
64
+ * @experimental
65
+ */
66
66
  declare const useLocalQuery: <T = Record<string, unknown>>(mirror: LocalMirror, sql: string, params?: ReadonlyArray<unknown>, _options?: UseLocalQueryOptions) => T[] | undefined;
67
67
  export { UseLocalQueryOptions, useLocalQuery };
package/dist/react.mjs CHANGED
@@ -2,8 +2,8 @@ import { useSyncExternalStore } from 'react';
2
2
 
3
3
  const useLocalQuery = (mirror, sql, params, _options) => {
4
4
  const subscribe = (onStoreChange) => mirror.onChange(onStoreChange);
5
- const getSnapshot = () => mirror.eventLog.size;
6
- const getServerSnapshot = () => mirror.eventLog.size;
5
+ const getSnapshot = () => mirror.version;
6
+ const getServerSnapshot = () => mirror.version;
7
7
  useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
8
8
  try {
9
9
  return mirror.query(sql, params);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/replica",
3
- "version": "1.0.0-alpha.4",
3
+ "version": "1.0.0-alpha.6",
4
4
  "description": "Local-first replica runtime + local SQLite mirror for Lunora",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -1,162 +0,0 @@
1
- class EventLog {
2
- #entries = [];
3
- #nextSeq = 0;
4
- // eslint-disable-next-line unicorn/no-null -- public contract uses `null` for an empty log head
5
- #headSeq = null;
6
- append(typeOrEvent, payload, tableDiffs, options) {
7
- const type = typeof typeOrEvent === "string" ? typeOrEvent : typeOrEvent.type;
8
- const pl = typeof typeOrEvent === "string" ? payload : typeOrEvent.payload;
9
- let diffs;
10
- let resolvedOptions;
11
- if (typeof typeOrEvent === "string") {
12
- diffs = tableDiffs;
13
- resolvedOptions = options;
14
- } else {
15
- diffs = void 0;
16
- resolvedOptions = payload;
17
- }
18
- const parentSeqNumber = resolvedOptions?.parentSeqNum ?? this.#headSeq ?? void 0;
19
- const seq = this.#nextSeq;
20
- this.#nextSeq += 1;
21
- const entry = {
22
- seq,
23
- type,
24
- payload: pl,
25
- timestamp: Date.now(),
26
- tableDiffs: diffs,
27
- clientId: resolvedOptions?.clientId,
28
- sessionId: resolvedOptions?.sessionId,
29
- parentSeqNum: parentSeqNumber
30
- };
31
- this.#entries.push(entry);
32
- this.#headSeq = entry.seq;
33
- return entry;
34
- }
35
- /**
36
- * Atomically append multiple events to the log.
37
- *
38
- * All events are assigned sequential global sequence numbers and
39
- * automatically wired as a causal chain (each event's `parentSeqNum`
40
- * points to the previous event in the batch, or to the log head for
41
- * the first event).
42
- * @param events An array of events to commit atomically.
43
- * @returns The newly created entries in order.
44
- */
45
- commitAll(events) {
46
- if (events.length === 0) {
47
- return [];
48
- }
49
- const entries = [];
50
- for (const event of events) {
51
- const { type } = event;
52
- const payload = "payload" in event ? event.payload : void 0;
53
- const ts = "timestamp" in event ? event.timestamp : Date.now();
54
- const parentSeqNumber = entries.at(-1)?.seq ?? this.#headSeq ?? void 0;
55
- const seq = this.#nextSeq;
56
- this.#nextSeq += 1;
57
- const entry = {
58
- seq,
59
- type,
60
- payload,
61
- timestamp: ts,
62
- parentSeqNum: parentSeqNumber
63
- };
64
- this.#entries.push(entry);
65
- entries.push(entry);
66
- }
67
- this.#headSeq = entries.at(-1)?.seq ?? this.#headSeq;
68
- return entries;
69
- }
70
- /**
71
- * Replace the log contents with a previously captured snapshot.
72
- * This is the restore counterpart of {@link EventLog#snapshot}.
73
- * Restores `headSeq` from the snapshot so auto-parenting continues
74
- * after restore.
75
- */
76
- load(snapshot) {
77
- this.#entries = [...snapshot.entries];
78
- this.#nextSeq = snapshot.nextSeq;
79
- this.#headSeq = snapshot.headSeq;
80
- }
81
- // ── Queries ───────────────────────────────────────────────────────
82
- /**
83
- * Return **all** entries whose `seq >= sinceSeq`.
84
- * Useful for catch-up: "give me everything since my last watermark".
85
- */
86
- getSince(sinceSeq) {
87
- if (sinceSeq <= 0) {
88
- return [...this.#entries];
89
- }
90
- const first = this.#entries.findIndex((entry) => entry.seq >= sinceSeq);
91
- return first === -1 ? [] : this.#entries.slice(first);
92
- }
93
- /**
94
- * Paginated read starting at `fromSeq`.
95
- * @returns `{ entries, hasMore }` where `hasMore` is `true` when more
96
- * entries exist beyond the requested page.
97
- */
98
- getFrom(fromSeq, limit = 50) {
99
- const first = this.#entries.findIndex((entry) => entry.seq >= fromSeq);
100
- if (first === -1) {
101
- return { entries: [], hasMore: false };
102
- }
103
- const slice = this.#entries.slice(first, first + limit);
104
- return {
105
- entries: slice,
106
- hasMore: first + limit < this.#entries.length
107
- };
108
- }
109
- /**
110
- * Return all entries as a snapshot suitable for serialisation.
111
- */
112
- snapshot() {
113
- return {
114
- entries: [...this.#entries],
115
- nextSeq: this.#nextSeq,
116
- headSeq: this.#headSeq
117
- };
118
- }
119
- /** Number of entries currently in the log. */
120
- get size() {
121
- return this.#entries.length;
122
- }
123
- /** The next sequence number that will be assigned. */
124
- get nextSeq() {
125
- return this.#nextSeq;
126
- }
127
- /** Return `true` when there are no entries. */
128
- get isEmpty() {
129
- return this.#entries.length === 0;
130
- }
131
- /**
132
- * The sequence number of the last (most recent) entry, or `null`
133
- * when the log is empty. Used internally for auto-parenting and
134
- * exposed for consumers that need the causal head.
135
- */
136
- get headSeq() {
137
- return this.#headSeq;
138
- }
139
- /** Remove all entries (primarily for testing). */
140
- clear() {
141
- this.#entries = [];
142
- this.#nextSeq = 0;
143
- this.#headSeq = null;
144
- }
145
- /**
146
- * Return an async generator that yields every entry starting from
147
- * `fromSeq` (default `0` = all entries).
148
- *
149
- * Because `EventLog` is purely in-memory, the generator yields all
150
- * matching entries synchronously on first iteration and then completes.
151
- * For a streaming / push-based variant see {@link EventSource.events}.
152
- */
153
- // eslint-disable-next-line @typescript-eslint/require-await -- kept async so callers can uniformly `for await` over any event stream
154
- async *events(fromSeq = 0) {
155
- const entries = this.getSince(fromSeq);
156
- for (const entry of entries) {
157
- yield entry;
158
- }
159
- }
160
- }
161
-
162
- export { EventLog };
@@ -1,40 +0,0 @@
1
- const applyDiff = (current, diff) => {
2
- const next = new Map(current);
3
- for (const change of diff.changes) {
4
- switch (change.type) {
5
- case "delete": {
6
- next.delete(change.id);
7
- break;
8
- }
9
- case "insert": {
10
- const rawId = change.data.id;
11
- const id = typeof rawId === "string" || typeof rawId === "number" ? String(rawId) : crypto.randomUUID();
12
- next.set(id, { ...change.data, id });
13
- break;
14
- }
15
- case "update": {
16
- const existing = next.get(change.id);
17
- if (existing) {
18
- next.set(change.id, { ...existing, ...change.data });
19
- }
20
- break;
21
- }
22
- }
23
- }
24
- return next;
25
- };
26
- const applyDiffs = (current, diffs) => {
27
- let result = new Map(current);
28
- for (const diff of diffs) {
29
- result = applyDiff(result, diff);
30
- }
31
- return result;
32
- };
33
- const applyDiffToSnapshot = (snapshot, diff) => {
34
- const next = new Map(snapshot);
35
- const tableMap = next.get(diff.table) ?? /* @__PURE__ */ new Map();
36
- next.set(diff.table, applyDiff(tableMap, diff));
37
- return next;
38
- };
39
-
40
- export { applyDiff, applyDiffToSnapshot, applyDiffs };