@lunora/client 1.0.0-alpha.4 → 1.0.0-alpha.5

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.
@@ -120,6 +120,40 @@ interface PersistenceAdapter {
120
120
  remove: (id: string) => Promise<void>;
121
121
  }
122
122
  /**
123
+ * One write handed to an {@link OutboxSink}. Mirrors {@link PersistedMutation}
124
+ * plus the custom-mutator identity (`clientId`/`mutationId`/`idempotencyKey`)
125
+ * the durable outbox needs to dedupe and watermark replays.
126
+ */
127
+ interface OutboxMutation {
128
+ args: Record<string, unknown>;
129
+ /** Stable per-client id; pairs with {@link OutboxMutation.mutationId} as `idempotencyKey`. */
130
+ clientId: string;
131
+ functionPath: string;
132
+ /** `${clientId}:${mutationId}` — sent as `x-lunora-mutation-id` so a replay is server-idempotent. */
133
+ idempotencyKey: string;
134
+ /** Issuing identity fingerprint (`null` = signed out); drives the sink's identity guard. */
135
+ identity: string | null;
136
+ /** Monotonic per-client mutation id, backing the server `__client_watermark`. */
137
+ mutationId: number;
138
+ shardKey?: string;
139
+ }
140
+ /**
141
+ * Pluggable durable outbox seam. When set on {@link LunoraClientOptions.outbox},
142
+ * the client delegates offline write durability + at-least-once replay to this
143
+ * sink instead of its built-in {@link PersistenceAdapter}-backed `OfflineQueue`.
144
+ * `@lunora/db` supplies the blessed implementation (`createExecutorOutboxSink`,
145
+ * backed by the TanStack `OfflineExecutor`); the interface itself is
146
+ * dependency-free so `@lunora/client` stays TanStack-free.
147
+ */
148
+ interface OutboxSink {
149
+ /**
150
+ * Persist and schedule a write for replay. Rejects with an
151
+ * `OFFLINE_QUEUE_OVERFLOW`-coded error when the sink's cap is exceeded, so
152
+ * the caller can surface back-pressure to the issuing mutation.
153
+ */
154
+ enqueue: (mutation: OutboxMutation) => Promise<void>;
155
+ }
156
+ /**
123
157
  * One persisted query result in the durable read cache (Pillar 2). Keyed in the
124
158
  * store by `shardKey + functionPath + argsKey`; the record carries everything
125
159
  * needed to render offline on reload and to resume the live subscription.
@@ -177,6 +211,16 @@ interface LunoraClientOptions {
177
211
  authBasePath?: string;
178
212
  bookmarkStorage?: BookmarkStorage;
179
213
  /**
214
+ * Stable per-client id backing the custom-mutator watermark. Sent on the
215
+ * `connect` envelope (so the server can scope this client's
216
+ * `__client_watermark`) and stamped onto every {@link OutboxMutation} the
217
+ * {@link LunoraClientOptions.outbox} sink persists, where it pairs with the
218
+ * monotonic mutation id to form the idempotency key. The `@lunora/db` path
219
+ * persists a stable id alongside the outbox and passes it here; omit for the
220
+ * standalone client, which generates an ephemeral per-session id.
221
+ */
222
+ clientId?: string;
223
+ /**
180
224
  * Default app context sent in the `connect` envelope right after each socket
181
225
  * opens, forwarded to the server's `onConnect`/`onDisconnect` lifecycle hooks
182
226
  * as `event.context`. A per-shard context registered via
@@ -204,6 +248,14 @@ interface LunoraClientOptions {
204
248
  */
205
249
  heartbeatIntervalMs?: number;
206
250
  offlineQueue?: OfflineQueueOptions;
251
+ /**
252
+ * Durable outbox seam for offline writes. When supplied (the `@lunora/db`
253
+ * path wires `createExecutorOutboxSink`), offline mutations are delegated to
254
+ * the sink and the built-in {@link PersistenceAdapter}-backed `OfflineQueue`
255
+ * is bypassed, so a db app has exactly one durable write path. Omit for the
256
+ * standalone client, which keeps using {@link LunoraClientOptions.persistence}.
257
+ */
258
+ outbox?: OutboxSink;
207
259
  /** Durable store for the offline mutation queue; omit to keep it in memory. */
