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

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-DLbxPGH9.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-DLbxPGH9.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-DLbxPGH9.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-DLbxPGH9.mjs";
3
+ export { p as preloadQuery, a as preloadedQueryResult } from "./packem_shared/preload.d-BAT6GeSD.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-DLbxPGH9.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-DLbxPGH9.js";
3
+ export { p as preloadQuery, a as preloadedQueryResult } from "./packem_shared/preload.d-CSkjbfEq.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 });
@@ -452,13 +452,6 @@ interface ServerDataMessage {
452
452
  /** The CDC epoch this frame's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
453
453
  epoch?: string;
454
454
  id: string;
455
- /**
456
- * The highest custom-mutator `mutationId` from this client the server has
457
- * now applied (the per-client `__client_watermark`). Echoed so the client's
458
- * outbox can drop confirmed pending mutations and let TanStack DB collapse
459
- * the matching optimistic overlay. Absent on shards without custom mutators.
460
- */
461
- lastMutationId?: number;
462
455
  type: "data" | "delta";
463
456
  }
464
457
  /**
@@ -472,10 +465,31 @@ interface ServerResumeMessage {
472
465
  /** The CDC epoch this resume's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
473
466
  epoch?: string;
474
467
  id: string;
475
- /** Per-client custom-mutator watermark (see {@link ServerDataMessage.lastMutationId}). */
476
- lastMutationId?: number;
477
468
  type: "resume";
478
469
  }
