@lunora/replica 1.0.0-alpha.13 → 1.0.0-alpha.14

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 CHANGED
@@ -896,8 +896,17 @@ interface EventsSyncOptions {
896
896
  * Produce {@link TableDiff | TableDiffs} from the current derived state.
897
897
  *
898
898
  * Called after every batch of events has been applied. The consumer
899
- * compares the state _before_ and _after_ the batch and returns the
900
- * diffs needed to bring the LocalMirror up to date.
899
+ * **recomputes a full diff from the current mirror-vs-source state** and
900
+ * returns the diffs needed to bring the LocalMirror up to date.
901
+ *
902
+ * **MUST be idempotent** — it must NOT advance a one-shot cursor as a side
903
+ * effect. A batch that fails partway (a `mirror.applyDiff` throws) is
904
+ * retried on the next poll from the same watermark; if this call consumed a
905
+ * cursor on the first attempt it would return `[]` on the retry and the
906
+ * un-mirrored diffs would be lost forever. Recompute-from-current-state has
907
+ * no such hazard: calling it again with no new events returns the same
908
+ * diffs, and calling it after a partial mirror write returns exactly the
909
+ * diffs still missing from the mirror.
901
910
  *
902
911
  * Return an empty array when there are no changes to push to the mirror.
903
912
  */
package/dist/index.d.ts CHANGED
@@ -896,8 +896,17 @@ interface EventsSyncOptions {
896
896
  * Produce {@link TableDiff | TableDiffs} from the current derived state.
897
897
  *
898
898
  * Called after every batch of events has been applied. The consumer
899
- * compares the state _before_ and _after_ the batch and returns the
900
- * diffs needed to bring the LocalMirror up to date.
899
+ * **recomputes a full diff from the current mirror-vs-source state** and
900
+ * returns the diffs needed to bring the LocalMirror up to date.
901
+ *
902
+ * **MUST be idempotent** — it must NOT advance a one-shot cursor as a side
903
+ * effect. A batch that fails partway (a `mirror.applyDiff` throws) is
904
+ * retried on the next poll from the same watermark; if this call consumed a
905
+ * cursor on the first attempt it would return `[]` on the retry and the
906
+ * un-mirrored diffs would be lost forever. Recompute-from-current-state has
907
+ * no such hazard: calling it again with no new events returns the same
908
+ * diffs, and calling it after a partial mirror write returns exactly the
909
+ * diffs still missing from the mirror.
901
910
  *
902
911
  * Return an empty array when there are no changes to push to the mirror.
903
912
  */
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-B-I9jKDe.mjs";import{applyDiffToDb as v,applyDiffsToDb as y}from"./packem_shared/applyDiffToDb-1M2I3BHi.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-B5UTlkl9.mjs";import{eventsContext as I}from"./packem_shared/eventsContext-Dxow9Y7S.mjs";import{LocalMirror as O}from"./packem_shared/LocalMirror-Q7cPH_rX.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-pK_hg9KP.mjs";import{classifyChanges as Q,createTableDiff as V,diffSize as X,isDiffEmpty as Y,mergeDiffs as Z}from"./packem_shared/classifyChanges-BnOMcDGj.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};
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-1M2I3BHi.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-B5UTlkl9.mjs";import{eventsContext as I}from"./packem_shared/eventsContext-Dxow9Y7S.mjs";import{LocalMirror as O}from"./packem_shared/LocalMirror-Q7cPH_rX.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
+ class e{#t;#i=0;#s;#r;constructor(t){this.#t=t}get watermark(){return this.#i}start(){if(this.#s!==void 0)return;const t=this.#t.pollInterval??5e3;this.#s=setInterval(()=>{this.#n().catch(()=>{})},t)}stop(){this.#s!==void 0&&(clearInterval(this.#s),this.#s=void 0)}async sync(){return this.#n()}async#n(){if(this.#r)return this.#r;const t=this.#e().finally(()=>{this.#r=void 0});return this.#r=t,t}async#e(){try{const t=await this.#t.fetchEventsSince(this.#i);if(t.length===0)return 0;this.#t.applyEvents(t);const s=this.#t.getTableDiffs();for(const i of s)this.#t.mirror.applyDiff(i);const r=t[t.length-1];return this.#i=r.seq+1,t.length}catch(t){return(this.#t.onError??console.error)(t),0}}}export{e as EventsSync};
@@ -0,0 +1 @@
1
+ const c=a=>{let e=a.initial();return{def:a,get state(){return Object.freeze(e)},setState(t){e=t},apply(t){e=a.handle(e,t)},reset(){e=a.initial()}}};class f{#e;#n;#i;#s;#t;constructor(e,t={}){this.#e=[...e],this.#t=this.#e.map(()=>0),this.#n=t.snapshotStore,this.#i=t.doClient,this.#s=t.unknownEventHandling??"warn"}get appliedSeq(){return this.#t.length>0?Math.min(...this.#t):0}applyEntries(e){let t=0;for(const i of e){let s=!1,n=!1;const r=[];for(const[o,h]of this.#e.entries()){const p=this.#t[o]??0;if(i.seq<p)continue;const l=h.state;h.apply(i),s=!0,h.state!==l&&(n=!0),r.push(o)}if(s){n||this.#r(i);for(const o of r)this.#t[o]=i.seq+1;t+=1}}return t}#r(e){const t=this.#s;if(typeof t=="function"){t(e);return}switch(t){case"ignore":return;case"fail":throw new Error(`MaterializerRuntime: unhandled event type "${e.type}" (seq ${String(e.seq)}). Configure \`unknownEventHandling\` to handle this event or change the strategy.`);default:console.warn(`[MaterializerRuntime] unhandled event type "${e.type}" (seq ${String(e.seq)}). The event was skipped. Configure \`unknownEventHandling\` if this is expected.`)}}async recoverFromSnapshots(){if(!this.#n)return 0;let e=0;for(const[t,i]of this.#e.entries()){const s=await this.#n.load(i.def.name);if(s!==null&&typeof s=="object"){const n=s;if(Number.isSafeInteger(n.appliedSeq)&&n.appliedSeq>=0&&n.state!==void 0){const r=n.appliedSeq;i.setState(n.state),this.#t[t]=r,r>e&&(e=r)}}}return e}async persistSnapshots(){if(this.#n)for(const[e,t]of this.#e.entries())await this.#n.save(t.def.name,{appliedSeq:this.#t[e]??0,state:t.state})}async initialize(){if(!this.#i)return 0;await this.recoverFromSnapshots();const e=this.#t.length>0?Math.min(...this.#t):0,t=await this.#i.getSince(e);return t.length===0?0:this.applyEntries(t)}async appendEvent(e){if(!this.#i)throw new Error("MaterializerRuntime.appendEvent requires a doClient — pass one in the constructor options.");const t=(await this.#i.append([e]))[0];if(!t)throw new Error("MaterializerRuntime.appendEvent: DO returned empty result");return this.applyEntries([t]),t}reset(){for(const[e,t]of this.#e.entries())this.#t[e]=0,t.reset()}get materializers(){return this.#e}}export{f as MaterializerRuntime,c as defineMaterializer};
@@ -0,0 +1 @@
1
+ import{fnv1a64Hex as s}from"./applyDiff-BUzddc6r.mjs";let o=0;const i=()=>{if(typeof crypto<"u"&&typeof crypto.randomUUID=="function")return crypto.randomUUID();o+=1;const t=typeof crypto<"u"&&typeof crypto.getRandomValues=="function"?Array.from(crypto.getRandomValues(new Uint8Array(8)),n=>n.toString(16).padStart(2,"0")).join(""):Math.random().toString(16).slice(2,12);return`id_${Date.now().toString(36)}_${o.toString(36)}_${t}`},p=(t,n,a,r)=>({table:t,changes:n,timestamp:a??Date.now(),id:r??i()}),c=t=>t.changes.length===0,g=t=>t.changes.length,m=t=>{const n=[],a=[],r=[];for(const e of t.changes)e.type==="insert"?n.push(e):e.type==="update"?a.push(e):r.push(e);return{inserts:n,updates:a,deletes:r}},l=t=>{if(t.length===0)return null;const n=t[0],a=t[t.length-1],r=`merge:${s(t.map(e=>e.id??String(e.timestamp)).join("|"))}`;return p(n.table,t.flatMap(e=>e.changes),a.timestamp,r)};export{m as classifyChanges,p as createTableDiff,g as diffSize,c as isDiffEmpty,l as mergeDiffs};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/replica",
3
- "version": "1.0.0-alpha.13",
3
+ "version": "1.0.0-alpha.14",
4
4
  "description": "Local-first replica runtime + local SQLite mirror for Lunora",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -1 +0,0 @@
1
- class o{#t;#i=0;#r;#s;constructor(t){this.#t=t}get watermark(){return this.#i}start(){if(this.#r!==void 0)return;const t=this.#t.pollInterval??5e3;this.#r=setInterval(()=>{this.#n().catch(()=>{})},t)}stop(){this.#r!==void 0&&(clearInterval(this.#r),this.#r=void 0)}async sync(){return this.#n()}async#n(){if(this.#s)return this.#s;const t=this.#e().finally(()=>{this.#s=void 0});return this.#s=t,t}async#e(){try{const t=await this.#t.fetchEventsSince(this.#i);if(t.length===0)return 0;let s=0;try{for(const r of t){this.#t.applyEvents([r]);const i=this.#t.getTableDiffs();for(const n of i)this.#t.mirror.applyDiff(n);this.#i=r.seq+1,s+=1}}catch(r){(this.#t.onError??console.error)(r)}return s}catch(t){return(this.#t.onError??console.error)(t),0}}}export{o as EventsSync};
@@ -1 +0,0 @@
1
- const l=r=>{let e=r.initial();return{def:r,get state(){return Object.freeze(e)},setState(t){e=t},apply(t){e=r.handle(e,t)},reset(){e=r.initial()}}};class u{#e;#n;#i;#s;#t;constructor(e,t={}){this.#e=[...e],this.#t=this.#e.map(()=>0),this.#n=t.snapshotStore,this.#i=t.doClient,this.#s=t.unknownEventHandling??"warn"}get appliedSeq(){return this.#t.length>0?Math.min(...this.#t):0}applyEntries(e){let t=0;for(const i of e){let s=!1,n=!1;for(const[o,a]of this.#e.entries()){const h=this.#t[o]??0;if(i.seq<h)continue;const p=a.state;a.apply(i),s=!0,a.state!==p&&(n=!0),this.#t[o]=i.seq+1}s&&(n||this.#r(i),t+=1)}return t}#r(e){const t=this.#s;if(typeof t=="function"){t(e);return}switch(t){case"ignore":return;case"fail":throw new Error(`MaterializerRuntime: unhandled event type "${e.type}" (seq ${String(e.seq)}). Configure \`unknownEventHandling\` to handle this event or change the strategy.`);default:console.warn(`[MaterializerRuntime] unhandled event type "${e.type}" (seq ${String(e.seq)}). The event was skipped. Configure \`unknownEventHandling\` if this is expected.`)}}async recoverFromSnapshots(){if(!this.#n)return 0;let e=0;for(const[t,i]of this.#e.entries()){const s=await this.#n.load(i.def.name);if(s!==null&&typeof s=="object"){const n=s;n.state!==void 0&&i.setState(n.state),this.#t[t]=n.appliedSeq,n.appliedSeq>e&&(e=n.appliedSeq)}}return e}async persistSnapshots(){if(this.#n)for(const[e,t]of this.#e.entries())await this.#n.save(t.def.name,{appliedSeq:this.#t[e]??0,state:t.state})}async initialize(){if(!this.#i)return 0;await this.recoverFromSnapshots();const e=this.#t.length>0?Math.min(...this.#t):0,t=await this.#i.getSince(e);return t.length===0?0:this.applyEntries(t)}async appendEvent(e){if(!this.#i)throw new Error("MaterializerRuntime.appendEvent requires a doClient — pass one in the constructor options.");const t=(await this.#i.append([e]))[0];if(!t)throw new Error("MaterializerRuntime.appendEvent: DO returned empty result");return this.applyEntries([t]),t}reset(){for(const[e,t]of this.#e.entries())this.#t[e]=0,t.reset()}get materializers(){return this.#e}}export{u as MaterializerRuntime,l as defineMaterializer};
@@ -1 +0,0 @@
1
- const i=(t,s,a,n)=>({table:t,changes:s,timestamp:a??Date.now(),id:n??crypto.randomUUID()}),r=t=>t.changes.length===0,p=t=>t.changes.length,g=t=>{const s=[],a=[],n=[];for(const e of t.changes)e.type==="insert"?s.push(e):e.type==="update"?a.push(e):n.push(e);return{inserts:s,updates:a,deletes:n}},f=t=>{if(t.length===0)return null;const s=t[0],a=t[t.length-1],n=`merge:${t.map(e=>e.id??String(e.timestamp)).join("|")}`;return i(s.table,t.flatMap(e=>e.changes),a.timestamp,n)};export{g as classifyChanges,i as createTableDiff,p as diffSize,r as isDiffEmpty,f as mergeDiffs};