@lunora/client 1.0.0-alpha.23 → 1.0.0-alpha.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/auth/index.d.mts +10 -10
  2. package/dist/auth/index.d.ts +10 -10
  3. package/dist/index.d.mts +344 -357
  4. package/dist/index.d.ts +344 -357
  5. package/dist/index.mjs +5 -5
  6. package/dist/packem_shared/{LunoraClient-BBCQjjbl.mjs → LunoraClient-D3h4P7hg.mjs} +18 -4
  7. package/dist/packem_shared/{OfflineQueue-B4HUF7rt.mjs → OfflineQueue-BgarnAub.mjs} +1 -1
  8. package/dist/packem_shared/{TabCoordinator-BwRR8H06.mjs → TabCoordinator-D_5oNTTt.mjs} +48 -12
  9. package/dist/packem_shared/{createClientQuery-CQ51bWAE.mjs → createClientQuery-dJZg1ohm.mjs} +15 -6
  10. package/dist/packem_shared/{createServerClient-CTTAmvMx.mjs → createServerClient-DzeC2J3A.mjs} +1 -1
  11. package/dist/packem_shared/{httpStream-BJU-aflc.mjs → httpStream-DIdL8NEw.mjs} +33 -24
  12. package/dist/packem_shared/lunora-client.d-C4ud8bej.d.mts +2834 -0
  13. package/dist/packem_shared/lunora-client.d-C4ud8bej.d.ts +2834 -0
  14. package/dist/packem_shared/{offline-queue-CF4_Co5k.mjs → offline-queue-N-1JvYb4.mjs} +14 -2
  15. package/dist/packem_shared/preload.d-B6-lqUf2.d.ts +20 -0
  16. package/dist/packem_shared/preload.d-Dvk8zg6m.d.mts +20 -0
  17. package/dist/pagination/index.d.mts +42 -42
  18. package/dist/pagination/index.d.ts +42 -42
  19. package/dist/query/index.d.mts +42 -42
  20. package/dist/query/index.d.ts +42 -42
  21. package/dist/ssr/index.d.mts +79 -79
  22. package/dist/ssr/index.d.ts +79 -79
  23. package/dist/ssr/index.mjs +1 -1
  24. package/package.json +2 -2
  25. package/dist/packem_shared/lunora-client.d-JvtVpf8A.d.mts +0 -2824
  26. package/dist/packem_shared/lunora-client.d-JvtVpf8A.d.ts +0 -2824
  27. package/dist/packem_shared/preload.d-C4_d_l5v.d.ts +0 -20
  28. package/dist/packem_shared/preload.d-DKbjGN5O.d.mts +0 -20