470
+ /**
471
+ * Settled acknowledgement for a **list** subscription: a write touched one of
472
+ * the subscription's read tables but produced a byte-identical result, so the
473
+ * server suppressed the data frame. Sent ONLY to a `@lunora/db` custom-mutator
474
+ * client (one that announced a `clientId`, hence has a server-side
475
+ * `__client_watermark`) so its optimistic list overlay drops even when no data
476
+ * frame arrives. Plain `useQuery` subscribers never receive it, and an older
477
+ * client safely ignores the unknown frame.
478
+ */
479
+ interface ServerSettledMessage {
480
+ cursor?: number;
481
+ /** The CDC epoch this settled frame's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
482
+ epoch?: string;
483
+ id: string;
484
+ /**
485
+ * The highest custom-mutator `mutationId` from this client the server has
486
+ * now applied (the per-client `__client_watermark`). Forwarded to a
487
+ * collection's `onCheckpoint` so it can drop the overlay for the confirmed
488
+ * write whose result didn't change this list.
489
+ */
490
+ lastMutationId?: number;
491
+ type: "settled";
492
+ }
479
493
  interface ServerErrorMessage {
480
494
  error?: unknown;
481
495
  id?: string;
@@ -543,7 +557,7 @@ interface ServerPokeStartMessage {
543
557
  }
544
558
  /** One shape's slice of an in-flight poke: the row-ops to apply for `shapeId`. */
545
559
  interface ServerPokePartMessage {
546
- /** Per-client custom-mutator watermark carried with this slice (see {@link ServerDataMessage.lastMutationId}). */
560
+ /** Per-client custom-mutator watermark carried with this slice (see {@link ServerSettledMessage.lastMutationId}). */
547
561
  lastMutationId?: number;
548
562
  pokeId: string;
549
563
  /** Ordered row-level changes for this shape, applied in sequence at `pokeEnd`. */
@@ -566,7 +580,7 @@ interface ServerPokeEndMessage {
566
580
  pokeId: string;
567
581
  type: "pokeEnd";
568
582
  }
569
- type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerPokeEndMessage | ServerPokePartMessage | ServerPokeStartMessage | ServerResumeMessage | ServerWhisperMessage;
583
+ type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerPokeEndMessage | ServerPokePartMessage | ServerPokeStartMessage | ServerResumeMessage | ServerSettledMessage | ServerWhisperMessage;
570
584
  /**
571
585
  * The authenticated user as exposed client-side, mirroring better-auth's
572
586
  * `user` row (the `user` field of the `get-session` response). Kept minimal
@@ -808,10 +822,34 @@ interface SubscriptionState {
808
822
  */
809
823
  readonly argsKey: string;
810
824
  readonly callbacks: Set<SubscriptionCallback>;
825
+ /**
826
+ * Notified when a `settled` frame advances this subscription's watermark — a
827
+ * write touched the subscription's tables but the result was byte-identical,
828
+ * so the server suppressed the data frame. A `@lunora/db` list collection
829
+ * uses this to drop the optimistic overlay for the confirmed write.
830
+ *
831
+ * A SET (not a single slot) because `SubscriptionState` is SHARED across
832
+ * every subscriber to the same `(fn, args, shardKey)`: a `@lunora/db`
833
+ * collection may subscribe to a query a plain `useQuery` already opened, so
834
+ * each subscriber registers its own callback (mirroring `callbacks` /
835
+ * `errorCallbacks`) and a `settled` frame fans out to all of them. Plain
836
+ * `useQuery` consumers register nothing, leaving the set empty.
837
+ */
838
+ readonly checkpointCallbacks: Set<(watermark: {
839
+ checkpoint?: number;
840
+ mutationId?: number;
841
+ }) => void>;
811
842
  /** Notified when the server rejects this subscription (e.g. admin auth). */
812
843
  readonly errorCallbacks: Set<SubscriptionErrorCallback>;
813
844
  readonly fn: FunctionReference;
814
845
  readonly id: string;
846
+ /**
847
+ * The highest custom-mutator `mutationId` from this client the server has
848
+ * applied, captured from the last `settled` frame (the suppressed-list-frame
849
+ * watermark). Forwarded to {@link SubscriptionState.checkpointCallbacks}.
850
+ * Absent until a `settled` frame arrives.
851
+ */
852
+ lastMutationId?: number;
815
853
  /** Last known value, used to short-circuit `useQuery`-style consumers. */
816
854
  lastValue: unknown;
817
855
  /**
@@ -1699,6 +1737,7 @@ declare class LunoraClient {
1699
1737
  userId?: string;
1700
1738
  }): Promise<AuthPage<AuthSession>>;
1701
1739
  subscribe<F extends FunctionReference>(function_: F, args: ArgsOf<F>, callback: (data: ReturnOf<F>) => void, options?: {
1740
+ onCheckpoint?: (watermark: SyncWatermark) => void;
1702
1741
  onError?: SubscriptionErrorCallback;
1703
1742
  shardKey?: string;
1704
1743
  }): Unsubscribe;
@@ -1902,6 +1941,25 @@ declare class LunoraClient {
1902
1941
  */
1903
1942
  private handleResumeMessage;
1904
1943
  /**
1944
+ * Handle a `settled` frame: a write touched one of this subscription's read
1945
+ * tables but produced a byte-identical result, so the server suppressed the
1946
+ * data frame. Like {@link handleResumeMessage} the value didn't change — we
1947
+ * advance the resume position and re-persist — but we ALSO surface the echoed
1948
+ * custom-mutator watermark via `onCheckpoint` so a `@lunora/db` list
1949
+ * collection drops the optimistic overlay for the confirmed write (otherwise
1950
+ * its checkpoint gate, fed only by data frames, would hang forever). Sent
1951
+ * only to custom-mutator clients; plain `useQuery` subscribers leave
1952
+ * `onCheckpoint` unset and this is a near no-op.
1953
+ */
1954
+ private handleSettledMessage;
1955
+ /**
1956
+ * Mark `state` acked and, when the frame carries a newer cursor/epoch than
1957
+ * the cached position, advance the resume watermark and re-persist. Shared by
1958
+ * the `resume` and `settled` frame handlers — both acknowledge "nothing the
1959
+ * client must re-render changed, but the resume position may have moved".
1960
+ */
1961
+ private ackAndAdvanceCursor;
1962
+ /**
1905
1963
  * Resolve the value to publish for a `data`/`delta` frame.
1906
1964
  *
1907
1965
  * A `data` frame is an authoritative snapshot (the server re-execution path)
@@ -452,13 +452,6 @@ interface ServerDataMessage {
452
452
  /** The CDC epoch this frame's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
453
453
  epoch?: string;
454
454
  id: string;
455
- /**
456
- * The highest custom-mutator `mutationId` from this client the server has
457
- * now applied (the per-client `__client_watermark`). Echoed so the client's
458
- * outbox can drop confirmed pending mutations and let TanStack DB collapse
459
- * the matching optimistic overlay. Absent on shards without custom mutators.
460
- */
461
- lastMutationId?: number;
462
455
  type: "data" | "delta";
463
456
  }
464
457
  /**
@@ -472,10 +465,31 @@ interface ServerResumeMessage {
472
465
  /** The CDC epoch this resume's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
473
466
  epoch?: string;
474
467
  id: string;
475
- /** Per-client custom-mutator watermark (see {@link ServerDataMessage.lastMutationId}). */
476
- lastMutationId?: number;
477
468
  type: "resume";
478
469
  }
