@lunora/replica 1.0.0-alpha.80 → 1.0.0-alpha.82

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 (33) hide show
  1. package/dist/adapters/better-sqlite3.d.mts +1 -0
  2. package/dist/adapters/better-sqlite3.d.ts +1 -0
  3. package/dist/adapters/better-sqlite3.mjs +1 -1
  4. package/dist/adapters/sqlite-wasm.mjs +1 -1
  5. package/dist/adapters/sqljs.d.mts +3 -1
  6. package/dist/adapters/sqljs.d.ts +3 -1
  7. package/dist/adapters/sqljs.mjs +1 -1
  8. package/dist/index.d.mts +53 -10
  9. package/dist/index.d.ts +53 -10
  10. package/dist/index.mjs +1 -1
  11. package/dist/packem_shared/EventLog-SuC_BKwj.mjs +1 -0
  12. package/dist/packem_shared/EventLogDO-BgUx2GGL.mjs +1 -0
  13. package/dist/packem_shared/EventSource-Bg6zNmRn.mjs +1 -0
  14. package/dist/packem_shared/LocalMirror-Cc4FE1Le.mjs +4 -0
  15. package/dist/packem_shared/MaterializerRuntime-DFXi-aqd.mjs +1 -0
  16. package/dist/packem_shared/applyDiff-C4pbSYUT.mjs +1 -0
  17. package/dist/packem_shared/applyDiffToDb-zb4sHy-6.mjs +1 -0
  18. package/dist/packem_shared/int64-CCVxepl4.mjs +1 -0
  19. package/dist/packem_shared/{local-mirror.d-CtQovAv_.d.mts → local-mirror.d-DzREvGNM.d.mts} +34 -10
  20. package/dist/packem_shared/{local-mirror.d-CErKffFW.d.ts → local-mirror.d-wbGXkXaE.d.ts} +34 -10
  21. package/dist/packem_shared/subscribeToMirror-uA3ZCsLa.mjs +1 -0
  22. package/dist/packem_shared/wire-key-2vOZ0mPa.mjs +1 -0
  23. package/dist/react.d.mts +1 -1
  24. package/dist/react.d.ts +1 -1
  25. package/package.json +1 -1
  26. package/dist/packem_shared/EventLog-B1-yhArT.mjs +0 -1
  27. package/dist/packem_shared/EventLogDO-BF9ZWc6C.mjs +0 -1
  28. package/dist/packem_shared/EventSource-BC0hKJSA.mjs +0 -1
  29. package/dist/packem_shared/LocalMirror-ZmB8SJFe.mjs +0 -4
  30. package/dist/packem_shared/MaterializerRuntime-S-Knx6BM.mjs +0 -1
  31. package/dist/packem_shared/applyDiff-DRJ1gap3.mjs +0 -1
  32. package/dist/packem_shared/applyDiffToDb-C6ek5Elp.mjs +0 -1
  33. package/dist/packem_shared/subscribeToMirror-BT4oOBng.mjs +0 -1
@@ -22,6 +22,7 @@ declare const createBetterSqlite3Adapter: (database: {
22
22
  run: (params?: unknown[]) => {
23
23
  lastInsertRowid: number | bigint;
24
24
  };
25
+ safeIntegers: (toggle?: boolean) => unknown;
25
26
  };
26
27
  transaction: (function_: () => void) => () => void;
27
28
  }) => SqliteAdapter;
@@ -22,6 +22,7 @@ declare const createBetterSqlite3Adapter: (database: {
22
22
  run: (params?: unknown[]) => {
23
23
  lastInsertRowid: number | bigint;
24
24
  };
25
+ safeIntegers: (toggle?: boolean) => unknown;
25
26
  };
26
27
  transaction: (function_: () => void) => () => void;
27
28
  }) => SqliteAdapter;
@@ -1 +1 @@
1
- const o=t=>({exec(r,e){e&&e.length>0?t.prepare(r).run([...e]):t.exec(r)},query(r,e){const n=t.prepare(r);return e&&e.length>0?n.all([...e]):n.all()},transaction(r){t.transaction(r)()},close(){t.close()}});export{o as createBetterSqlite3Adapter};
1
+ import{n as c}from"../packem_shared/int64-CCVxepl4.mjs";const p=t=>({exec(r,e){e&&e.length>0?t.prepare(r).run([...e]):t.exec(r)},query(r,e){const n=t.prepare(r);n.safeIntegers(!0);const o=e&&e.length>0?n.all([...e]):n.all();return c(o)},transaction(r){t.transaction(r)()},close(){t.close()}});export{p as createBetterSqlite3Adapter};
@@ -1 +1 @@
1
- const t=c=>({exec(r,e){e&&e.length>0?c.exec(r,{bind:[...e]}):c.exec(r)},query(r,e){return c.exec(r,{bind:e&&e.length>0?[...e]:void 0,returnValue:"resultRows",rowMode:"object"})??[]},transaction(r){c.exec("BEGIN");try{r(),c.exec("COMMIT")}catch(e){throw c.exec("ROLLBACK"),e}},close(){c.close()}});export{t as createSqliteWasmAdapter};
1
+ import{n}from"../packem_shared/int64-CCVxepl4.mjs";const x=r=>({exec(o,e){e&&e.length>0?r.exec(o,{bind:[...e]}):r.exec(o)},query(o,e){const c=r.exec(o,{bind:e&&e.length>0?[...e]:void 0,returnValue:"resultRows",rowMode:"object"});return n(c??[])},transaction(o){r.exec("BEGIN");try{o(),r.exec("COMMIT")}catch(e){throw r.exec("ROLLBACK"),e}},close(){r.close()}});export{x as createSqliteWasmAdapter};
@@ -11,7 +11,9 @@ import { S as SqliteAdapter } from "../packem_shared/types.d-BuLTPLaQ.mjs";
11
11
  */