@@ -0,0 +1,2834 @@
1
+ import { CronJobInfo, VectorIndexSummary, VectorQueryMatch, KvNamespaceSummary, KvKeyListResult, KvValueResult, AuthUser, AuthPage, AuthImpersonation, AuthCapabilities, AuthConfigInfo, AuthSession } from '@lunora/runtime';
2
+ /**
3
+ * Reactive key-value store for local-only client state.
4
+ *
5
+ * Unlike a server {@link SubscriptionState} (which tracks a live WS connection,
6
+ * an `acked` flag, `serverBase`, optimistic layers, and the full subscription
7
+ * machinery), a `ClientQueryRef` is purely local — no server round-trip, no
8
+ * WebSocket, no persistence. It exists so framework adapters can offer a
9
+ * `useClientQuery` hook whose values survive component remounts and are shared
10
+ * across every consumer of the same ref, with none of the ceremony or coupling
11
+ * of a dedicated context provider.
12
+ *
13
+ * The store lives inside `LunoraClient` (a private field) and is surfaced through
14
+ * `client.getClientQuery(ref)` / `setClientQuery(ref, value)` /
15
+ * `subscribeClientQuery(ref, callback)`.
16
+ */
17
+ /** Opaque handle for a typed client-local query slot. */
18
+ interface ClientQueryRef<T = unknown> {
19
+ /** Default value when no value has been set explicitly. */
20
+ readonly defaultValue: T;
21
+ /** Stable identity for the slot. Must be unique within a client instance. */
22
+ readonly key: string;
23
+ }
24
+ /**
25
+ * Create a typed {@link ClientQueryRef}. Call once per slot at module scope
26
+ * (or inside a component module) — the ref object is the stable identity.
27
+ * @example
28
+ * ```ts
29
+ * // lunora/client-queries.ts
30
+ * import { createClientQuery } from "@lunora/client";
31
+ *
32
+ * export const sidebarOpen = createClientQuery("sidebarOpen", true);
33
+ * export const selectedMessageId = createClientQuery("selectedMessageId", undefined as string | undefined);
34
+ * ```
35
+ */
36
+ declare const createClientQuery: <T>(key: string, defaultValue: T) => ClientQueryRef<T>;
37
+ /**
38
+ * The machine-readable error codes a client can observe on a failed
39
+ * RPC/batch/subscription. Mirrors the server's `CODE_STATUS` keys
40
+ * (`@lunora/server`'s `error.ts`) by hand — the client is framework-neutral and
41
+ * must never import the server package (wrong dependency direction / would pull
42
+ * the server into the browser bundle). Keep this list in sync when a server code
43
+ * is added or removed (see the drift-guard note in the plan/maintenance docs).
44
+ */
45
+ declare const LUNORA_ERROR_CODES: readonly ["BAD_REQUEST", "CONFLICT", "COUNT_RLS_UNSUPPORTED", "FORBIDDEN", "INTERNAL_SERVER_ERROR", "MASK_UNSUPPORTED", "NOT_FOUND", "NOT_IMPLEMENTED", "RELATION_PREDICATE_UNSUPPORTED", "TOO_MANY_REQUESTS", "UNAUTHORIZED", "UNPROCESSABLE"];
46
+ /** A machine-readable error `code` the client may observe. Mirror of the server's `LunoraErrorCode`. */
47
+ type LunoraErrorCode = (typeof LUNORA_ERROR_CODES)[number];
48
+ /** Error code the server uses for optimistic-concurrency conflicts (HTTP 409). */
49
+ declare const CONFLICT_ERROR_CODE = "CONFLICT";
50
+ /**
51
+ * Whether an unknown rejection is an optimistic-concurrency conflict — the
52
+ * server lost a write race and the caller should refetch and retry (or surface
53
+ * the conflict). Structural check on the `code` property the client attaches
54
+ * when decoding the worker's `{ error: { code, message } }` envelope.
55
+ */
56
+ declare const isConflictError: (error: unknown) => error is Error & {
57
+ code: "CONFLICT";
58
+ };
59
+ /**
60
+ * Whether a rejection is an RLS/policy denial (`FORBIDDEN`, HTTP 403) — the
61
+ * caller is authenticated but not permitted to read/write the row. The most
62
+ * common per-call error a UI must handle in an RLS-first app.
63
+ */
64
+ declare const isForbiddenError: (error: unknown) => error is Error & {
65
+ code: "FORBIDDEN";
66
+ };
67
+ /** Whether a rejection is an authentication failure (`UNAUTHORIZED`, HTTP 401) — no/invalid identity. */
68
+ declare const isUnauthorizedError: (error: unknown) => error is Error & {
69
+ code: "UNAUTHORIZED";
70
+ };
71
+ /**
72
+ * Whether a rejection is a rate-limit denial (`TOO_MANY_REQUESTS`, HTTP 429).
73
+ * The retry hint (if the server sent one) is read with {@link getRetryAfterMs}.
74
+ */
75
+ declare const isRateLimitedError: (error: unknown) => error is Error & {
76
+ code: "TOO_MANY_REQUESTS";
77
+ };
78
+ /**
79
+ * Read the server's machine-readable `code` off a rejection, narrowed to the
80
+ * known {@link LunoraErrorCode} union. Returns `undefined` for a non-`Error`, a
81
+ * missing code, or an unrecognized code string (forward-compat server codes read
82
+ * as `undefined` here rather than being falsely narrowed).
83
+ */
84
+ declare const getErrorCode: (error: unknown) => LunoraErrorCode | undefined;
85
+ /**
86
+ * Read the rate-limit retry hint (`data.retryAfterMs`) off a
87
+ * `TOO_MANY_REQUESTS` rejection without hand-casting the `unknown` `data`
88
+ * payload. Returns the finite millisecond value the server sent, or `undefined`
89
+ * when absent/non-numeric. Pair with {@link isRateLimitedError}.
90
+ */
91
+ declare const getRetryAfterMs: (error: unknown) => number | undefined;
92
+ /** The registered function kinds a {@link FunctionReference} can describe. `stream` is a query that yields multiple frames over the WS. */
93
+ type FunctionKind = "action" | "mutation" | "query" | "stream";
94
+ /**
95
+ * Opaque reference to a registered function emitted by `@lunora/codegen`.
96
+ *
97
+ * At runtime it carries the `&lt;file>:&lt;function>` identifier in `__lunoraRef`.
98
+ * Generated declarations decorate this with phantom type parameters so the
99
+ * client can infer args / return values per call site.
100
+ */
101
+ interface FunctionReference<Kind extends FunctionKind = FunctionKind, Args = unknown, Return = unknown> {
102
+ /**
103
+ * Phantom marker carrying the `Kind`/`Args`/`Return` type parameters for
104
+ * inference. Never present at runtime; declared as a covariant (output)
105
+ * position so a concrete reference stays assignable to a widened one.
106
+ */
107
+ readonly __lunoraPhantom?: {
108
+ args: Args;
109
+ kind: Kind;
110
+ returns: Return;
111
+ };
112
+ readonly __lunoraRef: string;
113
+ }
114
+ /** Extract the args type from a {@link FunctionReference}. */
115
+ type ArgsOf<F> = F extends FunctionReference<infer _K, infer A, infer _R> ? A : never;
116
+ /** Extract the return type from a {@link FunctionReference}. */
117
+ type ReturnOf<F> = F extends FunctionReference<infer _K, infer _A, infer R> ? R : never;
118
+ /**
119
+ * Typed reference to an HTTP-SSE stream route (`httpRoute.&lt;verb>(path).stream()`)
120
+ * emitted by `@lunora/codegen` as `httpStreams.&lt;namespace>.&lt;name>`.
121
+ *
122
+ * Distinct from {@link FunctionReference}: this is the **HTTP-SSE route stream**
123
+ * (opened with `fetch` + `ReadableStream` against the route's own URL), not the
124
+ * WS procedure stream (`kind: "stream"`). At runtime it carries the HTTP verb
125
+ * and the route path; the phantom marker carries the chunk / searchParams /
126
+ * params types so `httpStream` (and the framework hooks over it) infer the
127
+ * chunk type end-to-end.
128
+ * @experimental Reconnect/POST-body/wire-fidelity design questions are still open, so the shape may change.
129
+ */
130
+ interface HttpStreamRef<Chunk = unknown, SearchParams = unknown, Params = unknown> {
131
+ /**
132
+ * Phantom marker carrying the `Chunk`/`SearchParams`/`Params` type
133
+ * parameters for inference. Never present at runtime; declared in a
134
+ * covariant (output) position so a concrete reference stays assignable to
135
+ * a widened one.
136
+ */
137
+ readonly __lunoraHttpStream?: {
138
+ chunk: Chunk;
139
+ params: Params;
140
+ searchParams: SearchParams;
141
+ };
142
+ /** HTTP verb the route binds to (uppercased), e.g. `"GET"`. */
143
+ readonly method: string;
144
+ /** The route path as declared, e.g. `/api/tokens/:id` — `:name` segments are filled from `params`. */
145
+ readonly path: string;
146
+ }
147
+ /**
148
+ * The call-side args of an HTTP-SSE stream route: `:name` path params plus URL query params.
149
+ * @experimental Part of the HTTP-SSE stream surface.
150
+ */
151
+ interface HttpStreamCallArgs<SearchParams = unknown, Params = unknown> {
152
+ /** Values for the route path's `:name` segments. */
153
+ params?: Params;
154
+ /** URL query params, appended to the request URL (undefined entries are skipped). */
155
+ searchParams?: SearchParams;
156
+ }
157
+ /**
158
+ * Extract the chunk type from a {@link HttpStreamRef}.
159
+ * @experimental Part of the HTTP-SSE stream surface.
160
+ */
161
+ type HttpStreamChunkOf<R> = R extends HttpStreamRef<infer Chunk, infer _S, infer _P> ? Chunk : never;
162
+ /**
163
+ * Extract the call-side args type from a {@link HttpStreamRef}.
164
+ * @experimental Part of the HTTP-SSE stream surface.
165
+ */
166
+ type HttpStreamArgsOf<R> = R extends HttpStreamRef<infer _C, infer S, infer P> ? HttpStreamCallArgs<S, P> : never;
167
+ type Unsubscribe = () => void;
168
+ /**
169
+ * Serializable result of `preloadQuery`. Produced on the server during SSR,
170
+ * embedded in the rendered HTML, then handed to `usePreloadedQuery` on the
171
+ * client so the first render shows the server value with no loading flash
172
+ * before a live subscription attaches. Every field survives `JSON.stringify`.
173
+ */
174
+ interface Preloaded<T = unknown> {
175
+ readonly __lunoraPreloaded: true;
176
+ readonly args: Record<string, unknown>;
177
+ readonly functionPath: string;
178
+ readonly shardKey?: string;
179
+ readonly value: T;
180
+ }
181
+ /**
182
+ * Pluggable storage for the `x-d1-bookmark` value used to provide
183
+ * read-your-writes between a mutation and subsequent queries.
184
+ */
185
+ interface BookmarkStorage {
186
+ get: () => string | null;
187
+ set: (value: string | null) => void;
188
+ }
189
+ interface ReconnectOptions {
190
+ initialDelayMs?: number;
191
+ jitter?: boolean;
192
+ maxDelayMs?: number;
193
+ }
194
+ /** Which durable-storage operation failed, passed to {@link OfflineQueueOptions.onPersistenceError}. */
195
+ type PersistenceOperation = "append" | "clear" | "load" | "remove";
196
+ /** Context handed to a persistence-error handler. */
197
+ interface PersistenceErrorContext {
198
+ readonly error: unknown;
199
+ /** The mutation id involved, when the failing op was scoped to one (`append`/`remove`). */
200
+ readonly mutationId?: string;
201
+ readonly operation: PersistenceOperation;
202
+ }
203
+ interface OfflineQueueOptions {
204
+ maxItems?: number;
205
+ /**
206
+ * Invoked when a {@link PersistenceAdapter} call rejects (e.g. IndexedDB quota
207
+ * exceeded). Without a handler, failures are logged via `console.warn` so they
208
+ * are never fully silent. Note: a failed `append` means the write is queued in
209
+ * memory but NOT durable — it will not survive a reload.
210
+ */
211
+ onPersistenceError?: (context: PersistenceErrorContext) => void;
212
+ /**
213
+ * Queue mutations issued before a shard's first successful WebSocket
214
+ * connect (defaults to `false`). The standard behaviour (`LunoraClient`'s
215
+ * `mutation()`) queues only when the targeted shard has been connected at
216
+ * least once (`wasEverConnected`), so the registry / resubscribe handshake
217
+ * has run. Set this to `true` for offline-first apps that want to enqueue
218
+ * writes on the very first session before the WS is up.
219
+ */
220
+ queueBeforeFirstConnect?: boolean;
221
+ }
222
+ /**
223
+ * Serializable shape of an offline mutation, durably stored by a
224
+ * {@link PersistenceAdapter} so queued writes survive a reload/crash. The live
225
+ * `resolve`/`reject` callbacks of an in-flight `QueuedMutation` are *not*
226
+ * persisted — a restored mutation is replayed with no original awaiter.
227
+ */
228
+ interface PersistedMutation {
229
+ args: Record<string, unknown>;
230
+ functionPath: string;
231
+ id: string;
232
+ /**
233
+ * Issuing identity fingerprint, persisted so a hydrated write replays only
234
+ * under the identity that queued it (`null` = queued while signed out).
235
+ * Absent on records written by older client versions, which replay under
236
+ * the ambient identity for back-compat.
237
+ */
238
+ identity?: string | null;
239
+ shardKey?: string;
240
+ /**
241
+ * App/schema version stamped at enqueue (from `LunoraClientOptions.persistenceVersion`).
242
+ * On hydrate, a record whose `version` doesn't match the current one is dropped
243
+ * and purged rather than replayed — so a write persisted by an older deploy
244
+ * (with a now-changed function signature) can't replay against the new schema.
245
+ * Absent when no `persistenceVersion` is configured (no version gating).
246
+ */
247
+ version?: string;
248
+ }
249
+ /**
250
+ * Durable store for the offline mutation queue. The default client keeps the
251
+ * queue in memory; supplying an adapter (e.g. `createIndexedDbPersistence`)
252
+ * makes queued writes survive a page reload. Implementations must preserve FIFO
253
+ * (enqueue) order in `PersistenceAdapter.load`.
254
+ *
255
+ * Replay semantics are at-least-once: a mutation is removed only after the
256
+ * server confirms (or rejects) it, so a crash between commit and `remove` can
257
+ * replay it again on the next load.
258
+ */
259
+ interface PersistenceAdapter {
260
+ /** Append a mutation to durable storage (called on enqueue). */
261
+ append: (mutation: PersistedMutation) => Promise<void>;
262
+ /** Drop every persisted mutation (e.g. on logout). */
263
+ clear: () => Promise<void>;
264
+ /** Load all persisted mutations in FIFO order — called once at startup. */
265
+ load: () => Promise<PersistedMutation[]>;
266
+ /** Remove a mutation by id once it has been replayed (resolved or rejected). */
267
+ remove: (id: string) => Promise<void>;
268
+ }
269
+ /**
270
+ * One write handed to an {@link OutboxSink}. Mirrors {@link PersistedMutation}
271
+ * plus the custom-mutator identity (`clientId`/`mutationId`/`idempotencyKey`)
272
+ * the durable outbox needs to dedupe and watermark replays.
273
+ */
274
+ interface OutboxMutation {
275
+ args: Record<string, unknown>;
276
+ /** Stable per-client id; pairs with {@link OutboxMutation.mutationId} as `idempotencyKey`. */
277
+ clientId: string;
278
+ functionPath: string;
279
+ /** `${clientId}:${mutationId}` — sent as `x-lunora-mutation-id` so a replay is server-idempotent. */
280
+ idempotencyKey: string;
281
+ /** Issuing identity fingerprint (`null` = signed out); drives the sink's identity guard. */
282
+ identity: string | null;
283
+ /** Monotonic per-client mutation id, backing the server `__client_watermark`. */
284
+ mutationId: number;
285
+ shardKey?: string;
286
+ }
287
+ /**
288
+ * Pluggable durable outbox seam. When set on {@link LunoraClientOptions.outbox},
289
+ * the client delegates offline write durability + at-least-once replay to this
290
+ * sink instead of its built-in {@link PersistenceAdapter}-backed `OfflineQueue`.
291
+ * `@lunora/db` supplies the blessed implementation (`createExecutorOutboxSink`,
292
+ * backed by the TanStack `OfflineExecutor`); the interface itself is
293
+ * dependency-free so `@lunora/client` stays TanStack-free.
294
+ */
295
+ interface OutboxSink {
296
+ /**
297
+ * Persist and schedule a write for replay. Rejects with an
298
+ * `OFFLINE_QUEUE_OVERFLOW`-coded error when the sink's cap is exceeded, so
299
+ * the caller can surface back-pressure to the issuing mutation.
300
+ */
301
+ enqueue: (mutation: OutboxMutation) => Promise<void>;
302
+ }
303
+ /**
304
+ * One persisted query result in the durable read cache (Pillar 2). Keyed in the
305
+ * store by `shardKey + functionPath + argsKey`; the record carries everything
306
+ * needed to render offline on reload and to resume the live subscription.
307
+ */
308
+ interface CachedQuery {
309
+ /**
310
+ * Issuing identity fingerprint (same shape the offline queue stamps). A
311
+ * cached value only hydrates when it matches the current identity, so a
312
+ * signed-out cache never leaks into a new session. `null` = cached while
313
+ * signed out.
314
+ */
315
+ identity: string | null;
316
+ /**
317
+ * The `cursor` high-watermark this value reflects, replayed as `sinceSeq`
318
+ * on reconnect so the server can resume instead of re-snapshotting. Absent
319
+ * when the value predates CDC / no cursor was advertised.
320
+ */
321
+ serverCursor?: number;
322
+ /**
323
+ * The CDC `epoch` the `serverCursor` belongs to, replayed as `sinceEpoch`
324
+ * on reconnect so the server only resumes when the client is still on the
325
+ * same changelog timeline. Absent when no epoch was advertised.
326
+ */
327
+ serverEpoch?: string;
328
+ /** Wall-clock millis the value was written — drives LRU eviction. */
329
+ ts: number;
330
+ /** The full query result last seen from the server. */
331
+ value: unknown;
332
+ /**
333
+ * App/schema version stamped when persisted (from `LunoraClientOptions.persistenceVersion`).
334
+ * A cached value whose `version` doesn't match the current one is not hydrated —
335
+ * so a result of a now-changed shape from an older deploy can't render. Absent
336
+ * when no `persistenceVersion` is configured (no version gating).
337
+ */
338
+ version?: string;
339
+ }
340
+ /**
341
+ * Durable store for the client read cache (Pillar 2): query results survive a
342
+ * reload so reads hydrate from disk and render immediately while the socket
343
+ * reconnects. Opt-in via {@link LunoraClientOptions.queryCache}; omit to keep
344
+ * reads in memory only (today's behaviour). Mirrors {@link PersistenceAdapter}'s
345
+ * shape over the same IndexedDB plumbing.
346
+ */
347
+ interface QueryCacheAdapter {
348
+ /** Drop every cached query (e.g. on logout / identity change). */
349
+ clear: () => Promise<void>;
350
+ /** Load every cached query — called once at startup to hydrate reads. */
351
+ load: () => Promise<(CachedQuery & {
352
+ key: string;
353
+ })[]>;
354
+ /** Upsert one cached query by key (called when a subscription value advances). */
355
+ put: (key: string, entry: CachedQuery) => Promise<void>;
356
+ /** Remove one cached query by key. */
357
+ remove: (key: string) => Promise<void>;
358
+ }
359
+ /**
360
+ * Resolves the WS `?token=` credential fresh at every (re)connect — the channel
361
+ * for short-lived tokens (e.g. the ephemeral admin sub-token the worker mints
362
+ * at `POST /_lunora/admin/ws-token`) instead of a static secret in the URL.
363
+ * May return the token synchronously or as a Promise; returning `undefined`
364
+ * connects without a token. A thrown error / rejected Promise fails that
365
+ * connect attempt, and the client retries with its normal reconnect backoff.
366
+ */
367
+ type WsTokenProvider = () => Promise<string | undefined> | string | undefined;
368
+ interface LunoraClientOptions {
369
+ /**
370
+ * Base path the worker mounts better-auth at, used by the client's
371
+ * `getCurrentUser()` to reach the `get-session` route. Defaults to
372
+ * `/api/auth` (matching `@lunora/auth`'s `DEFAULT_AUTH_BASE_PATH`).
373
+ */
374
+ authBasePath?: string;
375
+ bookmarkStorage?: BookmarkStorage;
376
+ /**
377
+ * Stable per-client id backing the custom-mutator watermark. Sent on the
378
+ * `connect` envelope (so the server can scope this client's
379
+ * `__client_watermark`) and stamped onto every {@link OutboxMutation} the
380
+ * {@link LunoraClientOptions.outbox} sink persists, where it pairs with the
381
+ * monotonic mutation id to form the idempotency key. The `@lunora/db` path
382
+ * persists a stable id alongside the outbox and passes it here; omit for the
383
+ * standalone client, which generates an ephemeral per-session id.
384
+ */
385
+ clientId?: string;
386
+ /**
387
+ * Default app context sent in the `connect` envelope right after each socket
388
+ * opens, forwarded to the server's `onConnect`/`onDisconnect` lifecycle hooks
389
+ * as `event.context`. A per-shard context registered via
390
+ * `setConnectionContext` overrides this for that shard. Omit when no lifecycle
391
+ * hook needs connection context.
392
+ */
393
+ connectionContext?: Record<string, unknown>;
394
+ /**
395
+ * Fail-fast timeout (ms) for opening a subscription WebSocket. If the
396
+ * handshake doesn't complete within this window — a hung dev proxy or a cold
397
+ * worker that never upgrades — the client force-closes the socket and routes
398
+ * through its normal reconnect/backoff (surfacing `offline` status) instead
399
+ * of leaving the live channel silently stuck on the browser's much longer
400
+ * default. Does not affect HTTP queries/mutations (those never ride the WS).
401
+ * Defaults to 10000 (10s); set to `0` (or negative) to disable.
402
+ */
403
+ connectTimeoutMs?: number;
404
+ /**
405
+ * When `true`, tabs sharing the same origin coordinate via BroadcastChannel
406
+ * so only one tab (the "leader") opens WebSocket connections to the server.
407
+ * Follower tabs receive subscription data through the channel instead.
408
+ *
409
+ * Reduces simultaneous WS connections, bandwidth, and cross-tab state drift.
410
+ * Requires `BroadcastChannel` (browser-only); silently ignored otherwise.
411
+ * Defaults to `false`.
412
+ */
413
+ crossTabSync?: boolean;
414
+ fetch?: typeof fetch;
415
+ /**
416
+ * Interval (ms) between keepalive pings sent on each open subscription
417
+ * socket. The server answers them via the Durable Object's hibernation
418
+ * auto-response WITHOUT waking the DO, so an idle socket stays alive across
419
+ * hibernation without a billable wakeup. Defaults to 30000 (30s); set to
420
+ * `0` (or a negative value) to disable the heartbeat entirely.
421
+ */
422
+ heartbeatIntervalMs?: number;
423
+ /**
424
+ * When `true` and a `queryCache` is active, framework hooks (React, Vue, …)
425
+ * wait for the durable cache to finish hydrating before their first render
426
+ * with an enabled subscription, so users see cached data instead of an
427
+ * undefined flash before the socket round-trip. Defaults to `false`.
428
+ *
429
+ * Requires `queryCache` to be set (not `false`); silently ignored otherwise.
430
+ */
431
+ hydrateOnStart?: boolean;
432
+ offlineQueue?: OfflineQueueOptions;
433
+ /**
434
+ * Durable outbox seam for offline writes. When supplied (the `@lunora/db`
435
+ * path wires `createExecutorOutboxSink`), offline mutations are delegated to
436
+ * the sink and the built-in {@link PersistenceAdapter}-backed `OfflineQueue`
437
+ * is bypassed, so a db app has exactly one durable write path. Omit for the
438
+ * standalone client, which keeps using {@link LunoraClientOptions.persistence}.
439
+ */
440
+ outbox?: OutboxSink;
441
+ /**
442
+ * Durable store for the offline mutation queue. Tri-state — an explicit
443
+ * {@link PersistenceAdapter} is used as-is; `false` opts out (the queue stays
444
+ * in memory, lost on reload); omitted (the default) auto-probes a durable
445
+ * IndexedDB store when the `indexedDB` global is present (browsers), otherwise
446
+ * in-memory, so SSR/Node/React-Native keep the in-memory behaviour and only
447
+ * environments that can persist do. Pass `createAsyncStoragePersistence()` on
448
+ * React Native.
449
+ */
450
+ persistence?: false | PersistenceAdapter;
451
+ /**
452
+ * App/schema version stamped onto every persisted queued write and cached
453
+ * read. Bump it on a breaking change to a function signature or query shape:
454
+ * on the next boot, persisted writes / cached reads stamped with a different
455
+ * version are dropped (and purged) rather than replayed / hydrated against the
456
+ * new schema. Omit to disable version gating (records are never invalidated by
457
+ * version).
458
+ *
459
+ * **Adoption is itself an invalidation event:** records written before you set
460
+ * `persistenceVersion` carry no version, so the first boot after enabling it
461
+ * purges all currently-queued offline writes (and cached reads) as stale. Adopt
462
+ * it on a build where that clean slate is acceptable — typically the same
463
+ * breaking deploy you're protecting against — not purely speculatively.
464
+ */
465
+ persistenceVersion?: string;
466
+ /**
467
+ * Durable store for the read cache (Pillar 2). When active, query results
468
+ * are persisted as their subscriptions advance and hydrated on construction
469
+ * so a reload renders cached data before the socket reconnects, then resumes
470
+ * the live subscription from the persisted cursor. Tri-state — an explicit
471
+ * {@link QueryCacheAdapter} is used as-is; `false` opts out (reads stay in
472
+ * memory only); omitted (the default) auto-probes IndexedDB exactly like
473
+ * {@link LunoraClientOptions.persistence}.
474
+ */
475
+ queryCache?: QueryCacheAdapter | false;
476
+ reconnect?: ReconnectOptions;
477
+ url: string;
478
+ WebSocket?: typeof WebSocket;
479
+ /**
480
+ * Credential appended to the WebSocket URL as `?token=…`. The server matches
481
+ * it against `LUNORA_WS_BEARER` (to clear the upgrade gate) and/or
482
+ * `LUNORA_ADMIN_TOKEN` (to authorize `__lunora_admin__:*` subscriptions —
483
+ * what the studio supplies). Browsers can't set headers on the `WebSocket`
484
+ * constructor, so the query parameter is the only channel; it ends up in
485
+ * server logs and history, so prefer a short-lived rotating token in
486
+ * production over a static secret.
487
+ *
488
+ * Pass a {@link WsTokenProvider} function to resolve the token fresh at
489
+ * every (re)connect — the channel for short-lived credentials such as the
490
+ * ephemeral admin sub-token minted by `POST /_lunora/admin/ws-token`: the
491
+ * provider re-mints on each reconnect, including the one following a `4001`
492
+ * token-expired drop, so a static master token never has to ride the URL.
493
+ */
494
+ wsToken?: string | WsTokenProvider;
495
+ wsUrl?: string;
496
+ }
497
+ /** Wire envelope sent on `POST /_lunora/rpc`. */
498
+ interface RpcEnvelope {
499
+ args?: Record<string, unknown>;
500
+ /**
501
+ * Stable per-client identifier (custom-mutator push path). Pairs with
502
+ * {@link RpcEnvelope.mutationId} to form `idempotencyKey` and scope the
503
+ * server `__client_watermark`. Absent on plain `client.mutation` calls.
504
+ */
505
+ clientId?: string;
506
+ functionPath: string;
507
+ /**
508
+ * Idempotency key (`${clientId}:${mutationId}`) for the custom-mutator push
509
+ * path, mirrored into the `x-lunora-mutation-id` header. Absent on plain
510
+ * `client.mutation` calls.
511
+ */
512
+ idempotencyKey?: string;
513
+ /**
514
+ * Monotonic per-client mutation id (custom-mutator push path), backing the
515
+ * server-side per-client watermark: `id &lt;= watermark` is a replay (skipped),
516
+ * `id == watermark + 1` runs authoritatively, `id > watermark + 1` halts the
517
+ * batch so the client resends from `watermark + 1`. Absent on plain
518
+ * `client.mutation` calls.
519
+ */
520
+ mutationId?: number;
521
+ shardKey?: string;
522
+ }
523
+ /**
524
+ * Wire response from the shard's `/rpc` endpoint (forwarded by the runtime). A
525
+ * watermarked custom-mutator push additionally carries `lastMutationId` — the
526
+ * highest per-client sequence the DO has applied — which the client uses to keep
527
+ * its `clientSeq` generator monotonic across reloads (see `LunoraClient.callMutator`).
528
+ * A plain mutation on a CDC shard carries `commitCursor` — the cursor the write
529
+ * committed at — which gates the drop of a per-call optimistic layer.
530
+ */
531
+ type RpcResponseBody = {
532
+ error: {
533
+ code: string;
534
+ data?: unknown;
535
+ message: string;
536
+ };
537
+ } | {
538
+ commitCursor?: number;
539
+ lastMutationId?: number;
540
+ result: unknown;
541
+ };
542
+ /** Subscription protocol — client → server. */
543
+ interface ClientSubscribeMessage {
544
+ id: string;
545
+ /**
546
+ * `sinceSeq` is the persisted `cursor` high-watermark the client last saw
547
+ * for this shard (Pillar 1b resume). Present only when a durable
548
+ * {@link QueryCacheAdapter} restored a cached value with a cursor; the
549
+ * server replies with a lightweight `resume` frame instead of a full
550
+ * snapshot when nothing the query reads changed since it. Absent on a
551
+ * first-time subscribe.
552
+ */
553
+ query: {
554
+ args?: Record<string, unknown>;
555
+ functionPath?: string;
556
+ sinceEpoch?: string;
557
+ sinceSeq?: number;
558
+ table?: string;
559
+ };
560
+ type: "subscribe";
561
+ }
562
+ interface ClientUnsubscribeMessage {
563
+ id: string;
564
+ type: "unsubscribe";
565
+ }
566
+ /**
567
+ * One-shot control frame sent right after the socket opens. Registers the
568
+ * connection's app `context` (e.g. `{ roomId, sessionId }`) with the server and
569
+ * fires the `onConnect` lifecycle hooks; the same context is replayed to
570
+ * `onDisconnect` when the socket drops.
571
+ */
572
+ interface ClientConnectMessage {
573
+ /**
574
+ * Stable per-client id (persisted alongside the outbox). Lets the server
575
+ * scope this connection's `__client_watermark` so custom-mutator pokes can
576
+ * echo the right per-client `lastMutationId`. Omitted by clients that don't
577
+ * use custom mutators.
578
+ */
579
+ clientId?: string;
580
+ context?: Record<string, unknown>;
581
+ id: string;
582
+ type: "connect";
583
+ }
584
+ /**
585
+ * Subscribe to a declarative **shape** — server-side partial replication scoped
586
+ * by `shardBy` + the shape's predicate + RLS. The client sends the shape *name*
587
+ * + validated `args`; the server resolves the trusted `where` (identity/RLS
588
+ * `baseWhere` the client can't forge) and streams the matching rowset, then live
589
+ * {@link ServerPokePartMessage} diffs. `id` namespaces the subscription and is
590
+ * echoed as `shapeId` on every poke part.
591
+ */
592
+ interface ClientShapeSubscribeMessage {
593
+ id: string;
594
+ shape: {
595
+ args?: Record<string, unknown>;
596
+ name: string;
597
+ };
598
+ /**
599
+ * Resume from this checkpoint (the `__cdc_log` cursor the client last
600
+ * applied for this shape). When absent or below the server's retained floor
601
+ * (`minCdcSeq`), the server re-seeds with a full insert-poke instead of a
602
+ * delta.
603
+ */
604
+ sinceCheckpoint?: number;
605
+ /**
606
+ * The CDC epoch {@link ClientShapeSubscribeMessage.sinceCheckpoint} belongs
607
+ * to. A mismatch (forked changelog timeline) forces a full re-seed even when
608
+ * the cursor is numerically in range.
609
+ */
610
+ sinceEpoch?: string;
611
+ type: "shape_subscribe";
612
+ }
613
+ /** Cancel a shape subscription started with the same `id`. */
614
+ interface ClientShapeUnsubscribeMessage {
615
+ id: string;
616
+ type: "shape_unsubscribe";
617
+ }
618
+ interface ClientAckMessage {
619
+ id: string;
620
+ type: "ack";
621
+ }
622
+ /**
623
+ * Start a streaming query. The id namespaces a fresh stream and is echoed on
624
+ * every {@link ServerChunkMessage} the server pushes back. Cancel a running
625
+ * stream by sending a {@link ClientUnsubscribeMessage} with the same id —
626
+ * subscription and stream id-spaces share the cancel channel; the prefix
627
+ * (`sub_*` vs `stream_*`) keeps the local registries searchable.
628
+ */
629
+ interface ClientStreamMessage {
630
+ id: string;
631
+ query: {
632
+ args?: Record<string, unknown>;
633
+ functionPath: string;
634
+ shardKey?: string;
635
+ };
636
+ type: "stream";
637
+ }
638
+ /**
639
+ * Join or leave a whisper `topic` — an app-chosen ephemeral channel scoped to a
640
+ * shard. While joined, the client receives every {@link ServerWhisperMessage}
641
+ * other members broadcast to the topic.
642
+ */
643
+ interface ClientWhisperSubscribeMessage {
644
+ topic: string;
645
+ type: "whisper_subscribe" | "whisper_unsubscribe";
646
+ }
647
+ /**
648
+ * Broadcast ephemeral `data` to the topic's other members on the shard. The
649
+ * payload is relayed verbatim with no server-side persistence (no SQLite/CDC
650
+ * write) — for typing indicators, live cursors, presence pings. The sender does
651
+ * not receive its own whisper.
652
+ */
653
+ interface ClientWhisperMessage {
654
+ data?: unknown;
655
+ topic: string;
656
+ type: "whisper";
657
+ }
658
+ type ClientMessage = ClientAckMessage | ClientConnectMessage | ClientShapeSubscribeMessage | ClientShapeUnsubscribeMessage | ClientStreamMessage | ClientSubscribeMessage | ClientUnsubscribeMessage | ClientWhisperMessage | ClientWhisperSubscribeMessage;
659
+ /** Subscription protocol — server → client. */
660
+ interface ServerDataMessage {
661
+ /**
662
+ * The `__cdc_log` high-watermark covered by this frame (Pillar 1b). The
663
+ * client persists it as the query's `serverCursor` and replays it as
664
+ * `sinceSeq` on the next reconnect. Absent on shards that never enabled CDC.
665
+ */
666
+ cursor?: number;
667
+ data?: unknown;
668
+ delta?: unknown;
669
+ /** The CDC epoch this frame's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
670
+ epoch?: string;
671
+ id: string;
672
+ /**
673
+ * The highest custom-mutator `mutationId` from this client the server has
674
+ * now applied (the per-client `__client_watermark`). Echoed so the client's
675
+ * outbox can drop confirmed pending mutations and let TanStack DB collapse
676
+ * the matching optimistic overlay. Absent on shards without custom mutators.
677
+ */
678
+ lastMutationId?: number;
679
+ type: "data" | "delta";
680
+ }
681
+ /**
682
+ * Lightweight resume acknowledgement (Pillar 1b): the server determined that
683
+ * nothing the subscription reads changed since the client's `sinceSeq`, so it
684
+ * skips re-sending the snapshot. The client keeps its cached value and only
685
+ * advances `serverCursor` to `cursor`.
686
+ */
687
+ interface ServerResumeMessage {
688
+ cursor?: number;
689
+ /** The CDC epoch this resume's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
690
+ epoch?: string;
691
+ id: string;
692
+ /** Per-client custom-mutator watermark (see {@link ServerDataMessage.lastMutationId}). */
693
+ lastMutationId?: number;
694
+ type: "resume";
695
+ }
696
+ /**
697
+ * Settled acknowledgement for a **list** subscription: a write touched one of
698
+ * the subscription's read tables but produced a byte-identical result, so the
699
+ * server suppressed the data frame. Sent ONLY to a `@lunora/db` custom-mutator
700
+ * client (one that announced a `clientId`, hence has a server-side
701
+ * `__client_watermark`) so its optimistic list overlay drops even when no data
702
+ * frame arrives. Plain `useQuery` subscribers never receive it, and an older
703
+ * client safely ignores the unknown frame.
704
+ */
705
+ interface ServerSettledMessage {
706
+ cursor?: number;
707
+ /** The CDC epoch this settled frame's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
708
+ epoch?: string;
709
+ id: string;
710
+ /**
711
+ * The highest custom-mutator `mutationId` from this client the server has
712
+ * now applied (the per-client `__client_watermark`). Forwarded to a
713
+ * collection's `onCheckpoint` so it can drop the overlay for the confirmed
714
+ * write whose result didn't change this list.
715
+ */
716
+ lastMutationId?: number;
717
+ type: "settled";
718
+ }
719
+ interface ServerErrorMessage {
720
+ error?: unknown;
721
+ id?: string;
722
+ message?: string;
723
+ type: "error";
724
+ }
725
+ interface ServerAckMessage {
726
+ id: string;
727
+ type: "ack";
728
+ }
729
+ interface ServerCompleteMessage {
730
+ id: string;
731
+ type: "complete";
732
+ }
733
+ /** One frame of a streaming query — `data` carries the user-yielded chunk. */
734
+ interface ServerChunkMessage {
735
+ data: unknown;
736
+ id: string;
737
+ type: "chunk";
738
+ }
739
+ /**
740
+ * An ephemeral whisper relayed from another member of `topic` on the same shard
741
+ * (AnyCable-style whispering). `data` is the sender's payload verbatim; `from`
742
+ * is the sender's verified user id when known (absent for an anonymous sender).
743
+ * Never persisted server-side.
744
+ */
745
+ interface ServerWhisperMessage {
746
+ data: unknown;
747
+ from?: string;
748
+ topic: string;
749
+ type: "whisper";
750
+ }
751
+ /**
752
+ * One row-level change in a shape's replication stream — the wire form of the
753
+ * DO's `__cdc_log` `CdcChange`. `insert`/`update` carry the post-image in
754
+ * `value` (projected to the shape's `columns`); `delete` omits it, identifying
755
+ * the removed row by `key` alone. The client applies these to its local
756
+ * collection; an unknown `key` on a `delete` is a safe no-op (a row the client
757
+ * never had in this shape).
758
+ */
759
+ interface RowOp {
760
+ /** Row primary key (`_id`). */
761
+ key: string;
762
+ op: "delete" | "insert" | "update";
763
+ /** Logical table the row belongs to. */
764
+ table: string;
765
+ /** Post-image document for insert/update; absent on delete. */
766
+ value?: Record<string, unknown>;
767
+ }
768
+ /**
769
+ * Opens a **poke** — an atomically-applied batch of shape diffs (Zero's poke
770
+ * protocol). A `pokeStart` is followed by zero or more {@link ServerPokePartMessage}
771
+ * frames and closed by exactly one {@link ServerPokeEndMessage}; the client
772
+ * buffers every part and applies them in a single transaction at `pokeEnd`, so a
773
+ * socket that drops mid-poke simply re-seeds on reconnect (no torn view).
774
+ */
775
+ interface ServerPokeStartMessage {
776
+ /** The checkpoint the client's view is expected to be at before this poke applies (for ordering/gap detection). */
777
+ baseCheckpoint?: number;
778
+ /** CDC epoch this poke belongs to; a mismatch forces the client to re-seed rather than apply. */
779
+ epoch?: string;
780
+ /** Correlates this poke's `pokeStart`/`pokePart`/`pokeEnd` frames. */
781
+ pokeId: string;
782
+ type: "pokeStart";
783
+ }
784
+ /** One shape's slice of an in-flight poke: the row-ops to apply for `shapeId`. */
785
+ interface ServerPokePartMessage {
786
+ /** Per-client custom-mutator watermark carried with this slice (see {@link ServerSettledMessage.lastMutationId}). */
787
+ lastMutationId?: number;
788
+ pokeId: string;
789
+ /** Ordered row-level changes for this shape, applied in sequence at `pokeEnd`. */
790
+ rowsPatch: RowOp[];
791
+ /** The {@link ClientShapeSubscribeMessage.id} these row-ops belong to. */
792
+ shapeId: string;
793
+ type: "pokePart";
794
+ }
795
+ /**
796
+ * Closes a poke: the client commits the buffered parts atomically and advances
797
+ * its checkpoint to {@link ServerPokeEndMessage.checkpoint} (the `__cdc_log`
798
+ * cursor high-watermark the view now reflects), replayed as `sinceCheckpoint` on
799
+ * the next reconnect.
800
+ */
801
+ interface ServerPokeEndMessage {
802
+ /** The `__cdc_log` cursor the view is at after applying this poke. */
803
+ checkpoint?: number;
804
+ /** CDC epoch the {@link ServerPokeEndMessage.checkpoint} belongs to. */
805
+ epoch?: string;
806
+ pokeId: string;
807
+ type: "pokeEnd";
808
+ }
809
+ type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerPokeEndMessage | ServerPokePartMessage | ServerPokeStartMessage | ServerResumeMessage | ServerSettledMessage | ServerWhisperMessage;
810
+ /**
811
+ * The authenticated user as exposed client-side, mirroring better-auth's
812
+ * `user` row (the `user` field of the `get-session` response). Kept minimal
813
+ * and structural — only `id` is guaranteed; the rest are the common better-auth
814
+ * fields, and the index signature carries any plugin-contributed extras.
815
+ */
816
+ interface User {
817
+ readonly createdAt?: NullableTimestamp;
818
+ readonly email?: null | string;
819
+ readonly emailVerified?: boolean | null;
820
+ readonly id: string;
821
+ readonly image?: null | string;
822
+ readonly name?: null | string;
823
+ readonly [key: string]: unknown;
824
+ readonly updatedAt?: NullableTimestamp;
825
+ }
826
+ /**
827
+ * One pending scheduled function, as returned by the worker's
828
+ * `GET /_lunora/admin/scheduled` endpoint. Mirrors `@lunora/scheduler`'s
829
+ * `ScheduleRecord` structurally so the client carries no dependency on it.
830
+ */
831
+ interface ScheduleRecord {
832
+ args: Record<string, unknown>;
833
+ /**
834
+ * Dispatch attempts already made. Absent (treated as 0) until the first
835
+ * failure; on a dead-letter record it is the exhausted count (> the retry
836
+ * budget). Surfaced so the studio can show how hard a job tried before it
837
+ * was parked.
838
+ */
839
+ attempts?: number;
840
+ enqueuedAt: number;
841
+ functionPath: string;
842
+ id: string;
843
+ /** Logical workpool the job is routed to (concurrency-gated), when any. */
844
+ pool?: string;
845
+ scheduledFor: number;
846
+ shardKey?: string;
847
+ }
848
+ /**
849
+ * One workpool's live backlog, as returned by the worker's
850
+ * `GET /_lunora/admin/scheduled/status` endpoint. Mirrors `@lunora/scheduler`'s
851
+ * `SchedulerPoolStatus` structurally so the client carries no dependency on it.
852
+ */
853
+ interface SchedulerPoolStatus {
854
+ /** Jobs currently dispatched-but-not-yet-completed (the held concurrency slots). */
855
+ inFlight: number;
856
+ /** The pool's concurrency cap. */
857
+ maxConcurrency: number;
858
+ /** The logical workpool name. */
859
+ name: string;
860
+ /** Pending jobs routed to this pool but not yet dispatched. */
861
+ queued: number;
862
+ }
863
+ /**
864
+ * The app-level scheduler backlog, as returned by the worker's
865
+ * `GET /_lunora/admin/scheduled/status` endpoint. `pools` is the per-pool
866
+ * breakdown; `backlog` and `inFlight` are the app-wide sums of `queued` and
867
+ * `inFlight` across every pool — the headline numbers for the studio SLO
868
+ * view. Mirrors `@lunora/scheduler`'s `SchedulerStatus` structurally.
869
+ */
870
+ interface SchedulerStatus {
871
+ /** Sum of every pool's `queued` count — the total pending backlog. */
872
+ backlog: number;
873
+ /** Sum of every pool's `inFlight` count — the total held concurrency slots. */
874
+ inFlight: number;
875
+ /** Per-pool backlog breakdown. */
876
+ pools: SchedulerPoolStatus[];
877
+ }
878
+ /**
879
+ * One shard's request volume, as returned by the worker's
880
+ * `POST /_lunora/admin/shard-traffic` endpoint. The cross-shard traffic feed
881
+ * the studio's `hot_shard` advisor lint consumes: `requests` is the shard's
882
+ * lifetime dispatch total, `shardKey` the DO id name (`""` for the root shard).
883
+ */
884
+ interface ShardTrafficEntry {
885
+ requests: number;
886
+ shardKey: string;
887
+ }
888
+ /**
889
+ * The whole-shard-set traffic distribution returned by the worker's
890
+ * `POST /_lunora/admin/shard-traffic` endpoint. `shards` is one entry per live
891
+ * shard (a failed shard surfaces with `requests: 0`); `ok`/`failed` count the
892
+ * shards that returned vs. errored. Shaped to feed the advisor's `hot_shard`
893
+ * lint after the studio tags each entry with its sharded function `group`.
894
+ */
895
+ interface ShardTrafficResult {
896
+ failed: number;
897
+ ok: number;
898
+ shards: ShardTrafficEntry[];
899
+ }
900
+ /**
901
+ * One object in the storage bucket, as returned by the worker's
902
+ * `GET /_lunora/admin/storage` endpoint. Mirrors `@lunora/storage`'s
903
+ * `R2ObjectLike` structurally.
904
+ */
905
+ interface StorageObject {
906
+ customMetadata?: Record<string, string>;
907
+ etag: string;
908
+ httpMetadata?: {
909
+ contentType?: string;
910
+ };
911
+ key: string;
912
+ size: number;
913
+ /**
914
+ * When the object was stored. R2 emits a `Date`, which JSON-serializes to an
915
+ * ISO string over the wire; a mock may supply epoch ms — so consumers should
916
+ * normalise via `new Date(uploaded)`. Absent if the backend didn't report it.
917
+ */
918
+ uploaded?: number | string;
919
+ }
920
+ /** One page of {@link StorageObject}s plus the cursor to fetch the next, if any. */
921
+ interface StorageListPage {
922
+ cursor?: string;
923
+ objects: StorageObject[];
924
+ }
925
+ /**
926
+ * One argument of a registered function, derived from its `v.*` validator by the
927
+ * worker. A compact signature shape — enough to render a function's API without
928
+ * the build-time codegen types.
929
+ */
930
+ interface FunctionArgumentDescriptor {
931
+ /** Element validator kind for an `array` arg (one level), e.g. `string`. */
932
+ element?: string;
933
+ /** The (optional-unwrapped) validator kind, e.g. `string`, `id`, `object`. */
934
+ kind: string;
935
+ /** The argument name. */
936
+ name: string;
937
+ /** True when the arg is wrapped in `v.optional(...)`. */
938
+ optional: boolean;
939
+ /** Target table for an `id` arg (`v.id("table")`). */
940
+ table?: string;
941
+ }
942
+ /**
943
+ * One registered function, as returned by the worker's
944
+ * `GET /_lunora/admin/functions` endpoint: its `&lt;file>:&lt;function>` path, which
945
+ * client method (`query` / `mutation` / `action`) invokes it, and its argument
946
+ * signature. `args` is absent on responses from an older worker.
947
+ */
948
+ interface FunctionDescriptor {
949
+ args?: FunctionArgumentDescriptor[];
950
+ kind: "action" | "mutation" | "query";
951
+ path: string;
952
+ }
953
+ /** A `.global()` (D1-backed) table plus its row count, from `/_lunora/admin/global/tables`. */
954
+ interface GlobalTableInfo {
955
+ name: string;
956
+ rowCount: number;
957
+ }
958
+ /** A window of rows from one global table, from `/_lunora/admin/global/table`. */
959
+ interface GlobalTablePage {
960
+ columns: string[];
961
+ /** FK columns (local column → referenced table) for external tables with real `REFERENCES` constraints, from `PRAGMA foreign_key_list`. */
962
+ refs?: Record<string, string>;
963
+ rows: Record<string, unknown>[];
964
+ total: number;
965
+ }
966
+ /**
967
+ * One equality constraint a facet-value click adds to the global browser's view
968
+ * (`column = value`). `value` is the raw stored scalar the facet returned, sent
969
+ * as-is and bound server-side, so it never injects SQL.
970
+ */
971
+ interface GlobalFilterClause {
972
+ column: string;
973
+ value: unknown;
974
+ }
975
+ /** One distinct value of a faceted global column with its row count, from `/_lunora/admin/global/facet`. */
976
+ interface GlobalFacetValue {
977
+ count: number;
978
+ value: unknown;
979
+ }
980
+ /** Per-column distinct-value summary for the global browser, from `/_lunora/admin/global/facet`. */
981
+ interface GlobalFacetResult {
982
+ truncated: boolean;
983
+ values: GlobalFacetValue[];
984
+ }
985
+ /** A nullable timestamp field as better-auth serializes it: epoch-ms, ISO string, or null. */
986
+ type NullableTimestamp = null | number | string;
987
+ /** A workflow instance's lifecycle status. Mirrors Cloudflare's `InstanceStatus`. */
988
+ type WorkflowInstanceStatus = "complete" | "errored" | "paused" | "queued" | "running" | "terminated" | "unknown" | "waiting" | "waitingForPause";
989
+ /** The lifecycle mutations the status endpoint accepts. */
990
+ type WorkflowInstanceAction = "pause" | "resume" | "terminate";
991
+ /** One row of the workflow-instances list. */
992
+ interface WorkflowInstanceSummary {
993
+ createdOn?: string;
994
+ endedOn?: string;
995
+ id: string;
996
+ startedOn?: string;
997
+ status: WorkflowInstanceStatus;
998
+ }
999
+ /** One durable step of an instance's execution timeline. */
1000
+ interface WorkflowStepDetail {
1001
+ /** 1-based attempt count (`> 1` means the step retried). */
1002
+ attempts?: number;
1003
+ end?: string;
1004
+ error?: unknown;
1005
+ name: string;
1006
+ output?: unknown;
1007
+ start?: string;
1008
+ success?: boolean;
1009
+ /** `step` / `sleep` / `waitForEvent` / … (Cloudflare's step `type`). */
1010
+ type?: string;
1011
+ }
1012
+ /** A workflow instance's full detail: summary plus params/output/error and the step timeline. */
1013
+ interface WorkflowInstanceDetail extends WorkflowInstanceSummary {
1014
+ error?: unknown;
1015
+ output?: unknown;
1016
+ params?: unknown;
1017
+ steps: WorkflowStepDetail[];
1018
+ }
1019
+ /** A page of workflow instances. */
1020
+ interface WorkflowInstancePage {
1021
+ /**
1022
+ * Whether workflow inspection is configured on the worker (a Cloudflare
1023
+ * account id + API token). `false` when the admin proxy reports it can't
1024
+ * inspect instances; omitted (treated as configured) otherwise. Lets a
1025
+ * caller render a "set credentials" state without a failed request.
1026
+ */
1027
+ configured?: boolean;
1028
+ instances: WorkflowInstanceSummary[];
1029
+ page: number;
1030
+ perPage: number;
1031
+ totalCount?: number;
1032
+ }
1033
+ type SubscriptionCallback = (data: unknown) => void;
1034
+ /** A subscription-scoped error the server pushed for this subscription id. */
1035
+ interface SubscriptionError {
1036
+ code?: string;
1037
+ message: string;
1038
+ }
1039
+ type SubscriptionErrorCallback = (error: SubscriptionError) => void;
1040
+ /**
1041
+ * One active per-call optimistic transform layered onto a subscription. The
1042
+ * displayed value is the authoritative {@link SubscriptionState.serverBase}
1043
+ * folded through every layer's `transform`, in order — so an incoming server
1044
+ * frame re-folds the still-pending layers onto the new base (rebasing) instead
1045
+ * of clobbering them. A layer is dropped — gaplessly — once a `data`/`delta`
1046
+ * frame whose `cursor >= commitCursor` arrives (its write is now reflected in
1047
+ * `serverBase`); `commitCursor` is the CDC cursor the server echoed on the
1048
+ * mutation's response, and stays `undefined` while the write is still queued/
1049
+ * in-flight (so the overlay survives unrelated deltas until confirmed).
1050
+ */
1051
+ interface OptimisticLayer {
1052
+ /** The committed CDC cursor (from the mutation response); `undefined` until confirmed. */
1053
+ commitCursor?: number;
1054
+ readonly id: symbol;
1055
+ readonly transform: (current: unknown) => unknown;
1056
+ }
1057
+ interface SubscriptionState {
1058
+ /** True once the server has acked the subscription on the current socket. */
1059
+ acked: boolean;
1060
+ readonly args: Record<string, unknown>;
1061
+ /**
1062
+ * Stable wire-key of `args` (`stableWireKey`), computed once at subscribe
1063
+ * time. Cached so the optimistic-update fan-out can compare against a
1064
+ * mutation's args key without re-serializing every subscription's args on
1065
+ * every mutation.
1066
+ */
1067
+ readonly argsKey: string;
1068
+ readonly callbacks: Set<SubscriptionCallback>;
1069
+ /**
1070
+ * Notified when a `settled` frame advances this subscription's watermark — a
1071
+ * write touched the subscription's tables but the result was byte-identical,
1072
+ * so the server suppressed the data frame. A `@lunora/db` list collection
1073
+ * uses this to drop the optimistic overlay for the confirmed write.
1074
+ *
1075
+ * A SET (not a single slot) because `SubscriptionState` is SHARED across
1076
+ * every subscriber to the same `(fn, args, shardKey)`: a `@lunora/db`
1077
+ * collection may subscribe to a query a plain `useQuery` already opened, so
1078
+ * each subscriber registers its own callback (mirroring `callbacks` /
1079
+ * `errorCallbacks`) and a `settled` frame fans out to all of them. Plain
1080
+ * `useQuery` consumers register nothing, leaving the set empty.
1081
+ */
1082
+ readonly checkpointCallbacks: Set<(watermark: {
1083
+ checkpoint?: number;
1084
+ mutationId?: number;
1085
+ }) => void>;
1086
+ /** Notified when the server rejects this subscription (e.g. admin auth). */
1087
+ readonly errorCallbacks: Set<SubscriptionErrorCallback>;
1088
+ readonly fn: FunctionReference;
1089
+ readonly id: string;
1090
+ /**
1091
+ * The highest custom-mutator `mutationId` from this client the server has
1092
+ * applied, captured from the last `settled` frame (the suppressed-list-frame
1093
+ * watermark). Forwarded to {@link SubscriptionState.checkpointCallbacks}.
1094
+ * Absent until a `settled` frame arrives.
1095
+ */
1096
+ lastMutationId?: number;
1097
+ /** Last known value, used to short-circuit `useQuery`-style consumers. */
1098
+ lastValue: unknown;
1099
+ /**
1100
+ * Active per-call optimistic layers, in application order (see
1101
+ * {@link OptimisticLayer}). Empty for subscriptions with no pending per-call
1102
+ * optimistic write — the common case, where `lastValue` tracks `serverBase`
1103
+ * exactly and behaviour is identical to a plain server-value assignment.
1104
+ */
1105
+ optimisticLayers: OptimisticLayer[];
1106
+ /**
1107
+ * The authoritative server value the optimistic layers fold onto — the value
1108
+ * with NO optimistic overlay. Tracks `lastValue` exactly whenever no layers
1109
+ * are active; diverges only while a per-call optimistic write is pending. A
1110
+ * server frame updates this (and re-folds the layers); the durable read cache
1111
+ * persists this, never the optimistic overlay.
1112
+ */
1113
+ serverBase: unknown;
1114
+ /**
1115
+ * The `__cdc_log` high-watermark (`cursor`) the `lastValue` reflects,
1116
+ * captured from the last `data`/`delta`/`resume` frame. Persisted to the
1117
+ * durable read cache and replayed as `sinceSeq` on reconnect so the server
1118
+ * can resume instead of re-snapshotting (Pillar 1b/2). Absent until the
1119
+ * first cursor-stamped frame arrives.
1120
+ */
1121
+ serverCursor?: number;
1122
+ /**
1123
+ * The CDC `epoch` token the `serverCursor` belongs to, captured from the
1124
+ * same frame. Replayed as `sinceEpoch` on reconnect so the server resumes
1125
+ * only when the client is still on the same changelog timeline — a reset or
1126
+ * recycled shard advertises a new epoch, forcing a fresh snapshot. Absent
1127
+ * until the first epoch-stamped frame arrives.
1128
+ */
1129
+ serverEpoch?: string;
1130
+ readonly shardKey?: string;
1131
+ }
1132
+ /**
1133
+ * Active subscription registry. The client keys subscriptions by
1134
+ * `(functionPath, stableWireKey(args), shardKey)` so duplicate calls share a
1135
+ * single server-side registration. Args are stably encoded (keys sorted at every
1136
+ * depth) so two structurally-equal arg records constructed with a different key
1137
+ * order (`{ a, b }` vs `{ b, a }`) collapse to the same key instead of leaking a
1138
+ * duplicate subscription. Encoding the args' **wire form** keeps the key
1139
+ * byte-identical for pure-JSON args while giving wire-typed args (`bigint`,
1140
+ * `Date`, bytes, …) distinct stable tokens instead of a throw.
1141
+ */
1142
+ declare class SubscriptionRegistry {
1143
+ static key(functionPath: string, args: Record<string, unknown>, shardKey?: string): string;
1144
+ private readonly byKey;
1145
+ private readonly byId;
1146
+ get(key: string): SubscriptionState | undefined;
1147
+ getById(id: string): SubscriptionState | undefined;
1148
+ add(state: SubscriptionState): void;
1149
+ remove(state: SubscriptionState): void;
1150
+ all(): SubscriptionState[];
1151
+ }
1152
+ /**
1153
+ * Read/write handle over the client's live query cache, handed to a mutation's
1154
+ * `withOptimisticUpdate` callback so a single mutation can optimistically patch
1155
+ * many subscribed queries at once (Convex's `OptimisticLocalStore` model).
1156
+ *
1157
+ * `getQuery` reads the current value (server value or any still-pending
1158
+ * optimistic override) of a subscribed query; `setQuery` registers a constant
1159
+ * optimistic layer on top. The whole batch rebases onto incoming deltas and
1160
+ * settles together — confirmed on the mutation's commit cursor, or rolled back
1161
+ * on failure — the same per-subscription layer machinery the single-query
1162
+ * per-call `optimistic` transform uses, generalized to N queries.
1163
+ */
1164
+ interface OptimisticLocalStore {
1165
+ /**
1166
+ * Every loaded subscription on `function_`, regardless of args, paired with
1167
+ * the args it was subscribed under. Mirrors Convex's `getAllQueries` — handy
1168
+ * when a write must patch every variant of a list query (all channels,
1169
+ * all filters) without enumerating their args up front.
1170
+ */
1171
+ getAllQueries: <F extends FunctionReference>(function_: F) => {
1172
+ args: ArgsOf<F>;
1173
+ value: ReturnOf<F> | undefined;
1174
+ }[];
1175
+ /**
1176
+ * Current cached value for the subscribed `(function_, args)` query, or
1177
+ * `undefined` when nothing is subscribed/loaded for it. Reflects any
1178
+ * optimistic override already written in this batch.
1179
+ */
1180
+ getQuery: <F extends FunctionReference>(function_: F, args: ArgsOf<F>) => ReturnOf<F> | undefined;
1181
+ /**
1182
+ * Write an optimistic override for the subscribed `(function_, args)`
1183
+ * query. A no-op (returns without effect) when no subscription matches —
1184
+ * mirroring Convex, where you only patch queries the page is watching.
1185
+ */
1186
+ setQuery: <F extends FunctionReference>(function_: F, args: ArgsOf<F>, value: ReturnOf<F> | undefined) => void;
1187
+ }
1188
+ /** A mutation's multi-query optimistic update: read/write the cache via `localStore`. */
1189
+ type OptimisticUpdate<Args> = (localStore: OptimisticLocalStore, args: Args) => void;
1190
+ /**
1191
+ * Build an {@link OptimisticLocalStore} bound to a subscription registry and the
1192
+ * mutation's shard key. Each `setQuery(value)` registers a constant-value layer
1193
+ * on its target subscription (via `applyOptimisticLayer`): the predicted value
1194
+ * survives incoming server deltas (re-clamped, masking concurrent changes to that
1195
+ * query — not merged) and drops gaplessly on the mutation's commit cursor, like
1196
+ * the single-query per-call `optimistic` path. Returns the store plus the ordered
1197
+ * `confirm` (success) and `rollback` (failure) closures every `setQuery` produced,
1198
+ * so the caller settles the whole batch when the mutation does.
1199
+ */
1200
+ declare const createLocalStore: (subscriptions: SubscriptionRegistry, shardKey: string | undefined) => {
1201
+ confirms: ((commitCursor: number | undefined) => void)[];
1202
+ rollbacks: (() => void)[];
1203
+ store: OptimisticLocalStore;
1204
+ };
1205
+ declare const DEFAULT_MAX_BUFFER = 1024;
1206
+ interface StreamHandle<T = unknown> {
1207
+ /** Mark the stream complete (no more chunks); resolves any pending consumer to `done:true`. */
1208
+ readonly complete: () => void;
1209
+ /** Surface an error to any pending consumer; subsequent pushes are dropped. */
1210
+ readonly fail: (error: Error) => void;
1211
+ /**
1212
+ * Push one chunk. Silent no-op once the stream is `complete`, `fail`-ed,
1213
+ * or `cancel`-ed. When the buffer is already at `maxBuffer`, the stream
1214
+ * is failed with a `STREAM_BACKPRESSURE` error and the push is dropped —
1215
+ * the producer never sees a thrown exception.
1216
+ */
1217
+ readonly push: (value: T) => void;
1218
+ }
1219
+ interface StreamIterable<T> extends AsyncIterable<T> {
1220
+ /** Cancel the stream from the consumer side: closes the iterator and notifies the registered canceller. */
1221
+ cancel: () => void;
1222
+ }
1223
+ /**
1224
+ * Build a stream handle paired with an async-iterable. The handle is the
1225
+ * server-driven side (the WS dispatcher pushes chunks / completes / errors);
1226
+ * the iterable is what the user awaits. `onCancel` is invoked exactly once
1227
+ * when the consumer calls `.cancel()` (or `.return()`) so the client can
1228
+ * send a `{type:"unsubscribe"}` frame to the server.
1229
+ */
1230
+ declare const createStream: <T>(options: {
1231
+ maxBuffer?: number;
1232
+ onCancel: () => void;
1233
+ }) => {
1234
+ handle: StreamHandle<T>;
1235
+ iterable: StreamIterable<T>;
1236
+ };
1237
+ /**
1238
+ * Aggregate live-socket health across every shard connection, for a UI status
1239
+ * indicator. `idle` = no socket opened yet; `connecting` = at least one socket
1240
+ * is (re)connecting and none is open; `connected` = at least one socket is open;
1241
+ * `offline` = sockets exist but all are down (between reconnect attempts).
1242
+ */
1243
+ type ConnectionStatus = "connected" | "connecting" | "idle" | "offline";
1244
+ /**
1245
+ * Terminal verdict for a mutation that passed through the offline queue,
1246
+ * delivered to {@link LunoraClient.onMutationSettled}.
1247
+ *
1248
+ * Unlike the Promise returned by {@link LunoraClient.mutation} — which only the
1249
+ * original caller can await, and which no longer exists after a reload — this
1250
+ * fires for *every* queued write the server (or the queue) reaches a verdict on,
1251
+ * including writes restored from durable storage in a later session. It is the
1252
+ * channel a UI uses to tell the user "your queued change couldn't be saved"
1253
+ * instead of silently dropping a rolled-back optimistic row.
1254
+ *
1255
+ * `status: "rejected"` carries the failure `code` (e.g. `CONFLICT`,
1256
+ * `OFFLINE_QUEUE_OVERFLOW`, `OFFLINE_IDENTITY_CHANGED`) and the `error`.
1257
+ * `hadAwaiter` is `false` for a write whose original `mutation()` Promise is
1258
+ * gone (a hydrated/post-reload replay or an eviction), so a listener can tell
1259
+ * "the caller already saw this" apart from "nothing else will report this".
1260
+ */
1261
+ interface MutationSettledEvent {
1262
+ /** The write's args, so a listener can describe or re-offer the change. */
1263
+ readonly args: Record<string, unknown>;
1264
+ /** Server/queue error code on `rejected` (e.g. `CONFLICT`), when present. */
1265
+ readonly code?: string;
1266
+ /** The rejection error on `status: "rejected"`. */
1267
+ readonly error?: unknown;
1268
+ /** The `&lt;file>:&lt;function>` reference of the mutation. */
1269
+ readonly functionPath: string;
1270
+ /** Whether a live caller was still awaiting this write's `mutation()` Promise. */
1271
+ readonly hadAwaiter: boolean;
1272
+ /** The write's stable id (idempotency key / queue id). */
1273
+ readonly id: string;
1274
+ /** Shard the write targeted, if any. */
1275
+ readonly shardKey?: string;
1276
+ /** Terminal outcome. */
1277
+ readonly status: "committed" | "rejected";
1278
+ }
1279
+ /**
1280
+ * Per-call options for {@link LunoraClient.mutation} — the optimistic-update
1281
+ * machinery plus `shardKey`. Exported (at the end of this file) so the framework
1282
+ * adapters (`@lunora/react`, `/solid`, `/svelte`, `/vue`) can type their
1283
+ * `mutate(args, options?)` against one canonical definition instead of
1284
+ * re-declaring it.
1285
+ */
1286
+ interface MutationCallOptions<TCurrent = unknown, TValue = unknown, TArgs = unknown> {
1287
+ /**
1288
+ * Override the auto-generated idempotency key (`x-lunora-mutation-id`). Lets a
1289
+ * durable outbox replay a committed-but-unacked write under its *original* key
1290
+ * so the server dedups it instead of applying it twice. Omit for normal calls —
1291
+ * each then gets a fresh key.
1292
+ */
1293
+ mutationId?: string;
1294
+ optimistic?: (current: TCurrent | undefined) => TValue;
1295
+ /**
1296
+ * Convex-parity multi-query optimistic update. Receives an
1297
+ * `OptimisticLocalStore` over the live subscription cache plus the
1298
+ * mutation's args, so one mutation can patch many subscribed queries at
1299
+ * once; every write is rolled back atomically if the mutation fails.
1300
+ */
1301
+ optimisticUpdate?: OptimisticUpdate<TArgs>;
1302
+ /**
1303
+ * Sync predicate evaluated just before the offline queue replays this
1304
+ * write on reconnect. When it returns `false` the mutation is dropped
1305
+ * instead of replayed — use it to guard against replaying writes whose
1306
+ * assumptions are no longer valid (e.g. the document it referred to was
1307
+ * deleted by another client while this tab was offline).
1308
+ */
1309
+ precondition?: () => boolean;
1310
+ shardKey?: string;
1311
+ }
1312
+ /** Callback a shape subscription invokes with its materialized rowset on every applied poke. */
1313
+ type ShapeCallback = (rows: Record<string, unknown>[]) => void;
1314
+ /**
1315
+ * The high-water marks a shape poke has now synced to the client: `checkpoint`
1316
+ * is the op-log cursor and `mutationId` the highest custom-mutator id the server
1317
+ * echoed for this client. A `@lunora/db` collection feeds these into its
1318
+ * checkpoint registry to drop optimistic overlays once the server's authoritative
1319
+ * rows have landed.
1320
+ */
1321
+ interface SyncWatermark {
1322
+ checkpoint?: number;
1323
+ mutationId?: number;
1324
+ }
1325
+ /**
1326
+ * An `Error` carrying the server's machine-readable `code` and (for a
1327
+ * `LunoraError`) structured `data`, plus an optional actionable `hint` (Markdown)
1328
+ * and `docsUrl` resolved from the central error catalog. The client's public
1329
+ * error contract for RPC/batch failures — a UI can render `hint`/`docsUrl` to
1330
+ * tell the user how to fix the error. The `(string & {})` arm keeps
1331
+ * forward-compat/unknown server codes assignable without losing autocomplete on
1332
+ * the known {@link LunoraErrorCode} union.
1333
+ */
1334
+ type LunoraClientError = Error & {
1335
+ code?: LunoraErrorCode | (string & {});
1336
+ data?: unknown;
1337
+ docsUrl?: string;
1338
+ hint?: string | string[];
1339
+ };
1340
+ /** One demuxed result slot of a {@link LunoraClient.batch} call (plan 088). */
1341
+ type BatchSlot = {
1342
+ error: LunoraClientError;
1343
+ ok: false;
1344
+ } | {
1345
+ ok: true;
1346
+ value: unknown;
1347
+ };
1348
+ /**
1349
+ * Lunora browser/edge client. Talks RPC over HTTP and real-time deltas over
1350
+ * a single multiplexed WebSocket.
1351
+ *
1352
+ * Reconnect, offline queueing, and optimistic updates are all handled here;
1353
+ * see the package README for the wire protocol.
1354
+ */
1355
+ declare class LunoraClient {
1356
+ /** 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. */
1357
+ private static readonly MAX_POKE_BUFFERS;
1358
+ /**
1359
+ * Create a typed {@link ClientQueryRef}. Convenience wrapper around
1360
+ * {@link createClientQuery} so you don't need a separate import.
1361
+ * @example
1362
+ * ```ts
1363
+ * const sidebarOpen = LunoraClient.createClientQuery("sidebarOpen", true);
1364
+ * ```
1365
+ */
1366
+ static createClientQuery<T>(key: string, defaultValue: T): ClientQueryRef<T>;
1367
+ readonly url: string;
1368
+ readonly wsUrl: string;
1369
+ /** Local reactive store for {@link ClientQueryRef} values — no server round-trip. Private; reach it via `getClientQuery` / `setClientQuery` / `subscribeClientQuery`. */
1370
+ private readonly clientQueryStore;
1371
+ private wsToken;
1372
+ /** Better-auth base path (trailing slash stripped) for the `get-session` lookup. */
1373
+ private readonly authBasePath;
1374
+ private readonly fetchImpl;
1375
+ private readonly WebSocketImpl;
1376
+ private readonly bookmark;
1377
+ private readonly reconnectOptions;
1378
+ /** WS connect timeout (ms); `0` disables it. See {@link LunoraClientOptions.connectTimeoutMs}. */
1379
+ private readonly connectTimeoutMs;
1380
+ /** Keepalive cadence (ms); `0` disables the heartbeat. See {@link LunoraClientOptions.heartbeatIntervalMs}. */
1381
+ private readonly heartbeatIntervalMs;
1382
+ private readonly offlineQueue;
1383
+ /**
1384
+ * Durable outbox seam (the `@lunora/db` `createExecutorOutboxSink`). When
1385
+ * set, offline writes are delegated here and the built-in {@link OfflineQueue}
1386
+ * is bypassed, so a db app has exactly one durable write path.
1387
+ */
1388
+ private readonly outbox;
1389
+ /** Stable per-client id stamped onto every `OutboxMutation` (custom-mutator watermark). */
1390
+ private readonly clientId;
1391
+ /**
1392
+ * `true` when the constructor's hydration microtask has finished loading the
1393
+ * durable read cache (Pillar 2) into `hydratedQueryCache`. Signals that
1394
+ * the cache is ready for synchronous `peekHydratedQuery` reads.
1395
+ */
1396
+ private readyResolved;
1397
+ /** Resolvers for `whenReady()` — called once hydration completes. */
1398
+ private readyResolve;
1399
+ /**
1400
+ * Promise that resolves once the durable read cache has been loaded. When
1401
+ * `hydrateOnStart` is not set or no query cache is configured, resolves
1402
+ * immediately (the constructor creates an already-resolved promise).
1403
+ */
1404
+ private readonly readyPromise;
1405
+ /**
1406
+ * Highest custom-mutator watermark the server has echoed for this client,
1407
+ * keyed by shard bucket (`shardKey ?? ""`) since the DO tracks one
1408
+ * `__client_watermark` per shard. `callMutator` bumps it from every
1409
+ * ack; the `@lunora/db` mutator runtime seeds its `clientSeq` generator from
1410
+ * it so a reload (which resets the in-memory counter) never reissues a stale
1411
+ * sequence the server would silently swallow as a replay.
1412
+ */
1413
+ private readonly clientWatermarks;
1414
+ /** Monotonic per-client mutation counter backing the server `__client_watermark`. */
1415
+ private outboxMutationCounter;
1416
+ private readonly onPersistenceError;
1417
+ private readonly persistence;
1418
+ /** App/schema version stamped on persisted writes + cached reads; mismatches are purged. */
1419
+ private readonly persistenceVersion;
1420
+ /** Releases the multi-tab outbox-leader Web Lock on close (see `hydrateAsOutboxLeader`). */
1421
+ private outboxLeaderRelease;
1422
+ /** Durable read cache (Pillar 2); `undefined` when `queryCache` is omitted or `false`. */
1423
+ private readonly queryCache;
1424
+ /**
1425
+ * Values restored from the `queryCache` at construction, keyed by the
1426
+ * read-cache key, awaiting the `subscribe()` that will consume them. A
1427
+ * key is consumed (deleted) the first time its subscription is created, so
1428
+ * the cache only ever seeds the initial value — live frames take over after.
1429
+ */
1430
+ private readonly hydratedQueryCache;
1431
+ /**
1432
+ * Coalesced read-cache writes: the latest value per key, flushed to
1433
+ * the `queryCache` on a short debounce so a burst of deltas persists once.
1434
+ */
1435
+ private readonly pendingCacheWrites;
1436
+ private cacheFlushTimer;
1437
+ private readonly subscriptions;
1438
+ /**
1439
+ * Cross-tab coordinator; created only when `crossTabSync: true`. When the
1440
+ * client is not the elected leader, all WebSocket operations are skipped.
1441
+ * Not `readonly` — `close()` clears it (mirrors `outboxLeaderRelease`).
1442
+ */
1443
+ private tabCoordinator;
1444
+ /** One {@link ShardConnection} per shard key (keyed by `shardKey ?? ""`). */
1445
+ private readonly connections;
1446
+ /** Default `connect`-envelope context applied to a shard with no explicit override. */
1447
+ private readonly defaultConnectionContext;
1448
+ /**
1449
+ * Per-shard `connect`-envelope context registered via `setConnectionContext`
1450
+ * (keyed by `shardKey ?? ""`), overriding `defaultConnectionContext`. Sent
1451
+ * on every socket open so it replays across reconnects, and forwarded to the
1452
+ * server's `onConnect`/`onDisconnect` lifecycle hooks. This holds only the
1453
+ * imperative (last-writer-wins) override; refcounted holders registered via
1454
+ * `acquireConnectionContext` live in `connectionContextHolders` and take
1455
+ * precedence — see `effectiveConnectionContext`.
1456
+ */
1457
+ private readonly connectionContexts;
1458
+ /**
1459
+ * Per-shard stack of refcounted connection-context holders (keyed by
1460
+ * `shardKey ?? ""`), registered via `acquireConnectionContext`. Each holder
1461
+ * is an opaque token carrying its `context`; the most-recently acquired
1462
+ * holder wins (last-writer-wins among live holders), and the context is only
1463
+ * cleared for a shard once its last holder releases — so two concurrently
1464
+ * mounted presence hooks on the same shard can't stomp each other's context
1465
+ * on cleanup. A holder is identified by reference identity so a release
1466
+ * removes exactly the right one regardless of stack position.
1467
+ */
1468
+ private readonly connectionContextHolders;
1469
+ private authToken;
1470
+ /**
1471
+ * Optional STABLE identity subject (a user id), the basis of the offline-queue
1472
+ * identity stamp when supplied. Keeps a same-user token *refresh* from looking
1473
+ * like an identity change (which would discard queued writes). `undefined` =
1474
+ * not supplied, so identity falls back to a hash of the raw token. See
1475
+ * `setAuthToken` / `identityFingerprint`.
1476
+ */
1477
+ private authSubject;
1478
+ /**
1479
+ * Identity stamp recorded against each queued offline mutation, keyed by
1480
+ * the queue-assigned mutation id. Captured at enqueue from the auth token
1481
+ * in effect at the time, and re-checked at flush so a queued write can
1482
+ * never replay under a different identity than the one that issued it.
1483
+ * See `identityFingerprint` for the fingerprint shape.
1484
+ */
1485
+ private readonly queuedIdentities;
1486
+ private closed;
1487
+ /** Subscribers to auth-token changes (see `onAuthTokenChange`). */
1488
+ private readonly authTokenListeners;
1489
+ /** Subscribers to aggregate connection-status changes (see `onConnectionStatus`). */
1490
+ private readonly statusListeners;
1491
+ /** Subscribers notified when the server drops a socket for an expired token (see `onTokenExpired`). */
1492
+ private readonly tokenExpiredListeners;
1493
+ /** Subscribers to offline-queued mutation verdicts (see `onMutationSettled`). */
1494
+ private readonly mutationSettledListeners;
1495
+ /** Subscribers to the offline-queue pending-count (see `onPendingChange`). */
1496
+ private readonly pendingChangeListeners;
1497
+ /**
1498
+ * Whisper-topic handlers, keyed by `connectionKey(shardKey)` → topic → set
1499
+ * of callbacks. Membership doubles as the resubscribe set replayed on every
1500
+ * (re)connect so a topic survives a socket bounce.
1501
+ */
1502
+ private readonly whisperHandlers;
1503
+ /** Last status broadcast, so we only notify listeners on an actual change. */
1504
+ private lastStatus;
1505
+ private nextSubId;
1506
+ private nextStreamId;
1507
+ /**
1508
+ * In-flight client-side stream readers, keyed by the stream id sent on the
1509
+ * wire. The handle drives the underlying iterator queue and `shardKey`
1510
+ * tells us which socket to push the cancel frame onto when the consumer
1511
+ * calls `.cancel()` or the iterator is garbage-collected.
1512
+ */
1513
+ private readonly streams;
1514
+ /** Live shape subscriptions (partial replication), keyed by their wire id. */
1515
+ private readonly shapeSubscriptions;
1516
+ /** In-flight pokes being assembled between `pokeStart` and `pokeEnd`, keyed by `pokeId`. */
1517
+ private readonly pokeBuffers;
1518
+ private nextShapeId;
1519
+ constructor(options: LunoraClientOptions);
1520
+ /**
1521
+ * Set (or clear) the bearer token sent on every HTTP RPC. Notifies any
1522
+ * {@link onAuthTokenChange} listeners so React hooks like `useAuth` stay in
1523
+ * sync across all mounted instances.
1524
+ *
1525
+ * Pass a STABLE `subject` (the user id) to key the offline-queue identity on
1526
+ * it instead of the token bytes, so a token *refresh* (same user, new JWT)
1527
+ * doesn't read as an identity change and discard queued writes. The subject is
1528
+ * **sticky**: a later call that omits it (or passes `undefined`) keeps the
1529
+ * established subject — so `setAuthToken(refreshedToken)` after a prior
1530
+ * `setAuthToken(token, user.id)` retains the identity. Pass `null` to clear it
1531
+ * (an explicit sign-out). Establishing the subject for the first time on an
1532
+ * UNCHANGED token (e.g. the user id resolves a tick after the token was set)
1533
+ * re-stamps any in-flight queued writes rather than dropping them — same
1534
+ * credential, just a more stable label. A real user switch (the token AND
1535
+ * subject both change) still drops the previous user's writes.
1536
+ *
1537
+ * Does NOT update the WebSocket auth — the WS token is fixed at upgrade
1538
+ * time and lives in the URL. To refresh live WS auth, call
1539
+ * {@link setWsToken} explicitly, which closes existing shard sockets to
1540
+ * force a reconnect with the new credential.
1541
+ */
1542
+ setAuthToken(token: string | null, subject?: string | null): void;
1543
+ getAuthToken(): string | null;
1544
+ /**
1545
+ * The current identity fingerprint (the same stamp queued offline writes
1546
+ * carry). Exposed so a durable {@link OutboxSink}'s replay handler — which
1547
+ * owns its own at-least-once replay outside the built-in `OfflineQueue` —
1548
+ * can drop a persisted write whose captured `identity` no longer matches the
1549
+ * signed-in user, the guard the queue path applies in `flushOfflineQueue`.
1550
+ */
1551
+ currentIdentity(): string | null;
1552
+ /** This client's stable identifier — the watermark key the server's custom-mutator protocol advances per `clientSeq`. */
1553
+ clientIdentifier(): string;
1554
+ /**
1555
+ * The highest custom-mutator watermark the server has echoed for this client
1556
+ * on the given shard (0 if none yet). The `@lunora/db` mutator runtime seeds
1557
+ * its `clientSeq` generator from this so a reload never reissues a sequence
1558
+ * the server has already applied (which it would swallow as a replay, silently
1559
+ * dropping the write).
1560
+ */
1561
+ confirmedMutationWatermark(shardKey?: string): number;
1562
+ /**
1563
+ * Push a custom mutator to its authoritative server impl over the watermark
1564
+ * protocol (Phase 4): the request carries `x-lunora-client-id` + a monotonic
1565
+ * `x-lunora-client-seq`, so the DO runs it exactly once and advances this
1566
+ * client's `__client_watermark`.
1567
+ *
1568
+ * Returns the server `result` plus `applied`: `true` when the DO ran this push
1569
+ * as the next-in-order mutation, `false` when it was a replay ack (`clientSeq`
1570
+ * was at or below the stored watermark — e.g. a stale sequence after a reload).
1571
+ * A `false` verdict tells the caller to reissue above the now-known watermark
1572
+ * (echoed into {@link confirmedMutationWatermark}) rather than treat the benign
1573
+ * ack as a confirmed write. Every ack — applied or not — bumps the watermark.
1574
+ *
1575
+ * This is the online transport for `@lunora/db`'s client-mutator runtime; the
1576
+ * optimistic overlay + durable-outbox concerns live in that runtime, not here.
1577
+ */
1578
+ callMutator(functionPath: string, args: Record<string, unknown>, options?: {
1579
+ clientSeq?: number;
1580
+ shardKey?: string;
1581
+ }): Promise<{
1582
+ applied: boolean;
1583
+ result: unknown;
1584
+ }>;
1585
+ /**
1586
+ * Subscribe to auth-token changes. Returns an unsubscribe function. The
1587
+ * listener is NOT invoked on registration — use {@link getAuthToken} for
1588
+ * the current value.
1589
+ */
1590
+ onAuthTokenChange(listener: (token: string | null) => void): Unsubscribe;
1591
+ /**
1592
+ * Fetch the currently authenticated user from better-auth's `get-session`
1593
+ * endpoint, returning the `user` record or `null` when signed out. Sends
1594
+ * the stored bearer token (if any) and `credentials: "include"` so a
1595
+ * cookie-session is also honoured. A network/parse failure or a non-OK
1596
+ * response resolves to `null` rather than throwing — callers treat "couldn't
1597
+ * resolve identity" as "signed out".
1598
+ *
1599
+ * Framework-agnostic: pair it with {@link onAuthTokenChange} to refetch when
1600
+ * the token changes (that's what `@lunora/react`'s `useAuth` does).
1601
+ */
1602
+ getCurrentUser(): Promise<User | null>;
1603
+ /**
1604
+ * Replace the token appended to WS upgrade URLs as `?token=…` and close
1605
+ * every open shard socket so the reconnect picks up the new value. Call
1606
+ * this whenever the user's WS credential changes (rotating the admin token
1607
+ * in the studio, switching workspaces, etc.). Accepts a static string or a
1608
+ * {@link WsTokenProvider} resolved fresh at every (re)connect — the channel
1609
+ * for short-lived credentials like the minted ephemeral admin sub-token.
1610
+ * Bearer tokens for HTTP RPC are independent — see {@link setAuthToken}.
1611
+ */
1612
+ setWsToken(token: string | undefined | WsTokenProvider): void;
1613
+ /**
1614
+ * Register (or clear, with `undefined`) the app context sent in the `connect`
1615
+ * envelope for a shard's socket, overriding the client-wide
1616
+ * {@link LunoraClientOptions.connectionContext}. The server forwards it to the
1617
+ * `onConnect`/`onDisconnect` lifecycle hooks as `event.context` — e.g.
1618
+ * `@lunora/react`'s `usePresence` registers `{ roomId, sessionId }` so the
1619
+ * presence row is removed the instant the socket drops, with no TTL lag.
1620
+ *
1621
+ * Stored per shard and replayed on every (re)connect. When a socket for the
1622
+ * shard is already open, a fresh `connect` envelope is sent immediately so the
1623
+ * server sees the new context without waiting for a reconnect.
1624
+ */
1625
+ setConnectionContext(context: Record<string, unknown> | undefined, options?: {
1626
+ shardKey?: string;
1627
+ }): void;
1628
+ /**
1629
+ * Refcounted variant of {@link setConnectionContext}: register a connection
1630
+ * `context` for a shard and get back a release function. Unlike the imperative
1631
+ * setter, the context is only cleared once the *last* acquired holder releases
1632
+ * it — so two components (e.g. two mounted `usePresence` hooks) on the same
1633
+ * shard no longer clobber each other's context when one of them unmounts. The
1634
+ * most-recently acquired live holder wins (last-writer-wins), and releasing
1635
+ * the top holder falls back to the previous one rather than clearing.
1636
+ *
1637
+ * With a single holder the behaviour is identical to a
1638
+ * `setConnectionContext(context)` / `setConnectionContext(undefined)` pair.
1639
+ * Releasing more than once is a no-op (the holder is matched by reference, so
1640
+ * a double release can't drop a different holder).
1641
+ */
1642
+ acquireConnectionContext(context: Record<string, unknown>, options?: {
1643
+ shardKey?: string;
1644
+ }): Unsubscribe;
1645
+ /**
1646
+ * Join a whisper `topic` and receive every ephemeral message other members
1647
+ * broadcast to it on the same shard (typing indicators, live cursors,
1648
+ * presence pings). Whispers never touch the server's durable state — there's
1649
+ * no query, no row, no CDC entry. Returns an unsubscribe function; the topic
1650
+ * is left on the server once its last local handler unsubscribes.
1651
+ *
1652
+ * `handler` receives the raw `data` and the sender's verified `from` user id
1653
+ * (omitted for an anonymous sender). The topic is scoped to `options.shardKey`
1654
+ * (the default shard when omitted) — use the same shard you target with the
1655
+ * matching queries/mutations so members land on the same Durable Object.
1656
+ *
1657
+ * Security: whisper topics are NOT access-controlled beyond the shard
1658
+ * boundary — any client that can open a socket to the shard can join, read,
1659
+ * and inject on any topic name. `from` is server-stamped and unforgeable, but
1660
+ * do not put data on a whisper topic that some shard members shouldn't see,
1661
+ * and don't trust a whisper's `data` as authorization. Use a query/mutation
1662
+ * (with RLS) for anything privileged; whispers are for transient awareness.
1663
+ */
1664
+ whisperSubscribe(topic: string, handler: (data: unknown, from?: string) => void, options?: {
1665
+ shardKey?: string;
1666
+ }): Unsubscribe;
1667
+ /**
1668
+ * Broadcast an ephemeral `data` payload to the other members of a whisper
1669
+ * `topic` on `options.shardKey`'s shard. Fire-and-forget: the frame is
1670
+ * dropped when the shard socket isn't open (whispers are transient, never
1671
+ * queued), and the server silently drops it if the sender exceeds its
1672
+ * whisper rate budget. The sender never receives its own whisper. Omitting
1673
+ * `data` delivers JSON `null` to receivers (not `undefined`).
1674
+ */
1675
+ whisper(topic: string, data?: unknown, options?: {
1676
+ shardKey?: string;
1677
+ }): void;
1678
+ /**
1679
+ * Subscribe to token-expiry events: invoked whenever the server drops a
1680
+ * shard socket because the connection's credential lapsed (close code
1681
+ * `4001`). The client already reconnects automatically (re-resolving
1682
+ * identity from the cookie/token in effect); use this to refresh a
1683
+ * short-lived token first — e.g. call {@link setWsToken} / {@link setAuthToken}
1684
+ * with a freshly minted one. Returns an unsubscribe function.
1685
+ */
1686
+ onTokenExpired(listener: () => void): Unsubscribe;
1687
+ /**
1688
+ * Current aggregate live-socket status across all shard connections. See
1689
+ * {@link ConnectionStatus}.
1690
+ */
1691
+ connectionStatus(): ConnectionStatus;
1692
+ /**
1693
+ * Subscribe to aggregate connection-status changes. Invokes `listener`
1694
+ * immediately with the current status, then on every transition. Returns an
1695
+ * unsubscribe function.
1696
+ */
1697
+ onConnectionStatus(listener: (status: ConnectionStatus) => void): Unsubscribe;
1698
+ /**
1699
+ * Number of offline writes waiting in the built-in queue to be sent — the
1700
+ * depth for a "N changes waiting to sync" indicator. Counts writes that are
1701
+ * queued (offline / mid-reconnect), not ones already in flight on the wire.
1702
+ * A `@lunora/db` app whose writes ride the unified outbox should read
1703
+ * `LunoraDb.pendingCount()` instead (this counts only the built-in queue).
1704
+ */
1705
+ pendingCount(): number;
1706
+ /**
1707
+ * Subscribe to changes in {@link pendingCount}. Invokes `listener` immediately
1708
+ * with the current count, then whenever the queue depth changes (a write is
1709
+ * enqueued, flushed, or discarded). Returns an unsubscribe function.
1710
+ */
1711
+ onPendingChange(listener: (pending: number) => void): Unsubscribe;
1712
+ /**
1713
+ * Subscribe to terminal verdicts for offline-queued mutations. The listener
1714
+ * fires once per queued write that commits or is rejected — including a write
1715
+ * restored from durable storage after a reload, whose original `mutation()`
1716
+ * Promise no longer exists (`hadAwaiter: false`), and a write the queue
1717
+ * evicts on overflow or discards on an identity change. This is the durable
1718
+ * channel for surfacing a rolled-back optimistic write to the UI; an online
1719
+ * mutation that never queued still surfaces through the Promise `mutation()`
1720
+ * returns. The listener is NOT invoked on registration. Returns an
1721
+ * unsubscribe function. See {@link MutationSettledEvent}.
1722
+ */
1723
+ onMutationSettled(listener: (event: MutationSettledEvent) => void): Unsubscribe;
1724
+ /**
1725
+ * The `WebSocket` implementation this client was constructed with (an
1726
+ * explicit `options.WebSocket`, or the ambient global on platforms that have
1727
+ * one) — `undefined` if neither is available. This is the seam a feature
1728
+ * that opens its OWN socket outside the client's multiplexed connection
1729
+ * (e.g. a voice-agent hook) should default to, instead of reaching for
1730
+ * `globalThis.WebSocket` directly: on React Native the client wraps this
1731
+ * constructor to inject the auth-headers factory's credential onto the
1732
+ * upgrade request (`createLunoraClient`'s `withAuthWebSocket`), which a raw
1733
+ * `new globalThis.WebSocket(url)` would silently bypass.
1734
+ */
1735
+ getWebSocketImpl(): typeof WebSocket | undefined;
1736
+ /**
1737
+ * Read the current value for a {@link ClientQueryRef}. Returns
1738
+ * `ref.defaultValue` when no value has been explicitly set.
1739
+ */
1740
+ getClientQuery<T>(ref: ClientQueryRef<T>): T;
1741
+ /**
1742
+ * Set a new value for `ref` and notify every subscriber. Pass `undefined`
1743
+ * to reset the slot to `ref.defaultValue`.
1744
+ */
1745
+ setClientQuery<T>(ref: ClientQueryRef<T>, value: T): void;
1746
+ /**
1747
+ * Subscribe to changes for `ref`. The callback is NOT invoked on
1748
+ * registration — call {@link getClientQuery} for the current value.
1749
+ * Returns an unsubscribe function.
1750
+ */
1751
+ subscribeClientQuery(ref: ClientQueryRef, callback: (value: unknown) => void): Unsubscribe;
1752
+ /**
1753
+ * Reset a {@link ClientQueryRef} to its default value, notifying every
1754
+ * subscriber. Equivalent to `setClientQuery(ref, ref.defaultValue)` but
1755
+ * removes the stored entry so a future {@link getClientQuery} returns
1756
+ * the default rather than an explicitly-set value.
1757
+ */
1758
+ resetClientQuery(ref: ClientQueryRef): void;
1759
+ /**
1760
+ * Capture a snapshot of the current live query value at call time and
1761
+ * produce a `() => boolean` precondition that compares it against the
1762
+ * value at replay time (on queue drain / reconnect).
1763
+ *
1764
+ * When the precondition is checked it re-reads the query's current value
1765
+ * via `peekActiveQueryValue`. If the value differs from what was
1766
+ * captured at call time the precondition returns `false` and the offline
1767
+ * mutation is dropped as stale.
1768
+ *
1769
+ * This is a method wrapper around `createSnapshotPrecondition` that
1770
+ * binds the client instance for you — no need to pass `client` explicitly.
1771
+ * @example
1772
+ * ```ts
1773
+ * client.mutation(api.todos.update, { id, text }, {
1774
+ * precondition: client.snapshotPrecondition(api.todos.list, { userId }),
1775
+ * });
1776
+ * ```
1777
+ */
1778
+ snapshotPrecondition(functionRef: FunctionReference, args: Record<string, unknown>, shardKey?: string): () => boolean;
1779
+ /**
1780
+ * Resolves once the durable read cache has been loaded into memory. When
1781
+ * `hydrateOnStart` is not configured or no query cache adapter is active,
1782
+ * returns an already-resolved promise so callers can always await it
1783
+ * unconditionally.
1784
+ *
1785
+ * Framework adapters (React, Vue, etc.) use this to gate the first
1786
+ * (enabled) render of a live query behind hydration, so the user sees
1787
+ * cached data instead of an undefined flash before the socket round-trip.
1788
+ */
1789
+ whenReady(): Promise<void>;
1790
+ /**
1791
+ * Synchronously reports whether {@link whenReady} has already resolved (the
1792
+ * durable read cache is loaded, or none is configured). Framework adapters
1793
+ * read this to seed the hydration-gate state on the first render without
1794
+ * awaiting, then subscribe via {@link whenReady} for the pending case.
1795
+ */
1796
+ get isReady(): boolean;
1797
+ /**
1798
+ * Synchronously peek at a value the durable read cache loaded for the given
1799
+ * function path + args + shard key. Returns `undefined` when:
1800
+ *
1801
+ * - No query cache adapter is configured.
1802
+ * - Hydration hasn't completed yet (race — await {@link whenReady} first).
1803
+ * - The cached value's identity fingerprint doesn't match the current auth.
1804
+ *
1805
+ * Unlike the internal {@link takeHydratedCache}, this is a READ-ONLY peek:
1806
+ * the cached entry stays in `hydratedQueryCache` so the subscription created
1807
+ * later by {@link subscribe} consumes it normally.
1808
+ */
1809
+ peekHydratedQuery(functionPath: string, args: Record<string, unknown>, shardKey?: string): unknown;
1810
+ /**
1811
+ * Peek at the **current live value** of an active subscription, if one
1812
+ * exists. Returns the subscription's `lastValue` (which includes any
1813
+ * optimistic overlay) or `undefined` if no subscription is active for the
1814
+ * given `(functionPath, args, shardKey)`.
1815
+ *
1816
+ * Unlike {@link peekHydratedQuery} (which reads from the durable read cache
1817
+ * and is independent of active subscriptions), this method reflects the
1818
+ * current in-memory state of an already-opened subscription — useful for
1819
+ * offline mutation preconditions that need to snapshot the value at call time
1820
+ * and compare it at replay time.
1821
+ */
1822
+ peekActiveQueryValue(functionPath: string, args: Record<string, unknown>, shardKey?: string): unknown;
1823
+ query<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: {
1824
+ shardKey?: string;
1825
+ }): Promise<ReturnOf<F>>;
1826
+ /**
1827
+ * Batch several independent calls into ONE round trip (plan 088). Each call is
1828
+ * dispatched server-side exactly as an individual RPC — per-shard
1829
+ * authorization, `(identity, mutationId)` idempotency, and custom-mutator
1830
+ * watermark ordering are all preserved — and the worker splits the batch by
1831
+ * shard so calls to different shards fan out to their own DOs. Results are
1832
+ * demuxed back in input order; a failing call does NOT fail the batch (its
1833
+ * slot carries `{ ok: false, error }`, with `.code`/`.data` reconstructed like
1834
+ * a single call). Args/results ride the value codec (bytes/bigint survive).
1835
+ *
1836
+ * No promise pipelining and no capability passing — a call's args cannot
1837
+ * reference another call's result (see plan 088 §fence; capabilities are
1838
+ * incompatible with DO hibernation).
1839
+ */
1840
+ batch(calls: ReadonlyArray<{
1841
+ args?: Record<string, unknown>;
1842
+ fn: FunctionReference;
1843
+ shardKey?: string;
1844
+ }>): Promise<BatchSlot[]>;
1845
+ /**
1846
+ * Invoke a mutation. Errors propagate as rejections.
1847
+ *
1848
+ * Offline-queue semantics: a mutation is queued (and replayed on reconnect)
1849
+ * only when the targeted shard's socket was open at least once already
1850
+ * (`wasEverConnected`), so the registry / resubscribe handshake has run.
1851
+ * Mutations issued before the very first WS connect to a shard fail fast.
1852
+ * Opt into queueing-before-first-connect via
1853
+ * `OfflineQueueOptions.queueBeforeFirstConnect`.
1854
+ */
1855
+ mutation<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>): Promise<ReturnOf<F>>;
1856
+ action<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: {
1857
+ shardKey?: string;
1858
+ }): Promise<ReturnOf<F>>;
1859
+ /**
1860
+ * Read the cross-shard request distribution for a `.shardBy(...)` table —
1861
+ * the feed the studio's `hot_shard` advisor lint consumes. Hits the
1862
+ * admin-gated `POST /_lunora/admin/shard-traffic` endpoint, which fans the
1863
+ * cheap per-shard `getMetrics` read out across every live shard and returns
1864
+ * each shard's `{ shardKey, requests }` total (a failed shard surfaces with
1865
+ * `requests: 0`). Requires the worker to be built with a `queryCoordinator`
1866
+ * and `adminToken`, and this client's auth token to match; defaults any
1867
+ * absent field so an older worker yields an empty-but-valid shape.
1868
+ */
1869
+ shardTraffic(table: string): Promise<ShardTrafficResult>;
1870
+ /**
1871
+ * List the functions queued via `runAfter` / `runAt`, soonest-due last
1872
+ * (the worker returns them in storage order). Hits the admin-gated
1873
+ * `/_lunora/admin/scheduled` endpoint, so the worker must be built with a
1874
+ * `schedulerDO` namespace and `adminToken`, and this client's auth token
1875
+ * must match. Powers `@lunora/studio`'s scheduled-jobs panel.
1876
+ */
1877
+ listScheduledJobs(): Promise<ScheduleRecord[]>;
1878
+ /**
1879
+ * Read the app-level workpool backlog that powers `@lunora/studio`'s SLO
1880
+ * view: per-pool `{ name, queued, inFlight, maxConcurrency }` plus the
1881
+ * app-wide `backlog` (total queued) and `inFlight` (total held slots) sums.
1882
+ * Hits the admin-gated `GET /_lunora/admin/scheduled/status` endpoint, so the
1883
+ * same preconditions as {@link listScheduledJobs} apply (a `schedulerDO`
1884
+ * namespace + `adminToken` on the worker and a matching auth token here).
1885
+ * Defaults any absent field so an older worker still yields a valid shape.
1886
+ */
1887
+ schedulerStatus(): Promise<SchedulerStatus>;
1888
+ /** Cancel a pending scheduled job by id. Returns whether a job was removed. */
1889
+ cancelScheduledJob(id: string): Promise<{
1890
+ cancelled: boolean;
1891
+ }>;
1892
+ /**
1893
+ * List the dead-letter jobs: schedules that exhausted their retry budget
1894
+ * and were parked instead of dropped. These never appear in
1895
+ * {@link listScheduledJobs} (their live header is gone), so this is the only
1896
+ * way the studio surfaces a permanently-failed job. Hits the admin-gated
1897
+ * `GET /_lunora/admin/scheduled/dead`; same preconditions as
1898
+ * {@link listScheduledJobs}. Powers `@lunora/studio`'s dead-letter panel.
1899
+ */
1900
+ listDeadJobs(): Promise<ScheduleRecord[]>;
1901
+ /**
1902
+ * Resurrect a dead-letter job by id: it re-enters the schedule with a fresh
1903
+ * retry budget and fires on the next drain. Returns whether a parked record
1904
+ * matched. Hits the admin-gated `POST /_lunora/admin/scheduled/dead/retry`.
1905
+ */
1906
+ retryDeadJob(id: string): Promise<{
1907
+ retried: boolean;
1908
+ }>;
1909
+ /**
1910
+ * Permanently drop a dead-letter job by id (the operator has decided not to
1911
+ * recover it). Returns whether a parked record was removed. Hits the
1912
+ * admin-gated `POST /_lunora/admin/scheduled/dead/cancel`.
1913
+ */
1914
+ removeDeadJob(id: string): Promise<{
1915
+ removed: boolean;
1916
+ }>;
1917
+ /**
1918
+ * List a workflow's instances via the admin Workflows proxy
1919
+ * (`/_lunora/admin/workflows/instances`) — the Cloudflare control-plane data
1920
+ * the `Workflow` binding can't expose. Requires the worker to be built with a
1921
+ * `workflowsClient` (Cloudflare account id + API token). When one isn't
1922
+ * configured this does NOT reject: the proxy returns a `200 { configured:
1923
+ * false }` sentinel, so the result resolves with `configured === false` and an
1924
+ * empty `instances` list — callers should branch on that flag rather than
1925
+ * try/catch. (The instance-detail / status endpoints still reject with 501.)
1926
+ * `name` is the deployed workflow name.
1927
+ */
1928
+ listWorkflowInstances(options: {
1929
+ name: string;
1930
+ page?: number;
1931
+ perPage?: number;
1932
+ status?: WorkflowInstanceStatus;
1933
+ }): Promise<WorkflowInstancePage>;
1934
+ /** Read one workflow instance with its step timeline (`/_lunora/admin/workflows/instance`). */
1935
+ getWorkflowInstance(options: {
1936
+ id: string;
1937
+ name: string;
1938
+ }): Promise<WorkflowInstanceDetail>;
1939
+ /** Pause / resume / terminate a workflow instance (`/_lunora/admin/workflows/status`). Needs an Edit-scoped Cloudflare token. */
1940
+ setWorkflowInstanceStatus(options: {
1941
+ action: WorkflowInstanceAction;
1942
+ id: string;
1943
+ name: string;
1944
+ }): Promise<{
1945
+ status: WorkflowInstanceStatus;
1946
+ }>;
1947
+ /**
1948
+ * Subscribe to the live scheduled-jobs list over the SchedulerDO's admin
1949
+ * WebSocket. `onJobs` fires with the full list on connect and on every
1950
+ * change (schedule / cancel / alarm-fire). Reconnects with the client's
1951
+ * configured backoff. Requires `wsToken` to be set to an admin credential
1952
+ * (the browser can't send an `Authorization` header on a WS) — the master
1953
+ * token, or preferably a {@link WsTokenProvider} minting the ephemeral
1954
+ * sub-token so the master credential stays out of the URL. Returns an
1955
+ * unsubscribe function that closes the socket and stops reconnecting.
1956
+ */
1957
+ subscribeScheduledJobs(onJobs: (jobs: ScheduleRecord[]) => void): Unsubscribe;
1958
+ /**
1959
+ * List the registered public functions (queries / mutations / actions) with
1960
+ * their kinds. Hits the admin-gated `GET /_lunora/admin/functions` endpoint —
1961
+ * the worker must be built with a `functions` registry and `adminToken`, and
1962
+ * this client's auth token must match. Powers `@lunora/studio`'s function
1963
+ * runner auto-discovery.
1964
+ */
1965
+ listFunctions(): Promise<FunctionDescriptor[]>;
1966
+ /**
1967
+ * List the code-defined cron triggers (the `cronJobs()` map injected on the
1968
+ * worker), each flattened to its firing `cron` expression. Hits the
1969
+ * admin-gated `GET /_lunora/admin/cron-jobs` endpoint — the worker must be
1970
+ * built with a `cronJobs` map and `adminToken`, and this client's auth token
1971
+ * must match. These are static (Cloudflare exposes no runtime cron
1972
+ * introspection), so the studio renders them read-only alongside the dynamic
1973
+ * scheduler jobs.
1974
+ */
1975
+ getCronJobs(): Promise<CronJobInfo[]>;
1976
+ /**
1977
+ * Manually fire one code-defined cron job by name — the same dispatch the
1978
+ * scheduled trigger runs (dispatch the function, or start the durable
1979
+ * workflow), on demand. Hits the admin-gated `POST /_lunora/admin/cron-jobs/run`
1980
+ * endpoint; the worker must be built with a `cronJobs` map and `adminToken`,
1981
+ * and this client's auth token must match. Resolves when the job has run (a
1982
+ * function job's shard response is 2xx, or the workflow instance was created)
1983
+ * and rejects with the dispatch error otherwise.
1984
+ */
1985
+ runCronJob(name: string): Promise<{
1986
+ name: string;
1987
+ ran: boolean;
1988
+ }>;
1989
+ /**
1990
+ * Fetch the generated OpenAPI 3.1 document. Hits the admin-gated
1991
+ * `GET /_lunora/admin/openapi` endpoint — the worker must be built with an
1992
+ * `openApiSpec` and `adminToken`, and this client's auth token must match.
1993
+ * Powers `@lunora/studio`'s API-reference (Scalar) view. When the worker has
1994
+ * no spec wired, the endpoint still resolves with an empty-but-valid OpenAPI
1995
+ * document (no `paths`), so callers can render a "not configured" state.
1996
+ */
1997
+ fetchOpenApi(): Promise<Record<string, unknown>>;
1998
+ /**
1999
+ * Fetch the generated OpenRPC 1.x document. Hits the admin-gated
2000
+ * `GET /_lunora/admin/openrpc` endpoint — the worker must be built with an
2001
+ * `openRpcSpec` and `adminToken`, and this client's auth token must match.
2002
+ * OpenRPC is the RPC-native spec (a `methods` array over the JSON-RPC-shaped
2003
+ * `POST /_lunora/rpc` transport); it documents the RPC functions only.
2004
+ * Powers `@lunora/studio`'s OpenRPC API-reference view. When the worker has
2005
+ * no spec wired, the endpoint still resolves with an empty-but-valid OpenRPC
2006
+ * document (no `methods`), so callers can render a "not configured" state.
2007
+ */
2008
+ fetchOpenRpc(): Promise<Record<string, unknown>>;
2009
+ /**
2010
+ * List objects in the storage bucket, optionally under a `prefix` and from a
2011
+ * pagination `cursor`. Hits the admin-gated `GET /_lunora/admin/storage`
2012
+ * endpoint — the worker must be built with a `storageList` function and
2013
+ * `adminToken`, and this client's auth token must match. Powers
2014
+ * `@lunora/studio`'s file browser.
2015
+ */
2016
+ listStorageObjects(options?: {
2017
+ bucket?: string;
2018
+ cursor?: string;
2019
+ limit?: number;
2020
+ prefix?: string;
2021
+ }): Promise<StorageListPage>;
2022
+ /**
2023
+ * Delete one object from the storage bucket by key. Hits the admin-gated
2024
+ * `DELETE /_lunora/admin/storage?key=…` endpoint — the worker must be built
2025
+ * with a `storageDelete` function and `adminToken`. Powers the studio file
2026
+ * browser's per-row delete; resolves `{ deleted, key }`.
2027
+ */
2028
+ deleteStorageObject(key: string, options?: {
2029
+ bucket?: string;
2030
+ }): Promise<{
2031
+ deleted: boolean;
2032
+ key: string;
2033
+ }>;
2034
+ /**
2035
+ * List the storage bucket names the worker exposes, for the studio file
2036
+ * browser's bucket picker. Hits the admin-gated
2037
+ * `GET /_lunora/admin/storage/buckets` endpoint — always resolves (an empty
2038
+ * array when the worker configures no `storageBuckets`, i.e. single-bucket).
2039
+ */
2040
+ listStorageBuckets(): Promise<string[]>;
2041
+ /**
2042
+ * Upload one object to the storage bucket. Hits the admin-gated
2043
+ * `PUT /_lunora/admin/storage?key=…` endpoint with the raw body and an
2044
+ * optional `contentType` header — the worker must be built with a
2045
+ * `storageUpload` function and `adminToken`. Powers the studio file
2046
+ * browser's upload control; resolves `{ etag?, key }`.
2047
+ */
2048
+ uploadStorageObject(options: {
2049
+ body: ArrayBuffer | Blob;
2050
+ bucket?: string;
2051
+ contentType?: string;
2052
+ key: string;
2053
+ }): Promise<{
2054
+ etag?: string;
2055
+ key: string;
2056
+ }>;
2057
+ /**
2058
+ * Build a (signed or public) URL for one object. Hits the admin-gated
2059
+ * `GET /_lunora/admin/storage/url?key=…` endpoint — the worker must be built
2060
+ * with a `storageSignedUrl` function and `adminToken`. Powers the studio
2061
+ * file browser's copy-URL action; resolves the URL string.
2062
+ *
2063
+ * `options.expiresInSeconds` requests a share-link lifetime, which is
2064
+ * validated/clamped server-side. The options object mirrors the worker's
2065
+ * `StorageSignedUrlFunction` options (a `password` / download-limit are noted
2066
+ * as future fields there).
2067
+ */
2068
+ signedStorageUrl(key: string, options?: {
2069
+ bucket?: string;
2070
+ expiresInSeconds?: number;
2071
+ }): Promise<string>;
2072
+ /**
2073
+ * List the `.global()` (D1-backed) tables with their row counts. Hits the
2074
+ * admin-gated `GET /_lunora/admin/global/tables` endpoint — the worker must
2075
+ * be built with a `globalIntrospector` and `adminToken`. Powers the data
2076
+ * browser's global mode.
2077
+ */
2078
+ listGlobalTables(): Promise<GlobalTableInfo[]>;
2079
+ /**
2080
+ * Read a page of rows from one `.global()` table. `filters` AND-narrows the
2081
+ * page to rows matching each `column = value` eq constraint — the drill-down a
2082
+ * facet-value click applies; the array is JSON-encoded into the `filters`
2083
+ * query param and the values are bound server-side.
2084
+ */
2085
+ readGlobalTablePage(options: {
2086
+ filters?: GlobalFilterClause[];
2087
+ limit?: number;
2088
+ offset?: number;
2089
+ table: string;
2090
+ }): Promise<GlobalTablePage>;
2091
+ /**
2092
+ * Summarise the distinct values of one column in a `.global()` table over the
2093
+ * active view (the same eq `filters` the browser is previewing) — the global
2094
+ * twin of the shard browser's facet. Hits the admin-gated
2095
+ * `GET /_lunora/admin/global/facet` endpoint; `column` is validated + bound
2096
+ * server-side. Powers the global data browser's facet sidebar.
2097
+ */
2098
+ facetGlobalColumn(options: {
2099
+ column: string;
2100
+ filters?: GlobalFilterClause[];
2101
+ limit?: number;
2102
+ table: string;
2103
+ }): Promise<GlobalFacetResult>;
2104
+ /**
2105
+ * List the schema's Vectorize indexes with their declared shape (table,
2106
+ * field, dimensions, metric, metadata) and live stats (vector count,
2107
+ * processing watermark) when the binding is reachable. Hits the admin-gated
2108
+ * `GET /_lunora/admin/vector/indexes` endpoint — the worker must be built
2109
+ * with a `vectorIntrospector` and `adminToken`. Powers the studio's vector
2110
+ * browser. Vectorize can't enumerate indexes at runtime, so this list comes
2111
+ * from the generated `LUNORA_VECTOR_INDEXES` registry.
2112
+ */
2113
+ listVectorIndexes(): Promise<VectorIndexSummary[]>;
2114
+ /**
2115
+ * Run a nearest-neighbour similarity query against one vector index: the
2116
+ * worker embeds `text` via the index's embedder and returns the top matches.
2117
+ * Hits the admin-gated `POST /_lunora/admin/vector/query` endpoint. Throws
2118
+ * `VECTOR_QUERY_UNSUPPORTED` when the worker's introspector has no embedder
2119
+ * wired (the index lists read-only).
2120
+ */
2121
+ queryVectorIndex(options: {
2122
+ name: string;
2123
+ text: string;
2124
+ topK?: number;
2125
+ }): Promise<VectorQueryMatch[]>;
2126
+ /**
2127
+ * List the worker's registered Workers KV namespaces (binding names). Hits
2128
+ * the admin-gated `GET /_lunora/admin/kv/namespaces` endpoint — the worker
2129
+ * must be built with a `kvIntrospector` and `adminToken`. Powers the
2130
+ * studio's KV browser.
2131
+ */
2132
+ listKvNamespaces(): Promise<KvNamespaceSummary[]>;
2133
+ /**
2134
+ * List keys in a KV namespace, optionally filtered by `prefix` and
2135
+ * paginated via `cursor`. Hits the admin-gated
2136
+ * `GET /_lunora/admin/kv/keys` endpoint.
2137
+ */
2138
+ listKvKeys(options: {
2139
+ cursor?: string;
2140
+ limit?: number;
2141
+ namespace: string;
2142
+ prefix?: string;
2143
+ }): Promise<KvKeyListResult>;
2144
+ /**
2145
+ * Read a KV value (as text) and its metadata. Hits the admin-gated
2146
+ * `GET /_lunora/admin/kv/value` endpoint. Returns `{ value: null, metadata: null }`
2147
+ * when the key is absent.
2148
+ */
2149
+ getKvValue(options: {
2150
+ key: string;
2151
+ namespace: string;
2152
+ }): Promise<KvValueResult>;
2153
+ /**
2154
+ * Write a string value to a KV namespace. Accepts an absolute `expiration`
2155
+ * (Unix seconds) or a relative `expirationTtl`, plus optional `metadata` —
2156
+ * re-send the loaded values on edit so a save preserves rather than clears
2157
+ * them. Hits the admin-gated `PUT /_lunora/admin/kv/value` endpoint.
2158
+ */
2159
+ putKvValue(options: {
2160
+ expiration?: number;
2161
+ expirationTtl?: number;
2162
+ key: string;
2163
+ metadata?: unknown;
2164
+ namespace: string;
2165
+ value: string;
2166
+ }): Promise<void>;
2167
+ /**
2168
+ * Delete a key from a KV namespace. No-op when the key is absent. Hits the
2169
+ * admin-gated `DELETE /_lunora/admin/kv/value` endpoint.
2170
+ */
2171
+ deleteKvKey(options: {
2172
+ key: string;
2173
+ namespace: string;
2174
+ }): Promise<void>;
2175
+ /**
2176
+ * List authenticated users, paged and optionally searched / filtered / sorted.
2177
+ * Hits the admin-gated `GET /_lunora/admin/auth/users` endpoint — the worker
2178
+ * must be built with an `authAdmin` and `adminToken`. Powers the studio's
2179
+ * users dashboard.
2180
+ */
2181
+ listAuthUsers(options?: {
2182
+ filterField?: string;
2183
+ filterValue?: string;
2184
+ limit?: number;
2185
+ offset?: number;
2186
+ search?: string;
2187
+ searchField?: string;
2188
+ sortBy?: string;
2189
+ sortDirection?: "asc" | "desc";
2190
+ }): Promise<AuthPage<AuthUser>>;
2191
+ /**
2192
+ * Create a user. Hits the admin-gated `POST /_lunora/admin/auth/users/create`
2193
+ * endpoint (requires the worker's `authAdmin` to implement `createUser`).
2194
+ * `data` carries any app-defined `user.additionalFields`.
2195
+ */
2196
+ createAuthUser(input: {
2197
+ data?: Record<string, unknown>;
2198
+ email: string;
2199
+ name: string;
2200
+ password?: string;
2201
+ role?: string | string[];
2202
+ }): Promise<AuthUser>;
2203
+ /** Set a user's role (string, or array joined comma-wise server-side). */
2204
+ setAuthUserRole(input: {
2205
+ role: string | string[];
2206
+ userId: string;
2207
+ }): Promise<AuthUser>;
2208
+ /** Ban a user. `expiresInSeconds` sets a temporary ban; omit it for a permanent one. Revokes the user's live sessions. */
2209
+ banAuthUser(input: {
2210
+ expiresInSeconds?: number;
2211
+ reason?: string;
2212
+ userId: string;
2213
+ }): Promise<AuthUser>;
2214
+ /** Lift a user's ban. */
2215
+ unbanAuthUser(input: {
2216
+ userId: string;
2217
+ }): Promise<AuthUser>;
2218
+ /** Set a user's password (admin override — no current-password challenge). */
2219
+ setAuthUserPassword(input: {
2220
+ newPassword: string;
2221
+ userId: string;
2222
+ }): Promise<void>;
2223
+ /** Permanently delete a user and revoke their sessions. */
2224
+ removeAuthUser(input: {
2225
+ userId: string;
2226
+ }): Promise<void>;
2227
+ /**
2228
+ * Mint an impersonation session for a user, returning its bearer `token`.
2229
+ * The caller is responsible for using the token (e.g. setting the session
2230
+ * cookie); the server performs no cookie round-trip.
2231
+ */
2232
+ impersonateAuthUser(input: {
2233
+ userId: string;
2234
+ }): Promise<AuthImpersonation>;
2235
+ /** Revoke a single session by its id (force sign-out of one device). */
2236
+ revokeAuthSession(input: {
2237
+ sessionId: string;
2238
+ }): Promise<void>;
2239
+ /** Revoke every session for a user (force sign-out everywhere). */
2240
+ revokeAuthUserSessions(input: {
2241
+ userId: string;
2242
+ }): Promise<void>;
2243
+ /**
2244
+ * Report which auth dashboard surfaces are available — derived server-side
2245
+ * from the enabled better-auth plugins. The studio renders only the panels
2246
+ * whose capability is `true`.
2247
+ */
2248
+ getAuthCapabilities(): Promise<AuthCapabilities>;
2249
+ /** Update a user's fields (name/email/app-defined `additionalFields`). */
2250
+ updateAuthUser(input: {
2251
+ data: Record<string, unknown>;
2252
+ userId: string;
2253
+ }): Promise<AuthUser>;
2254
+ /** List a user's linked accounts (credential / OAuth providers). Token material is stripped server-side. */
2255
+ listAuthAccounts(input: {
2256
+ userId: string;
2257
+ }): Promise<Record<string, unknown>[]>;
2258
+ /** Unlink a linked account from a user. */
2259
+ unlinkAuthAccount(input: {
2260
+ accountId: string;
2261
+ userId: string;
2262
+ }): Promise<void>;
2263
+ /** List a user's registered passkeys (requires the passkey plugin). */
2264
+ listAuthPasskeys(input: {
2265
+ userId: string;
2266
+ }): Promise<Record<string, unknown>[]>;
2267
+ /** Delete a passkey by id (requires the passkey plugin). */
2268
+ deleteAuthPasskey(input: {
2269
+ passkeyId: string;
2270
+ }): Promise<void>;
2271
+ /** Disable two-factor auth for a user (requires the two-factor plugin). */
2272
+ disableAuthTwoFactor(input: {
2273
+ userId: string;
2274
+ }): Promise<void>;
2275
+ /** List organizations, paged (requires the organization plugin). */
2276
+ listAuthOrganizations(options?: {
2277
+ limit?: number;
2278
+ offset?: number;
2279
+ }): Promise<AuthPage<Record<string, unknown>>>;
2280
+ /** List the members of an organization (requires the organization plugin). */
2281
+ listAuthOrgMembers(input: {
2282
+ limit?: number;
2283
+ offset?: number;
2284
+ organizationId: string;
2285
+ }): Promise<AuthPage<Record<string, unknown>>>;
2286
+ /** List an organization's pending invitations (requires the organization plugin). */
2287
+ listAuthOrgInvitations(input: {
2288
+ limit?: number;
2289
+ offset?: number;
2290
+ organizationId: string;
2291
+ }): Promise<AuthPage<Record<string, unknown>>>;
2292
+ /** Remove a member from an organization. */
2293
+ removeAuthOrgMember(input: {
2294
+ memberId: string;
2295
+ }): Promise<void>;
2296
+ /** Cancel a pending organization invitation. */
2297
+ cancelAuthOrgInvitation(input: {
2298
+ invitationId: string;
2299
+ }): Promise<void>;
2300
+ /**
2301
+ * Report the deployment's auth configuration — enabled plugins, sign-in
2302
+ * methods, user-settable create-user fields, organization sub-features
2303
+ * (teams / roles), and session / rate-limit policy. Drives the config panel
2304
+ * and the dynamic create-user form. Never carries a secret.
2305
+ */
2306
+ getAuthConfig(): Promise<AuthConfigInfo>;
2307
+ /** Create an organization; optionally seed an `owner` member for `ownerId`. */
2308
+ createAuthOrganization(input: {
2309
+ logo?: string;
2310
+ metadata?: Record<string, unknown>;
2311
+ name: string;
2312
+ ownerId?: string;
2313
+ slug?: string;
2314
+ }): Promise<Record<string, unknown>>;
2315
+ /** Update an organization's name/slug/logo/metadata. */
2316
+ updateAuthOrganization(input: {
2317
+ logo?: string;
2318
+ metadata?: Record<string, unknown>;
2319
+ name?: string;
2320
+ organizationId: string;
2321
+ slug?: string;
2322
+ }): Promise<Record<string, unknown>>;
2323
+ /** Delete an organization and cascade its members, invitations, teams, and custom roles. */
2324
+ deleteAuthOrganization(input: {
2325
+ organizationId: string;
2326
+ }): Promise<void>;
2327
+ /** Directly add an existing user to an organization (no invitation/acceptance). */
2328
+ addAuthOrgMember(input: {
2329
+ organizationId: string;
2330
+ role?: string;
2331
+ userId: string;
2332
+ }): Promise<Record<string, unknown>>;
2333
+ /** Create a pending email invitation to an organization. */
2334
+ inviteAuthOrgMember(input: {
2335
+ email: string;
2336
+ inviterId?: string;
2337
+ organizationId: string;
2338
+ role?: string;
2339
+ }): Promise<Record<string, unknown>>;
2340
+ /** Change a member's role. */
2341
+ setAuthOrgMemberRole(input: {
2342
+ memberId: string;
2343
+ role: string | string[];
2344
+ }): Promise<Record<string, unknown>>;
2345
+ /** List an organization's teams (requires the organization plugin with teams enabled). */
2346
+ listAuthOrgTeams(input: {
2347
+ limit?: number;
2348
+ offset?: number;
2349
+ organizationId: string;
2350
+ }): Promise<AuthPage<Record<string, unknown>>>;
2351
+ /** Create a team under an organization. */
2352
+ createAuthOrgTeam(input: {
2353
+ name: string;
2354
+ organizationId: string;
2355
+ }): Promise<Record<string, unknown>>;
2356
+ /** Rename a team. */
2357
+ updateAuthOrgTeam(input: {
2358
+ name: string;
2359
+ teamId: string;
2360
+ }): Promise<Record<string, unknown>>;
2361
+ /** Delete a team and its memberships. */
2362
+ removeAuthOrgTeam(input: {
2363
+ teamId: string;
2364
+ }): Promise<void>;
2365
+ /** List a team's members. */
2366
+ listAuthOrgTeamMembers(input: {
2367
+ limit?: number;
2368
+ offset?: number;
2369
+ teamId: string;
2370
+ }): Promise<AuthPage<Record<string, unknown>>>;
2371
+ /** Add a user to a team. */
2372
+ addAuthOrgTeamMember(input: {
2373
+ teamId: string;
2374
+ userId: string;
2375
+ }): Promise<Record<string, unknown>>;
2376
+ /** Remove a member from a team. */
2377
+ removeAuthOrgTeamMember(input: {
2378
+ teamMemberId: string;
2379
+ }): Promise<void>;
2380
+ /** List an organization's custom roles (requires the organization plugin with dynamic access control). */
2381
+ listAuthOrgRoles(input: {
2382
+ limit?: number;
2383
+ offset?: number;
2384
+ organizationId: string;
2385
+ }): Promise<AuthPage<Record<string, unknown>>>;
2386
+ /** Create a custom org role with a permission grant (a `resource -> actions[]` map). */
2387
+ createAuthOrgRole(input: {
2388
+ organizationId: string;
2389
+ permission: Record<string, string[]>;
2390
+ role: string;
2391
+ }): Promise<Record<string, unknown>>;
2392
+ /** Replace a custom org role's permission grant. */
2393
+ updateAuthOrgRole(input: {
2394
+ permission: Record<string, string[]>;
2395
+ roleId: string;
2396
+ }): Promise<Record<string, unknown>>;
2397
+ /** Delete a custom org role. */
2398
+ deleteAuthOrgRole(input: {
2399
+ roleId: string;
2400
+ }): Promise<void>;
2401
+ /** List auth sessions, paged and optionally filtered to one user. */
2402
+ listAuthSessions(options?: {
2403
+ limit?: number;
2404
+ offset?: number;
2405
+ userId?: string;
2406
+ }): Promise<AuthPage<AuthSession>>;
2407
+ subscribe<F extends FunctionReference>(function_: F, args: ArgsOf<F>, callback: (data: ReturnOf<F>) => void, options?: {
2408
+ onCheckpoint?: (watermark: SyncWatermark) => void;
2409
+ onError?: SubscriptionErrorCallback;
2410
+ shardKey?: string;
2411
+ }): Unsubscribe;
2412
+ /**
2413
+ * Subscribe to a declarative **shape** — server-side partial replication
2414
+ * scoped by `shardBy` + the shape's predicate + RLS. The parallel to
2415
+ * {@link subscribe} for the poke protocol: the client sends the shape *name* +
2416
+ * validated `args` (never a `where` the client could forge), the server seeds
2417
+ * the current membership as an insert-poke and streams live membership diffs.
2418
+ * Each applied poke materializes the shape's rowset and invokes `callback`.
2419
+ *
2420
+ * Unlike {@link subscribe}, shape subscriptions are NOT deduped by
2421
+ * (name, args): the server resolves them under the socket's verified identity,
2422
+ * so every call gets its own id + view. The returned function unsubscribes.
2423
+ */
2424
+ subscribeShape(shape: {
2425
+ args?: Record<string, unknown>;
2426
+ name: string;
2427
+ }, callback: ShapeCallback, options?: {
2428
+ onCheckpoint?: (watermark: SyncWatermark) => void;
2429
+ onError?: SubscriptionErrorCallback;
2430
+ shardKey?: string;
2431
+ }): Unsubscribe;
2432
+ /**
2433
+ * Open a streaming query. The function reference must be a
2434
+ * `kind:"stream"` registration (built with `c.query.input(...).stream(...)`);
2435
+ * the type constraint catches accidental use of a query/mutation/action
2436
+ * reference at compile time. The returned iterable yields one element per
2437
+ * chunk frame the server pushes, terminating when the server sends
2438
+ * `complete` or the consumer calls `.cancel()`. Errors arrive as a
2439
+ * rejection on the next `next()`.
2440
+ *
2441
+ * Streams ride the same WS as subscriptions and share the unsubscribe
2442
+ * channel: cancelling sends `{type:"unsubscribe", id}` with the stream id,
2443
+ * which the DO recognises as an abort signal for the in-flight iterator.
2444
+ *
2445
+ * Stream-start frames buffered while the socket is (re)connecting are
2446
+ * capped at {@link MAX_PENDING_STREAMS} per connection — overflowing the
2447
+ * cap drops the oldest queued frame (and fails its consumer) so a stuck
2448
+ * reconnect can't OOM the page.
2449
+ */
2450
+ stream<F extends FunctionReference<"stream">>(function_: F, args: ArgsOf<F>, options?: {
2451
+ maxBuffer?: number;
2452
+ shardKey?: string;
2453
+ }): StreamIterable<ReturnOf<F>>;
2454
+ /**
2455
+ * Open a typed **HTTP-SSE route stream** (`httpRoute.&lt;verb>(path).stream()`).
2456
+ * Distinct from {@link LunoraClient.stream}, which consumes the WS procedure
2457
+ * stream (`kind: "stream"`): this one opens the route's own URL with `fetch`
2458
+ * and parses the Server-Sent Events framing the route pump writes (`data:`
2459
+ * chunks, a final `event: complete`, an `event: error` on throw).
2460
+ *
2461
+ * The reference comes from the generated `httpStreams.*` registry, so the
2462
+ * yielded chunk type is the route handler's yielded type. Cancelling the
2463
+ * returned iterable (or aborting `options.signal`) aborts the fetch, which
2464
+ * the server handler observes via its `signal`. The client's bearer token
2465
+ * (when set) rides as an `authorization` header.
2466
+ * @experimental Reconnect/POST-body/wire-fidelity design questions are still open, so the shape may change.
2467
+ */
2468
+ httpStream<Ref extends HttpStreamRef>(route: Ref, args?: HttpStreamArgsOf<Ref>, options?: {
2469
+ headers?: Record<string, string>;
2470
+ maxBuffer?: number;
2471
+ signal?: AbortSignal;
2472
+ }): StreamIterable<HttpStreamChunkOf<Ref>>;
2473
+ close(): void;
2474
+ /**
2475
+ * Persist a mutation that can't go out on the wire right now (offline, or
2476
+ * mid-reconnect after a prior connect). The optimistic update has already
2477
+ * been applied by `mutation`; this only chooses the durable write path and
2478
+ * rolls the optimistic write back if persistence is rejected.
2479
+ *
2480
+ * Two paths: when an `outbox` sink is wired (the `@lunora/db` executor) it
2481
+ * owns persistence + at-least-once replay, so we delegate and return
2482
+ * optimistically (confirmation rides the synced view). Otherwise the
2483
+ * built-in `OfflineQueue` resolves/rejects the returned promise on replay.
2484
+ */
2485
+ private enqueueOfflineMutation;
2486
+ /**
2487
+ * Restore offline mutations persisted in a prior session and open a socket
2488
+ * for each shard they target so they flush once the WS reconnects. Failures
2489
+ * are swallowed — a broken durable store must not stop the client booting.
2490
+ */
2491
+ private hydratePersistedQueue;
2492
+ /**
2493
+ * Re-queue the durable offline writes — but only as the multi-tab LEADER. The
2494
+ * persisted queue is shared across a profile's tabs; without coordination
2495
+ * every tab would re-queue and replay the same writes (correct only because
2496
+ * the server dedups by idempotency key, but wasteful + racy). A Web Lock makes
2497
+ * exactly one tab hydrate; it holds the lock for its lifetime, so when it
2498
+ * closes another tab acquires the lock and takes over. Falls back to
2499
+ * unconditional hydration where Web Locks are unavailable (React Native, older
2500
+ * browsers, SSR) — single-context there, so no coordination is needed.
2501
+ */
2502
+ private hydrateAsOutboxLeader;
2503
+ /**
2504
+ * Load every cached query into {@link hydratedQueryCache} so the next
2505
+ * `subscribe()` for each key seeds its initial value off disk. A
2506
+ * subscription created before this resolves simply misses the cache (it
2507
+ * gets a live snapshot as before); the gate at seed time also drops any
2508
+ * entry whose stamped identity no longer matches the current one.
2509
+ */
2510
+ private hydrateQueryCache;
2511
+ /**
2512
+ * Consume the hydrated read-cache entry for a key (if any), gated on
2513
+ * identity. The entry is removed whether or not it matches — the cache only
2514
+ * ever seeds a subscription's first value. A mismatch (the cache was written
2515
+ * under a different identity) yields `undefined` so a signed-out cache never
2516
+ * leaks into a new session.
2517
+ */
2518
+ private takeHydratedCache;
2519
+ /**
2520
+ * Queue a coalesced read-cache write for a subscription's current value.
2521
+ * Latest-wins per key; flushed on a short debounce so a delta burst writes
2522
+ * once. No-op when the read cache is disabled or the value is undefined
2523
+ * (nothing to render offline).
2524
+ */
2525
+ private persistQueryValue;
2526
+ /** Drain {@link pendingCacheWrites} to the durable store. */
2527
+ private flushQueryCacheWrites;
2528
+ /** Derive the aggregate status from the per-shard socket states. */
2529
+ private computeStatus;
2530
+ /** Recompute the aggregate status and notify listeners if it changed. */
2531
+ private emitConnectionStatus;
2532
+ /**
2533
+ * Build a {@link MutationSettledEvent} from a queued entry and emit it on the
2534
+ * {@link onMutationSettled} channel. `item.id` is always assigned by the time
2535
+ * a write settles (`enqueue`/`hydrate` guarantee it), so the `?? ""` fallback
2536
+ * is unreachable — present only to satisfy the optional queue-id type.
2537
+ */
2538
+ private emitItemSettled;
2539
+ /**
2540
+ * Apply an optimistic update to the subscription that matches the mutation's
2541
+ * `(functionRef, args, shardKey)` triple, returning the rollback callbacks to
2542
+ * invoke if the mutation later fails.
2543
+ *
2544
+ * The registry is already indexed by exactly this triple via
2545
+ * `SubscriptionRegistry.key`, so at most one subscription can match. A direct
2546
+ * O(1) keyed lookup replaces the former O(N) linear scan over all subscriptions.
2547
+ *
2548
+ * `shardKey` normalization: both `undefined` and `""` map to the empty string
2549
+ * inside `SubscriptionRegistry.key` (via `?? ""`), so a mutation fired without
2550
+ * a shardKey correctly matches a subscription registered without one regardless
2551
+ * of whether the caller passed `undefined` or omitted the field.
2552
+ */
2553
+ private applyOptimisticUpdates;
2554
+ /**
2555
+ * Run a Convex-parity `optimisticUpdate` callback against a localStore bound
2556
+ * to the live subscription registry. Each `setQuery` registers a constant
2557
+ * optimistic LAYER on its target subscription (via the same engine the
2558
+ * per-call `optimistic` path uses), so the multi-query patch rebases onto
2559
+ * incoming deltas and drops gaplessly on its commit cursor — its `confirm` /
2560
+ * `rollback` closures are appended to the mutation's settle lists. A throwing
2561
+ * callback unwinds its own partial writes — LIFO over just the rollbacks it
2562
+ * produced — and is swallowed, so a buggy optimistic update can never fail the
2563
+ * mutation or leave a partial patch live.
2564
+ */
2565
+ private applyOptimisticUpdate;
2566
+ private getConnection;
2567
+ private getOrCreateConnection;
2568
+ private wsUrlFor;
2569
+ /**
2570
+ * Build the outbound RPC headers: JSON content type, optional bearer auth,
2571
+ * the optional mutation-replay idempotency key, and the D1 read-your-writes
2572
+ * bookmark when the caller opted into `attachBookmark`. The mutation id
2573
+ * rides both the direct send and any offline-queue replay of the same write,
2574
+ * so a mutation the server already committed returns its cached result
2575
+ * instead of running twice.
2576
+ */
2577
+ private rpcRequestHeaders;
2578
+ private rpc;
2579
+ /**
2580
+ * Authenticated request to a non-RPC admin endpoint (the scheduler list /
2581
+ * cancel routes). Attaches the bearer token, parses JSON, and surfaces the
2582
+ * worker's `{ error: { code, message } }` envelope as a coded `Error` —
2583
+ * mirroring {@link rpc} so callers see the same failure shape.
2584
+ */
2585
+ private adminFetch;
2586
+ /**
2587
+ * Resolve the effective connection context for a shard: the most-recently
2588
+ * acquired refcounted holder ({@link acquireConnectionContext}) wins, falling
2589
+ * back to the imperative {@link setConnectionContext} override, then the
2590
+ * client-wide default. Returns `undefined` when none apply.
2591
+ */
2592
+ private effectiveConnectionContext;
2593
+ /** Re-send the `connect` envelope for a shard whose effective context just changed (if its socket is open). */
2594
+ private refreshConnectionContext;
2595
+ /**
2596
+ * Send the one-shot `connect` envelope on an open shard socket. Always sent
2597
+ * once per socket open, so the server's `onConnect` hooks fire symmetrically
2598
+ * with `onDisconnect` (which the DO dispatches unconditionally at close for
2599
+ * every lifecycle-aware socket). The DO no-ops cheaply when no `onConnect`
2600
+ * hooks are registered, so the single frame costs nothing in the common case.
2601
+ *
2602
+ * The shard's registered context (or the client-wide default) rides along
2603
+ * when one is set — the DO records it on the attachment for replay to
2604
+ * `onDisconnect`. A socket with no registered context still announces itself;
2605
+ * the envelope simply omits `context`, which is optional on the wire.
2606
+ * Register a context — e.g. `setConnectionContext({})` — to attach app state
2607
+ * to the lifecycle dispatch.
2608
+ */
2609
+ private sendConnectEnvelope;
2610
+ /**
2611
+ * Re-send every shape subscription bound to `shardKey` over its (now open)
2612
+ * socket. Each frame carries the shape's last applied checkpoint, so the
2613
+ * server resumes from it — or re-seeds when the cursor fell below CDC
2614
+ * retention or the epoch forked.
2615
+ */
2616
+ private resendShapeSubscriptions;
2617
+ private ensureSocket;
2618
+ /**
2619
+ * Resolve the {@link WsTokenProvider} and open the shard socket with the
2620
+ * minted token. The connection is already in the `connecting` state, so the
2621
+ * async gap is race-guarded: a client `close()`, a `setWsToken` bounce, or a
2622
+ * competing connect that landed first all abandon this attempt. A provider
2623
+ * failure fails the attempt through {@link handleDisconnect}, which arms the
2624
+ * normal reconnect backoff — a broken mint endpoint degrades to retries, not
2625
+ * a silent tokenless socket the admin gate would reject.
2626
+ */
2627
+ private openSocketWithProvidedToken;
2628
+ /** Construct the shard socket and wire its lifecycle handlers. The connection must already be in the `connecting` state. */
2629
+ private openSocket;
2630
+ private handleDisconnect;
2631
+ /**
2632
+ * Begin the keepalive heartbeat on an open connection. Each tick sends a
2633
+ * {@link WS_KEEPALIVE_PING} text frame the server answers from its
2634
+ * hibernation auto-response without waking the DO. A no-op when the
2635
+ * heartbeat is disabled (an interval of zero or less); idempotent — any
2636
+ * existing timer is cleared first so a reconnect can't leak intervals.
2637
+ */
2638
+ private startHeartbeat;
2639
+ /** Clear a connection's keepalive timer, if any. Safe to call repeatedly. */
2640
+ private stopHeartbeat;
2641
+ /** Mark every subscription bound to `shardKey` as needing a fresh ack. */
2642
+ private markShardPendingAck;
2643
+ private sendSubscribeIfOpen;
2644
+ private sendShapeSubscribeIfOpen;
2645
+ private handleServerMessage;
2646
+ private handleErrorMessage;
2647
+ private handlePokeStart;
2648
+ private handlePokePart;
2649
+ private handlePokeEnd;
2650
+ /** Materialize a shape's keyed view to an array and invoke its callbacks. */
2651
+ private emitShapeRows;
2652
+ private handleDataMessage;
2653
+ /**
2654
+ * Handle a `resume` frame (Pillar 1b): the server proved nothing the
2655
+ * subscription reads changed since our `sinceSeq`, so the cached value is
2656
+ * still current. We keep `lastValue` as-is, mark the sub acked, and advance
2657
+ * the cursor (re-persisting so the next reconnect resumes from the newer
2658
+ * watermark). No callback fires — the value didn't change, and `subscribe()`
2659
+ * already replayed the cached value to every consumer synchronously.
2660
+ */
2661
+ private handleResumeMessage;
2662
+ /**
2663
+ * Handle a `settled` frame: a write touched one of this subscription's read
2664
+ * tables but produced a byte-identical result, so the server suppressed the
2665
+ * data frame. Like {@link handleResumeMessage} the value didn't change — we
2666
+ * advance the resume position and re-persist — but we ALSO surface the echoed
2667
+ * custom-mutator watermark via `onCheckpoint` so a `@lunora/db` list
2668
+ * collection drops the optimistic overlay for the confirmed write (otherwise
2669
+ * its checkpoint gate, fed only by data frames, would hang forever). Sent
2670
+ * only to custom-mutator clients; plain `useQuery` subscribers leave
2671
+ * `onCheckpoint` unset and this is a near no-op.
2672
+ */
2673
+ private handleSettledMessage;
2674
+ /**
2675
+ * Mark `state` acked and, when the frame carries a newer cursor/epoch than
2676
+ * the cached position, advance the resume watermark and re-persist. Shared by
2677
+ * the `resume` and `settled` frame handlers — both acknowledge "nothing the
2678
+ * client must re-render changed, but the resume position may have moved".
2679
+ */
2680
+ private ackAndAdvanceCursor;
2681
+ /**
2682
+ * Resolve the value to publish for a `data`/`delta` frame.
2683
+ *
2684
+ * A `data` frame is an authoritative snapshot (the server re-execution path)
2685
+ * and always replaces the cached value wholesale. A `delta` frame carrying a
2686
+ * structured `MutationDelta` (the `broadcastDelta` row-change path) is
2687
+ * merged incrementally into the cached list — preserving order, no dup/loss —
2688
+ * so each subscription (including every paginated page) updates by delta
2689
+ * rather than a full re-send. We fall back to full replacement when the
2690
+ * delta isn't a recognisable row change, when there's no cached value yet,
2691
+ * or when it can't be applied cleanly against the current cached shape.
2692
+ */
2693
+ private resolveDataPayload;
2694
+ /** Route an inbound whisper to the topic's handlers on the originating shard. */
2695
+ private dispatchWhisper;
2696
+ /** Notify every {@link onTokenExpired} listener (best-effort, listener throws swallowed). */
2697
+ private notifyTokenExpired;
2698
+ private handleCompleteMessage;
2699
+ private unpersist;
2700
+ /**
2701
+ * Stable, non-reversible fingerprint of the current auth identity used to
2702
+ * stamp queued offline writes. `null` (signed out) is its own identity and
2703
+ * never matches a bearer-token fingerprint. The raw token is never stored;
2704
+ * a length-prefixed FNV-1a hash is enough to detect an identity *change*
2705
+ * without keeping the credential around in the queue map.
2706
+ */
2707
+ private identityFingerprint;
2708
+ /**
2709
+ * Stable token-hash fingerprint of a bearer token (the `&lt;len>:&lt;fnv>:&lt;djb2>`
2710
+ * format a token-stamped queued write carries). Extracted so the replay gate
2711
+ * can recompute the hash of the current credential and recognise a write
2712
+ * stamped under it — even after the fingerprint was relabelled to a subject.
2713
+ *
2714
+ * Two independent 32-bit passes (FNV-1a + djb2) give a ~64-bit digest, so
2715
+ * two distinct equal-length tokens are astronomically unlikely to share a
2716
+ * fingerprint. A single 32-bit hash collides ~1-in-4e9 per equal-length
2717
+ * pair — enough that, on a shared device, user B could hydrate A's cached
2718
+ * reads. Different algorithms (not the same FNV with a different seed, which
2719
+ * would be affine-related) keep the two passes genuinely independent.
2720
+ * Still synchronous (no crypto) and stable across surrogate pairs.
2721
+ */
2722
+ private hashToken;
2723
+ /**
2724
+ * True when `stamped` is a token-hash of the SAME credential still held now,
2725
+ * even though the live identity has since been relabelled to a subject. Covers
2726
+ * `setAuthToken(token, userId)` where the subject resolved a tick after the
2727
+ * token was set: a write persisted (or requeued) under the token hash must
2728
+ * still replay — the credential never changed, only its label — instead of
2729
+ * being dropped as an identity mismatch. This is the durable counterpart to
2730
+ * {@link restampQueuedIdentity}, which only relabels the in-memory live stamp
2731
+ * (consumed on the first flush) and never touches `item.identity` or the
2732
+ * persisted record, so a reload or a transient-failure requeue would otherwise
2733
+ * fall back to the stale token-hash and wrongly reject the same user's write.
2734
+ */
2735
+ private isSameCredentialUnderTokenHash;
2736
+ /**
2737
+ * Drain every in-memory offline write and reject it because the auth
2738
+ * identity changed. Durable entries are also dropped from persistence so a
2739
+ * later `hydrate` can't resurrect another user's writes. Stamps are cleared
2740
+ * alongside. Persisted entries restored without a live awaiter still get
2741
+ * unpersisted here.
2742
+ */
2743
+ private rejectQueuedForIdentityChange;
2744
+ /**
2745
+ * Migrate every live identity stamp from `from` to `to` — used when the auth
2746
+ * identity label changes but the underlying credential (token) does NOT, e.g.
2747
+ * the user id resolves a tick after the token was set. The in-memory
2748
+ * `queuedIdentities` map is the flush-time source of truth, so re-stamping it
2749
+ * keeps the in-flight writes replayable under the new (more stable) identity
2750
+ * instead of the flush guard discarding them as a mismatch.
2751
+ */
2752
+ private restampQueuedIdentity;
2753
+ /**
2754
+ * Drop the durable read cache on an identity change so a cached value stamped
2755
+ * under the previous identity can never hydrate into a new session. Clears
2756
+ * the in-flight write batch and the not-yet-consumed hydrated entries too;
2757
+ * the durable `clear()` is best-effort.
2758
+ */
2759
+ private clearQueryCacheForIdentityChange;
2760
+ private flushOfflineQueue;
2761
+ /**
2762
+ * Partition already-gated writes into the encodable ones (returned) and reject
2763
+ * the rest terminally. A write whose args can't be wire-encoded (e.g. a RegExp
2764
+ * or class instance in a `v.any()` field) can NEVER replay — the codec failure
2765
+ * is deterministic, not transient. Rejecting here is essential: otherwise
2766
+ * `encodeWire` throws mid-flush, is classified as transient (a codec error has
2767
+ * no `.code`), and re-queues forever — a silent hang where the caller's Promise
2768
+ * never settles and the optimistic write never rolls back. Encoding is cheap;
2769
+ * the flush is the slow reconnect path.
2770
+ */
2771
+ private encodableOrSettleTerminal;
2772
+ /**
2773
+ * Identity guard for one queued write about to replay: a write stamped under
2774
+ * one identity must never replay under another. The live `queuedIdentities`
2775
+ * map is the source of truth for the current session; a hydrated write whose
2776
+ * id isn't in the map falls back to the stamp persisted with the record
2777
+ * (`item.identity`), so a reload can't replay another user's queued writes.
2778
+ * Only legacy records (persisted before stamps were durable —
2779
+ * `item.identity === undefined`) replay under whatever identity is current.
2780
+ *
2781
+ * `Map.get` returns `undefined` for unstamped/hydrated ids and `item.identity`
2782
+ * is `undefined` for legacy records; a persisted `null` (queued while signed
2783
+ * out) is a real value that must not collapse into `undefined` — hence the
2784
+ * explicit `=== undefined` check rather than `??`. Returns `true` when the
2785
+ * write may replay; otherwise settles it `OFFLINE_IDENTITY_CHANGED` and returns
2786
+ * `false`. Either way the live stamp is consumed.
2787
+ */
2788
+ private passesReplayIdentityGate;
2789
+ /** Settle a write that replayed successfully: confirm its optimistic layer against the echoed commit cursor BEFORE resolving, so the gapless drop is in place when the awaiter (and any confirming frame) observes the settle. */
2790
+ private settleReplaySuccess;
2791
+ /** Settle a write the server reached a coded verdict on: replaying would re-trigger the same failure (a poison-message loop), so drop it. */
2792
+ private settleReplayTerminal;
2793
+ /**
2794
+ * Replay already-identity-gated writes one at a time on the single-call `/rpc`
2795
+ * path, preserving FIFO order (parallel `.then()` chains would race the
2796
+ * ordering callers depend on). Each replays under its stable `mutationId` so
2797
+ * the server dedups a write it already committed (exactly-once). A coded error
2798
+ * is a server verdict (drop it); a codeless (transport/transient) failure stops
2799
+ * the flush and re-queues this write and every unreplayed one for the next
2800
+ * reconnect — their callers stay pending, and the identity guard re-applies on
2801
+ * retry via each record's persisted stamp.
2802
+ */
2803
+ private replaySequential;
2804
+ /**
2805
+ * Coalesce already-identity-gated writes for a single shard into ONE
2806
+ * `/_lunora/rpc-batch` round trip (plan 088 follow-on). The worker forwards
2807
+ * them to the shard DO, which replays each through its single-call dispatch, so
2808
+ * per-entry `mutationId` idempotency and in-order application are inherited from
2809
+ * the proven path. Per-slot demux mirrors {@link replaySequential}'s
2810
+ * classification: success confirms the optimistic layer against the echoed
2811
+ * `commitCursor`; a coded application verdict is terminal; a transient shard
2812
+ * failure (`SHARD_UNAVAILABLE`/`SHARD_ERROR`), a missing slot, or a whole-batch
2813
+ * transport failure re-queues for the next reconnect (never dropping a durable
2814
+ * write). A whole-batch coded rejection (bad request / authorization denial the
2815
+ * server reached a verdict on) is terminal for every entry.
2816
+ *
2817
+ * Returns the writes that must be re-queued and `stop` — `true` when the whole
2818
+ * chunk failed at the transport level, so the caller leaves later chunks queued
2819
+ * rather than sending on. The caller re-queues once, in order, so requeuing is
2820
+ * NOT done here.
2821
+ */
2822
+ private replayBatched;
2823
+ /**
2824
+ * Demux a `/_lunora/rpc-batch` reply back onto the queued writes it replayed,
2825
+ * in input order. Each slot's envelope classifies its write the same way
2826
+ * {@link replaySequential} does: a success confirms the optimistic layer
2827
+ * against the echoed `commitCursor`; a coded application verdict is terminal;
2828
+ * a transient shard failure ({@link TRANSIENT_BATCH_ERROR_CODES}) or a slot the
2829
+ * server never returned is returned for the caller to re-queue.
2830
+ * @returns the writes that must be re-queued (transient slots), in input order
2831
+ */
2832
+ private settleReplayBatchSlots;
2833
+ }
2834
+ export { ServerPokePartMessage as $, ArgsOf as A, BookmarkStorage as B, CONFLICT_ERROR_CODE as C, DEFAULT_MAX_BUFFER as D, OptimisticUpdate as E, FunctionReference as F, GlobalFacetResult as G, HttpStreamRef as H, OutboxMutation as I, OutboxSink as J, PersistedMutation as K, LunoraClient as L, MutationCallOptions as M, RowOp as N, OfflineQueueOptions as O, Preloaded as P, QueryCacheAdapter as Q, ReturnOf as R, SubscriptionError as S, RpcEnvelope as T, User as U, RpcResponseBody as V, ScheduleRecord as W, SchedulerPoolStatus as X, SchedulerStatus as Y, ServerMessage as Z, ServerPokeEndMessage as _, Unsubscribe as a, ServerPokeStartMessage as a0, ShardTrafficEntry as a1, ShardTrafficResult as a2, StorageListPage as a3, StorageObject as a4, StreamHandle as a5, SubscriptionCallback as a6, SubscriptionRegistry as a7, SubscriptionState as a8, SyncWatermark as a9, WorkflowInstanceAction as aa, WorkflowInstanceDetail as ab, WorkflowInstancePage as ac, WorkflowInstanceStatus as ad, WorkflowInstanceSummary as ae, WorkflowStepDetail as af, WsTokenProvider as ag, createClientQuery as ah, createLocalStore as ai, createStream as aj, getErrorCode as ak, getRetryAfterMs as al, isConflictError as am, isForbiddenError as an, isRateLimitedError as ao, isUnauthorizedError as ap, SubscriptionErrorCallback as b, PersistenceAdapter as c, HttpStreamArgsOf as d, HttpStreamChunkOf as e, StreamIterable as f, ReconnectOptions as g, BatchSlot as h, CachedQuery as i, ClientMessage as j, ClientQueryRef as k, ClientShapeSubscribeMessage as l, ClientShapeUnsubscribeMessage as m, ConnectionStatus as n, FunctionArgumentDescriptor as o, FunctionDescriptor as p, GlobalFacetValue as q, GlobalFilterClause as r, GlobalTableInfo as s, GlobalTablePage as t, HttpStreamCallArgs as u, LunoraClientError as v, LunoraClientOptions as w, LunoraErrorCode as x, MutationSettledEvent as y, OptimisticLocalStore as z };