470
+ /**
471
+ * Settled acknowledgement for a **list** subscription: a write touched one of
472
+ * the subscription's read tables but produced a byte-identical result, so the
473
+ * server suppressed the data frame. Sent ONLY to a `@lunora/db` custom-mutator
474
+ * client (one that announced a `clientId`, hence has a server-side
475
+ * `__client_watermark`) so its optimistic list overlay drops even when no data
476
+ * frame arrives. Plain `useQuery` subscribers never receive it, and an older
477
+ * client safely ignores the unknown frame.
478
+ */
479
+ interface ServerSettledMessage {
480
+ cursor?: number;
481
+ /** The CDC epoch this settled frame's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
482
+ epoch?: string;
483
+ id: string;
484
+ /**
485
+ * The highest custom-mutator `mutationId` from this client the server has
486
+ * now applied (the per-client `__client_watermark`). Forwarded to a
487
+ * collection's `onCheckpoint` so it can drop the overlay for the confirmed
488
+ * write whose result didn't change this list.
489
+ */
490
+ lastMutationId?: number;
491
+ type: "settled";
492
+ }
479
493
  interface ServerErrorMessage {
480
494
  error?: unknown;
481
495
  id?: string;
@@ -543,7 +557,7 @@ interface ServerPokeStartMessage {
543
557
  }
544
558
  /** One shape's slice of an in-flight poke: the row-ops to apply for `shapeId`. */
545
559
  interface ServerPokePartMessage {
546
- /** Per-client custom-mutator watermark carried with this slice (see {@link ServerDataMessage.lastMutationId}). */
560
+ /** Per-client custom-mutator watermark carried with this slice (see {@link ServerSettledMessage.lastMutationId}). */
547
561
  lastMutationId?: number;
548
562
  pokeId: string;
549
563
  /** Ordered row-level changes for this shape, applied in sequence at `pokeEnd`. */
@@ -566,7 +580,7 @@ interface ServerPokeEndMessage {
566
580
  pokeId: string;
567
581
  type: "pokeEnd";
568
582
  }
569
- type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerPokeEndMessage | ServerPokePartMessage | ServerPokeStartMessage | ServerResumeMessage | ServerWhisperMessage;
583
+ type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerPokeEndMessage | ServerPokePartMessage | ServerPokeStartMessage | ServerResumeMessage | ServerSettledMessage | ServerWhisperMessage;
570
584
  /**
571
585
  * The authenticated user as exposed client-side, mirroring better-auth's
572
586
  * `user` row (the `user` field of the `get-session` response). Kept minimal
@@ -808,10 +822,34 @@ interface SubscriptionState {
808
822
  */
809
823
  readonly argsKey: string;
810
824
  readonly callbacks: Set<SubscriptionCallback>;
825
+ /**
826
+ * Notified when a `settled` frame advances this subscription's watermark — a
827
+ * write touched the subscription's tables but the result was byte-identical,
828
+ * so the server suppressed the data frame. A `@lunora/db` list collection
829
+ * uses this to drop the optimistic overlay for the confirmed write.
830
+ *
831
+ * A SET (not a single slot) because `SubscriptionState` is SHARED across
832
+ * every subscriber to the same `(fn, args, shardKey)`: a `@lunora/db`
833
+ * collection may subscribe to a query a plain `useQuery` already opened, so
834
+ * each subscriber registers its own callback (mirroring `callbacks` /
835
+ * `errorCallbacks`) and a `settled` frame fans out to all of them. Plain
836
+ * `useQuery` consumers register nothing, leaving the set empty.
837
+ */
838
+ readonly checkpointCallbacks: Set<(watermark: {
839
+ checkpoint?: number;
840
+ mutationId?: number;
841
+ }) => void>;
811
842
  /** Notified when the server rejects this subscription (e.g. admin auth). */
812
843
  readonly errorCallbacks: Set<SubscriptionErrorCallback>;
813
844
  readonly fn: FunctionReference;
814
845
  readonly id: string;
846
+ /**
847
+ * The highest custom-mutator `mutationId` from this client the server has
848
+ * applied, captured from the last `settled` frame (the suppressed-list-frame
849
+ * watermark). Forwarded to {@link SubscriptionState.checkpointCallbacks}.
850
+ * Absent until a `settled` frame arrives.
851
+ */
852
+ lastMutationId?: number;
815
853
  /** Last known value, used to short-circuit `useQuery`-style consumers. */
816
854
  lastValue: unknown;
817
855
  /**
@@ -1699,6 +1737,7 @@ declare class LunoraClient {
1699
1737
  userId?: string;
1700
1738
  }): Promise<AuthPage<AuthSession>>;
1701
1739
  subscribe<F extends FunctionReference>(function_: F, args: ArgsOf<F>, callback: (data: ReturnOf<F>) => void, options?: {
1740
+ onCheckpoint?: (watermark: SyncWatermark) => void;
1702
1741
  onError?: SubscriptionErrorCallback;
1703
1742
  shardKey?: string;
1704
1743
  }): Unsubscribe;
@@ -1902,6 +1941,25 @@ declare class LunoraClient {
1902
1941
  */
1903
1942
  private handleResumeMessage;
1904
1943
  /**
1944
+ * Handle a `settled` frame: a write touched one of this subscription's read
1945
+ * tables but produced a byte-identical result, so the server suppressed the
1946
+ * data frame. Like {@link handleResumeMessage} the value didn't change — we
1947
+ * advance the resume position and re-persist — but we ALSO surface the echoed
1948
+ * custom-mutator watermark via `onCheckpoint` so a `@lunora/db` list
1949
+ * collection drops the optimistic overlay for the confirmed write (otherwise
1950
+ * its checkpoint gate, fed only by data frames, would hang forever). Sent
1951
+ * only to custom-mutator clients; plain `useQuery` subscribers leave
1952
+ * `onCheckpoint` unset and this is a near no-op.
1953
+ */
1954
+ private handleSettledMessage;
1955
+ /**
1956
+ * Mark `state` acked and, when the frame carries a newer cursor/epoch than
1957
+ * the cached position, advance the resume watermark and re-persist. Shared by
1958
+ * the `resume` and `settled` frame handlers — both acknowledge "nothing the
1959
+ * client must re-render changed, but the resume position may have moved".
1960
+ */
1961
+ private ackAndAdvanceCursor;
1962
+ /**
1905
1963
  * Resolve the value to publish for a `data`/`delta` frame.
1906
1964
  *
1907
1965
  * 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.mjs";
1
+ import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-DLbxPGH9.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,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-DLbxPGH9.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,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-DLbxPGH9.mjs";
2
+ export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-DLbxPGH9.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-DLbxPGH9.js";
2
+ export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-DLbxPGH9.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-DLbxPGH9.mjs";
2
+ export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-DLbxPGH9.mjs";
3
+ export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-BAT6GeSD.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-DLbxPGH9.js";
2
+ export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-DLbxPGH9.js";
3
+ export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-CSkjbfEq.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.7",
4
4
  "description": "Lunora browser SDK: WebSocket transport, optimistic updates, and an offline mutation queue",
5
5
  "keywords": [
6
6
  "cloudflare",