208
260
  persistence?: PersistenceAdapter;
209
261
  /**
@@ -232,17 +284,43 @@ interface LunoraClientOptions {
232
284
  /** Wire envelope sent on `POST /_lunora/rpc`. */
233
285
  interface RpcEnvelope {
234
286
  args?: Record<string, unknown>;
287
+ /**
288
+ * Stable per-client identifier (custom-mutator push path). Pairs with
289
+ * {@link RpcEnvelope.mutationId} to form `idempotencyKey` and scope the
290
+ * server `__client_watermark`. Absent on plain `client.mutation` calls.
291
+ */
292
+ clientId?: string;
235
293
  functionPath: string;
294
+ /**
295
+ * Idempotency key (`${clientId}:${mutationId}`) for the custom-mutator push
296
+ * path, mirrored into the `x-lunora-mutation-id` header. Absent on plain
297
+ * `client.mutation` calls.
298
+ */
299
+ idempotencyKey?: string;
300
+ /**
301
+ * Monotonic per-client mutation id (custom-mutator push path), backing the
302
+ * server-side per-client watermark: `id &lt;= watermark` is a replay (skipped),
303
+ * `id == watermark + 1` runs authoritatively, `id > watermark + 1` halts the
304
+ * batch so the client resends from `watermark + 1`. Absent on plain
305
+ * `client.mutation` calls.
306
+ */
307
+ mutationId?: number;
236
308
  shardKey?: string;
237
309
  }
238
- /** Wire response from the shard's `/rpc` endpoint (forwarded by the runtime). */
310
+ /**
311
+ * Wire response from the shard's `/rpc` endpoint (forwarded by the runtime). A
312
+ * watermarked custom-mutator push additionally carries `lastMutationId` — the
313
+ * highest per-client sequence the DO has applied — which the client uses to keep
314
+ * its `clientSeq` generator monotonic across reloads (see `LunoraClient.callMutator`).
315
+ */
239
316
  type RpcResponseBody = {
240
- result: unknown;
241
- } | {
242
317
  error: {
243
318
  code: string;
244
319
  message: string;
245
320
  };
321
+ } | {
322
+ lastMutationId?: number;
323
+ result: unknown;
246
324
  };
247
325
  /** Subscription protocol — client → server. */
