@lunora/client 1.0.0-alpha.6 → 1.0.0-alpha.8

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.
@@ -1,4 +1,4 @@
1
- import { U as User, L as LunoraClient } from "../packem_shared/lunora-client.d-DzEWBYeP.mjs";
1
+ import { U as User, L as LunoraClient } from "../packem_shared/lunora-client.d-CeUZ8lE1.mjs";
2
2
  import '@lunora/runtime';
3
3
  interface IdentityStore {
4
4
  /** Read the current resolved user, or `null` when signed out / not yet resolved. */
@@ -1,4 +1,4 @@
1
- import { U as User, L as LunoraClient } from "../packem_shared/lunora-client.d-DzEWBYeP.js";
1
+ import { U as User, L as LunoraClient } from "../packem_shared/lunora-client.d-CeUZ8lE1.js";
2
2
  import '@lunora/runtime';
3
3
  interface IdentityStore {
4
4
  /** Read the current resolved user, or `null` when signed out / not yet resolved. */
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { c as PersistenceAdapter, B as BookmarkStorage, F as FunctionReference, A as ArgsOf, M as MutationCallOptions, R as ReturnOf, O as OfflineQueueOptions, Q as QueryCacheAdapter, d as ReconnectOptions } from "./packem_shared/lunora-client.d-DzEWBYeP.mjs";
2
- export { type C as CachedQuery, type e as ClientMessage, type f as ClientShapeSubscribeMessage, type g as ClientShapeUnsubscribeMessage, type h as ConnectionStatus, D as DEFAULT_MAX_BUFFER, type i as FunctionArgumentDescriptor, type j as FunctionDescriptor, type G as GlobalFacetResult, type k as GlobalFacetValue, type l as GlobalFilterClause, type m as GlobalTableInfo, type n as GlobalTablePage, L as LunoraClient, type o as LunoraClientOptions, type p as OptimisticLocalStore, type q as OptimisticUpdate, type r as OutboxMutation, type s as OutboxSink, type t as PersistedMutation, type P as Preloaded, type u as RowOp, type v as RpcEnvelope, type w as RpcResponseBody, type x as ScheduleRecord, type y as SchedulerPoolStatus, type z as SchedulerStatus, type E as ServerMessage, type H as ServerPokeEndMessage, type I as ServerPokePartMessage, type J as ServerPokeStartMessage, type K as ShardTrafficEntry, type N as ShardTrafficResult, type T as StorageListPage, type V as StorageObject, type W as StreamHandle, type X as StreamIterable, type Y as SubscriptionCallback, type S as SubscriptionError, type b as SubscriptionErrorCallback, Z as SubscriptionRegistry, type _ as SubscriptionState, type $ as SyncWatermark, type a as Unsubscribe, type U as User, type a0 as WorkflowInstanceAction, type a1 as WorkflowInstanceDetail, type a2 as WorkflowInstancePage, type a3 as WorkflowInstanceStatus, type a4 as WorkflowInstanceSummary, type a5 as WorkflowStepDetail, a6 as createLocalStore, a7 as createStream } from "./packem_shared/lunora-client.d-DzEWBYeP.mjs";
3
- export { p as preloadQuery, a as preloadedQueryResult } from "./packem_shared/preload.d-DuYqEQ5J.mjs";
1
+ import { c as PersistenceAdapter, B as BookmarkStorage, F as FunctionReference, A as ArgsOf, M as MutationCallOptions, R as ReturnOf, O as OfflineQueueOptions, Q as QueryCacheAdapter, d as ReconnectOptions } from "./packem_shared/lunora-client.d-CeUZ8lE1.mjs";
2
+ export { type C as CachedQuery, type e as ClientMessage, type f as ClientShapeSubscribeMessage, type g as ClientShapeUnsubscribeMessage, type h as ConnectionStatus, D as DEFAULT_MAX_BUFFER, type i as FunctionArgumentDescriptor, type j as FunctionDescriptor, type G as GlobalFacetResult, type k as GlobalFacetValue, type l as GlobalFilterClause, type m as GlobalTableInfo, type n as GlobalTablePage, L as LunoraClient, type o as LunoraClientOptions, type p as OptimisticLocalStore, type q as OptimisticUpdate, type r as OutboxMutation, type s as OutboxSink, type t as PersistedMutation, type P as Preloaded, type u as RowOp, type v as RpcEnvelope, type w as RpcResponseBody, type x as ScheduleRecord, type y as SchedulerPoolStatus, type z as SchedulerStatus, type E as ServerMessage, type H as ServerPokeEndMessage, type I as ServerPokePartMessage, type J as ServerPokeStartMessage, type K as ShardTrafficEntry, type N as ShardTrafficResult, type T as StorageListPage, type V as StorageObject, type W as StreamHandle, type X as StreamIterable, type Y as SubscriptionCallback, type S as SubscriptionError, type b as SubscriptionErrorCallback, Z as SubscriptionRegistry, type _ as SubscriptionState, type $ as SyncWatermark, type a as Unsubscribe, type U as User, type a0 as WorkflowInstanceAction, type a1 as WorkflowInstanceDetail, type a2 as WorkflowInstancePage, type a3 as WorkflowInstanceStatus, type a4 as WorkflowInstanceSummary, type a5 as WorkflowStepDetail, a6 as createLocalStore, a7 as createStream } from "./packem_shared/lunora-client.d-CeUZ8lE1.mjs";
3
+ export { p as preloadQuery, a as preloadedQueryResult } from "./packem_shared/preload.d-DipqU0PI.mjs";
4
4
  export type { AuthCapabilities, AuthImpersonation, AuthPage, AuthSession, AuthUser, CronJobInfo, VectorIndexSummary, VectorQueryMatch } from '@lunora/runtime';
5
5
  /**
6
6
  * The slice of React Native's `AsyncStorage` (or any async key/value store —
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { c as PersistenceAdapter, B as BookmarkStorage, F as FunctionReference, A as ArgsOf, M as MutationCallOptions, R as ReturnOf, O as OfflineQueueOptions, Q as QueryCacheAdapter, d as ReconnectOptions } from "./packem_shared/lunora-client.d-DzEWBYeP.js";
2
- export { type C as CachedQuery, type e as ClientMessage, type f as ClientShapeSubscribeMessage, type g as ClientShapeUnsubscribeMessage, type h as ConnectionStatus, D as DEFAULT_MAX_BUFFER, type i as FunctionArgumentDescriptor, type j as FunctionDescriptor, type G as GlobalFacetResult, type k as GlobalFacetValue, type l as GlobalFilterClause, type m as GlobalTableInfo, type n as GlobalTablePage, L as LunoraClient, type o as LunoraClientOptions, type p as OptimisticLocalStore, type q as OptimisticUpdate, type r as OutboxMutation, type s as OutboxSink, type t as PersistedMutation, type P as Preloaded, type u as RowOp, type v as RpcEnvelope, type w as RpcResponseBody, type x as ScheduleRecord, type y as SchedulerPoolStatus, type z as SchedulerStatus, type E as ServerMessage, type H as ServerPokeEndMessage, type I as ServerPokePartMessage, type J as ServerPokeStartMessage, type K as ShardTrafficEntry, type N as ShardTrafficResult, type T as StorageListPage, type V as StorageObject, type W as StreamHandle, type X as StreamIterable, type Y as SubscriptionCallback, type S as SubscriptionError, type b as SubscriptionErrorCallback, Z as SubscriptionRegistry, type _ as SubscriptionState, type $ as SyncWatermark, type a as Unsubscribe, type U as User, type a0 as WorkflowInstanceAction, type a1 as WorkflowInstanceDetail, type a2 as WorkflowInstancePage, type a3 as WorkflowInstanceStatus, type a4 as WorkflowInstanceSummary, type a5 as WorkflowStepDetail, a6 as createLocalStore, a7 as createStream } from "./packem_shared/lunora-client.d-DzEWBYeP.js";
3
- export { p as preloadQuery, a as preloadedQueryResult } from "./packem_shared/preload.d-CmMMC-wm.js";
1
+ import { c as PersistenceAdapter, B as BookmarkStorage, F as FunctionReference, A as ArgsOf, M as MutationCallOptions, R as ReturnOf, O as OfflineQueueOptions, Q as QueryCacheAdapter, d as ReconnectOptions } from "./packem_shared/lunora-client.d-CeUZ8lE1.js";
2
+ export { type C as CachedQuery, type e as ClientMessage, type f as ClientShapeSubscribeMessage, type g as ClientShapeUnsubscribeMessage, type h as ConnectionStatus, D as DEFAULT_MAX_BUFFER, type i as FunctionArgumentDescriptor, type j as FunctionDescriptor, type G as GlobalFacetResult, type k as GlobalFacetValue, type l as GlobalFilterClause, type m as GlobalTableInfo, type n as GlobalTablePage, L as LunoraClient, type o as LunoraClientOptions, type p as OptimisticLocalStore, type q as OptimisticUpdate, type r as OutboxMutation, type s as OutboxSink, type t as PersistedMutation, type P as Preloaded, type u as RowOp, type v as RpcEnvelope, type w as RpcResponseBody, type x as ScheduleRecord, type y as SchedulerPoolStatus, type z as SchedulerStatus, type E as ServerMessage, type H as ServerPokeEndMessage, type I as ServerPokePartMessage, type J as ServerPokeStartMessage, type K as ShardTrafficEntry, type N as ShardTrafficResult, type T as StorageListPage, type V as StorageObject, type W as StreamHandle, type X as StreamIterable, type Y as SubscriptionCallback, type S as SubscriptionError, type b as SubscriptionErrorCallback, Z as SubscriptionRegistry, type _ as SubscriptionState, type $ as SyncWatermark, type a as Unsubscribe, type U as User, type a0 as WorkflowInstanceAction, type a1 as WorkflowInstanceDetail, type a2 as WorkflowInstancePage, type a3 as WorkflowInstanceStatus, type a4 as WorkflowInstanceSummary, type a5 as WorkflowStepDetail, a6 as createLocalStore, a7 as createStream } from "./packem_shared/lunora-client.d-CeUZ8lE1.js";
3
+ export { p as preloadQuery, a as preloadedQueryResult } from "./packem_shared/preload.d-CzHLgVfw.js";
4
4
  export type { AuthCapabilities, AuthImpersonation, AuthPage, AuthSession, AuthUser, CronJobInfo, VectorIndexSummary, VectorQueryMatch } from '@lunora/runtime';
5
5
  /**
6
6
  * The slice of React Native's `AsyncStorage` (or any async key/value store —
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ export { default as createInMemoryBookmarkStorage } from './packem_shared/create
3
3
  export { applyDelta, isMutationDelta } from './packem_shared/applyDelta-4jFGTPA3.mjs';
4
4
  export { CONFLICT_ERROR_CODE, isConflictError } from './packem_shared/CONFLICT_ERROR_CODE-aUdVbEDw.mjs';
5
5
  export { createLocalStore } from './packem_shared/createLocalStore-DSUfoLqY.mjs';
6
- export { LunoraClient } from './packem_shared/LunoraClient-DhwA_5Kj.mjs';
6
+ export { LunoraClient } from './packem_shared/LunoraClient-Bsv8immC.mjs';
7
7
  export { createMutationRunner } from './packem_shared/createMutationRunner-BqsavzvG.mjs';
8
8
  export { createMutatorRunner } from './packem_shared/createMutatorRunner-BETvCd0p.mjs';
9
9
  export { OfflineQueue } from './packem_shared/OfflineQueue-D-ASeqL7.mjs';
@@ -1341,6 +1341,7 @@ class LunoraClient {
1341
1341
  args: argsRecord,
1342
1342
  argsKey,
1343
1343
  callbacks: /* @__PURE__ */ new Set(),
1344
+ checkpointCallbacks: /* @__PURE__ */ new Set(),
1344
1345
  errorCallbacks: /* @__PURE__ */ new Set(),
1345
1346
  fn: function_,
1346
1347
  id,
@@ -1356,6 +1357,9 @@ class LunoraClient {
1356
1357
  if (errorCallback) {
1357
1358
  state.errorCallbacks.add(errorCallback);
1358
1359
  }
1360
+ if (options.onCheckpoint) {
1361
+ state.checkpointCallbacks.add(options.onCheckpoint);
1362
+ }
1359
1363
  if (state.lastValue !== void 0) {
1360
1364
  try {
1361
1365
  subscriptionCallback(state.lastValue);
@@ -1370,6 +1374,9 @@ class LunoraClient {
1370
1374
  if (errorCallback) {
1371
1375
  subscriptionState.errorCallbacks.delete(errorCallback);
1372
1376
  }
1377
+ if (options.onCheckpoint) {
1378
+ subscriptionState.checkpointCallbacks.delete(options.onCheckpoint);
1379
+ }
1373
1380
  if (subscriptionState.callbacks.size === 0) {
1374
1381
  const conn = this.getConnection(subscriptionState.shardKey);
1375
1382
  const ok = conn ? sendOn(conn, { id: subscriptionState.id, type: "unsubscribe" }) : false;
@@ -2197,6 +2204,10 @@ class LunoraClient {
2197
2204
  this.handleResumeMessage(message);
2198
2205
  break;
2199
2206
  }
2207
+ case "settled": {
2208
+ this.handleSettledMessage(message);
2209
+ break;
2210
+ }
2200
2211
  case "whisper": {
2201
2212
  this.dispatchWhisper(message, shardKey);
2202
2213
  break;
@@ -2330,13 +2341,46 @@ class LunoraClient {
2330
2341
  if (!state) {
2331
2342
  return;
2332
2343
  }
2344
+ this.ackAndAdvanceCursor(state, message.cursor, message.epoch);
2345
+ }
2346
+ /**
2347
+ * Handle a `settled` frame: a write touched one of this subscription's read
2348
+ * tables but produced a byte-identical result, so the server suppressed the
2349
+ * data frame. Like {@link handleResumeMessage} the value didn't change — we
2350
+ * advance the resume position and re-persist — but we ALSO surface the echoed
2351
+ * custom-mutator watermark via `onCheckpoint` so a `@lunora/db` list
2352
+ * collection drops the optimistic overlay for the confirmed write (otherwise
2353
+ * its checkpoint gate, fed only by data frames, would hang forever). Sent
2354
+ * only to custom-mutator clients; plain `useQuery` subscribers leave
2355
+ * `onCheckpoint` unset and this is a near no-op.
2356
+ */
2357
+ handleSettledMessage(message) {
2358
+ const state = this.subscriptions.getById(message.id);
2359
+ if (!state) {
2360
+ return;
2361
+ }
2362
+ this.ackAndAdvanceCursor(state, message.cursor, message.epoch);
2363
+ if (message.lastMutationId !== void 0) {
2364
+ state.lastMutationId = message.lastMutationId;
2365
+ }
2366
+ for (const onCheckpoint of state.checkpointCallbacks) {
2367
+ onCheckpoint({ checkpoint: state.serverCursor, mutationId: state.lastMutationId });
2368
+ }
2369
+ }
2370
+ /**
2371
+ * Mark `state` acked and, when the frame carries a newer cursor/epoch than
2372
+ * the cached position, advance the resume watermark and re-persist. Shared by
2373
+ * the `resume` and `settled` frame handlers — both acknowledge "nothing the
2374
+ * client must re-render changed, but the resume position may have moved".
2375
+ */
2376
+ ackAndAdvanceCursor(state, cursor, epoch) {
2333
2377
  state.acked = true;
2334
- if (message.cursor !== void 0 && message.cursor !== state.serverCursor || message.epoch !== void 0 && message.epoch !== state.serverEpoch) {
2335
- if (message.cursor !== void 0) {
2336
- state.serverCursor = message.cursor;
2378
+ if (cursor !== void 0 && cursor !== state.serverCursor || epoch !== void 0 && epoch !== state.serverEpoch) {
2379
+ if (cursor !== void 0) {
2380
+ state.serverCursor = cursor;
2337
2381
  }
2338
- if (message.epoch !== void 0) {
2339
- state.serverEpoch = message.epoch;
2382
+ if (epoch !== void 0) {
2383
+ state.serverEpoch = epoch;
2340
2384
  }
2341
2385
  this.persistQueryValue(state);
2342
2386
  }
@@ -1,4 +1,4 @@
1
- import { LunoraClient } from './LunoraClient-DhwA_5Kj.mjs';
1
+ import { LunoraClient } from './LunoraClient-Bsv8immC.mjs';
2
2
 
3
3
  const createServerClient = (options) => {
4
4
  const client = new LunoraClient({ fetch: options.fetch, url: options.url });
@@ -476,6 +476,29 @@ interface ServerResumeMessage {
476
476
  lastMutationId?: number;
477
477
  type: "resume";
478
478
  }
479
+ /**
480
+ * Settled acknowledgement for a **list** subscription: a write touched one of
481
+ * the subscription's read tables but produced a byte-identical result, so the
482
+ * server suppressed the data frame. Sent ONLY to a `@lunora/db` custom-mutator
483
+ * client (one that announced a `clientId`, hence has a server-side
484
+ * `__client_watermark`) so its optimistic list overlay drops even when no data
485
+ * frame arrives. Plain `useQuery` subscribers never receive it, and an older
486
+ * client safely ignores the unknown frame.
487
+ */
488
+ interface ServerSettledMessage {
489
+ cursor?: number;
490
+ /** The CDC epoch this settled frame's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
491
+ epoch?: string;
492
+ id: string;
493
+ /**
494
+ * The highest custom-mutator `mutationId` from this client the server has
495
+ * now applied (the per-client `__client_watermark`). Forwarded to a
496
+ * collection's `onCheckpoint` so it can drop the overlay for the confirmed
497
+ * write whose result didn't change this list.
498
+ */
499
+ lastMutationId?: number;
500
+ type: "settled";
501
+ }
479
502
  interface ServerErrorMessage {
480
503
  error?: unknown;
481
504
  id?: string;
@@ -543,7 +566,7 @@ interface ServerPokeStartMessage {
543
566
  }
544
567
  /** One shape's slice of an in-flight poke: the row-ops to apply for `shapeId`. */
545
568
  interface ServerPokePartMessage {
546
- /** Per-client custom-mutator watermark carried with this slice (see {@link ServerDataMessage.lastMutationId}). */
569
+ /** Per-client custom-mutator watermark carried with this slice (see {@link ServerSettledMessage.lastMutationId}). */
547
570
  lastMutationId?: number;
548
571
  pokeId: string;
549
572
  /** Ordered row-level changes for this shape, applied in sequence at `pokeEnd`. */
@@ -566,7 +589,7 @@ interface ServerPokeEndMessage {
566
589
  pokeId: string;
567
590
  type: "pokeEnd";
568
591
  }
569
- type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerPokeEndMessage | ServerPokePartMessage | ServerPokeStartMessage | ServerResumeMessage | ServerWhisperMessage;
592
+ type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerPokeEndMessage | ServerPokePartMessage | ServerPokeStartMessage | ServerResumeMessage | ServerSettledMessage | ServerWhisperMessage;
570
593
  /**
571
594
  * The authenticated user as exposed client-side, mirroring better-auth's
572
595
  * `user` row (the `user` field of the `get-session` response). Kept minimal
@@ -808,10 +831,34 @@ interface SubscriptionState {
808
831
  */
809
832
  readonly argsKey: string;
810
833
  readonly callbacks: Set<SubscriptionCallback>;
834
+ /**
835
+ * Notified when a `settled` frame advances this subscription's watermark — a
836
+ * write touched the subscription's tables but the result was byte-identical,
837
+ * so the server suppressed the data frame. A `@lunora/db` list collection
838
+ * uses this to drop the optimistic overlay for the confirmed write.
839
+ *
840
+ * A SET (not a single slot) because `SubscriptionState` is SHARED across
841
+ * every subscriber to the same `(fn, args, shardKey)`: a `@lunora/db`
842
+ * collection may subscribe to a query a plain `useQuery` already opened, so
843
+ * each subscriber registers its own callback (mirroring `callbacks` /
844
+ * `errorCallbacks`) and a `settled` frame fans out to all of them. Plain
845
+ * `useQuery` consumers register nothing, leaving the set empty.
846
+ */
847
+ readonly checkpointCallbacks: Set<(watermark: {
848
+ checkpoint?: number;
849
+ mutationId?: number;
850
+ }) => void>;
811
851
  /** Notified when the server rejects this subscription (e.g. admin auth). */
812
852
  readonly errorCallbacks: Set<SubscriptionErrorCallback>;
813
853
  readonly fn: FunctionReference;
814
854
  readonly id: string;
855
+ /**
856
+ * The highest custom-mutator `mutationId` from this client the server has
857
+ * applied, captured from the last `settled` frame (the suppressed-list-frame
858
+ * watermark). Forwarded to {@link SubscriptionState.checkpointCallbacks}.
859
+ * Absent until a `settled` frame arrives.
860
+ */
861
+ lastMutationId?: number;
815
862
  /** Last known value, used to short-circuit `useQuery`-style consumers. */
816
863
  lastValue: unknown;
817
864
  /**
@@ -1699,6 +1746,7 @@ declare class LunoraClient {
1699
1746
  userId?: string;
1700
1747
  }): Promise<AuthPage<AuthSession>>;
1701
1748
  subscribe<F extends FunctionReference>(function_: F, args: ArgsOf<F>, callback: (data: ReturnOf<F>) => void, options?: {
1749
+ onCheckpoint?: (watermark: SyncWatermark) => void;
1702
1750
  onError?: SubscriptionErrorCallback;
1703
1751
  shardKey?: string;
1704
1752
  }): Unsubscribe;
@@ -1902,6 +1950,25 @@ declare class LunoraClient {
1902
1950
  */
1903
1951
  private handleResumeMessage;
1904
1952
  /**
1953
+ * Handle a `settled` frame: a write touched one of this subscription's read
1954
+ * tables but produced a byte-identical result, so the server suppressed the
1955
+ * data frame. Like {@link handleResumeMessage} the value didn't change — we
1956
+ * advance the resume position and re-persist — but we ALSO surface the echoed
1957
+ * custom-mutator watermark via `onCheckpoint` so a `@lunora/db` list
1958
+ * collection drops the optimistic overlay for the confirmed write (otherwise
1959
+ * its checkpoint gate, fed only by data frames, would hang forever). Sent
1960
+ * only to custom-mutator clients; plain `useQuery` subscribers leave
1961
+ * `onCheckpoint` unset and this is a near no-op.
1962
+ */
1963
+ private handleSettledMessage;
1964
+ /**
1965
+ * Mark `state` acked and, when the frame carries a newer cursor/epoch than
1966
+ * the cached position, advance the resume watermark and re-persist. Shared by
1967
+ * the `resume` and `settled` frame handlers — both acknowledge "nothing the
1968
+ * client must re-render changed, but the resume position may have moved".
1969
+ */
1970
+ private ackAndAdvanceCursor;
1971
+ /**
1905
1972
  * Resolve the value to publish for a `data`/`delta` frame.
1906
1973
  *
1907
1974
  * A `data` frame is an authoritative snapshot (the server re-execution path)
@@ -476,6 +476,29 @@ interface ServerResumeMessage {
476
476
  lastMutationId?: number;
477
477
  type: "resume";
478
478
  }
479
+ /**
480
+ * Settled acknowledgement for a **list** subscription: a write touched one of
481
+ * the subscription's read tables but produced a byte-identical result, so the
482
+ * server suppressed the data frame. Sent ONLY to a `@lunora/db` custom-mutator
483
+ * client (one that announced a `clientId`, hence has a server-side
484
+ * `__client_watermark`) so its optimistic list overlay drops even when no data
485
+ * frame arrives. Plain `useQuery` subscribers never receive it, and an older
486
+ * client safely ignores the unknown frame.
487
+ */
488
+ interface ServerSettledMessage {
489
+ cursor?: number;
490
+ /** The CDC epoch this settled frame's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
491
+ epoch?: string;
492
+ id: string;
493
+ /**
494
+ * The highest custom-mutator `mutationId` from this client the server has
495
+ * now applied (the per-client `__client_watermark`). Forwarded to a
496
+ * collection's `onCheckpoint` so it can drop the overlay for the confirmed
497
+ * write whose result didn't change this list.
498
+ */
499
+ lastMutationId?: number;
500
+ type: "settled";
501
+ }
479
502
  interface ServerErrorMessage {
480
503
  error?: unknown;
481
504
  id?: string;
@@ -543,7 +566,7 @@ interface ServerPokeStartMessage {
543
566
  }
544
567
  /** One shape's slice of an in-flight poke: the row-ops to apply for `shapeId`. */
545
568
  interface ServerPokePartMessage {
546
- /** Per-client custom-mutator watermark carried with this slice (see {@link ServerDataMessage.lastMutationId}). */
569
+ /** Per-client custom-mutator watermark carried with this slice (see {@link ServerSettledMessage.lastMutationId}). */
547
570
  lastMutationId?: number;
548
571
  pokeId: string;
549
572
  /** Ordered row-level changes for this shape, applied in sequence at `pokeEnd`. */
@@ -566,7 +589,7 @@ interface ServerPokeEndMessage {
566
589
  pokeId: string;
567
590
  type: "pokeEnd";
568
591
  }
569
- type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerPokeEndMessage | ServerPokePartMessage | ServerPokeStartMessage | ServerResumeMessage | ServerWhisperMessage;
592
+ type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerPokeEndMessage | ServerPokePartMessage | ServerPokeStartMessage | ServerResumeMessage | ServerSettledMessage | ServerWhisperMessage;
570
593
  /**
571
594
  * The authenticated user as exposed client-side, mirroring better-auth's
572
595
  * `user` row (the `user` field of the `get-session` response). Kept minimal
@@ -808,10 +831,34 @@ interface SubscriptionState {
808
831
  */
809
832
  readonly argsKey: string;
810
833
  readonly callbacks: Set<SubscriptionCallback>;
834
+ /**
835
+ * Notified when a `settled` frame advances this subscription's watermark — a
836
+ * write touched the subscription's tables but the result was byte-identical,
837
+ * so the server suppressed the data frame. A `@lunora/db` list collection
838
+ * uses this to drop the optimistic overlay for the confirmed write.
839
+ *
840
+ * A SET (not a single slot) because `SubscriptionState` is SHARED across
841
+ * every subscriber to the same `(fn, args, shardKey)`: a `@lunora/db`
842
+ * collection may subscribe to a query a plain `useQuery` already opened, so
843
+ * each subscriber registers its own callback (mirroring `callbacks` /
844
+ * `errorCallbacks`) and a `settled` frame fans out to all of them. Plain
845
+ * `useQuery` consumers register nothing, leaving the set empty.
846
+ */
847
+ readonly checkpointCallbacks: Set<(watermark: {
848
+ checkpoint?: number;
849
+ mutationId?: number;
850
+ }) => void>;
811
851
  /** Notified when the server rejects this subscription (e.g. admin auth). */
812
852
  readonly errorCallbacks: Set<SubscriptionErrorCallback>;
813
853
  readonly fn: FunctionReference;
814
854
  readonly id: string;
855
+ /**
856
+ * The highest custom-mutator `mutationId` from this client the server has
857
+ * applied, captured from the last `settled` frame (the suppressed-list-frame
858
+ * watermark). Forwarded to {@link SubscriptionState.checkpointCallbacks}.
859
+ * Absent until a `settled` frame arrives.
860
+ */
861
+ lastMutationId?: number;
815
862
  /** Last known value, used to short-circuit `useQuery`-style consumers. */
816
863
  lastValue: unknown;
817
864
  /**
@@ -1699,6 +1746,7 @@ declare class LunoraClient {
1699
1746
  userId?: string;
1700
1747
  }): Promise<AuthPage<AuthSession>>;
1701
1748
  subscribe<F extends FunctionReference>(function_: F, args: ArgsOf<F>, callback: (data: ReturnOf<F>) => void, options?: {
1749
+ onCheckpoint?: (watermark: SyncWatermark) => void;
1702
1750
  onError?: SubscriptionErrorCallback;
1703
1751
  shardKey?: string;
1704
1752
  }): Unsubscribe;
@@ -1902,6 +1950,25 @@ declare class LunoraClient {
1902
1950
  */
1903
1951
  private handleResumeMessage;
1904
1952
  /**
1953
+ * Handle a `settled` frame: a write touched one of this subscription's read
1954
+ * tables but produced a byte-identical result, so the server suppressed the
1955
+ * data frame. Like {@link handleResumeMessage} the value didn't change — we
1956
+ * advance the resume position and re-persist — but we ALSO surface the echoed
1957
+ * custom-mutator watermark via `onCheckpoint` so a `@lunora/db` list
1958
+ * collection drops the optimistic overlay for the confirmed write (otherwise
1959
+ * its checkpoint gate, fed only by data frames, would hang forever). Sent
1960
+ * only to custom-mutator clients; plain `useQuery` subscribers leave
1961
+ * `onCheckpoint` unset and this is a near no-op.
1962
+ */
1963
+ private handleSettledMessage;
1964
+ /**
1965
+ * Mark `state` acked and, when the frame carries a newer cursor/epoch than
1966
+ * the cached position, advance the resume watermark and re-persist. Shared by
1967
+ * the `resume` and `settled` frame handlers — both acknowledge "nothing the
1968
+ * client must re-render changed, but the resume position may have moved".
1969
+ */
1970
+ private ackAndAdvanceCursor;
1971
+ /**
1905
1972
  * Resolve the value to publish for a `data`/`delta` frame.
1906
1973
  *
1907
1974
  * A `data` frame is an authoritative snapshot (the server re-execution path)
@@ -1,4 +1,4 @@
1
- import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-DzEWBYeP.js";
1
+ import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-CeUZ8lE1.js";
2
2
  /**
3
3
  * Run a query once on the server (during SSR) and capture its result in a
4
4
  * serializable {@link Preloaded} token. Embed the token in the rendered HTML and
@@ -1,4 +1,4 @@
1
- import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-DzEWBYeP.mjs";
1
+ import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-CeUZ8lE1.mjs";
2
2
  /**
3
3
  * Run a query once on the server (during SSR) and capture its result in a
4
4
  * serializable {@link Preloaded} token. Embed the token in the rendered HTML and
@@ -1,5 +1,5 @@
1
- import { S as SubscriptionError, F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-DzEWBYeP.mjs";
2
- export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-DzEWBYeP.mjs";
1
+ import { S as SubscriptionError, F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-CeUZ8lE1.mjs";
2
+ export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-CeUZ8lE1.mjs";
3
3
  import '@lunora/runtime';
4
4
  /**
5
5
  * The sentinel a framework adapter resolves its reactive args to when it wants
@@ -1,5 +1,5 @@
1
- import { S as SubscriptionError, F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-DzEWBYeP.js";
2
- export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-DzEWBYeP.js";
1
+ import { S as SubscriptionError, F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-CeUZ8lE1.js";
2
+ export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-CeUZ8lE1.js";
3
3
  import '@lunora/runtime';
4
4
  /**
5
5
  * The sentinel a framework adapter resolves its reactive args to when it wants
@@ -1,6 +1,6 @@
1
- import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-DzEWBYeP.mjs";
2
- export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-DzEWBYeP.mjs";
3
- export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-DuYqEQ5J.mjs";
1
+ import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-CeUZ8lE1.mjs";
2
+ export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-CeUZ8lE1.mjs";
3
+ export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-DipqU0PI.mjs";
4
4
  import '@lunora/runtime';
5
5
  /**
6
6
  * Structural shape of a better-auth `getSession` call's resolved value.
@@ -1,6 +1,6 @@
1
- import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-DzEWBYeP.js";
2
- export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-DzEWBYeP.js";
3
- export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-CmMMC-wm.js";
1
+ import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-CeUZ8lE1.js";
2
+ export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-CeUZ8lE1.js";
3
+ export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-CzHLgVfw.js";
4
4
  import '@lunora/runtime';
5
5
  /**
6
6
  * Structural shape of a better-auth `getSession` call's resolved value.
@@ -1,4 +1,4 @@
1
1
  export { getServerSession } from '../packem_shared/getServerSession-8jXewqxd.mjs';
2
2
  export { deserializePreloaded, serializePreloaded } from '../packem_shared/deserializePreloaded-C0eJTY_W.mjs';
3
- export { createServerClient } from '../packem_shared/createServerClient-XahK8bPv.mjs';
3
+ export { createServerClient } from '../packem_shared/createServerClient-DucOyOz3.mjs';
4
4
  export { preloadQuery, preloadedQueryResult } from '../packem_shared/preloadQuery-lobFkD2Z.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/client",
3
- "version": "1.0.0-alpha.6",
3
+ "version": "1.0.0-alpha.8",
4
4
  "description": "Lunora browser SDK: WebSocket transport, optimistic updates, and an offline mutation queue",
5
5
  "keywords": [
6
6
  "cloudflare",