@lunora/replica 1.0.0-alpha.34 → 1.0.0-alpha.35
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/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/EventSource-ArQhJa6P.mjs +1 -0
- package/dist/packem_shared/LocalMirror-hEYEB31D.mjs +4 -0
- package/dist/packem_shared/applyDiffToDb-DSnSZmL4.mjs +1 -0
- package/dist/react.d.mts +40 -13
- package/dist/react.d.ts +40 -13
- package/dist/react.mjs +1 -1
- package/package.json +1 -1
- package/dist/packem_shared/EventSource-B5UTlkl9.mjs +0 -1
- package/dist/packem_shared/LocalMirror-Q7cPH_rX.mjs +0 -4
- package/dist/packem_shared/applyDiffToDb-1M2I3BHi.mjs +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -409,8 +409,10 @@ declare class EventSource<S extends Record<string, unknown> = Record<string, unk
|
|
|
409
409
|
* starting from the events currently in the log and continuing with
|
|
410
410
|
* every future `applyEvent` / `replayFromLog` call.
|
|
411
411
|
*
|
|
412
|
-
* The generator runs indefinitely
|
|
413
|
-
* break out of the `for await` loop or
|
|
412
|
+
* The generator runs indefinitely unless given a `signal` — callers
|
|
413
|
+
* should break out of the `for await` loop or pass an `AbortSignal` to
|
|
414
|
+
* stop it (an abort settles the generator, `done: true`, on its next
|
|
415
|
+
* iteration step; it does not throw).
|
|
414
416
|
* @example
|
|
415
417
|
* ```ts
|
|
416
418
|
* for await (const entry of source.events()) {
|
package/dist/index.d.ts
CHANGED
|
@@ -409,8 +409,10 @@ declare class EventSource<S extends Record<string, unknown> = Record<string, unk
|
|
|
409
409
|
* starting from the events currently in the log and continuing with
|
|
410
410
|
* every future `applyEvent` / `replayFromLog` call.
|
|
411
411
|
*
|
|
412
|
-
* The generator runs indefinitely
|
|
413
|
-
* break out of the `for await` loop or
|
|
412
|
+
* The generator runs indefinitely unless given a `signal` — callers
|
|
413
|
+
* should break out of the `for await` loop or pass an `AbortSignal` to
|
|
414
|
+
* stop it (an abort settles the generator, `done: true`, on its next
|
|
415
|
+
* iteration step; it does not throw).
|
|
414
416
|
* @example
|
|
415
417
|
* ```ts
|
|
416
418
|
* for await (const entry of source.events()) {
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createBetterSqlite3Adapter as o}from"./adapters/better-sqlite3.mjs";import{createSqliteWasmAdapter as f}from"./adapters/sqlite-wasm.mjs";import{createSqlJsAdapter as i}from"./adapters/sqljs.mjs";import{applyDiff as m,applyDiffToSnapshot as n,applyDiffs as x}from"./packem_shared/applyDiff-BUzddc6r.mjs";import{defineEvents as l}from"./packem_shared/defineEvents-CJZV8Bgi.mjs";import{MaterializerRuntime as S,defineMaterializer as E}from"./packem_shared/MaterializerRuntime-CqGGjSxl.mjs";import{applyDiffToDb as v,applyDiffsToDb as y}from"./packem_shared/applyDiffToDb-
|
|
1
|
+
import{createBetterSqlite3Adapter as o}from"./adapters/better-sqlite3.mjs";import{createSqliteWasmAdapter as f}from"./adapters/sqlite-wasm.mjs";import{createSqlJsAdapter as i}from"./adapters/sqljs.mjs";import{applyDiff as m,applyDiffToSnapshot as n,applyDiffs as x}from"./packem_shared/applyDiff-BUzddc6r.mjs";import{defineEvents as l}from"./packem_shared/defineEvents-CJZV8Bgi.mjs";import{MaterializerRuntime as S,defineMaterializer as E}from"./packem_shared/MaterializerRuntime-CqGGjSxl.mjs";import{applyDiffToDb as v,applyDiffsToDb as y}from"./packem_shared/applyDiffToDb-DSnSZmL4.mjs";import{EventEmitter as d}from"./packem_shared/EventEmitter-ovTsLeAj.mjs";import{EventLog as M}from"./packem_shared/EventLog-DmlRY_4Z.mjs";import{EventLogDO as u}from"./packem_shared/EventLogDO-Cb-7iN9w.mjs";import{EventLogDOClient as T}from"./packem_shared/EventLogDOClient-C6gA3b3u.mjs";import{EventSource as C,UNHANDLED as h}from"./packem_shared/EventSource-ArQhJa6P.mjs";import{eventsContext as I}from"./packem_shared/eventsContext-Dxow9Y7S.mjs";import{LocalMirror as O}from"./packem_shared/LocalMirror-hEYEB31D.mjs";import{isClientSeq as G,isGlobalSeq as H,isInputEvent as J}from"./packem_shared/isClientSeq-DSXBJskD.mjs";import{InMemorySnapshotStore as U}from"./packem_shared/InMemorySnapshotStore-C4taIG5K.mjs";import{subscribeToMirror as j}from"./packem_shared/subscribeToMirror-Dru_AYBu.mjs";import{SubscriptionManager as w}from"./packem_shared/SubscriptionManager-CbSjG_GA.mjs";import{EventsSync as K}from"./packem_shared/EventsSync-DWyGGZQZ.mjs";import{classifyChanges as Q,createTableDiff as V,diffSize as X,isDiffEmpty as Y,mergeDiffs as Z}from"./packem_shared/classifyChanges-IoOGDVVx.mjs";export{d as EventEmitter,M as EventLog,u as EventLogDO,T as EventLogDOClient,C as EventSource,K as EventsSync,U as InMemorySnapshotStore,O as LocalMirror,S as MaterializerRuntime,w as SubscriptionManager,h as UNHANDLED,m as applyDiff,v as applyDiffToDb,n as applyDiffToSnapshot,x as applyDiffs,y as applyDiffsToDb,Q as classifyChanges,o as createBetterSqlite3Adapter,i as createSqlJsAdapter,f as createSqliteWasmAdapter,V as createTableDiff,l as defineEvents,E as defineMaterializer,X as diffSize,I as eventsContext,G as isClientSeq,Y as isDiffEmpty,H as isGlobalSeq,J as isInputEvent,Z as mergeDiffs,j as subscribeToMirror};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{EventEmitter as g}from"./EventEmitter-ovTsLeAj.mjs";import{EventLog as y}from"./EventLog-DmlRY_4Z.mjs";const c=Symbol("lunora.replica.event-source.unhandled");class v{emitter=new g;log;#t;#e;#n=!1;#s;#r=-1;constructor(t,e,n){this.#t={...t},this.#e=e,this.#s=n?.unknownEventHandling??"warn",this.log=new y({maxEntries:n?.maxLogEntries})}get state(){return this.#t}get replayed(){return this.#n}applyEvent(t,e,n){let r,o,s;typeof t=="string"?(r=t,o=e,s=n):(r=t.type,o=t.payload,s=e);const i={seq:this.log.nextSeq,type:r,payload:o,timestamp:Date.now(),clientId:s?.clientId,sessionId:s?.sessionId,parentSeqNum:s?.parentSeqNum??this.log.headSeq??void 0};let a;try{a=this.#e(this.#t,i)}catch(l){const p=l instanceof Error?l:new Error(String(l));throw this.emitter.emit("replay-error",{entry:i,error:p}),p}let d=!0;a===c&&(d=this.#i(i));const h=this.log.append(r,o,void 0,{...s,timestamp:i.timestamp});return a===c?(d&&this.emitter.emit("state-changed",{state:this.#t,entry:h}),h):(this.#t=a,this.emitter.emit("state-changed",{state:this.#t,entry:h}),h)}replayFromLog(t){const e=t.getSince(this.#r+1);for(const n of e)try{const r=this.#e(this.#t,n);r!==c&&(this.#t=r),this.log.append(n.type,n.payload,n.tableDiffs,{clientId:n.clientId,sessionId:n.sessionId,parentSeqNum:n.parentSeqNum})}catch(r){this.emitter.emit("replay-error",{entry:n,error:r instanceof Error?r:new Error(String(r))})}finally{this.#r=n.seq}this.#n=!0,this.emitter.emit("ready",{entryCount:this.log.size})}reset(t,e=-1){this.#t={...t},this.#r=e,this.#n=e>=0}async*events(t){const e=[];let n;const r=this.emitter.on("state-changed",({entry:s})=>{e.push(s),n?.()}),o=()=>{n?.()};t?.addEventListener("abort",o,{once:!0});try{let s=0;for(;s<this.log.size;){if(t?.aborted)return;for(const i of this.log.getSince(s))yield i;s=this.log.nextSeq}for(;!t?.aborted;){for(;e.length>0;){const i=e.shift();i&&(yield i)}e.length===0&&await new Promise(i=>{n=i})}}finally{r(),t?.removeEventListener("abort",o)}}#i(t){const e=this.#s;if(typeof e=="function")return e(t);switch(e){case"ignore":return!1;case"fail":throw new Error(`EventSource: unhandled event type "${t.type}" (seq ${String(t.seq)}). Configure \`unknownEventHandling\` to handle this event or change the strategy.`);default:return console.warn(`[EventSource] unhandled event type "${t.type}" (seq ${String(t.seq)}). The event was skipped. Configure \`unknownEventHandling\` if this is expected.`),!1}}}export{v as EventSource,c as UNHANDLED};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{createSqlJsAdapter as T}from"../adapters/sqljs.mjs";import{applyDiffToDb as u,escapeIdentifier as a}from"./applyDiffToDb-DSnSZmL4.mjs";import{EventLog as y}from"./EventLog-DmlRY_4Z.mjs";const c="__lunora_mirror_meta",m=o=>{o.exec(`CREATE TABLE IF NOT EXISTS ${c} (
|
|
2
|
+
key TEXT PRIMARY KEY NOT NULL,
|
|
3
|
+
value TEXT NOT NULL
|
|
4
|
+
)`)},l="schema_version",f=2,b=o=>typeof o=="bigint"?"INTEGER":typeof o=="number"?Number.isInteger(o)?"INTEGER":"REAL":typeof o=="boolean"?"INTEGER":"TEXT";class E{#t;#e;#s;#r=new Set;#i=0;static create(t,e){const s=T(t);return new E({db:s,tables:e?.tables})}constructor(t){this.#t=t.db,this.#e={...t.tables},this.#s=new y({maxEntries:t.maxEventLogEntries}),m(this.#t),this.#a()}onChange(t){return this.#r.add(t),()=>{this.#r.delete(t)}}get eventLog(){return this.#s}get db(){return this.#t}get version(){return this.#i}applyDiff(t){if(t.changes.length===0)return;const e=this.#e[t.table]?.primaryKey??"id";this.#c(t),u(this.#t,t,e),this.#s.append("table-diff",t,[t]),this.#i+=1;for(const s of this.#r)try{s()}catch{}}query(t,e){return this.#t.query(t,e)}clearData(){const t=this.#n();this.#t.transaction(()=>{for(const{name:e}of t)this.#t.exec(`DELETE FROM ${a(e)}`)}),this.#i+=1;for(const e of this.#r)try{e()}catch{}}close(){this.#t.close(),this.#s.clear()}registerTable(t,e){this.#e[t]=e}get mirroredTables(){return Object.keys(this.#e)}#n(){return this.#t.query(String.raw`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE '\_\_lunora\_%' ESCAPE '\' AND name NOT LIKE 'sqlite\_%' ESCAPE '\'`)}#a(){if(this.#t.query(`SELECT key, value FROM ${c}`).find(e=>e.key===l)?.value===String(f))return;const t=this.#n();this.#t.transaction(()=>{for(const{name:e}of t)this.#t.exec(`DROP TABLE IF EXISTS ${a(e)}`);this.#t.exec(`INSERT OR REPLACE INTO ${c} (key, value) VALUES (?, ?)`,[l,String(f)])})}static#o(t,e){const s=new Set;for(const i of t.changes)if(i.type!=="delete")for(const n of Object.keys(i.data))n!==e&&s.add(n);return s}static#E(t,e,s){const i=new Map;for(const n of t.changes)if(!(n.type==="delete"||i.size===s.size))for(const r of s){if(r===e||i.has(r))continue;const h=n.data[r];h!=null&&i.set(r,b(h))}return i}#c(t){const e=this.#e[t.table]?.primaryKey??"id",s=E.#o(t,e),i=E.#E(t,e,s);if(this.#t.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?",[t.table]).length===0){let n=`${a(e)} TEXT PRIMARY KEY NOT NULL`;for(const r of s)n+=`, ${a(r)} ${i.get(r)??"TEXT"}`;this.#t.exec(`CREATE TABLE IF NOT EXISTS ${a(t.table)} (${n})`)}else if(s.size>0){const n=new Set(this.#t.query(`PRAGMA table_info(${a(t.table)})`).map(r=>r.name));for(const r of s)n.has(r)||this.#t.exec(`ALTER TABLE ${a(t.table)} ADD COLUMN ${a(r)} ${i.get(r)??"TEXT"}`)}}}export{E as LocalMirror};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t=e=>`\`${e.replaceAll("`","``")}\``,g=e=>e.map(n=>`${t(n)} = ?`).join(", "),E=e=>`(${e.map(n=>t(n)).join(", ")})`,$=e=>`(${Array.from({length:e}).fill("?").join(", ")})`,y=e=>e==null?null:typeof e=="number"||typeof e=="bigint"||typeof e=="string"?e:typeof e=="boolean"?e?1:0:e instanceof Uint8Array?e:typeof e=="object"?JSON.stringify(e):String(e),b=(e,n,o)=>{if(n.changes.length===0)return;const c=t(n.table),p=t(o);for(const i of n.changes)switch(i.type){case"delete":{e.exec(`DELETE FROM ${c} WHERE ${p} = ?`,[i.id]);break}case"insert":{const{data:s}=i,a=Object.keys(s);if(a.length===0)continue;const r=`INSERT OR REPLACE INTO ${c} ${E(a)} VALUES ${$(a.length)}`,f=a.map(l=>y(s[l]));e.exec(r,f);break}case"update":{const{data:s}=i,a=Object.keys(s);if(a.length===0)continue;const r=`UPDATE ${c} SET ${g(a)} WHERE ${p} = ?`,f=[...a.map(l=>y(s[l])),i.id];e.exec(r,f);break}}},d=(e,n,o)=>{e.transaction(()=>{b(e,n,o??"id")})},h=(e,n)=>{n.length!==0&&e.transaction(()=>{for(const o of n)b(e,o,"id")})};export{d as applyDiffToDb,h as applyDiffsToDb,t as escapeIdentifier,y as normalizeBindValue};
|
package/dist/react.d.mts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { L as LocalMirror } from "./packem_shared/local-mirror.d-DTavX_y0.mjs";
|
|
2
2
|
import "./packem_shared/types.d-CkMkSwLJ.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* Result of {@link useLocalQuery} — a discriminated union so callers get a
|
|
5
|
+
* typed error instead of a swallowed `undefined`.
|
|
6
|
+
* @experimental
|
|
7
|
+
*/
|
|
8
|
+
type LocalQueryResult<T> = {
|
|
9
|
+
readonly data: T[];
|
|
10
|
+
readonly error?: undefined;
|
|
11
|
+
} | {
|
|
12
|
+
readonly data?: undefined;
|
|
13
|
+
readonly error: Error;
|
|
14
|
+
};
|
|
3
15
|
/**
|
|
4
16
|
* Options for the {@link useLocalQuery} hook.
|
|
5
17
|
* @experimental
|
|
@@ -19,8 +31,21 @@ interface UseLocalQueryOptions {
|
|
|
19
31
|
* React hook that subscribes to a local SQLite query and returns
|
|
20
32
|
* live-updating results whenever the mirror applies a diff.
|
|
21
33
|
*
|
|
22
|
-
* Uses `useSyncExternalStore`
|
|
23
|
-
*
|
|
34
|
+
* Uses `useSyncExternalStore` with `getSnapshot` reading {@link LocalMirror.version}
|
|
35
|
+
* — a plain number, unconditionally `Object.is`-stable across calls with no
|
|
36
|
+
* mutation in between. The actual query runs in a `useMemo` keyed on
|
|
37
|
+
* `[mirror, version, sql, stableParamsKey(params)]`, so it only re-executes
|
|
38
|
+
* when the mirror actually advances (or `sql`/`params` change) — not on
|
|
39
|
+
* every unrelated re-render.
|
|
40
|
+
*
|
|
41
|
+
* This intentionally does NOT cache query results inside {@link LocalMirror}
|
|
42
|
+
* itself: an LRU-capped cache in the mirror core evicts still-mounted hooks'
|
|
43
|
+
* entries once more than the cap's worth of distinct live queries are open
|
|
44
|
+
* on one mirror, which makes `getSnapshot` return a fresh (non-identical)
|
|
45
|
+
* object on the next read — `useSyncExternalStore` then force-re-renders,
|
|
46
|
+
* re-inserts, evicts another entry, and so on without bound. Keying the
|
|
47
|
+
* external-store snapshot on the version primitive instead of a cached
|
|
48
|
+
* query result has no such cap, so it can't loop.
|
|
24
49
|
*
|
|
25
50
|
* The hook works with React 18+ concurrent features, Suspense, and
|
|
26
51
|
* server-side rendering. During SSR the same value is returned as on
|
|
@@ -34,26 +59,28 @@ interface UseLocalQueryOptions {
|
|
|
34
59
|
* placeholders in `sql`.
|
|
35
60
|
* @param _options Optional configuration (currently unused; reserved for
|
|
36
61
|
* future features like shard key routing).
|
|
37
|
-
* @returns
|
|
38
|
-
* `
|
|
39
|
-
* because no matching diff has been applied
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* `undefined`. Use a try-catch around `mirror.query(...)` directly if you
|
|
44
|
-
* need finer-grained error diagnostics.
|
|
62
|
+
* @returns `{ data }` with the result rows typed via the generic parameter
|
|
63
|
+
* `T`, or `{ error }` when the query fails (e.g. malformed SQL, or the
|
|
64
|
+
* target table doesn't exist yet because no matching diff has been applied
|
|
65
|
+
* — that specific case surfaces as a "no such table" `Error`). Never
|
|
66
|
+
* collapses a failure to `undefined` — check `error` explicitly rather than
|
|
67
|
+
* treating a missing `data` as "still loading".
|
|
45
68
|
* @example
|
|
46
69
|
* ```tsx
|
|
47
70
|
* import { useLocalQuery } from "@lunora/replica/react";
|
|
48
71
|
* import { mirror } from "./mirror";
|
|
49
72
|
*
|
|
50
73
|
* function UserList() {
|
|
51
|
-
* const users = useLocalQuery<{ id: string; name: string }>(
|
|
74
|
+
* const { data: users, error } = useLocalQuery<{ id: string; name: string }>(
|
|
52
75
|
* mirror,
|
|
53
76
|
* "SELECT id, name FROM fn_todos_list WHERE name LIKE ?",
|
|
54
77
|
* ["%alice%"],
|
|
55
78
|
* );
|
|
56
79
|
*
|
|
80
|
+
* if (error) {
|
|
81
|
+
* return <p>Query failed: {error.message}</p>;
|
|
82
|
+
* }
|
|
83
|
+
*
|
|
57
84
|
* if (users === undefined) {
|
|
58
85
|
* return <p>Waiting for data…</p>;
|
|
59
86
|
* }
|
|
@@ -63,5 +90,5 @@ interface UseLocalQueryOptions {
|
|
|
63
90
|
* ```
|
|
64
91
|
* @experimental
|
|
65
92
|
*/
|
|
66
|
-
declare const useLocalQuery: <T = Record<string, unknown>>(mirror: LocalMirror, sql: string, params?: ReadonlyArray<unknown>, _options?: UseLocalQueryOptions) => T
|
|
67
|
-
export { UseLocalQueryOptions, useLocalQuery };
|
|
93
|
+
declare const useLocalQuery: <T = Record<string, unknown>>(mirror: LocalMirror, sql: string, params?: ReadonlyArray<unknown>, _options?: UseLocalQueryOptions) => LocalQueryResult<T>;
|
|
94
|
+
export { type LocalQueryResult, type UseLocalQueryOptions, useLocalQuery };
|
package/dist/react.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { L as LocalMirror } from "./packem_shared/local-mirror.d-CyGOpUES.js";
|
|
2
2
|
import "./packem_shared/types.d-CkMkSwLJ.js";
|
|
3
|
+
/**
|
|
4
|
+
* Result of {@link useLocalQuery} — a discriminated union so callers get a
|
|
5
|
+
* typed error instead of a swallowed `undefined`.
|
|
6
|
+
* @experimental
|
|
7
|
+
*/
|
|
8
|
+
type LocalQueryResult<T> = {
|
|
9
|
+
readonly data: T[];
|
|
10
|
+
readonly error?: undefined;
|
|
11
|
+
} | {
|
|
12
|
+
readonly data?: undefined;
|
|
13
|
+
readonly error: Error;
|
|
14
|
+
};
|
|
3
15
|
/**
|
|
4
16
|
* Options for the {@link useLocalQuery} hook.
|
|
5
17
|
* @experimental
|
|
@@ -19,8 +31,21 @@ interface UseLocalQueryOptions {
|
|
|
19
31
|
* React hook that subscribes to a local SQLite query and returns
|
|
20
32
|
* live-updating results whenever the mirror applies a diff.
|
|
21
33
|
*
|
|
22
|
-
* Uses `useSyncExternalStore`
|
|
23
|
-
*
|
|
34
|
+
* Uses `useSyncExternalStore` with `getSnapshot` reading {@link LocalMirror.version}
|
|
35
|
+
* — a plain number, unconditionally `Object.is`-stable across calls with no
|
|
36
|
+
* mutation in between. The actual query runs in a `useMemo` keyed on
|
|
37
|
+
* `[mirror, version, sql, stableParamsKey(params)]`, so it only re-executes
|
|
38
|
+
* when the mirror actually advances (or `sql`/`params` change) — not on
|
|
39
|
+
* every unrelated re-render.
|
|
40
|
+
*
|
|
41
|
+
* This intentionally does NOT cache query results inside {@link LocalMirror}
|
|
42
|
+
* itself: an LRU-capped cache in the mirror core evicts still-mounted hooks'
|
|
43
|
+
* entries once more than the cap's worth of distinct live queries are open
|
|
44
|
+
* on one mirror, which makes `getSnapshot` return a fresh (non-identical)
|
|
45
|
+
* object on the next read — `useSyncExternalStore` then force-re-renders,
|
|
46
|
+
* re-inserts, evicts another entry, and so on without bound. Keying the
|
|
47
|
+
* external-store snapshot on the version primitive instead of a cached
|
|
48
|
+
* query result has no such cap, so it can't loop.
|
|
24
49
|
*
|
|
25
50
|
* The hook works with React 18+ concurrent features, Suspense, and
|
|
26
51
|
* server-side rendering. During SSR the same value is returned as on
|
|
@@ -34,26 +59,28 @@ interface UseLocalQueryOptions {
|
|
|
34
59
|
* placeholders in `sql`.
|
|
35
60
|
* @param _options Optional configuration (currently unused; reserved for
|
|
36
61
|
* future features like shard key routing).
|
|
37
|
-
* @returns
|
|
38
|
-
* `
|
|
39
|
-
* because no matching diff has been applied
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* `undefined`. Use a try-catch around `mirror.query(...)` directly if you
|
|
44
|
-
* need finer-grained error diagnostics.
|
|
62
|
+
* @returns `{ data }` with the result rows typed via the generic parameter
|
|
63
|
+
* `T`, or `{ error }` when the query fails (e.g. malformed SQL, or the
|
|
64
|
+
* target table doesn't exist yet because no matching diff has been applied
|
|
65
|
+
* — that specific case surfaces as a "no such table" `Error`). Never
|
|
66
|
+
* collapses a failure to `undefined` — check `error` explicitly rather than
|
|
67
|
+
* treating a missing `data` as "still loading".
|
|
45
68
|
* @example
|
|
46
69
|
* ```tsx
|
|
47
70
|
* import { useLocalQuery } from "@lunora/replica/react";
|
|
48
71
|
* import { mirror } from "./mirror";
|
|
49
72
|
*
|
|
50
73
|
* function UserList() {
|
|
51
|
-
* const users = useLocalQuery<{ id: string; name: string }>(
|
|
74
|
+
* const { data: users, error } = useLocalQuery<{ id: string; name: string }>(
|
|
52
75
|
* mirror,
|
|
53
76
|
* "SELECT id, name FROM fn_todos_list WHERE name LIKE ?",
|
|
54
77
|
* ["%alice%"],
|
|
55
78
|
* );
|
|
56
79
|
*
|
|
80
|
+
* if (error) {
|
|
81
|
+
* return <p>Query failed: {error.message}</p>;
|
|
82
|
+
* }
|
|
83
|
+
*
|
|
57
84
|
* if (users === undefined) {
|
|
58
85
|
* return <p>Waiting for data…</p>;
|
|
59
86
|
* }
|
|
@@ -63,5 +90,5 @@ interface UseLocalQueryOptions {
|
|
|
63
90
|
* ```
|
|
64
91
|
* @experimental
|
|
65
92
|
*/
|
|
66
|
-
declare const useLocalQuery: <T = Record<string, unknown>>(mirror: LocalMirror, sql: string, params?: ReadonlyArray<unknown>, _options?: UseLocalQueryOptions) => T
|
|
67
|
-
export { UseLocalQueryOptions, useLocalQuery };
|
|
93
|
+
declare const useLocalQuery: <T = Record<string, unknown>>(mirror: LocalMirror, sql: string, params?: ReadonlyArray<unknown>, _options?: UseLocalQueryOptions) => LocalQueryResult<T>;
|
|
94
|
+
export { type LocalQueryResult, type UseLocalQueryOptions, useLocalQuery };
|
package/dist/react.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useSyncExternalStore as
|
|
1
|
+
import{useSyncExternalStore as c,useMemo as u}from"react";const y=r=>JSON.stringify(r??[],(o,t)=>typeof t=="bigint"?`${t.toString()}n`:t),f=(r,o,t,g)=>{const s=n=>r.onChange(n),e=()=>r.version,a=c(s,e,e),i=y(t);return u(()=>{try{return{data:r.query(o,t)}}catch(n){return{error:n instanceof Error?n:new Error(String(n))}}},[r,a,o,i])};export{f as useLocalQuery};
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{EventEmitter as g}from"./EventEmitter-ovTsLeAj.mjs";import{EventLog as y}from"./EventLog-DmlRY_4Z.mjs";const c=Symbol("lunora.replica.event-source.unhandled");class E{emitter=new g;log;#t;#e;#n=!1;#s;#r=-1;constructor(t,e,n){this.#t={...t},this.#e=e,this.#s=n?.unknownEventHandling??"warn",this.log=new y({maxEntries:n?.maxLogEntries})}get state(){return this.#t}get replayed(){return this.#n}applyEvent(t,e,n){let r,i,s;typeof t=="string"?(r=t,i=e,s=n):(r=t.type,i=t.payload,s=e);const o={seq:this.log.nextSeq,type:r,payload:i,timestamp:Date.now(),clientId:s?.clientId,sessionId:s?.sessionId,parentSeqNum:s?.parentSeqNum??this.log.headSeq??void 0};let a;try{a=this.#e(this.#t,o)}catch(l){const p=l instanceof Error?l:new Error(String(l));throw this.emitter.emit("replay-error",{entry:o,error:p}),p}let d=!0;a===c&&(d=this.#i(o));const h=this.log.append(r,i,void 0,{...s,timestamp:o.timestamp});return a===c?(d&&this.emitter.emit("state-changed",{state:this.#t,entry:h}),h):(this.#t=a,this.emitter.emit("state-changed",{state:this.#t,entry:h}),h)}replayFromLog(t){const e=t.getSince(this.#r+1);for(const n of e)try{const r=this.#e(this.#t,n);r!==c&&(this.#t=r),this.log.append(n.type,n.payload,n.tableDiffs,{clientId:n.clientId,sessionId:n.sessionId,parentSeqNum:n.parentSeqNum})}catch(r){this.emitter.emit("replay-error",{entry:n,error:r instanceof Error?r:new Error(String(r))})}finally{this.#r=n.seq}this.#n=!0,this.emitter.emit("ready",{entryCount:this.log.size})}reset(t,e=-1){this.#t={...t},this.#r=e,this.#n=e>=0}async*events(t){const e=[];let n;const r=this.emitter.on("state-changed",({entry:i})=>{e.push(i),n?.()});try{let i=0;for(;i<this.log.size;){if(t?.aborted)return;for(const s of this.log.getSince(i))yield s;i=this.log.nextSeq}for(;!t?.aborted;){for(;e.length>0;){const s=e.shift();s&&(yield s)}e.length===0&&await new Promise(s=>{n=s})}}finally{r()}}#i(t){const e=this.#s;if(typeof e=="function")return e(t);switch(e){case"ignore":return!1;case"fail":throw new Error(`EventSource: unhandled event type "${t.type}" (seq ${String(t.seq)}). Configure \`unknownEventHandling\` to handle this event or change the strategy.`);default:return console.warn(`[EventSource] unhandled event type "${t.type}" (seq ${String(t.seq)}). The event was skipped. Configure \`unknownEventHandling\` if this is expected.`),!1}}}export{E as EventSource,c as UNHANDLED};
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import{createSqlJsAdapter as c}from"../adapters/sqljs.mjs";import{applyDiffToDb as h,escapeIdentifier as a}from"./applyDiffToDb-1M2I3BHi.mjs";import{EventLog as E}from"./EventLog-DmlRY_4Z.mjs";const l="__lunora_mirror_meta",T=o=>{o.exec(`CREATE TABLE IF NOT EXISTS ${l} (
|
|
2
|
-
key TEXT PRIMARY KEY NOT NULL,
|
|
3
|
-
value TEXT NOT NULL
|
|
4
|
-
)`)};class n{#t;#e;#s;#r=new Set;#i=0;static create(t,e){const s=c(t);return new n({db:s,tables:e?.tables})}constructor(t){this.#t=t.db,this.#e={...t.tables},this.#s=new E({maxEntries:t.maxEventLogEntries}),T(this.#t)}onChange(t){return this.#r.add(t),()=>{this.#r.delete(t)}}get eventLog(){return this.#s}get db(){return this.#t}get version(){return this.#i}applyDiff(t){if(t.changes.length===0)return;const e=this.#e[t.table]?.primaryKey??"id";this.#n(t),h(this.#t,t,e),this.#s.append("table-diff",t,[t]),this.#i+=1;for(const s of this.#r)try{s()}catch{}}query(t,e){return this.#t.query(t,e)}clearData(){const t=this.#t.query(String.raw`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE '\_\_lunora\_%' ESCAPE '\' AND name NOT LIKE 'sqlite\_%' ESCAPE '\'`);this.#t.transaction(()=>{for(const{name:e}of t)this.#t.exec(`DELETE FROM ${a(e)}`)}),this.#i+=1;for(const e of this.#r)try{e()}catch{}}close(){this.#t.close(),this.#s.clear()}registerTable(t,e){this.#e[t]=e}get mirroredTables(){return Object.keys(this.#e)}static#a(t,e){const s=new Set;for(const i of t.changes)if(i.type!=="delete")for(const r of Object.keys(i.data))r!==e&&s.add(r);return s}#n(t){const e=this.#e[t.table]?.primaryKey??"id",s=n.#a(t,e);if(this.#t.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?",[t.table]).length===0){let i=`${a(e)} TEXT PRIMARY KEY NOT NULL`;for(const r of s)i+=`, ${a(r)} TEXT`;this.#t.exec(`CREATE TABLE IF NOT EXISTS ${a(t.table)} (${i})`)}else if(s.size>0){const i=new Set(this.#t.query(`PRAGMA table_info(${a(t.table)})`).map(r=>r.name));for(const r of s)i.has(r)||this.#t.exec(`ALTER TABLE ${a(t.table)} ADD COLUMN ${a(r)} TEXT`)}}}export{n as LocalMirror};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const o=e=>`\`${e.replaceAll("`","``")}\``,$=e=>e.map(a=>`${o(a)} = ?`).join(", "),d=e=>`(${e.map(a=>o(a)).join(", ")})`,b=e=>`(${Array.from({length:e}).fill("?").join(", ")})`,E=(e,a,t)=>{if(a.changes.length===0)return;const i=o(a.table),p=o(t);for(const c of a.changes)switch(c.type){case"delete":{e.exec(`DELETE FROM ${i} WHERE ${p} = ?`,[c.id]);break}case"insert":{const{data:s}=c,n=Object.keys(s);if(n.length===0)continue;const l=`INSERT OR REPLACE INTO ${i} ${d(n)} VALUES ${b(n.length)}`,r=n.map(f=>s[f]);e.exec(l,r);break}case"update":{const{data:s}=c,n=Object.keys(s);if(n.length===0)continue;const l=`UPDATE ${i} SET ${$(n)} WHERE ${p} = ?`,r=[...n.map(f=>s[f]),c.id];e.exec(l,r);break}}},g=(e,a,t)=>{e.transaction(()=>{E(e,a,t??"id")})},h=(e,a)=>{a.length!==0&&e.transaction(()=>{for(const t of a)E(e,t,"id")})};export{g as applyDiffToDb,h as applyDiffsToDb,o as escapeIdentifier};
|