248
326
  interface ClientSubscribeMessage {
@@ -275,10 +353,51 @@ interface ClientUnsubscribeMessage {
275
353
  * `onDisconnect` when the socket drops.
276
354
  */
277
355
  interface ClientConnectMessage {
356
+ /**
357
+ * Stable per-client id (persisted alongside the outbox). Lets the server
358
+ * scope this connection's `__client_watermark` so custom-mutator pokes can
359
+ * echo the right per-client `lastMutationId`. Omitted by clients that don't
360
+ * use custom mutators.
361
+ */
362
+ clientId?: string;
278
363
  context?: Record<string, unknown>;
279
364
  id: string;
280
365
  type: "connect";
281
366
  }
367
+ /**
368
+ * Subscribe to a declarative **shape** — server-side partial replication scoped
369
+ * by `shardBy` + the shape's predicate + RLS. The client sends the shape *name*
370
+ * + validated `args`; the server resolves the trusted `where` (identity/RLS
371
+ * `baseWhere` the client can't forge) and streams the matching rowset, then live
372
+ * {@link ServerPokePartMessage} diffs. `id` namespaces the subscription and is
373
+ * echoed as `shapeId` on every poke part.
374
+ */
375
+ interface ClientShapeSubscribeMessage {
376
+ id: string;
377
+ shape: {
378
+ args?: Record<string, unknown>;
379
+ name: string;
380
+ };
381
+ /**
382
+ * Resume from this checkpoint (the `__cdc_log` cursor the client last
383
+ * applied for this shape). When absent or below the server's retained floor
384
+ * (`minCdcSeq`), the server re-seeds with a full insert-poke instead of a
385
+ * delta.
386
+ */
387
+ sinceCheckpoint?: number;
388
+ /**
389
+ * The CDC epoch {@link ClientShapeSubscribeMessage.sinceCheckpoint} belongs
390
+ * to. A mismatch (forked changelog timeline) forces a full re-seed even when
391
+ * the cursor is numerically in range.
392
+ */
393
+ sinceEpoch?: string;
394
+ type: "shape_subscribe";
395
+ }
396
+ /** Cancel a shape subscription started with the same `id`. */
397
+ interface ClientShapeUnsubscribeMessage {
398
+ id: string;
399
+ type: "shape_unsubscribe";
400
+ }
282
401
  interface ClientAckMessage {
283
402
  id: string;
284
403
  type: "ack";
@@ -319,7 +438,7 @@ interface ClientWhisperMessage {
319
438
  topic: string;
320
439
  type: "whisper";
321
440
  }
322
- type ClientMessage = ClientAckMessage | ClientConnectMessage | ClientStreamMessage | ClientSubscribeMessage | ClientUnsubscribeMessage | ClientWhisperMessage | ClientWhisperSubscribeMessage;
441
+ type ClientMessage = ClientAckMessage | ClientConnectMessage | ClientShapeSubscribeMessage | ClientShapeUnsubscribeMessage | ClientStreamMessage | ClientSubscribeMessage | ClientUnsubscribeMessage | ClientWhisperMessage | ClientWhisperSubscribeMessage;
323
442
  /** Subscription protocol — server → client. */
324
443
  interface ServerDataMessage {
325
444
  /**
@@ -333,6 +452,13 @@ interface ServerDataMessage {
333
452
  /** The CDC epoch this frame's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
334
453
  epoch?: string;
335
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;
336
462
  type: "data" | "delta";
337
463
  }
338
464
  /**
@@ -346,6 +472,8 @@ interface ServerResumeMessage {
346
472
  /** The CDC epoch this resume's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
347
473
  epoch?: string;
348
474
  id: string;
475
+ /** Per-client custom-mutator watermark (see {@link ServerDataMessage.lastMutationId}). */
476
+ lastMutationId?: number;
349
477
  type: "resume";
350
478
  }
351
479
  interface ServerErrorMessage {
@@ -380,7 +508,65 @@ interface ServerWhisperMessage {
380
508
  topic: string;
381
509
  type: "whisper";
382
510
  }
383
- type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerResumeMessage | ServerWhisperMessage;
511
+ /**
512
+ * One row-level change in a shape's replication stream — the wire form of the
513
+ * DO's `__cdc_log` `CdcChange`. `insert`/`update` carry the post-image in
514
+ * `value` (projected to the shape's `columns`); `delete` omits it, identifying
515
+ * the removed row by `key` alone. The client applies these to its local
516
+ * collection; an unknown `key` on a `delete` is a safe no-op (a row the client
517
+ * never had in this shape).
518
+ */
519
+ interface RowOp {
520
+ /** Row primary key (`_id`). */
521
+ key: string;
522
+ op: "delete" | "insert" | "update";
523
+ /** Logical table the row belongs to. */
524
+ table: string;
525
+ /** Post-image document for insert/update; absent on delete. */
526
+ value?: Record<string, unknown>;
527
+ }
528
+ /**
529
+ * Opens a **poke** — an atomically-applied batch of shape diffs (Zero's poke
530
+ * protocol). A `pokeStart` is followed by zero or more {@link ServerPokePartMessage}
531
+ * frames and closed by exactly one {@link ServerPokeEndMessage}; the client
532
+ * buffers every part and applies them in a single transaction at `pokeEnd`, so a
533
+ * socket that drops mid-poke simply re-seeds on reconnect (no torn view).
534
+ */
535
+ interface ServerPokeStartMessage {
536
+ /** The checkpoint the client's view is expected to be at before this poke applies (for ordering/gap detection). */
537
+ baseCheckpoint?: number;
538
+ /** CDC epoch this poke belongs to; a mismatch forces the client to re-seed rather than apply. */
539
+ epoch?: string;
540
+ /** Correlates this poke's `pokeStart`/`pokePart`/`pokeEnd` frames. */
541
+ pokeId: string;
542
+ type: "pokeStart";
543
+ }
544
+ /** One shape's slice of an in-flight poke: the row-ops to apply for `shapeId`. */
545
+ interface ServerPokePartMessage {
546
+ /** Per-client custom-mutator watermark carried with this slice (see {@link ServerDataMessage.lastMutationId}). */
547
+ lastMutationId?: number;
548
+ pokeId: string;
549
+ /** Ordered row-level changes for this shape, applied in sequence at `pokeEnd`. */
550
+ rowsPatch: RowOp[];
551
+ /** The {@link ClientShapeSubscribeMessage.id} these row-ops belong to. */
552
+ shapeId: string;
553
+ type: "pokePart";
554
+ }
555
+ /**
556
+ * Closes a poke: the client commits the buffered parts atomically and advances
557
+ * its checkpoint to {@link ServerPokeEndMessage.checkpoint} (the `__cdc_log`
558
+ * cursor high-watermark the view now reflects), replayed as `sinceCheckpoint` on
559
+ * the next reconnect.
560
+ */
561
+ interface ServerPokeEndMessage {
562
+ /** The `__cdc_log` cursor the view is at after applying this poke. */
563
+ checkpoint?: number;
564
+ /** CDC epoch the {@link ServerPokeEndMessage.checkpoint} belongs to. */
565
+ epoch?: string;
566
+ pokeId: string;
567
+ type: "pokeEnd";
568
+ }
569
+ type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerPokeEndMessage | ServerPokePartMessage | ServerPokeStartMessage | ServerResumeMessage | ServerWhisperMessage;
384
570
  /**
385
571
  * The authenticated user as exposed client-side, mirroring better-auth's
386
572
  * `user` row (the `user` field of the `get-session` response). Kept minimal
@@ -780,6 +966,13 @@ type ConnectionStatus = "connected" | "connecting" | "idle" | "offline";
780
966
  * re-declaring it.
781
967
  */
782
968
  interface MutationCallOptions<TCurrent = unknown, TValue = unknown, TArgs = unknown> {
969
+ /**
970
+ * Override the auto-generated idempotency key (`x-lunora-mutation-id`). Lets a
971
+ * durable outbox replay a committed-but-unacked write under its *original* key
972
+ * so the server dedups it instead of applying it twice. Omit for normal calls —
973
+ * each then gets a fresh key.
974
+ */
975
+ mutationId?: string;
783
976
  optimistic?: (current: TCurrent | undefined) => TValue;
784
977
  /**
785
978
  * Convex-parity multi-query optimistic update. Receives an
@@ -790,6 +983,19 @@ interface MutationCallOptions<TCurrent = unknown, TValue = unknown, TArgs = unkn
790
983
  optimisticUpdate?: OptimisticUpdate<TArgs>;
791
984
  shardKey?: string;
792
985
  }
986
+ /** Callback a shape subscription invokes with its materialized rowset on every applied poke. */
987
+ type ShapeCallback = (rows: Record<string, unknown>[]) => void;
988
+ /**
989
+ * The high-water marks a shape poke has now synced to the client: `checkpoint`
990
+ * is the op-log cursor and `mutationId` the highest custom-mutator id the server
991
+ * echoed for this client. A `@lunora/db` collection feeds these into its
992
+ * checkpoint registry to drop optimistic overlays once the server's authoritative
993
+ * rows have landed.
994
+ */
995
+ interface SyncWatermark {
996
+ checkpoint?: number;
997
+ mutationId?: number;
998
+ }
793
999
  /**
794
1000
  * Lunora browser/edge client. Talks RPC over HTTP and real-time deltas over
795
1001
  * a single multiplexed WebSocket.
@@ -798,6 +1004,8 @@ interface MutationCallOptions<TCurrent = unknown, TValue = unknown, TArgs = unkn
798
1004
  * see the package README for the wire protocol.
799
1005
  */
800
1006
  declare class LunoraClient {
1007
+ /** Hard cap on concurrently-buffered pokes — a backstop that reclaims buffers abandoned by a mid-poke disconnect (no `pokeEnd`). Far above any real concurrent-in-flight count. */
1008
+ private static readonly MAX_POKE_BUFFERS;
801
1009
  readonly url: string;
802
1010
  readonly wsUrl: string;
803
1011
  private wsToken;
@@ -812,6 +1020,25 @@ declare class LunoraClient {
812
1020
  /** Keepalive cadence (ms); `0` disables the heartbeat. See {@link LunoraClientOptions.heartbeatIntervalMs}. */
813
1021
  private readonly heartbeatIntervalMs;
814
1022
  private readonly offlineQueue;
1023
+ /**
1024
+ * Durable outbox seam (the `@lunora/db` `createExecutorOutboxSink`). When
1025
+ * set, offline writes are delegated here and the built-in {@link OfflineQueue}
1026
+ * is bypassed, so a db app has exactly one durable write path.
1027
+ */
1028
+ private readonly outbox;
1029
+ /** Stable per-client id stamped onto every `OutboxMutation` (custom-mutator watermark). */
1030
+ private readonly clientId;
1031
+ /**
1032
+ * Highest custom-mutator watermark the server has echoed for this client,
1033
+ * keyed by shard bucket (`shardKey ?? ""`) since the DO tracks one
1034
+ * `__client_watermark` per shard. `callMutator` bumps it from every
1035
+ * ack; the `@lunora/db` mutator runtime seeds its `clientSeq` generator from
1036
+ * it so a reload (which resets the in-memory counter) never reissues a stale
1037
+ * sequence the server would silently swallow as a replay.
1038
+ */
1039
+ private readonly clientWatermarks;
1040
+ /** Monotonic per-client mutation counter backing the server `__client_watermark`. */
1041
+ private outboxMutationCounter;
815
1042
  private readonly onPersistenceError;
816
1043
  private readonly persistence;
817
1044
  /** Durable read cache (Pillar 2); `undefined` when `queryCache` is omitted or `false`. */
@@ -888,6 +1115,11 @@ declare class LunoraClient {
888
1115
  * calls `.cancel()` or the iterator is garbage-collected.
889
1116
  */
890
1117
  private readonly streams;
1118
+ /** Live shape subscriptions (partial replication), keyed by their wire id. */
1119
+ private readonly shapeSubscriptions;
1120
+ /** In-flight pokes being assembled between `pokeStart` and `pokeEnd`, keyed by `pokeId`. */
1121
+ private readonly pokeBuffers;
1122
+ private nextShapeId;
891
1123
  constructor(options: LunoraClientOptions);
892
1124
  /**
893
1125
  * Set (or clear) the bearer token sent on every HTTP RPC. Notifies any
@@ -902,6 +1134,47 @@ declare class LunoraClient {
902
1134
  setAuthToken(token: string | null): void;
903
1135
  getAuthToken(): string | null;
904
1136
  /**
1137
+ * The current identity fingerprint (the same stamp queued offline writes
1138
+ * carry). Exposed so a durable {@link OutboxSink}'s replay handler — which
1139
+ * owns its own at-least-once replay outside the built-in `OfflineQueue` —
1140
+ * can drop a persisted write whose captured `identity` no longer matches the
1141
+ * signed-in user, the guard the queue path applies in `flushOfflineQueue`.
1142
+ */
1143
+ currentIdentity(): string | null;
1144
+ /** This client's stable identifier — the watermark key the server's custom-mutator protocol advances per `clientSeq`. */
1145
+ clientIdentifier(): string;
1146
+ /**
1147
+ * The highest custom-mutator watermark the server has echoed for this client
1148
+ * on the given shard (0 if none yet). The `@lunora/db` mutator runtime seeds
1149
+ * its `clientSeq` generator from this so a reload never reissues a sequence
1150
+ * the server has already applied (which it would swallow as a replay, silently
1151
+ * dropping the write).
1152
+ */
1153
+ confirmedMutationWatermark(shardKey?: string): number;
1154
+ /**
1155
+ * Push a custom mutator to its authoritative server impl over the watermark
1156
+ * protocol (Phase 4): the request carries `x-lunora-client-id` + a monotonic
1157
+ * `x-lunora-client-seq`, so the DO runs it exactly once and advances this
1158
+ * client's `__client_watermark`.
1159
+ *
1160
+ * Returns the server `result` plus `applied`: `true` when the DO ran this push
1161
+ * as the next-in-order mutation, `false` when it was a replay ack (`clientSeq`
1162
+ * was at or below the stored watermark — e.g. a stale sequence after a reload).
1163
+ * A `false` verdict tells the caller to reissue above the now-known watermark
1164
+ * (echoed into {@link confirmedMutationWatermark}) rather than treat the benign
1165
+ * ack as a confirmed write. Every ack — applied or not — bumps the watermark.
1166
+ *
1167
+ * This is the online transport for `@lunora/db`'s client-mutator runtime; the
1168
+ * optimistic overlay + durable-outbox concerns live in that runtime, not here.
1169
+ */
1170
+ callMutator(functionPath: string, args: Record<string, unknown>, options?: {
1171
+ clientSeq?: number;
1172
+ shardKey?: string;
1173
+ }): Promise<{
1174
+ applied: boolean;
1175
+ result: unknown;
1176
+ }>;
1177
+ /**
905
1178
  * Subscribe to auth-token changes. Returns an unsubscribe function. The
906
1179
  * listener is NOT invoked on registration — use {@link getAuthToken} for
907
1180
  * the current value.
@@ -1430,6 +1703,26 @@ declare class LunoraClient {
1430
1703
  shardKey?: string;
1431
1704
  }): Unsubscribe;
1432
1705
  /**
1706
+ * Subscribe to a declarative **shape** — server-side partial replication
1707
+ * scoped by `shardBy` + the shape's predicate + RLS. The parallel to
1708
+ * {@link subscribe} for the poke protocol: the client sends the shape *name* +
1709
+ * validated `args` (never a `where` the client could forge), the server seeds
1710
+ * the current membership as an insert-poke and streams live membership diffs.
1711
+ * Each applied poke materializes the shape's rowset and invokes `callback`.
1712
+ *
1713
+ * Unlike {@link subscribe}, shape subscriptions are NOT deduped by
1714
+ * (name, args): the server resolves them under the socket's verified identity,
1715
+ * so every call gets its own id + view. The returned function unsubscribes.
1716
+ */
1717
+ subscribeShape(shape: {
1718
+ args?: Record<string, unknown>;
1719
+ name: string;
1720
+ }, callback: ShapeCallback, options?: {
1721
+ onCheckpoint?: (watermark: SyncWatermark) => void;
1722
+ onError?: SubscriptionErrorCallback;
1723
+ shardKey?: string;
1724
+ }): Unsubscribe;
1725
+ /**
1433
1726
  * Open a streaming query. The function reference must be a
1434
1727
  * `kind:"stream"` registration (built with `c.query.input(...).stream(...)`);
1435
1728
  * the type constraint catches accidental use of a query/mutation/action
@@ -1453,6 +1746,18 @@ declare class LunoraClient {
1453
1746
  }): StreamIterable<ReturnOf<F>>;
1454
1747
  close(): void;
1455
1748
  /**
1749
+ * Persist a mutation that can't go out on the wire right now (offline, or
1750
+ * mid-reconnect after a prior connect). The optimistic update has already
1751
+ * been applied by `mutation`; this only chooses the durable write path and
1752
+ * rolls the optimistic write back if persistence is rejected.
1753
+ *
1754
+ * Two paths: when an `outbox` sink is wired (the `@lunora/db` executor) it
1755
+ * owns persistence + at-least-once replay, so we delegate and return
1756
+ * optimistically (confirmation rides the synced view). Otherwise the
1757
+ * built-in `OfflineQueue` resolves/rejects the returned promise on replay.
1758
+ */
1759
+ private enqueueOfflineMutation;
1760
+ /**
1456
1761
  * Restore offline mutations persisted in a prior session and open a socket
1457
1762
  * for each shard they target so they flush once the WS reconnects. Failures
1458
1763
  * are swallowed — a broken durable store must not stop the client booting.
@@ -1549,6 +1854,13 @@ declare class LunoraClient {
1549
1854
  * to the lifecycle dispatch.
1550
1855
  */
1551
1856
  private sendConnectEnvelope;
1857
+ /**
1858
+ * Re-send every shape subscription bound to `shardKey` over its (now open)
1859
+ * socket. Each frame carries the shape's last applied checkpoint, so the
1860
+ * server resumes from it — or re-seeds when the cursor fell below CDC
1861
+ * retention or the epoch forked.
1862
+ */
1863
+ private resendShapeSubscriptions;
1552
1864
  private ensureSocket;
1553
1865
  private handleDisconnect;
1554
1866
  /**
@@ -1564,8 +1876,14 @@ declare class LunoraClient {
1564
1876
  /** Mark every subscription bound to `shardKey` as needing a fresh ack. */
1565
1877
  private markShardPendingAck;
1566
1878
  private sendSubscribeIfOpen;
1879
+ private sendShapeSubscribeIfOpen;
1567
1880
  private handleServerMessage;
1568
1881
  private handleErrorMessage;
1882
+ private handlePokeStart;
1883
+ private handlePokePart;
1884
+ private handlePokeEnd;
1885
+ /** Materialize a shape's keyed view to an array and invoke its callbacks. */
1886
+ private emitShapeRows;
1569
1887
  private handleDataMessage;
1570
1888
  /**
1571
1889
  * Handle a `resume` frame (Pillar 1b): the server proved nothing the
@@ -1620,4 +1938,4 @@ declare class LunoraClient {
1620
1938
  private clearQueryCacheForIdentityChange;
1621
1939
  private flushOfflineQueue;
1622
1940
  }
1623
- export { ArgsOf as A, BookmarkStorage as B, CachedQuery as C, DEFAULT_MAX_BUFFER as D, StreamHandle as E, FunctionReference as F, GlobalFacetResult as G, StreamIterable as H, SubscriptionCallback as I, SubscriptionRegistry as J, SubscriptionState as K, LunoraClient as L, MutationCallOptions as M, WorkflowInstanceDetail as N, OfflineQueueOptions as O, Preloaded as P, QueryCacheAdapter as Q, ReturnOf as R, SubscriptionError as S, WorkflowInstancePage as T, User as U, WorkflowInstanceStatus as V, WorkflowInstanceAction as W, WorkflowInstanceSummary as X, WorkflowStepDetail as Y, createLocalStore as Z, createStream as _, Unsubscribe as a, SubscriptionErrorCallback as b, PersistenceAdapter as c, ReconnectOptions as d, ClientMessage as e, ConnectionStatus as f, FunctionArgumentDescriptor as g, FunctionDescriptor as h, GlobalFacetValue as i, GlobalFilterClause as j, GlobalTableInfo as k, GlobalTablePage as l, LunoraClientOptions as m, OptimisticLocalStore as n, OptimisticUpdate as o, PersistedMutation as p, RpcEnvelope as q, RpcResponseBody as r, ScheduleRecord as s, SchedulerPoolStatus as t, SchedulerStatus as u, ServerMessage as v, ShardTrafficEntry as w, ShardTrafficResult as x, StorageListPage as y, StorageObject as z };
1941
+ export { SyncWatermark as $, ArgsOf as A, BookmarkStorage as B, CachedQuery as C, DEFAULT_MAX_BUFFER as D, ServerMessage as E, FunctionReference as F, GlobalFacetResult as G, ServerPokeEndMessage as H, ServerPokePartMessage as I, ServerPokeStartMessage as J, ShardTrafficEntry as K, LunoraClient as L, MutationCallOptions as M, ShardTrafficResult as N, OfflineQueueOptions as O, Preloaded as P, QueryCacheAdapter as Q, ReturnOf as R, SubscriptionError as S, StorageListPage as T, User as U, StorageObject as V, StreamHandle as W, StreamIterable as X, SubscriptionCallback as Y, SubscriptionRegistry as Z, SubscriptionState as _, Unsubscribe as a, WorkflowInstanceAction as a0, WorkflowInstanceDetail as a1, WorkflowInstancePage as a2, WorkflowInstanceStatus as a3, WorkflowInstanceSummary as a4, WorkflowStepDetail as a5, createLocalStore as a6, createStream as a7, SubscriptionErrorCallback as b, PersistenceAdapter as c, ReconnectOptions as d, ClientMessage as e, ClientShapeSubscribeMessage as f, ClientShapeUnsubscribeMessage as g, ConnectionStatus as h, FunctionArgumentDescriptor as i, FunctionDescriptor as j, GlobalFacetValue as k, GlobalFilterClause as l, GlobalTableInfo as m, GlobalTablePage as n, LunoraClientOptions as o, OptimisticLocalStore as p, OptimisticUpdate as q, OutboxMutation as r, OutboxSink as s, PersistedMutation as t, RowOp as u, RpcEnvelope as v, RpcResponseBody as w, ScheduleRecord as x, SchedulerPoolStatus as y, SchedulerStatus as z };