12
12
  declare const createSqlJsAdapter: (database: {
13
13
  close: () => void;
14
- exec: (sql: string, params?: unknown[]) => {
14
+ exec: (sql: string, params?: unknown[], config?: {
15
+ useBigInt?: boolean;
16
+ }) => {
15
17
  columns: string[];
16
18
  values: unknown[][];
17
19
  }[];
@@ -11,7 +11,9 @@ import { S as SqliteAdapter } from "../packem_shared/types.d-BuLTPLaQ.js";
11
11
  */
12
12
  declare const createSqlJsAdapter: (database: {
13
13
  close: () => void;
14
- exec: (sql: string, params?: unknown[]) => {
14
+ exec: (sql: string, params?: unknown[], config?: {
15
+ useBigInt?: boolean;
16
+ }) => {
15
17
  columns: string[];
16
18
  values: unknown[][];
17
19
  }[];
@@ -1 +1 @@
1
- const h=r=>({exec(e,o){o&&o.length>0?r.run(e,[...o]):r.exec(e)},query(e,o){const c=(o&&o.length>0?r.exec(e,[...o]):r.exec(e))[0];if(!c)return[];const u=c.columns,n=[];for(const l of c.values){const t={};for(const[s,f]of u.entries())t[f]=l[s];n.push(t)}return n},transaction(e){r.run("BEGIN");try{e(),r.run("COMMIT")}catch(o){throw r.run("ROLLBACK"),o}},close(){r.close()}});export{h as createSqlJsAdapter};
1
+ import{n as i}from"../packem_shared/int64-CCVxepl4.mjs";const w=o=>({exec(e,r){r&&r.length>0?o.run(e,[...r]):o.exec(e)},query(e,r){const n=o.exec(e,r&&r.length>0?[...r]:void 0,{useBigInt:!0})[0];if(!n)return[];const u=n.columns,t=[];for(const s of n.values){const c={};for(const[l,f]of u.entries())c[f]=s[l];t.push(c)}return i(t)},transaction(e){o.run("BEGIN");try{e(),o.run("COMMIT")}catch(r){throw o.run("ROLLBACK"),r}},close(){o.close()}});export{w as createSqlJsAdapter};
package/dist/index.d.mts CHANGED
@@ -17,8 +17,8 @@ createBetterSqlite3Adapter } from "./adapters/better-sqlite3.mjs";
17
17
  export { createSqliteWasmAdapter } from "./adapters/sqlite-wasm.mjs";
18
18
  export { createSqlJsAdapter } from "./adapters/sqljs.mjs";
19
19
  import { S as SqliteAdapter } from "./packem_shared/types.d-BuLTPLaQ.mjs";
20
- 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-CtQovAv_.mjs";
21
- export { type C as ClientSeq, type b as EventLogOptions, type c as EventLogSnapshot, type G as GlobalSeq, type d as LocalMirrorOptions, type M as MirrorTableDef, type R as RowChange, e as classifyChanges, f as createTableDiff, g as diffSize, i as isClientSeq, h as isDiffEmpty, j as isGlobalSeq, k as isInputEvent, m as mergeDiffs } from "./packem_shared/local-mirror.d-CtQovAv_.mjs";
20
+ 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-DzREvGNM.mjs";
21
+ export { type C as ClientSeq, type b as EventLogOptions, type c as EventLogSnapshot, type G as GlobalSeq, type d as LocalMirrorOptions, type M as MirrorTableDef, type R as RowChange, e as classifyChanges, f as createTableDiff, g as diffSize, i as isClientSeq, h as isDiffEmpty, j as isGlobalSeq, k as isInputEvent, m as mergeDiffs } from "./packem_shared/local-mirror.d-DzREvGNM.mjs";
22
22
  /**
23
23
  * Apply a single {@link TableDiff} to an in-memory row map and return
24
24
  * the updated map.
@@ -223,6 +223,12 @@ declare class EventLogDOClient {
223
223
  getSize(): Promise<number>;
224
224
  /**
225
225
  * Return the full log state — all entries plus the next seq number.
226
+ *
227
+ * Only for a log small enough to answer as one body: the DO refuses with a
228
+ * 413 past its page ceiling, since serialising an unbounded log into one
229
+ * response is what {@link EventLogDOClient.getSince} was bounded to avoid.
230
+ * A catch-up walks `getSince` instead.
231
+ * @throws Error when the log is too large to return in one body
226
232
  */
227
233
  getState(): Promise<{
228
234
  entries: EventLogEntry[];
@@ -487,9 +493,16 @@ declare class InMemorySnapshotStore implements SnapshotStore {
487
493
  *
488
494
  * Pure functions are strongly encouraged: given the same event and state,
489
495
  * they must produce the same next state for deterministic replay.
496
+ *
497
+ * Return {@link UNHANDLED} for an event `type` the reducer does not recognise —
498
+ * that, and only that, is what {@link MaterializerRuntimeOptions.unknownEventHandling}
499
+ * reacts to. Returning the current `state` is a legitimate, idempotent no-op for
500
+ * a type the reducer DOES handle; reference equality cannot tell the two apart
501
+ * (REPLICA-07), and reading it as "unhandled" warned about — or, under `"fail"`,
502
+ * threw on — an event type the reducer explicitly recognised.
490
503
  * @experimental
491
504
  */
492
- type MaterializerReducer<S> = (state: S, entry: EventLogEntry) => S;
505
+ type MaterializerReducer<S> = (state: S, entry: EventLogEntry) => S | typeof UNHANDLED;
493
506
  /**
494
507
  * Options for defining a single materializer.
495
508
  * @experimental
@@ -498,7 +511,8 @@ interface MaterializerDef<S> {
498
511
  /**
499
512
  * Reducer invoked for every event in the log.
500
513
  *
501
- * Return the current state unchanged to skip the event.
514
+ * Return the current state unchanged for a recognised event with nothing to
515
+ * do; return {@link UNHANDLED} for a `type` this reducer does not process.
502
516
  */
503
517
  handle: MaterializerReducer<S>;
504
518
  /** Factory for the initial (empty) state. */
@@ -511,8 +525,12 @@ interface MaterializerDef<S> {
511
525
  * @experimental
512
526
  */
513
527
  interface Materializer<S> {
514
- /** Apply a single event entry through the reducer. */
515
- apply: (entry: EventLogEntry) => void;
528
+ /**
529
+ * Apply a single event entry through the reducer.
530
+ * @returns `false` when the reducer returned {@link UNHANDLED} (state left
531
+ * untouched), `true` otherwise.
532
+ */
533
+ apply: (entry: EventLogEntry) => boolean;
516
534
  readonly def: MaterializerDef<S>;
517
535
  /** Reset to the initial state. */
518
536
  reset: () => void;
@@ -554,7 +572,21 @@ interface MaterializerRuntimeOptions {
554
572
  /** Optional snapshot store for persisting/recovering materialized state. */
555
573
  snapshotStore?: SnapshotStore;
556
574
  /**
557
- * How to handle events whose type no materializer handles.
575
+ * How to handle an event that every materializer explicitly DECLINED — one
576
+ * for which each reducer returned {@link UNHANDLED}.
577
+ *
578
+ * A reducer that instead falls through to `return state` for a type it does
579
+ * not recognise has, as far as the runtime can tell, handled the event: it
580
+ * changed nothing, but it did not decline. `"fail"` and `"warn"` are inert
581
+ * for such a reducer, and no option here can make them otherwise — write the
582
+ * reducer's default branch as `return UNHANDLED` if you want to hear about
583
+ * unknown types.
584
+ *
585
+ * A materializer whose own watermark is already past the entry does not run
586
+ * for it, and does not count as declining it: an entry that was already
587
+ * applied has already been classified, so a catch-up replaying it for a
588
+ * LAGGING materializer alone never re-reports it. Without that, `"fail"`
589
+ * aborted a catch-up on events a snapshot-recovered sibling had processed.
558
590
  * @default "warn"
559
591
  */
560
592
  unknownEventHandling?: UnknownEventHandling;
@@ -814,15 +846,26 @@ interface SubscriptionClient {
814
846
  * hooks subscribed to the mirror.
815
847
  *
816
848
  * Rows are keyed by the table's primary key (`id` unless the table was
817
- * registered with another `primaryKey`); a row without one can't be diffed or
818
- * reconciled on removal, and because the mirror's key column is `NOT NULL`
819
- * will fail the insert.
849
+ * registered with another `primaryKey`). A row without one still lands the
850
+ * apply path derives a key from the ROW's own content but it cannot be
851
+ * diffed: it is re-emitted on every frame, and it is never reconciled on
852
+ * removal because only keyed rows are recorded for the delete pass. Repeating an
853
+ * identical frame is therefore a no-op upsert, but each distinct content the
854
+ * un-keyed row ever holds leaves a row behind for the life of the mirror.
855
+ * **Mirror a query that selects the primary key.** An un-keyed shape (an
856
+ * aggregate, or a projection that drops `id`) is supported only so that one such
857
+ * row cannot take the rest of the frame down with it.
820
858
  *
821
859
  * The mirror table name is derived from the function ref alone (not `args`), so
822
860
  * do NOT mirror two subscriptions to the same function with different `args`
823
861
  * into the same mirror: they'd share one table and the snapshot-delete pass of
824
862
  * one could remove rows still live in the other.
825
863
  *
864
+ * `mirror.clearData()` resets the remembered frame. Without that reset the next
865
+ * identical frame diffed clean against a mirror that no longer held the rows —
866
+ * no changes, no `applyDiff` — and the table stayed empty until some row's
867
+ * content happened to change or the page reloaded.
868
+ *
826
869
  * Call the returned unsubscribe function to tear down both the client
827
870
  * subscription and future mirror writes.
828
871
  * @example
package/dist/index.d.ts CHANGED
@@ -17,8 +17,8 @@ createBetterSqlite3Adapter } from "./adapters/better-sqlite3.js";
17
17
  export { createSqliteWasmAdapter } from "./adapters/sqlite-wasm.js";
18
18
  export { createSqlJsAdapter } from "./adapters/sqljs.js";
19
19
  import { S as SqliteAdapter } from "./packem_shared/types.d-BuLTPLaQ.js";
20
- 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-CErKffFW.js";
21
- export { type C as ClientSeq, type b as EventLogOptions, type c as EventLogSnapshot, type G as GlobalSeq, type d as LocalMirrorOptions, type M as MirrorTableDef, type R as RowChange, e as classifyChanges, f as createTableDiff, g as diffSize, i as isClientSeq, h as isDiffEmpty, j as isGlobalSeq, k as isInputEvent, m as mergeDiffs } from "./packem_shared/local-mirror.d-CErKffFW.js";
20
+ 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-wbGXkXaE.js";
21
+ export { type C as ClientSeq, type b as EventLogOptions, type c as EventLogSnapshot, type G as GlobalSeq, type d as LocalMirrorOptions, type M as MirrorTableDef, type R as RowChange, e as classifyChanges, f as createTableDiff, g as diffSize, i as isClientSeq, h as isDiffEmpty, j as isGlobalSeq, k as isInputEvent, m as mergeDiffs } from "./packem_shared/local-mirror.d-wbGXkXaE.js";
22
22
  /**
23
23
  * Apply a single {@link TableDiff} to an in-memory row map and return
24
24
  * the updated map.
@@ -223,6 +223,12 @@ declare class EventLogDOClient {
223
223
  getSize(): Promise<number>;
224
224
  /**
225
225
  * Return the full log state — all entries plus the next seq number.
226
+ *
227
+ * Only for a log small enough to answer as one body: the DO refuses with a
228
+ * 413 past its page ceiling, since serialising an unbounded log into one
229
+ * response is what {@link EventLogDOClient.getSince} was bounded to avoid.
230
+ * A catch-up walks `getSince` instead.
231
+ * @throws Error when the log is too large to return in one body
226
232
  */
227
233
  getState(): Promise<{
228
234
  entries: EventLogEntry[];
@@ -487,9 +493,16 @@ declare class InMemorySnapshotStore implements SnapshotStore {
487
493
  *
488
494
  * Pure functions are strongly encouraged: given the same event and state,
489
495
  * they must produce the same next state for deterministic replay.
496
+ *
497
+ * Return {@link UNHANDLED} for an event `type` the reducer does not recognise —
498
+ * that, and only that, is what {@link MaterializerRuntimeOptions.unknownEventHandling}
499
+ * reacts to. Returning the current `state` is a legitimate, idempotent no-op for
500
+ * a type the reducer DOES handle; reference equality cannot tell the two apart
501
+ * (REPLICA-07), and reading it as "unhandled" warned about — or, under `"fail"`,
502
+ * threw on — an event type the reducer explicitly recognised.
490
503
  * @experimental
491
504
  */
492
- type MaterializerReducer<S> = (state: S, entry: EventLogEntry) => S;
505
+ type MaterializerReducer<S> = (state: S, entry: EventLogEntry) => S | typeof UNHANDLED;
493
506
  /**
494
507
  * Options for defining a single materializer.
495
508
  * @experimental
@@ -498,7 +511,8 @@ interface MaterializerDef<S> {
498
511
  /**
499
512
  * Reducer invoked for every event in the log.
500
513
  *
501
- * Return the current state unchanged to skip the event.
514
+ * Return the current state unchanged for a recognised event with nothing to
515
+ * do; return {@link UNHANDLED} for a `type` this reducer does not process.
502
516
  */
503
517
  handle: MaterializerReducer<S>;
504
518
  /** Factory for the initial (empty) state. */
@@ -511,8 +525,12 @@ interface MaterializerDef<S> {
511
525
  * @experimental
512
526
  */
513
527
  interface Materializer<S> {
514
- /** Apply a single event entry through the reducer. */
515
- apply: (entry: EventLogEntry) => void;
528
+ /**
529
+ * Apply a single event entry through the reducer.
530
+ * @returns `false` when the reducer returned {@link UNHANDLED} (state left
531
+ * untouched), `true` otherwise.
532
+ */
533
+ apply: (entry: EventLogEntry) => boolean;
516
534
  readonly def: MaterializerDef<S>;
517
535
  /** Reset to the initial state. */
518
536
  reset: () => void;
@@ -554,7 +572,21 @@ interface MaterializerRuntimeOptions {
554
572
  /** Optional snapshot store for persisting/recovering materialized state. */
555
573
  snapshotStore?: SnapshotStore;
556
574
  /**
557
- * How to handle events whose type no materializer handles.
575
+ * How to handle an event that every materializer explicitly DECLINED — one
576
+ * for which each reducer returned {@link UNHANDLED}.
577
+ *
578
+ * A reducer that instead falls through to `return state` for a type it does
579
+ * not recognise has, as far as the runtime can tell, handled the event: it
580
+ * changed nothing, but it did not decline. `"fail"` and `"warn"` are inert
581
+ * for such a reducer, and no option here can make them otherwise — write the
582
+ * reducer's default branch as `return UNHANDLED` if you want to hear about
583
+ * unknown types.
584
+ *
585
+ * A materializer whose own watermark is already past the entry does not run
586
+ * for it, and does not count as declining it: an entry that was already
587
+ * applied has already been classified, so a catch-up replaying it for a
588
+ * LAGGING materializer alone never re-reports it. Without that, `"fail"`
589
+ * aborted a catch-up on events a snapshot-recovered sibling had processed.
558
590
  * @default "warn"
559
591
  */
560
592
  unknownEventHandling?: UnknownEventHandling;
@@ -814,15 +846,26 @@ interface SubscriptionClient {
814
846
  * hooks subscribed to the mirror.
815
847
  *
816
848
  * Rows are keyed by the table's primary key (`id` unless the table was
817
- * registered with another `primaryKey`); a row without one can't be diffed or
818
- * reconciled on removal, and because the mirror's key column is `NOT NULL`
819
- * will fail the insert.
849
+ * registered with another `primaryKey`). A row without one still lands the
850
+ * apply path derives a key from the ROW's own content but it cannot be
851
+ * diffed: it is re-emitted on every frame, and it is never reconciled on
852
+ * removal because only keyed rows are recorded for the delete pass. Repeating an
853
+ * identical frame is therefore a no-op upsert, but each distinct content the
854
+ * un-keyed row ever holds leaves a row behind for the life of the mirror.
855
+ * **Mirror a query that selects the primary key.** An un-keyed shape (an
856
+ * aggregate, or a projection that drops `id`) is supported only so that one such
857
+ * row cannot take the rest of the frame down with it.
820
858
  *
821
859
  * The mirror table name is derived from the function ref alone (not `args`), so
822
860
  * do NOT mirror two subscriptions to the same function with different `args`
823
861
  * into the same mirror: they'd share one table and the snapshot-delete pass of
824
862
  * one could remove rows still live in the other.
825
863
  *
864
+ * `mirror.clearData()` resets the remembered frame. Without that reset the next
865
+ * identical frame diffed clean against a mirror that no longer held the rows —
866
+ * no changes, no `applyDiff` — and the table stayed empty until some row's
867
+ * content happened to change or the page reloaded.
868
+ *
826
869
  * Call the returned unsubscribe function to tear down both the client
827
870
  * subscription and future mirror writes.
828
871
  * @example
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-DRJ1gap3.mjs";import{defineEvents as l}from"./packem_shared/defineEvents-DHo-VK7G.mjs";import{MaterializerRuntime as S,defineMaterializer as E}from"./packem_shared/MaterializerRuntime-S-Knx6BM.mjs";import{applyDiffToDb as v,applyDiffsToDb as y}from"./packem_shared/applyDiffToDb-C6ek5Elp.mjs";import{EventEmitter as d}from"./packem_shared/EventEmitter-uo75adUL.mjs";import{EventLog as M}from"./packem_shared/EventLog-B1-yhArT.mjs";import{EventLogDO as u}from"./packem_shared/EventLogDO-BF9ZWc6C.mjs";import{EventLogDOClient as T}from"./packem_shared/EventLogDOClient-DWerZ3_n.mjs";import{EventSource as C,UNHANDLED as h}from"./packem_shared/EventSource-BC0hKJSA.mjs";import{eventsContext as I}from"./packem_shared/eventsContext-Dxow9Y7S.mjs";import{LocalMirror as O}from"./packem_shared/LocalMirror-ZmB8SJFe.mjs";import{isClientSeq as G,isGlobalSeq as H,isInputEvent as J}from"./packem_shared/isClientSeq-D2Xm0_lj.mjs";import{InMemorySnapshotStore as U}from"./packem_shared/InMemorySnapshotStore-C4taIG5K.mjs";import{subscribeToMirror as j}from"./packem_shared/subscribeToMirror-BT4oOBng.mjs";import{SubscriptionManager as w}from"./packem_shared/SubscriptionManager-AhPw3lFc.mjs";import{EventsSync as K}from"./packem_shared/EventsSync-B3wzXm-b.mjs";import{classifyChanges as Q,createTableDiff as V,diffSize as X,isDiffEmpty as Y,mergeDiffs as Z}from"./packem_shared/classifyChanges-BBc0-770.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-C4pbSYUT.mjs";import{defineEvents as l}from"./packem_shared/defineEvents-DHo-VK7G.mjs";import{MaterializerRuntime as S,defineMaterializer as E}from"./packem_shared/MaterializerRuntime-DFXi-aqd.mjs";import{applyDiffToDb as v,applyDiffsToDb as y}from"./packem_shared/applyDiffToDb-zb4sHy-6.mjs";import{EventEmitter as d}from"./packem_shared/EventEmitter-uo75adUL.mjs";import{EventLog as M}from"./packem_shared/EventLog-SuC_BKwj.mjs";import{EventLogDO as u}from"./packem_shared/EventLogDO-BgUx2GGL.mjs";import{EventLogDOClient as T}from"./packem_shared/EventLogDOClient-DWerZ3_n.mjs";import{EventSource as C,UNHANDLED as h}from"./packem_shared/EventSource-Bg6zNmRn.mjs";import{eventsContext as I}from"./packem_shared/eventsContext-Dxow9Y7S.mjs";import{LocalMirror as O}from"./packem_shared/LocalMirror-Cc4FE1Le.mjs";import{isClientSeq as G,isGlobalSeq as H,isInputEvent as J}from"./packem_shared/isClientSeq-D2Xm0_lj.mjs";import{InMemorySnapshotStore as U}from"./packem_shared/InMemorySnapshotStore-C4taIG5K.mjs";import{subscribeToMirror as j}from"./packem_shared/subscribeToMirror-uA3ZCsLa.mjs";import{SubscriptionManager as w}from"./packem_shared/SubscriptionManager-AhPw3lFc.mjs";import{EventsSync as K}from"./packem_shared/EventsSync-B3wzXm-b.mjs";import{classifyChanges as Q,createTableDiff as V,diffSize as X,isDiffEmpty as Y,mergeDiffs as Z}from"./packem_shared/classifyChanges-BBc0-770.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 f{#e=[];#t=0;#i=0;#s=null;#n;constructor(t){this.#n=f.#o(t?.maxEntries)}static#o(t){if(t!==void 0){if(!Number.isSafeInteger(t)||t<0)throw new RangeError("maxEntries must be a non-negative safe integer");return t}}#r(){return this.#e.slice(this.#t)}#h(){if(this.#n===void 0)return;const e=this.#e.length-this.#t-this.#n;e>0&&(this.#t+=e),this.#t>0&&this.#t>=this.#e.length-this.#t&&(this.#e=this.#e.slice(this.#t),this.#t=0)}append(t,e,s,n){const r=typeof t=="string"?t:t.type,o=typeof t=="string"?e:t.payload;let h,i;typeof t=="string"?(h=s,i=n):(h=void 0,i=e);const a=i?.parentSeqNum??this.#s??void 0,l=this.#i;this.#i+=1;const c={seq:l,type:r,payload:o,timestamp:i?.timestamp??(typeof t=="string"?void 0:t.timestamp)??Date.now(),tableDiffs:h,clientId:i?.clientId,sessionId:i?.sessionId,parentSeqNum:a};return this.#e.push(c),this.#s=c.seq,this.#h(),c}commitAll(t){if(t.length===0)return[];const e=[];for(const s of t){const{type:n}=s,r="payload"in s?s.payload:void 0,o="timestamp"in s?s.timestamp:Date.now(),h=e.at(-1)?.seq??this.#s??void 0,i=this.#i;this.#i+=1;const a={seq:i,type:n,payload:r,timestamp:o,parentSeqNum:h};this.#e.push(a),e.push(a)}return this.#s=e.at(-1)?.seq??this.#s,this.#h(),e}load(t){this.#e=[...t.entries],this.#t=0,this.#i=t.nextSeq,this.#s=t.headSeq,this.#h()}getSince(t){const e=this.#r();if(t<=0)return e;const s=e.findIndex(n=>n.seq>=t);return s===-1?[]:e.slice(s)}getFrom(t,e=50){if(!Number.isSafeInteger(e)||e<1)throw new RangeError("limit must be a positive safe integer");const s=this.#e,n=this.#t,r=s.findIndex((h,i)=>i>=n&&h.seq>=t);if(r===-1)return{entries:[],hasMore:!1};const o=Math.min(r+e,s.length);return{entries:s.slice(r,o),hasMore:o<s.length}}snapshot(){return{entries:this.#r(),nextSeq:this.#i,headSeq:this.#s}}get size(){return this.#e.length-this.#t}get nextSeq(){return this.#i}get isEmpty(){return this.size===0}get headSeq(){return this.#s}clear(){this.#e=[],this.#t=0,this.#i=0,this.#s=null}truncateBelow(t){if(!Number.isSafeInteger(t)||t<0)throw new RangeError("floorSeq must be a non-negative safe integer");const e=this.#r(),s=e.findIndex(n=>n.seq>=t);this.#e=s===-1?[]:e.slice(s),this.#t=0}async*events(t=0){const e=this.getSince(t);for(const s of e)yield s}}export{f as EventLog};
@@ -0,0 +1 @@
1
+ const N=Symbol("lunora.replica.event-log-do.idempotency-conflict"),_=n=>{const t=new Error(n);return Object.defineProperty(t,N,{value:!0}),t},R=n=>n instanceof Error&&N in n,y=n=>{if(Array.isArray(n))return n.map(t=>y(t));if(n!==null&&typeof n=="object"){const t=n,e=Object.keys(t);e.sort();const s={};for(const r of e)s[r]=y(t[r]);return s}return n},O=async n=>{const t=JSON.stringify(y(n)),e=new TextEncoder().encode(t),s=await crypto.subtle.digest("SHA-256",e);return[...new Uint8Array(s)].map(r=>r.toString(16).padStart(2,"0")).join("")},v=500,m=1e3,g=32768,S=4194304,E=(n,t=200)=>Response.json(n,{status:t,headers:{"content-type":"application/json"}}),p=(n,t,e)=>E({error:{code:t,message:e}},n),l=n=>typeof n.toArray=="function"?n.toArray():typeof n[Symbol.iterator]=="function"?[...n]:[],T=n=>({seq:n.seq,type:n.type,payload:JSON.parse(n.payload),timestamp:n.timestamp,clientId:n.client_id??void 0,sessionId:n.session_id??void 0,parentSeqNum:n.parent_seq??void 0}),b=n=>l(n).map(t=>T(t)),L=n=>n.reduce((t,e)=>t+e.payload.length,0),C=(n,t)=>{const e=Math.min(n.length,t);let s=0;for(let r=0;r<e;r+=1)if(s+=n[r].payload.length,s>S&&r>0)return r;return e};class u{state;env;#t=!1;constructor(t,e){this.state=t,this.env=e}async fetch(t){this.#c();const e=new URL(t.url);try{if(t.method==="POST"&&e.pathname==="/append")return await this.#e(t);if(t.method==="GET"&&e.pathname==="/since")return this.#i(e);if(t.method==="GET"&&e.pathname==="/size")return this.#a();if(t.method==="GET"&&e.pathname==="/state")return this.#o()}catch(s){return console.error("[event-log-do] request failed:",s),p(500,"INTERNAL_ERROR","internal error")}return p(404,"NOT_FOUND","unknown route")}async#e(t){let e;try{e=await t.json()}catch{return p(400,"BAD_REQUEST","invalid JSON body")}const s=u.#s(e);if(s)return p(400,"BAD_REQUEST",s);const{sql:r}=this.state.storage,{batchId:a}=e,o=()=>u.#n(r,e,a),{transaction:i}=this.state.storage;let c;try{c=typeof i=="function"?await i(o):await o()}catch(f){if(R(f))return p(409,"CONFLICT",f.message);throw f}return E({entries:c})}static async#n(t,e,s){let r;if(typeof s=="string"){r=await O(e.events);const i=u.#p(t,s);if(i){if(i.fingerprint!==r)throw _(`batchId "${s}" was already used for a different event batch`);return i.entries}}const a=Date.now(),o=[];for(const i of e.events){const d={seq:u.#d(t),type:i.type,payload:i.payload,timestamp:i.timestamp??a,clientId:i.clientId,sessionId:i.sessionId,parentSeqNum:i.parentSeqNum};u.#l(t,d),o.push(d)}if(typeof s=="string"&&r!==void 0){const i=o[0]?.seq,c=o.at(-1)?.seq;i!==void 0&&c!==void 0&&u.#u(t,s,i,c,r)}return o}static#s(t){if(!Array.isArray(t.events)||t.events.length===0)return"events[] with a non-empty string `type` required";if(t.batchId!==void 0&&(typeof t.batchId!="string"||t.batchId.length===0))return"batchId must be a non-empty string";for(const e of t.events){const s=u.#r(e);if(s!==void 0)return s}}static#r(t){if(typeof t.type!="string"||t.type.length===0)return"events[] with a non-empty string `type` required";if(t.timestamp!==void 0&&!Number.isFinite(t.timestamp))return"events[].timestamp must be a finite number";if(t.clientId!==void 0&&typeof t.clientId!="string")return"events[].clientId must be a string";if(t.sessionId!==void 0&&typeof t.sessionId!="string")return"events[].sessionId must be a string";if(t.parentSeqNum!==void 0&&(typeof t.parentSeqNum!="number"||!Number.isInteger(t.parentSeqNum)||t.parentSeqNum<0))return"events[].parentSeqNum must be a non-negative integer";if(t.payload===void 0)return"events[].payload is required";if(JSON.stringify(t.payload).length>g)return`events[].payload must serialise to at most ${String(g)} characters`}#i(t){const e=t.searchParams.get("seq"),s=e===null?0:Number(e),r=t.searchParams.get("limit"),a=r===null?v:Number(r);if(!Number.isSafeInteger(s)||s<0)return p(400,"BAD_REQUEST","invalid seq");if(!Number.isSafeInteger(a)||a<1||a>m)return p(400,"BAD_REQUEST","invalid limit");const{sql:o}=this.state.storage,i=o.exec("SELECT seq, type, payload, timestamp, client_id, session_id, parent_seq FROM events WHERE seq >= ? ORDER BY seq ASC LIMIT ?",s,a+1),c=l(i),d=C(c,a),f=d<c.length,h=c.slice(0,d).map(I=>T(I)),q=h.at(-1),A=f&&q!==void 0?{entries:h,truncated:!0,cursor:q.seq+1}:{entries:h,truncated:!1};return E(A)}#a(){const{sql:t}=this.state.storage,e=t.exec("SELECT COUNT(*) AS count FROM events"),r=l(e)[0]?.count??0;return E({count:r})}#o(){const{sql:t}=this.state.storage,e=t.exec("SELECT seq, type, payload, timestamp, client_id, session_id, parent_seq FROM events ORDER BY seq ASC LIMIT ?",m+1),s=l(e);if(s.length>m||L(s)>S)return p(413,"PAYLOAD_TOO_LARGE",`the log is larger than /state can return in one body (over ${String(m)} entries, or over ${String(S)} characters of payload) — walk /since?seq=0 with its truncated/cursor pages instead`);const a=s.map(i=>T(i)),o=(a.at(-1)?.seq??-1)+1;return E({entries:a,nextSeq:o})}#c(){if(this.#t)return;const{sql:t}=this.state.storage;t.exec("CREATE TABLE IF NOT EXISTS events (seq INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL, payload TEXT NOT NULL, timestamp INTEGER NOT NULL, client_id TEXT, session_id TEXT, parent_seq INTEGER)"),t.exec("CREATE TABLE IF NOT EXISTS event_batches (batch_id TEXT PRIMARY KEY, first_seq INTEGER NOT NULL, last_seq INTEGER NOT NULL, fingerprint TEXT NOT NULL)"),this.#t=!0}static#p(t,e){const s=t.exec("SELECT first_seq, last_seq, fingerprint FROM event_batches WHERE batch_id = ?",e),a=l(s)[0];if(!a)return;const o=t.exec("SELECT seq, type, payload, timestamp, client_id, session_id, parent_seq FROM events WHERE seq >= ? AND seq <= ? ORDER BY seq ASC",a.first_seq,a.last_seq);return{entries:b(o),fingerprint:a.fingerprint}}static#u(t,e,s,r,a){t.exec("INSERT INTO event_batches (batch_id, first_seq, last_seq, fingerprint) VALUES (?, ?, ?, ?)",e,s,r,a)}static#d(t){const e=t.exec("SELECT COALESCE(MAX(seq), -1) + 1 AS next_seq FROM events");return l(e)[0]?.next_seq??0}static#l(t,e){const s=typeof e.parentSeqNum=="number"?e.parentSeqNum:null;t.exec("INSERT INTO events (seq, type, payload, timestamp, client_id, session_id, parent_seq) VALUES (?, ?, ?, ?, ?, ?, ?)",e.seq,e.type,JSON.stringify(e.payload),e.timestamp,e.clientId??null,e.sessionId??null,s)}}export{u as EventLogDO};
@@ -0,0 +1 @@
1
+ import{EventEmitter as m}from"./EventEmitter-uo75adUL.mjs";import{EventLog as u}from"./EventLog-SuC_BKwj.mjs";const d=Symbol("lunora.replica.event-source.unhandled");class w{emitter=new m;log;#e;#t;#n=!1;#i;#r=-1;constructor(e,n,t){this.#e={...e},this.#t=n,this.#i=t?.unknownEventHandling??"warn",this.log=new u({maxEntries:t?.maxLogEntries})}get state(){return this.#e}get replayed(){return this.#n}applyEvent(e,n,t){let r,o,i;typeof e=="string"?(r=e,o=n,i=t):(r=e.type,o=e.payload,i=n);const s={seq:this.log.nextSeq,type:r,payload:o,timestamp:Date.now(),clientId:i?.clientId,sessionId:i?.sessionId,parentSeqNum:i?.parentSeqNum??this.log.headSeq??void 0};let a;try{a=this.#t(this.#e,s)}catch(l){const p=l instanceof Error?l:new Error(String(l));throw this.emitter.emit("replay-error",{entry:s,error:p}),p}let c=!0;a===d&&(c=this.#s(s));const h=this.log.append(r,o,void 0,{...i,timestamp:s.timestamp});return a===d?(c&&this.emitter.emit("state-changed",{state:this.#e,entry:h}),h):(this.#e=a,this.emitter.emit("state-changed",{state:this.#e,entry:h}),h)}replayFromLog(e){const n=e.getSince(this.#r+1);for(const t of n)try{const r=this.#t(this.#e,t);r!==d&&(this.#e=r);const o=this.log.append(t.type,t.payload,t.tableDiffs,{clientId:t.clientId,sessionId:t.sessionId,parentSeqNum:t.parentSeqNum,timestamp:t.timestamp});this.emitter.emit("state-changed",{state:this.#e,entry:o})}catch(r){this.emitter.emit("replay-error",{entry:t,error:r instanceof Error?r:new Error(String(r))})}finally{this.#r=t.seq}this.#n=!0,this.emitter.emit("ready",{entryCount:this.log.size})}reset(e,n=-1){this.#e={...e},this.#r=n,this.#n=n>=0}async*events(e){const n=[];let t;const r=this.emitter.on("state-changed",({entry:i})=>{n.push(i),t?.()}),o=()=>{t?.()};e?.addEventListener("abort",o,{once:!0});try{let i=0;for(;i<this.log.size;){if(e?.aborted)return;for(const s of this.log.getSince(i))yield s;i=this.log.nextSeq}for(;!e?.aborted;){for(;n.length>0;){const s=n.shift();s&&(yield s)}n.length===0&&await new Promise(s=>{t=s})}}finally{r(),e?.removeEventListener("abort",o)}}#s(e){const n=this.#i;if(typeof n=="function")return n(e);switch(n){case"ignore":return!1;case"fail":throw new Error(`EventSource: unhandled event type "${e.type}" (seq ${String(e.seq)}). Configure \`unknownEventHandling\` to handle this event or change the strategy.`);default:return console.warn(`[EventSource] unhandled event type "${e.type}" (seq ${String(e.seq)}). The event was skipped. Configure \`unknownEventHandling\` if this is expected.`),!1}}}export{w as EventSource,d as UNHANDLED};
@@ -0,0 +1,4 @@
1
+ import{createSqlJsAdapter as m}from"../adapters/sqljs.mjs";import{applyDiffToDb as b,escapeIdentifier as o}from"./applyDiffToDb-zb4sHy-6.mjs";import{EventLog as p}from"./EventLog-SuC_BKwj.mjs";const A=1e3,f="__lunora_mirror_meta",R=a=>{a.exec(`CREATE TABLE IF NOT EXISTS ${f} (
2
+ key TEXT PRIMARY KEY NOT NULL,
3
+ value TEXT NOT NULL
4
+ )`)},T="schema_version",y=4,l=a=>typeof a=="bigint"||typeof a=="boolean"?"INTEGER":typeof a=="number"?Number.isInteger(a)?"INTEGER":"REAL":"TEXT",h=a=>a??"";class E{#e;#t;#n;#s=new Set;#i=0;static create(e,t){const n=m(e);return new E({db:n,tables:t?.tables})}constructor(e){this.#e=e.db,this.#t={...e.tables},this.#n=new p({maxEntries:e.maxEventLogEntries??A}),R(this.#e),this.#o()}onChange(e){return this.#s.add(e),()=>{this.#s.delete(e)}}get eventLog(){return this.#n}get db(){return this.#e}get version(){return this.#i}applyDiff(e){e.changes.length!==0&&(this.#h(e),b(this.#e,e,this.primaryKeyOf(e.table)),this.#n.append("table-diff",e,[e]),this.#r("diff"))}query(e,t){return this.#e.query(e,t)}clearData(){const e=this.#a();this.#e.transaction(()=>{for(const{name:t}of e)this.#e.exec(`DELETE FROM ${o(t)}`)}),this.#r("clear")}#r(e){this.#i+=1;for(const t of this.#s)try{t(e)}catch{}}close(){this.#e.close(),this.#n.clear(),this.#s.clear()}registerTable(e,t){this.#t[e]={...this.#t[e],...t}}primaryKeyOf(e){return this.#t[e]?.primaryKey??"id"}get mirroredTables(){return Object.keys(this.#t)}#a(){return this.#e.query(String.raw`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE '\_\_lunora\_%' ESCAPE '\' AND name NOT LIKE 'sqlite\_%' ESCAPE '\'`)}#o(){if(this.#e.query(`SELECT key, value FROM ${f}`).find(s=>s.key===T)?.value===String(y))return;const n=this.#a();this.#e.transaction(()=>{for(const{name:s}of n)this.#e.exec(`DROP TABLE IF EXISTS ${o(s)}`);this.#e.exec(`INSERT OR REPLACE INTO ${f} (key, value) VALUES (?, ?)`,[T,String(y)])})}static#c(e,t){const n=new Set;for(const s of e.changes)if(s.type!=="delete")for(const c of Object.keys(s.data))c!==t&&n.add(c);return n}static#E(e,t,n){const s=new Map;for(const c of e.changes)if(!(c.type==="delete"||s.size===n.size))for(const r of n){if(r===t||s.has(r))continue;const i=c.data[r];i!=null&&s.set(r,l(i))}return s}static#l(e,t){for(const n of e.changes){const s=n.type==="delete"?n.id:n.data[t];if(s!=null)return l(s);if(n.type==="update")return l(n.id)}}#h(e){const t=this.primaryKeyOf(e.table),n=E.#c(e,t),s=E.#E(e,t,n);if(this.#e.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?",[e.table]).length===0){const r=E.#l(e,t);let i=`${o(t)} ${r==="INTEGER"?"INT":h(r)} PRIMARY KEY NOT NULL`;for(const u of n)i+=`, ${o(u)} ${h(s.get(u))}`;this.#e.exec(`CREATE TABLE IF NOT EXISTS ${o(e.table)} (${i})`)}else if(n.size>0){const r=new Set(this.#e.query(`PRAGMA table_info(${o(e.table)})`).map(i=>i.name));for(const i of n)r.has(i)||this.#e.exec(`ALTER TABLE ${o(e.table)} ADD COLUMN ${o(i)} ${h(s.get(i))}`)}}}export{E as LocalMirror};
@@ -0,0 +1 @@
1
+ import{UNHANDLED as o}from"./EventSource-Bg6zNmRn.mjs";const h=1e3,p=s=>{let e=s.initial();return{def:s,get state(){return Object.freeze(e)},setState(t){e=t},apply(t){const n=s.handle(e,t);return n===o?!1:(e=n,!0)},reset(){e=s.initial()}}};class c{#e;#n;#i;#r;#t;constructor(e,t={}){this.#e=[...e],this.#t=this.#e.map(()=>0),this.#n=t.snapshotStore,this.#i=t.doClient,this.#r=t.unknownEventHandling??"warn"}get appliedSeq(){return this.#t.length>0?Math.min(...this.#t):0}applyEntries(e){let t=0;for(const n of e){const{advancedIndices:r,anyHandled:i}=this.#a(n);if(r.length!==0){!i&&r.length===this.#e.length&&this.#o(n);for(const a of r)this.#t[a]=n.seq+1;t+=1}}return t}#a(e){const t=[];let n=!1;for(const[r,i]of this.#e.entries())e.seq<(this.#t[r]??0)||(i.apply(e)&&(n=!0),t.push(r));return{advancedIndices:t,anyHandled:n}}#o(e){const t=this.#r;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,n]of this.#e.entries()){const r=await this.#n.load(n.def.name);if(r!==null&&typeof r=="object"){const i=r;if(Number.isSafeInteger(i.appliedSeq)&&i.appliedSeq>=0&&i.state!==void 0){const a=i.appliedSeq;n.setState(i.state),this.#t[t]=a,a>e&&(e=a)}}}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(){return this.#i?(await this.recoverFromSnapshots(),this.#s()):0}async#s(){const e=this.#i;if(!e)return 0;let t=this.appliedSeq,n=0;for(let r=0;r<h;r+=1){const i=await e.getSince(t);if(n+=this.applyEntries(i.entries),!i.truncated||i.cursor===void 0||i.cursor<=t)return n;t=i.cursor}return n}async appendEvent(e){if(!this.#i)throw new Error("MaterializerRuntime.appendEvent requires a doClient — pass one in the constructor options.");const n=(await this.#i.append([e]))[0];if(!n)throw new Error("MaterializerRuntime.appendEvent: DO returned empty result");return this.#e.length>0&&this.appliedSeq<n.seq&&(await this.#s(),this.appliedSeq<n.seq)||this.applyEntries([n]),n}reset(){for(const[e,t]of this.#e.entries())this.#t[e]=0,t.reset()}get materializers(){return this.#e}}export{c as MaterializerRuntime,p as defineMaterializer};
@@ -0,0 +1 @@
1
+ import{f as r}from"./fnv1a-BNN96GYb.mjs";import{s as p}from"./wire-key-2vOZ0mPa.mjs";const c=(e,n)=>`row-${r(`${e.table}::${p(n)}`)}`,o=(e,n)=>{for(const t of n.changes)switch(t.type){case"delete":{e.delete(t.id);break}case"insert":{const a=t.data.id,s=typeof a=="bigint"||typeof a=="number"||typeof a=="string"?String(a):c(n,t.data);e.set(s,{...t.data,id:s});break}case"update":{const a=e.get(t.id);a&&e.set(t.id,{...a,...t.data});break}}},i=(e,n)=>{const t=new Map(e);return o(t,n),t},l=(e,n)=>{const t=new Map(e);for(const a of n)o(t,a);return t},b=(e,n)=>{const t=new Map(e),a=t.get(n.table)??new Map;return t.set(n.table,i(a,n)),t};export{i as applyDiff,b as applyDiffToSnapshot,l as applyDiffs,c as deriveInsertId,r as fnv1a64Hex};
@@ -0,0 +1 @@
1
+ import{deriveInsertId as $}from"./applyDiff-C4pbSYUT.mjs";const p=t=>`\`${t.replaceAll("`","``")}\``,D=t=>t.map(n=>`${p(n)} = ?`).join(", "),h=t=>`(${t.map(n=>p(n)).join(", ")})`,T=t=>`(${Array.from({length:t}).fill("?").join(", ")})`,f=t=>t==null?null:typeof t=="number"||typeof t=="bigint"||typeof t=="string"?t:typeof t=="boolean"?t?1:0:t instanceof Uint8Array?t:typeof t=="object"?JSON.stringify(t):String(t),g=(t,n,o)=>{if(n.changes.length===0)return;const a=p(n.table),l=p(o);for(const i of n.changes)switch(i.type){case"delete":{t.exec(`DELETE FROM ${a} WHERE ${l} = ?`,[f(i.id)]);break}case"insert":{const{data:e}=i;if(Object.keys(e).length===0)continue;const s=e[o],c=typeof s=="bigint"||typeof s=="number"||typeof s=="string"?e:{...e,[o]:$(n,e)},r=Object.keys(c),y=`INSERT OR REPLACE INTO ${a} ${h(r)} VALUES ${T(r.length)}`,b=r.map(E=>f(c[E]));t.exec(y,b);break}case"update":{const{data:e}=i,s=Object.keys(e);if(s.length===0)continue;const c=`UPDATE ${a} SET ${D(s)} WHERE ${l} = ?`,r=[...s.map(y=>f(e[y])),f(i.id)];t.exec(c,r);break}}},d=(t,n,o)=>{t.transaction(()=>{g(t,n,o??"id")})},j=(t,n)=>{n.length!==0&&t.transaction(()=>{for(const o of n)g(t,o,"id")})};export{d as applyDiffToDb,j as applyDiffsToDb,p as escapeIdentifier,f as normalizeBindValue};
@@ -0,0 +1 @@
1
+ const I=n=>typeof n=="bigint"&&n>=BigInt(Number.MIN_SAFE_INTEGER)&&n<=BigInt(Number.MAX_SAFE_INTEGER)?Number(n):n,c=n=>n.map(t=>{let e;for(const[s,r]of Object.entries(t)){const o=I(r);o!==r&&(e??={...t},e[s]=o)}return e??t});export{c as n};
@@ -31,13 +31,14 @@ interface TableDiff {
31
31
  /**
32
32
  * Optional stable identity for this diff, distinct from `timestamp`
33
33
  * (multiple diffs can legitimately share a millisecond, so `timestamp`
34
- * alone is not a unique diff identity). Used by `deriveInsertId` in
35
- * `apply-diff.ts` to derive deterministic row ids for id-less inserts:
36
- * replaying the SAME diff (same `id`) must always mint the SAME id,
37
- * while two DIFFERENT diffs emitted in the same millisecond must not
38
- * alias onto the same one. `createTableDiff` auto-generates one when
39
- * omitted; diffs built as plain object literals (bypassing the helper)
40
- * simply fall back to `timestamp` for that derivation.
34
+ * alone is not a unique diff identity). `createTableDiff` auto-generates
35
+ * one when omitted.
36
+ *
37
+ * **Nothing in the apply path reads it.** `deriveInsertId` (`apply-diff.ts`)
38
+ * keys an id-less insert off the ROW'S OWN content hashing the diff id
39
+ * into it is what made `subscribeToMirror`'s per-frame re-emission mint a
40
+ * fresh row every second, so that derivation is gone. This field is
41
+ * carried for consumers that want to recognise a diff they have seen.
41
42
  */
42
43
  readonly id?: string;
43
44
  /** Logical table name (matches the schema table name). */
@@ -312,6 +313,11 @@ declare class EventLog {
312
313
  getSince(sinceSeq: number): ReadonlyArray<EventLogEntry>;
313
314
  /**
314
315
  * Paginated read starting at `fromSeq`.
316
+ *
317
+ * `limit` must be a positive safe integer, validated like
318
+ * {@link EventLog#truncateBelow}'s floor and the constructor's `maxEntries`:
319
+ * a `limit` of `0` returned an empty page with `hasMore: true`, which spins a
320
+ * paginating caller forever on a page it can never advance past.
315
321
  * @returns `{ entries, hasMore }` where `hasMore` is `true` when more
316
322
  * entries exist beyond the requested page.
317
323
  */
@@ -405,6 +411,15 @@ interface LocalMirrorOptions {
405
411
  */
406
412
  readonly tables?: Record<string, MirrorTableDef>;
407
413
  }
414
+ /**
415
+ * Why the mirror changed: `"diff"` for the rows an {@link LocalMirror.applyDiff}
416
+ * wrote, `"clear"` for the wholesale {@link LocalMirror.clearData} sweep.
417
+ *
418
+ * A subscriber that caches what it believes the mirror holds has to tell the two
419
+ * apart: `"diff"` reports a change it usually made itself, while `"clear"` means
420
+ * every row it was tracking is gone regardless of who wrote it.
421
+ */
422
+ type MirrorChangeReason = "clear" | "diff";
408
423
  /**
409
424
  * Local SQLite mirror that maintains a client-side replica of server
410
425
  * tables by applying {@link TableDiff} deltas.
@@ -429,7 +444,7 @@ interface LocalMirrorOptions {
429
444
  * );
430
445
  * ```
431
446
  */
432
- type ChangeSubscriber = () => void;
447
+ type ChangeSubscriber = (reason: MirrorChangeReason) => void;
433
448
  /**
434
449
  * `LocalMirror` is part of the experimental `@lunora/replica` API and may change without a major version bump.
435
450
  * @experimental
@@ -507,8 +522,17 @@ declare class LocalMirror {
507
522
  */
508
523
  query<T = Record<string, unknown>>(sql: string, params?: ReadonlyArray<unknown>): T[];
509
524
  /**
510
- * Delete every row from all known tables (preserves the event log
511
- * and schema). Useful when re-syncing from scratch.
525
+ * Delete every row from every data table in the adapter's database
526
+ * (preserves the event log and schema). Useful when re-syncing from scratch.
527
+ *
528
+ * **The mirror owns its database.** The sweep is `sqlite_master` minus the
529
+ * reserved prefixes, NOT {@link LocalMirror.mirroredTables} — a table this
530
+ * mirror never registered is cleared too, and `#reconcileSchemaVersion`
531
+ * DROPs on the same list. It cannot be narrowed to the registered set: that
532
+ * runs from the constructor, before any `applyDiff` has re-registered the
533
+ * tables a previous session persisted, and those are exactly the
534
+ * stale-schema tables it exists to drop. So hand the adapter a database
535
+ * dedicated to the mirror, never one that also holds your own tables.
512
536
  *
513
537
  * Notifies `onChange` subscribers and bumps {@link LocalMirror.version}
514
538
  * (REPLICA-09) even though nothing is appended to the event log — a
@@ -31,13 +31,14 @@ interface TableDiff {
31
31
  /**
32
32
  * Optional stable identity for this diff, distinct from `timestamp`
33
33
  * (multiple diffs can legitimately share a millisecond, so `timestamp`
34
- * alone is not a unique diff identity). Used by `deriveInsertId` in
35
- * `apply-diff.ts` to derive deterministic row ids for id-less inserts:
36
- * replaying the SAME diff (same `id`) must always mint the SAME id,
37
- * while two DIFFERENT diffs emitted in the same millisecond must not
38
- * alias onto the same one. `createTableDiff` auto-generates one when
39
- * omitted; diffs built as plain object literals (bypassing the helper)
40
- * simply fall back to `timestamp` for that derivation.
34
+ * alone is not a unique diff identity). `createTableDiff` auto-generates
35
+ * one when omitted.
36
+ *
37
+ * **Nothing in the apply path reads it.** `deriveInsertId` (`apply-diff.ts`)
38
+ * keys an id-less insert off the ROW'S OWN content hashing the diff id
39
+ * into it is what made `subscribeToMirror`'s per-frame re-emission mint a
40
+ * fresh row every second, so that derivation is gone. This field is
41
+ * carried for consumers that want to recognise a diff they have seen.
41
42
  */
42
43
  readonly id?: string;
43
44
  /** Logical table name (matches the schema table name). */
@@ -312,6 +313,11 @@ declare class EventLog {
312
313
  getSince(sinceSeq: number): ReadonlyArray<EventLogEntry>;
313
314
  /**
314
315
  * Paginated read starting at `fromSeq`.
316
+ *
317
+ * `limit` must be a positive safe integer, validated like
318
+ * {@link EventLog#truncateBelow}'s floor and the constructor's `maxEntries`:
319
+ * a `limit` of `0` returned an empty page with `hasMore: true`, which spins a
320
+ * paginating caller forever on a page it can never advance past.
315
321
  * @returns `{ entries, hasMore }` where `hasMore` is `true` when more
316
322
  * entries exist beyond the requested page.
317
323
  */
@@ -405,6 +411,15 @@ interface LocalMirrorOptions {
405
411
  */
406
412
  readonly tables?: Record<string, MirrorTableDef>;
407
413
  }
414
+ /**
415
+ * Why the mirror changed: `"diff"` for the rows an {@link LocalMirror.applyDiff}
416
+ * wrote, `"clear"` for the wholesale {@link LocalMirror.clearData} sweep.
417
+ *
418
+ * A subscriber that caches what it believes the mirror holds has to tell the two
419
+ * apart: `"diff"` reports a change it usually made itself, while `"clear"` means
420
+ * every row it was tracking is gone regardless of who wrote it.
421
+ */
422
+ type MirrorChangeReason = "clear" | "diff";
408
423
  /**
409
424
  * Local SQLite mirror that maintains a client-side replica of server
410
425
  * tables by applying {@link TableDiff} deltas.
@@ -429,7 +444,7 @@ interface LocalMirrorOptions {
429
444
  * );
430
445
  * ```
431
446
  */
432
- type ChangeSubscriber = () => void;
447
+ type ChangeSubscriber = (reason: MirrorChangeReason) => void;
433
448
  /**
434
449
  * `LocalMirror` is part of the experimental `@lunora/replica` API and may change without a major version bump.
435
450
  * @experimental
@@ -507,8 +522,17 @@ declare class LocalMirror {
507
522
  */
508
523
  query<T = Record<string, unknown>>(sql: string, params?: ReadonlyArray<unknown>): T[];
509
524
  /**
510
- * Delete every row from all known tables (preserves the event log
511
- * and schema). Useful when re-syncing from scratch.
525
+ * Delete every row from every data table in the adapter's database
526
+ * (preserves the event log and schema). Useful when re-syncing from scratch.
527
+ *
528
+ * **The mirror owns its database.** The sweep is `sqlite_master` minus the
529
+ * reserved prefixes, NOT {@link LocalMirror.mirroredTables} — a table this
530
+ * mirror never registered is cleared too, and `#reconcileSchemaVersion`
531
+ * DROPs on the same list. It cannot be narrowed to the registered set: that
532
+ * runs from the constructor, before any `applyDiff` has re-registered the
533
+ * tables a previous session persisted, and those are exactly the
534
+ * stale-schema tables it exists to drop. So hand the adapter a database
535
+ * dedicated to the mirror, never one that also holds your own tables.
512
536
  *
513
537
  * Notifies `onChange` subscribers and bumps {@link LocalMirror.version}
514
538
  * (REPLICA-09) even though nothing is appended to the event log — a
@@ -0,0 +1 @@
1
+ import{s as A}from"./wire-key-2vOZ0mPa.mjs";const _=e=>`fn_${e.replaceAll(/[/:.]/g,"_")}`,k=e=>Array.isArray(e)?e:e!==null&&typeof e=="object"?[e]:[],x=(e,o,l,y,g)=>{const a=_(l.__lunoraRef);o.registerTable(a,{});const d=o.primaryKeyOf(a);let r=new Map,f=0;const w=o.onChange(p=>{p==="clear"&&(f+=1,r=new Map)}),h=e.subscribe(l,y,p=>{const M=f,c=new Map,b=new Map,s=[];for(const t of k(p)){const n=t,i=n[d];if(typeof i!="string"&&typeof i!="number"&&typeof i!="bigint"){s.push({type:"insert",data:n});continue}const u=String(i),m=A(n);c.set(u,m),b.set(u,n)}for(const[t,n]of c)r.get(t)!==n&&s.push({data:b.get(t),type:"insert"});for(const t of r.keys())c.has(t)||s.push({type:"delete",id:t});s.length>0&&o.applyDiff({table:a,changes:s,timestamp:Date.now()}),M===f&&(r=c)},{shardKey:g});return()=>{w(),h()}};export{x as subscribeToMirror};
@@ -0,0 +1 @@
1
+ const m=/["\\\u0000-\u001F\uD800-\uDFFF]/,d=r=>m.test(r)?JSON.stringify(r):`"${r}"`,b=r=>{if(r===void 0)return"null";if(typeof r=="bigint")throw new TypeError("stableStringify: cannot use a bigint in a stable JSON cache key — pass it as a string, or use stableWireKey");if(typeof r=="number"){if(Number.isNaN(r))return"nan";if(r===1/0)return"inf";if(r===-1/0)return"-inf";if(Object.is(r,-0))return"-0"}if(typeof r=="string")return d(r);if(r===null||typeof r!="object")return JSON.stringify(r);if(Array.isArray(r)){let e="[";for(let i=0;i<r.length;i++)i>0&&(e+=","),e+=b(r[i]);return e+"]"}const n=Object.getPrototypeOf(r);if(n!==null&&n!==Object.prototype){const e=r.constructor?.name??"value";throw new TypeError(`stableStringify: cannot use a ${e} in a stable JSON cache key — only plain objects, arrays, and JSON primitives are supported (wire-typed values key via stableWireKey)`)}const y=r,s=Object.keys(y).sort();let f="{",t=!0;for(const e of s){const i=y[e];i!==void 0&&(t?t=!1:f+=",",f+=d(e),f+=":",f+=b(i))}return f+"}"},u=r=>{let n="";for(let s=0;s<r.length;s+=32768)n+=String.fromCharCode(...r.subarray(s,s+32768));return btoa(n)},o="$lunora.wire$";const g="__proto__",w=r=>{if(r===null||typeof r!="object")return!1;const n=Object.getPrototypeOf(r);return n===null||n===Object.prototype},c=(r,n=0)=>{if(n>64)throw new RangeError("wire-codec: value nesting exceeds the 64-level limit");if(r===void 0)return[o,"undefined"];if(r===null)return null;const y=typeof r;if(y==="bigint")return[o,"bigint",r.toString()];if(y==="number"){const t=r;return Number.isNaN(t)?[o,"nan"]:t===1/0?[o,"inf"]:t===-1/0?[o,"-inf"]:t}if(y!=="object")return r;if(r instanceof Date)return[o,"date",c(r.getTime(),n+1)];if(r instanceof Error){const t=r,e={};for(const a of Object.keys(t)){if(t[a]===void 0)continue;const p=c(t[a],n+1);a===g?Object.defineProperty(e,a,{configurable:!0,enumerable:!0,value:p,writable:!0}):e[a]=p}const i=[o,"error",t.name,t.message,e];return t.cause!==void 0&&i.push(c(t.cause,n+1)),i}if(r instanceof URL)return[o,"url",r.href];if(r instanceof Map)return[o,"map",[...r.entries()].map(([t,e])=>[c(t,n+1),c(e,n+1)])];if(r instanceof Set)return[o,"set",[...r].map(t=>c(t,n+1))];if(r instanceof ArrayBuffer)return[o,"bytes",u(new Uint8Array(r)),"ArrayBuffer"];if(ArrayBuffer.isView(r)){const t=r,e=t.constructor.name,i=new Uint8Array(t.buffer,t.byteOffset,t.byteLength);return e==="Uint8Array"?[o,"bytes",u(i)]:[o,"bytes",u(i),e]}if(Array.isArray(r)){const t=r.map(e=>c(e,n+1));return t.length>0&&t[0]===o?[o,"arr",t]:t}if(!w(r)){const t=r.constructor?.name??"value";throw new TypeError(`wire-codec: cannot encode a ${t} over the Lunora wire — only plain objects, arrays, and the supported built-ins (Date, Error, URL, Map, Set, ArrayBuffer/typed arrays, bigint) round-trip`)}const s=r,f={};for(const t of Object.keys(s)){const e=s[t];if(e===void 0)continue;const i=c(e,n+1);t===g?Object.defineProperty(f,t,{configurable:!0,enumerable:!0,value:i,writable:!0}):f[t]=i}return f},O=r=>b(c(r));export{O as s};
package/dist/react.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { L as LocalMirror } from "./packem_shared/local-mirror.d-CtQovAv_.mjs";
1
+ import { L as LocalMirror } from "./packem_shared/local-mirror.d-DzREvGNM.mjs";
2
2
  import "./packem_shared/types.d-BuLTPLaQ.mjs";
3
3
  /**
4
4
  * Result of {@link useLocalQuery} — a discriminated union so callers get a
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { L as LocalMirror } from "./packem_shared/local-mirror.d-CErKffFW.js";
1
+ import { L as LocalMirror } from "./packem_shared/local-mirror.d-wbGXkXaE.js";
2
2
  import "./packem_shared/types.d-BuLTPLaQ.js";
3
3
  /**
4
4
  * Result of {@link useLocalQuery} — a discriminated union so callers get a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/replica",
3
- "version": "1.0.0-alpha.80",
3
+ "version": "1.0.0-alpha.82",
4
4
  "description": "Local-first replica runtime + local SQLite mirror for Lunora",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -1 +0,0 @@
1
- class l{#e=[];#t=0;#i=0;#s=null;#n;constructor(t){this.#n=l.#o(t?.maxEntries)}static#o(t){if(t!==void 0){if(!Number.isSafeInteger(t)||t<0)throw new RangeError("maxEntries must be a non-negative safe integer");return t}}#r(){return this.#e.slice(this.#t)}#h(){if(this.#n===void 0)return;const e=this.#e.length-this.#t-this.#n;e>0&&(this.#t+=e),this.#t>0&&this.#t>=this.#e.length-this.#t&&(this.#e=this.#e.slice(this.#t),this.#t=0)}append(t,e,s,n){const r=typeof t=="string"?t:t.type,o=typeof t=="string"?e:t.payload;let h,i;typeof t=="string"?(h=s,i=n):(h=void 0,i=e);const a=i?.parentSeqNum??this.#s??void 0,f=this.#i;this.#i+=1;const c={seq:f,type:r,payload:o,timestamp:i?.timestamp??Date.now(),tableDiffs:h,clientId:i?.clientId,sessionId:i?.sessionId,parentSeqNum:a};return this.#e.push(c),this.#s=c.seq,this.#h(),c}commitAll(t){if(t.length===0)return[];const e=[];for(const s of t){const{type:n}=s,r="payload"in s?s.payload:void 0,o="timestamp"in s?s.timestamp:Date.now(),h=e.at(-1)?.seq??this.#s??void 0,i=this.#i;this.#i+=1;const a={seq:i,type:n,payload:r,timestamp:o,parentSeqNum:h};this.#e.push(a),e.push(a)}return this.#s=e.at(-1)?.seq??this.#s,this.#h(),e}load(t){this.#e=[...t.entries],this.#t=0,this.#i=t.nextSeq,this.#s=t.headSeq,this.#h()}getSince(t){const e=this.#r();if(t<=0)return e;const s=e.findIndex(n=>n.seq>=t);return s===-1?[]:e.slice(s)}getFrom(t,e=50){const s=this.#e,n=this.#t,r=s.findIndex((h,i)=>i>=n&&h.seq>=t);if(r===-1)return{entries:[],hasMore:!1};const o=Math.min(r+e,s.length);return{entries:s.slice(r,o),hasMore:o<s.length}}snapshot(){return{entries:this.#r(),nextSeq:this.#i,headSeq:this.#s}}get size(){return this.#e.length-this.#t}get nextSeq(){return this.#i}get isEmpty(){return this.size===0}get headSeq(){return this.#s}clear(){this.#e=[],this.#t=0,this.#i=0,this.#s=null}truncateBelow(t){if(!Number.isSafeInteger(t)||t<0)throw new RangeError("floorSeq must be a non-negative safe integer");const e=this.#r(),s=e.findIndex(n=>n.seq>=t);this.#e=s===-1?[]:e.slice(s),this.#t=0}async*events(t=0){const e=this.getSince(t);for(const s of e)yield s}}export{l as EventLog};
@@ -1 +0,0 @@
1
- const y=Symbol("lunora.replica.event-log-do.idempotency-conflict"),q=n=>{const t=new Error(n);return Object.defineProperty(t,y,{value:!0}),t},N=n=>n instanceof Error&&y in n,T=n=>{if(Array.isArray(n))return n.map(t=>T(t));if(n!==null&&typeof n=="object"){const t=n,e=Object.keys(t);e.sort();const s={};for(const r of e)s[r]=T(t[r]);return s}return n},I=async n=>{const t=JSON.stringify(T(n)),e=new TextEncoder().encode(t),s=await crypto.subtle.digest("SHA-256",e);return[...new Uint8Array(s)].map(r=>r.toString(16).padStart(2,"0")).join("")},g=500,A=1e3,f=(n,t=200)=>Response.json(n,{status:t,headers:{"content-type":"application/json"}}),d=(n,t,e)=>f({error:{code:t,message:e}},n),m=n=>typeof n.toArray=="function"?n.toArray():typeof n[Symbol.iterator]=="function"?[...n]:[],E=n=>m(n).map(e=>({seq:e.seq,type:e.type,payload:JSON.parse(e.payload),timestamp:e.timestamp,clientId:e.client_id??void 0,sessionId:e.session_id??void 0,parentSeqNum:e.parent_seq??void 0}));class p{state;env;#t=!1;constructor(t,e){this.state=t,this.env=e}async fetch(t){this.#c();const e=new URL(t.url);try{if(t.method==="POST"&&e.pathname==="/append")return await this.#e(t);if(t.method==="GET"&&e.pathname==="/since")return this.#i(e);if(t.method==="GET"&&e.pathname==="/size")return this.#o();if(t.method==="GET"&&e.pathname==="/state")return this.#a()}catch(s){return console.error("[event-log-do] request failed:",s),d(500,"INTERNAL_ERROR","internal error")}return d(404,"NOT_FOUND","unknown route")}async#e(t){let e;try{e=await t.json()}catch{return d(400,"BAD_REQUEST","invalid JSON body")}const s=p.#n(e);if(s)return d(400,"BAD_REQUEST",s);const{sql:r}=this.state.storage,{batchId:o}=e,a=()=>p.#s(r,e,o),{transaction:i}=this.state.storage;let c;try{c=typeof i=="function"?await i(a):await a()}catch(u){if(N(u))return d(409,"CONFLICT",u.message);throw u}return f({entries:c})}static async#s(t,e,s){let r;if(typeof s=="string"){r=await I(e.events);const i=p.#p(t,s);if(i){if(i.fingerprint!==r)throw q(`batchId "${s}" was already used for a different event batch`);return i.entries}}const o=Date.now(),a=[];for(const i of e.events){const l={seq:p.#d(t),type:i.type,payload:i.payload,timestamp:i.timestamp??o,clientId:i.clientId,sessionId:i.sessionId,parentSeqNum:i.parentSeqNum};p.#l(t,l),a.push(l)}if(typeof s=="string"&&r!==void 0){const i=a[0]?.seq,c=a.at(-1)?.seq;i!==void 0&&c!==void 0&&p.#u(t,s,i,c,r)}return a}static#n(t){if(!Array.isArray(t.events)||t.events.length===0)return"events[] with a non-empty string `type` required";if(t.batchId!==void 0&&(typeof t.batchId!="string"||t.batchId.length===0))return"batchId must be a non-empty string";for(const e of t.events){const s=p.#r(e);if(s!==void 0)return s}}static#r(t){if(typeof t.type!="string"||t.type.length===0)return"events[] with a non-empty string `type` required";if(t.timestamp!==void 0&&!Number.isFinite(t.timestamp))return"events[].timestamp must be a finite number";if(t.clientId!==void 0&&typeof t.clientId!="string")return"events[].clientId must be a string";if(t.sessionId!==void 0&&typeof t.sessionId!="string")return"events[].sessionId must be a string";if(t.parentSeqNum!==void 0&&(typeof t.parentSeqNum!="number"||!Number.isInteger(t.parentSeqNum)||t.parentSeqNum<0))return"events[].parentSeqNum must be a non-negative integer"}#i(t){const e=t.searchParams.get("seq"),s=e===null?0:Number(e),r=t.searchParams.get("limit"),o=r===null?g:Number(r);if(!Number.isSafeInteger(s)||s<0)return d(400,"BAD_REQUEST","invalid seq");if(!Number.isSafeInteger(o)||o<1||o>A)return d(400,"BAD_REQUEST","invalid limit");const{sql:a}=this.state.storage,i=a.exec("SELECT seq, type, payload, timestamp, client_id, session_id, parent_seq FROM events WHERE seq >= ? ORDER BY seq ASC LIMIT ?",s,o+1),c=E(i),l=c.length>o,u=l?c.slice(0,o):c,h=u.at(-1),S=l&&h!==void 0?{entries:u,truncated:!0,cursor:h.seq+1}:{entries:u,truncated:!1};return f(S)}#o(){const{sql:t}=this.state.storage,e=t.exec("SELECT COUNT(*) AS count FROM events"),r=m(e)[0]?.count??0;return f({count:r})}#a(){const{sql:t}=this.state.storage,e=t.exec("SELECT seq, type, payload, timestamp, client_id, session_id, parent_seq FROM events ORDER BY seq ASC"),s=E(e),r=(s.at(-1)?.seq??-1)+1;return f({entries:s,nextSeq:r})}#c(){if(this.#t)return;const{sql:t}=this.state.storage;t.exec("CREATE TABLE IF NOT EXISTS events (seq INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL, payload TEXT NOT NULL, timestamp INTEGER NOT NULL, client_id TEXT, session_id TEXT, parent_seq INTEGER)"),t.exec("CREATE TABLE IF NOT EXISTS event_batches (batch_id TEXT PRIMARY KEY, first_seq INTEGER NOT NULL, last_seq INTEGER NOT NULL, fingerprint TEXT NOT NULL)"),this.#t=!0}static#p(t,e){const s=t.exec("SELECT first_seq, last_seq, fingerprint FROM event_batches WHERE batch_id = ?",e),o=m(s)[0];if(!o)return;const a=t.exec("SELECT seq, type, payload, timestamp, client_id, session_id, parent_seq FROM events WHERE seq >= ? AND seq <= ? ORDER BY seq ASC",o.first_seq,o.last_seq);return{entries:E(a),fingerprint:o.fingerprint}}static#u(t,e,s,r,o){t.exec("INSERT INTO event_batches (batch_id, first_seq, last_seq, fingerprint) VALUES (?, ?, ?, ?)",e,s,r,o)}static#d(t){const e=t.exec("SELECT COALESCE(MAX(seq), -1) + 1 AS next_seq FROM events");return m(e)[0]?.next_seq??0}static#l(t,e){const s=typeof e.parentSeqNum=="number"?e.parentSeqNum:null;t.exec("INSERT INTO events (seq, type, payload, timestamp, client_id, session_id, parent_seq) VALUES (?, ?, ?, ?, ?, ?, ?)",e.seq,e.type,JSON.stringify(e.payload),e.timestamp,e.clientId??null,e.sessionId??null,s)}}export{p as EventLogDO};
@@ -1 +0,0 @@
1
- import{EventEmitter as p}from"./EventEmitter-uo75adUL.mjs";import{EventLog as f}from"./EventLog-B1-yhArT.mjs";const d=Symbol("lunora.replica.event-source.unhandled");class w{emitter=new p;log;#e;#t;#n=!1;#i;#r=-1;constructor(e,t,n){this.#e={...e},this.#t=t,this.#i=n?.unknownEventHandling??"warn",this.log=new f({maxEntries:n?.maxLogEntries})}get state(){return this.#e}get replayed(){return this.#n}applyEvent(e,t,n){let r,o,i;typeof e=="string"?(r=e,o=t,i=n):(r=e.type,o=e.payload,i=t);const s={seq:this.log.nextSeq,type:r,payload:o,timestamp:Date.now(),clientId:i?.clientId,sessionId:i?.sessionId,parentSeqNum:i?.parentSeqNum??this.log.headSeq??void 0};let a;try{a=this.#t(this.#e,s)}catch(h){const u=h instanceof Error?h:new Error(String(h));throw this.emitter.emit("replay-error",{entry:s,error:u}),u}let c=!0;a===d&&(c=this.#s(s));const l=this.log.append(r,o,void 0,{...i,timestamp:s.timestamp});return a===d?(c&&this.emitter.emit("state-changed",{state:this.#e,entry:l}),l):(this.#e=a,this.emitter.emit("state-changed",{state:this.#e,entry:l}),l)}replayFromLog(e){const t=e.getSince(this.#r+1);for(const n of t)try{const r=this.#t(this.#e,n);r!==d&&(this.#e=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(e,t=-1){this.#e={...e},this.#r=t,this.#n=t>=0}async*events(e){const t=[];let n;const r=this.emitter.on("state-changed",({entry:i})=>{t.push(i),n?.()}),o=()=>{n?.()};e?.addEventListener("abort",o,{once:!0});try{let i=0;for(;i<this.log.size;){if(e?.aborted)return;for(const s of this.log.getSince(i))yield s;i=this.log.nextSeq}for(;!e?.aborted;){for(;t.length>0;){const s=t.shift();s&&(yield s)}t.length===0&&await new Promise(s=>{n=s})}}finally{r(),e?.removeEventListener("abort",o)}}#s(e){const t=this.#i;if(typeof t=="function")return t(e);switch(t){case"ignore":return!1;case"fail":throw new Error(`EventSource: unhandled event type "${e.type}" (seq ${String(e.seq)}). Configure \`unknownEventHandling\` to handle this event or change the strategy.`);default:return console.warn(`[EventSource] unhandled event type "${e.type}" (seq ${String(e.seq)}). The event was skipped. Configure \`unknownEventHandling\` if this is expected.`),!1}}}export{w as EventSource,d as UNHANDLED};
@@ -1,4 +0,0 @@
1
- import{createSqlJsAdapter as y}from"../adapters/sqljs.mjs";import{applyDiffToDb as m,escapeIdentifier as a}from"./applyDiffToDb-C6ek5Elp.mjs";import{EventLog as b}from"./EventLog-B1-yhArT.mjs";const A=1e3,l="__lunora_mirror_meta",R=o=>{o.exec(`CREATE TABLE IF NOT EXISTS ${l} (
2
- key TEXT PRIMARY KEY NOT NULL,
3
- value TEXT NOT NULL
4
- )`)},u="schema_version",f=3,h=o=>typeof o=="bigint"||typeof o=="boolean"?"INTEGER":typeof o=="number"?Number.isInteger(o)?"INTEGER":"REAL":"TEXT";class E{#e;#t;#s;#n=new Set;#i=0;static create(e,s){const t=y(e);return new E({db:t,tables:s?.tables})}constructor(e){this.#e=e.db,this.#t={...e.tables},this.#s=new b({maxEntries:e.maxEventLogEntries??A}),R(this.#e),this.#o()}onChange(e){return this.#n.add(e),()=>{this.#n.delete(e)}}get eventLog(){return this.#s}get db(){return this.#e}get version(){return this.#i}applyDiff(e){e.changes.length!==0&&(this.#l(e),m(this.#e,e,this.primaryKeyOf(e.table)),this.#s.append("table-diff",e,[e]),this.#r())}query(e,s){return this.#e.query(e,s)}clearData(){const e=this.#a();this.#e.transaction(()=>{for(const{name:s}of e)this.#e.exec(`DELETE FROM ${a(s)}`)}),this.#r()}#r(){this.#i+=1;for(const e of this.#n)try{e()}catch{}}close(){this.#e.close(),this.#s.clear(),this.#n.clear()}registerTable(e,s){this.#t[e]={...this.#t[e],...s}}primaryKeyOf(e){return this.#t[e]?.primaryKey??"id"}get mirroredTables(){return Object.keys(this.#t)}#a(){return this.#e.query(String.raw`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE '\_\_lunora\_%' ESCAPE '\' AND name NOT LIKE 'sqlite\_%' ESCAPE '\'`)}#o(){if(this.#e.query(`SELECT key, value FROM ${l}`).find(n=>n.key===u)?.value===String(f))return;const t=this.#a();this.#e.transaction(()=>{for(const{name:n}of t)this.#e.exec(`DROP TABLE IF EXISTS ${a(n)}`);this.#e.exec(`INSERT OR REPLACE INTO ${l} (key, value) VALUES (?, ?)`,[u,String(f)])})}static#c(e,s){const t=new Set;for(const n of e.changes)if(n.type!=="delete")for(const c of Object.keys(n.data))c!==s&&t.add(c);return t}static#E(e,s,t){const n=new Map;for(const c of e.changes)if(!(c.type==="delete"||n.size===t.size))for(const r of t){if(r===s||n.has(r))continue;const i=c.data[r];i!=null&&n.set(r,h(i))}return n}static#h(e,s){for(const t of e.changes){const n=t.type==="delete"?t.id:t.data[s];if(n!=null)return h(n);if(t.type==="update")return h(t.id)}return"TEXT"}#l(e){const s=this.primaryKeyOf(e.table),t=E.#c(e,s),n=E.#E(e,s,t);if(this.#e.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?",[e.table]).length===0){const r=E.#h(e,s);let i=`${a(s)} ${r==="INTEGER"?"INT":r} PRIMARY KEY NOT NULL`;for(const T of t)i+=`, ${a(T)} ${n.get(T)??"TEXT"}`;this.#e.exec(`CREATE TABLE IF NOT EXISTS ${a(e.table)} (${i})`)}else if(t.size>0){const r=new Set(this.#e.query(`PRAGMA table_info(${a(e.table)})`).map(i=>i.name));for(const i of t)r.has(i)||this.#e.exec(`ALTER TABLE ${a(e.table)} ADD COLUMN ${a(i)} ${n.get(i)??"TEXT"}`)}}}export{E as LocalMirror};
@@ -1 +0,0 @@
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 u{#e;#i;#n;#s;#t;constructor(e,t={}){this.#e=[...e],this.#t=this.#e.map(()=>0),this.#i=t.snapshotStore,this.#n=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,p]of this.#e.entries()){const h=this.#t[o]??0;if(i.seq<h)continue;const l=p.state;p.apply(i),s=!0,p.state!==l&&(n=!0),r.push(o)}if(s){n||this.#a(i);for(const o of r)this.#t[o]=i.seq+1;t+=1}}return t}#a(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.#i)return 0;let e=0;for(const[t,i]of this.#e.entries()){const s=await this.#i.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.#i)for(const[e,t]of this.#e.entries())await this.#i.save(t.def.name,{appliedSeq:this.#t[e]??0,state:t.state})}async initialize(){return this.#n?(await this.recoverFromSnapshots(),this.#r()):0}async#r(){const e=this.#n;if(!e)return 0;let t=this.appliedSeq,i=0;for(let s=0;s<1e3;s+=1){const n=await e.getSince(t);if(i+=this.applyEntries(n.entries),!n.truncated||n.cursor===void 0||n.cursor<=t)return i;t=n.cursor}return i}async appendEvent(e){if(!this.#n)throw new Error("MaterializerRuntime.appendEvent requires a doClient — pass one in the constructor options.");const i=(await this.#n.append([e]))[0];if(!i)throw new Error("MaterializerRuntime.appendEvent: DO returned empty result");return this.#e.length>0&&this.appliedSeq<i.seq&&(await this.#r(),this.appliedSeq<i.seq)||this.applyEntries([i]),i}reset(){for(const[e,t]of this.#e.entries())this.#t[e]=0,t.reset()}get materializers(){return this.#e}}export{u as MaterializerRuntime,c as defineMaterializer};
@@ -1 +0,0 @@
1
- import{f as i}from"./fnv1a-BNN96GYb.mjs";const r=t=>{if(Array.isArray(t))return t.map(e=>r(e));if(t!==null&&typeof t=="object"){const e=t,s=Object.keys(e);s.sort();const n={};for(const o of s)n[o]=r(e[o]);return n}return t},p=(t,e,s)=>{const n=t.id??String(t.timestamp),o=`${t.table}::${n}::${String(e)}::${JSON.stringify(r(s))}`;return`row-${i(o)}`},c=(t,e)=>{for(const[s,n]of e.changes.entries())switch(n.type){case"delete":{t.delete(n.id);break}case"insert":{const o=n.data.id,a=typeof o=="string"||typeof o=="number"?String(o):p(e,s,n.data);t.set(a,{...n.data,id:a});break}case"update":{const o=t.get(n.id);o&&t.set(n.id,{...o,...n.data});break}}},f=(t,e)=>{const s=new Map(t);return c(s,e),s},y=(t,e)=>{const s=new Map(t);for(const n of e)c(s,n);return s},b=(t,e)=>{const s=new Map(t),n=s.get(e.table)??new Map;return s.set(e.table,f(n,e)),s};export{f as applyDiff,b as applyDiffToSnapshot,y as applyDiffs,r as canonicalizeForHash,p as deriveInsertId,i as fnv1a64Hex};
@@ -1 +0,0 @@
1
- const r=t=>`\`${t.replaceAll("`","``")}\``,E=t=>t.map(n=>`${r(n)} = ?`).join(", "),$=t=>`(${t.map(n=>r(n)).join(", ")})`,b=t=>`(${Array.from({length:t}).fill("?").join(", ")})`,c=t=>t==null?null:typeof t=="number"||typeof t=="bigint"||typeof t=="string"?t:typeof t=="boolean"?t?1:0:t instanceof Uint8Array?t:typeof t=="object"?JSON.stringify(t):String(t),g=(t,n,o)=>{if(n.changes.length===0)return;const f=r(n.table),y=r(o);for(const s of n.changes)switch(s.type){case"delete":{t.exec(`DELETE FROM ${f} WHERE ${y} = ?`,[c(s.id)]);break}case"insert":{const{data:i}=s,e=Object.keys(i);if(e.length===0)continue;const a=`INSERT OR REPLACE INTO ${f} ${$(e)} VALUES ${b(e.length)}`,p=e.map(l=>c(i[l]));t.exec(a,p);break}case"update":{const{data:i}=s,e=Object.keys(i);if(e.length===0)continue;const a=`UPDATE ${f} SET ${E(e)} WHERE ${y} = ?`,p=[...e.map(l=>c(i[l])),c(s.id)];t.exec(a,p);break}}},D=(t,n,o)=>{t.transaction(()=>{g(t,n,o??"id")})},h=(t,n)=>{n.length!==0&&t.transaction(()=>{for(const o of n)g(t,o,"id")})};export{D as applyDiffToDb,h as applyDiffsToDb,r as escapeIdentifier,c as normalizeBindValue};
@@ -1 +0,0 @@
1
- const k=/["\\\u0000-\u001F\uD800-\uDFFF]/,O=r=>k.test(r)?JSON.stringify(r):`"${r}"`,w=r=>{if(r===void 0)return"null";if(typeof r=="bigint")throw new TypeError("stableStringify: cannot use a bigint in a stable JSON cache key — pass it as a string, or use stableWireKey");if(typeof r=="number"){if(Number.isNaN(r))return"nan";if(r===1/0)return"inf";if(r===-1/0)return"-inf";if(Object.is(r,-0))return"-0"}if(typeof r=="string")return O(r);if(r===null||typeof r!="object")return JSON.stringify(r);if(Array.isArray(r)){let e="[";for(let o=0;o<r.length;o++)o>0&&(e+=","),e+=w(r[o]);return e+"]"}const n=Object.getPrototypeOf(r);if(n!==null&&n!==Object.prototype){const e=r.constructor?.name??"value";throw new TypeError(`stableStringify: cannot use a ${e} in a stable JSON cache key — only plain objects, arrays, and JSON primitives are supported (wire-typed values key via stableWireKey)`)}const f=r,s=Object.keys(f).sort();let c="{",t=!0;for(const e of s){const o=f[e];o!==void 0&&(t?t=!1:c+=",",c+=O(e),c+=":",c+=w(o))}return c+"}"},l=r=>{let n="";for(let s=0;s<r.length;s+=32768)n+=String.fromCharCode(...r.subarray(s,s+32768));return btoa(n)},i="$lunora.wire$";const j="__proto__",E=r=>{if(r===null||typeof r!="object")return!1;const n=Object.getPrototypeOf(r);return n===null||n===Object.prototype},y=(r,n=0)=>{if(n>64)throw new RangeError("wire-codec: value nesting exceeds the 64-level limit");if(r===void 0)return[i,"undefined"];if(r===null)return null;const f=typeof r;if(f==="bigint")return[i,"bigint",r.toString()];if(f==="number"){const t=r;return Number.isNaN(t)?[i,"nan"]:t===1/0?[i,"inf"]:t===-1/0?[i,"-inf"]:t}if(f!=="object")return r;if(r instanceof Date)return[i,"date",y(r.getTime(),n+1)];if(r instanceof Error){const t=r,e={};for(const b of Object.keys(t))t[b]!==void 0&&(e[b]=y(t[b],n+1));const o=[i,"error",t.name,t.message,e];return t.cause!==void 0&&o.push(y(t.cause,n+1)),o}if(r instanceof URL)return[i,"url",r.href];if(r instanceof Map)return[i,"map",[...r.entries()].map(([t,e])=>[y(t,n+1),y(e,n+1)])];if(r instanceof Set)return[i,"set",[...r].map(t=>y(t,n+1))];if(r instanceof ArrayBuffer)return[i,"bytes",l(new Uint8Array(r)),"ArrayBuffer"];if(ArrayBuffer.isView(r)){const t=r,e=t.constructor.name,o=new Uint8Array(t.buffer,t.byteOffset,t.byteLength);return e==="Uint8Array"?[i,"bytes",l(o)]:[i,"bytes",l(o),e]}if(Array.isArray(r)){const t=r.map(e=>y(e,n+1));return t.length>0&&t[0]===i?[i,"arr",t]:t}if(!E(r)){const t=r.constructor?.name??"value";throw new TypeError(`wire-codec: cannot encode a ${t} over the Lunora wire — only plain objects, arrays, and the supported built-ins (Date, Error, URL, Map, Set, ArrayBuffer/typed arrays, bigint) round-trip`)}const s=r,c={};for(const t of Object.keys(s)){const e=s[t];if(e===void 0)continue;const o=y(e,n+1);t===j?Object.defineProperty(c,t,{configurable:!0,enumerable:!0,value:o,writable:!0}):c[t]=o}return c},N=r=>w(y(r)),S=r=>`fn_${r.replaceAll(/[/:.]/g,"_")}`,_=r=>Array.isArray(r)?r:r!==null&&typeof r=="object"?[r]:[],T=(r,n,f,s,c)=>{const t=S(f.__lunoraRef);n.registerTable(t,{});const e=n.primaryKeyOf(t);let o=new Map;return r.subscribe(f,s,b=>{const d=new Map,m=new Map,p=[];for(const a of _(b)){const u=a,g=u[e];if(typeof g!="string"&&typeof g!="number"&&typeof g!="bigint"){p.push({type:"insert",data:u});continue}const A=String(g),h=N(u);d.set(A,h),m.set(A,u)}for(const[a,u]of d)o.get(a)!==u&&p.push({data:m.get(a),type:"insert"});for(const a of o.keys())d.has(a)||p.push({type:"delete",id:a});p.length>0&&n.applyDiff({table:t,changes:p,timestamp:Date.now()}),o=d},{shardKey:c})};export{T as subscribeToMirror};