@lunora/client 1.0.0-alpha.2 → 1.0.0-alpha.21
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.
- package/LICENSE.md +6 -0
- package/README.md +2 -0
- package/__assets__/package-og.svg +1 -1
- package/dist/auth/index.d.mts +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/index.d.mts +144 -24
- package/dist/index.d.ts +144 -24
- package/dist/index.mjs +10 -9
- package/dist/packem_shared/CONFLICT_ERROR_CODE-B8gQ8tyU.mjs +33 -0
- package/dist/packem_shared/{DEFAULT_MAX_BUFFER-BDkqO5PW.mjs → DEFAULT_MAX_BUFFER-7hFnzNk9.mjs} +6 -3
- package/dist/packem_shared/{LunoraClient-UiULzH_1.mjs → LunoraClient-kXpHNyaE.mjs} +1562 -267
- package/dist/packem_shared/OfflineQueue-GGYJRmhF.mjs +1 -0
- package/dist/packem_shared/SubscriptionRegistry-DjGKZsqq.mjs +1 -0
- package/dist/packem_shared/{applyDelta-4jFGTPA3.mjs → applyDelta-CRKZ1PBt.mjs} +21 -1
- package/dist/packem_shared/createInMemoryPersistence-DZ2VHWgm.mjs +79 -0
- package/dist/packem_shared/{createInMemoryQueryCache-B1PQ9Twl.mjs → createInMemoryQueryCache-DiaGZkA2.mjs} +25 -51
- package/dist/packem_shared/createLocalStore-jRoqmazl.mjs +2 -0
- package/dist/packem_shared/createMutatorRunner-BETvCd0p.mjs +31 -0
- package/dist/packem_shared/{createServerClient-BjZc3gD8.mjs → createServerClient-DF-3mLmb.mjs} +1 -1
- package/dist/packem_shared/idb-utility-DrSVX43Q.mjs +48 -0
- package/dist/packem_shared/local-store-BveBeFEo.mjs +106 -0
- package/dist/packem_shared/{lunora-client.d-DGvyuJ_p.d.mts → lunora-client.d-BYkEjCEJ.d.mts} +1001 -58
- package/dist/packem_shared/{lunora-client.d-DGvyuJ_p.d.ts → lunora-client.d-BYkEjCEJ.d.ts} +1001 -58
- package/dist/packem_shared/{OfflineQueue-D5p_QgF_.mjs → offline-queue-B9vfdSqp.mjs} +49 -6
- package/dist/packem_shared/{preload.d-BoDmFqSG.d.ts → preload.d-B-vyHnml.d.ts} +1 -1
- package/dist/packem_shared/{preload.d-dSaRMuhL.d.mts → preload.d-DrfuisCE.d.mts} +1 -1
- package/dist/packem_shared/subscription-BjynOXCU.mjs +68 -0
- package/dist/query/index.d.mts +2 -2
- package/dist/query/index.d.ts +2 -2
- package/dist/ssr/index.d.mts +3 -3
- package/dist/ssr/index.d.ts +3 -3
- package/dist/ssr/index.mjs +1 -1
- package/package.json +5 -2
- package/dist/packem_shared/CONFLICT_ERROR_CODE-aUdVbEDw.mjs +0 -4
- package/dist/packem_shared/SubscriptionRegistry-B-Qx_Gux.mjs +0 -26
- package/dist/packem_shared/createInMemoryPersistence-CW82inU5.mjs +0 -105
- package/dist/packem_shared/createLocalStore-DSUfoLqY.mjs +0 -36
|
@@ -1,4 +1,59 @@
|
|
|
1
|
-
import { CronJobInfo, VectorIndexSummary, VectorQueryMatch, AuthUser, AuthPage, AuthImpersonation, AuthCapabilities, AuthSession } from '@lunora/runtime';
|
|
1
|
+
import { CronJobInfo, VectorIndexSummary, VectorQueryMatch, KvNamespaceSummary, KvKeyListResult, KvValueResult, AuthUser, AuthPage, AuthImpersonation, AuthCapabilities, AuthConfigInfo, AuthSession } from '@lunora/runtime';
|
|
2
|
+
/**
|
|
3
|
+
* The machine-readable error codes a client can observe on a failed
|
|
4
|
+
* RPC/batch/subscription. Mirrors the server's `CODE_STATUS` keys
|
|
5
|
+
* (`@lunora/server`'s `error.ts`) by hand — the client is framework-neutral and
|
|
6
|
+
* must never import the server package (wrong dependency direction / would pull
|
|
7
|
+
* the server into the browser bundle). Keep this list in sync when a server code
|
|
8
|
+
* is added or removed (see the drift-guard note in the plan/maintenance docs).
|
|
9
|
+
*/
|
|
10
|
+
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"];
|
|
11
|
+
/** A machine-readable error `code` the client may observe. Mirror of the server's `LunoraErrorCode`. */
|
|
12
|
+
type LunoraErrorCode = (typeof LUNORA_ERROR_CODES)[number];
|
|
13
|
+
/** Error code the server uses for optimistic-concurrency conflicts (HTTP 409). */
|
|
14
|
+
declare const CONFLICT_ERROR_CODE = "CONFLICT";
|
|
15
|
+
/**
|
|
16
|
+
* Whether an unknown rejection is an optimistic-concurrency conflict — the
|
|
17
|
+
* server lost a write race and the caller should refetch and retry (or surface
|
|
18
|
+
* the conflict). Structural check on the `code` property the client attaches
|
|
19
|
+
* when decoding the worker's `{ error: { code, message } }` envelope.
|
|
20
|
+
*/
|
|
21
|
+
declare const isConflictError: (error: unknown) => error is Error & {
|
|
22
|
+
code: "CONFLICT";
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Whether a rejection is an RLS/policy denial (`FORBIDDEN`, HTTP 403) — the
|
|
26
|
+
* caller is authenticated but not permitted to read/write the row. The most
|
|
27
|
+
* common per-call error a UI must handle in an RLS-first app.
|
|
28
|
+
*/
|
|
29
|
+
declare const isForbiddenError: (error: unknown) => error is Error & {
|
|
30
|
+
code: "FORBIDDEN";
|
|
31
|
+
};
|
|
32
|
+
/** Whether a rejection is an authentication failure (`UNAUTHORIZED`, HTTP 401) — no/invalid identity. */
|
|
33
|
+
declare const isUnauthorizedError: (error: unknown) => error is Error & {
|
|
34
|
+
code: "UNAUTHORIZED";
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Whether a rejection is a rate-limit denial (`TOO_MANY_REQUESTS`, HTTP 429).
|
|
38
|
+
* The retry hint (if the server sent one) is read with {@link getRetryAfterMs}.
|
|
39
|
+
*/
|
|
40
|
+
declare const isRateLimitedError: (error: unknown) => error is Error & {
|
|
41
|
+
code: "TOO_MANY_REQUESTS";
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Read the server's machine-readable `code` off a rejection, narrowed to the
|
|
45
|
+
* known {@link LunoraErrorCode} union. Returns `undefined` for a non-`Error`, a
|
|
46
|
+
* missing code, or an unrecognized code string (forward-compat server codes read
|
|
47
|
+
* as `undefined` here rather than being falsely narrowed).
|
|
48
|
+
*/
|
|
49
|
+
declare const getErrorCode: (error: unknown) => LunoraErrorCode | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Read the rate-limit retry hint (`data.retryAfterMs`) off a
|
|
52
|
+
* `TOO_MANY_REQUESTS` rejection without hand-casting the `unknown` `data`
|
|
53
|
+
* payload. Returns the finite millisecond value the server sent, or `undefined`
|
|
54
|
+
* when absent/non-numeric. Pair with {@link isRateLimitedError}.
|
|
55
|
+
*/
|
|
56
|
+
declare const getRetryAfterMs: (error: unknown) => number | undefined;
|
|
2
57
|
/** The registered function kinds a {@link FunctionReference} can describe. `stream` is a query that yields multiple frames over the WS. */
|
|
3
58
|
type FunctionKind = "action" | "mutation" | "query" | "stream";
|
|
4
59
|
/**
|
|
@@ -98,6 +153,14 @@ interface PersistedMutation {
|
|
|
98
153
|
*/
|
|
99
154
|
identity?: string | null;
|
|
100
155
|
shardKey?: string;
|
|
156
|
+
/**
|
|
157
|
+
* App/schema version stamped at enqueue (from `LunoraClientOptions.persistenceVersion`).
|
|
158
|
+
* On hydrate, a record whose `version` doesn't match the current one is dropped
|
|
159
|
+
* and purged rather than replayed — so a write persisted by an older deploy
|
|
160
|
+
* (with a now-changed function signature) can't replay against the new schema.
|
|
161
|
+
* Absent when no `persistenceVersion` is configured (no version gating).
|
|
162
|
+
*/
|
|
163
|
+
version?: string;
|
|
101
164
|
}
|
|
102
165
|
/**
|
|
103
166
|
* Durable store for the offline mutation queue. The default client keeps the
|
|
@@ -120,6 +183,40 @@ interface PersistenceAdapter {
|
|
|
120
183
|
remove: (id: string) => Promise<void>;
|
|
121
184
|
}
|
|
122
185
|
/**
|
|
186
|
+
* One write handed to an {@link OutboxSink}. Mirrors {@link PersistedMutation}
|
|
187
|
+
* plus the custom-mutator identity (`clientId`/`mutationId`/`idempotencyKey`)
|
|
188
|
+
* the durable outbox needs to dedupe and watermark replays.
|
|
189
|
+
*/
|
|
190
|
+
interface OutboxMutation {
|
|
191
|
+
args: Record<string, unknown>;
|
|
192
|
+
/** Stable per-client id; pairs with {@link OutboxMutation.mutationId} as `idempotencyKey`. */
|
|
193
|
+
clientId: string;
|
|
194
|
+
functionPath: string;
|
|
195
|
+
/** `${clientId}:${mutationId}` — sent as `x-lunora-mutation-id` so a replay is server-idempotent. */
|
|
196
|
+
idempotencyKey: string;
|
|
197
|
+
/** Issuing identity fingerprint (`null` = signed out); drives the sink's identity guard. */
|
|
198
|
+
identity: string | null;
|
|
199
|
+
/** Monotonic per-client mutation id, backing the server `__client_watermark`. */
|
|
200
|
+
mutationId: number;
|
|
201
|
+
shardKey?: string;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Pluggable durable outbox seam. When set on {@link LunoraClientOptions.outbox},
|
|
205
|
+
* the client delegates offline write durability + at-least-once replay to this
|
|
206
|
+
* sink instead of its built-in {@link PersistenceAdapter}-backed `OfflineQueue`.
|
|
207
|
+
* `@lunora/db` supplies the blessed implementation (`createExecutorOutboxSink`,
|
|
208
|
+
* backed by the TanStack `OfflineExecutor`); the interface itself is
|
|
209
|
+
* dependency-free so `@lunora/client` stays TanStack-free.
|
|
210
|
+
*/
|
|
211
|
+
interface OutboxSink {
|
|
212
|
+
/**
|
|
213
|
+
* Persist and schedule a write for replay. Rejects with an
|
|
214
|
+
* `OFFLINE_QUEUE_OVERFLOW`-coded error when the sink's cap is exceeded, so
|
|
215
|
+
* the caller can surface back-pressure to the issuing mutation.
|
|
216
|
+
*/
|
|
217
|
+
enqueue: (mutation: OutboxMutation) => Promise<void>;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
123
220
|
* One persisted query result in the durable read cache (Pillar 2). Keyed in the
|
|
124
221
|
* store by `shardKey + functionPath + argsKey`; the record carries everything
|
|
125
222
|
* needed to render offline on reload and to resume the live subscription.
|
|
@@ -148,6 +245,13 @@ interface CachedQuery {
|
|
|
148
245
|
ts: number;
|
|
149
246
|
/** The full query result last seen from the server. */
|
|
150
247
|
value: unknown;
|
|
248
|
+
/**
|
|
249
|
+
* App/schema version stamped when persisted (from `LunoraClientOptions.persistenceVersion`).
|
|
250
|
+
* A cached value whose `version` doesn't match the current one is not hydrated —
|
|
251
|
+
* so a result of a now-changed shape from an older deploy can't render. Absent
|
|
252
|
+
* when no `persistenceVersion` is configured (no version gating).
|
|
253
|
+
*/
|
|
254
|
+
version?: string;
|
|
151
255
|
}
|
|
152
256
|
/**
|
|
153
257
|
* Durable store for the client read cache (Pillar 2): query results survive a
|
|
@@ -177,6 +281,16 @@ interface LunoraClientOptions {
|
|
|
177
281
|
authBasePath?: string;
|
|
178
282
|
bookmarkStorage?: BookmarkStorage;
|
|
179
283
|
/**
|
|
284
|
+
* Stable per-client id backing the custom-mutator watermark. Sent on the
|
|
285
|
+
* `connect` envelope (so the server can scope this client's
|
|
286
|
+
* `__client_watermark`) and stamped onto every {@link OutboxMutation} the
|
|
287
|
+
* {@link LunoraClientOptions.outbox} sink persists, where it pairs with the
|
|
288
|
+
* monotonic mutation id to form the idempotency key. The `@lunora/db` path
|
|
289
|
+
* persists a stable id alongside the outbox and passes it here; omit for the
|
|
290
|
+
* standalone client, which generates an ephemeral per-session id.
|
|
291
|
+
*/
|
|
292
|
+
clientId?: string;
|
|
293
|
+
/**
|
|
180
294
|
* Default app context sent in the `connect` envelope right after each socket
|
|
181
295
|
* opens, forwarded to the server's `onConnect`/`onDisconnect` lifecycle hooks
|
|
182
296
|
* as `event.context`. A per-shard context registered via
|
|
@@ -184,6 +298,16 @@ interface LunoraClientOptions {
|
|
|
184
298
|
* hook needs connection context.
|
|
185
299
|
*/
|
|
186
300
|
connectionContext?: Record<string, unknown>;
|
|
301
|
+
/**
|
|
302
|
+
* Fail-fast timeout (ms) for opening a subscription WebSocket. If the
|
|
303
|
+
* handshake doesn't complete within this window — a hung dev proxy or a cold
|
|
304
|
+
* worker that never upgrades — the client force-closes the socket and routes
|
|
305
|
+
* through its normal reconnect/backoff (surfacing `offline` status) instead
|
|
306
|
+
* of leaving the live channel silently stuck on the browser's much longer
|
|
307
|
+
* default. Does not affect HTTP queries/mutations (those never ride the WS).
|
|
308
|
+
* Defaults to 10000 (10s); set to `0` (or negative) to disable.
|
|
309
|
+
*/
|
|
310
|
+
connectTimeoutMs?: number;
|
|
187
311
|
fetch?: typeof fetch;
|
|
188
312
|
/**
|
|
189
313
|
* Interval (ms) between keepalive pings sent on each open subscription
|
|
@@ -194,14 +318,47 @@ interface LunoraClientOptions {
|
|
|
194
318
|
*/
|
|
195
319
|
heartbeatIntervalMs?: number;
|
|
196
320
|
offlineQueue?: OfflineQueueOptions;
|
|
197
|
-
/** Durable store for the offline mutation queue; omit to keep it in memory. */
|
|
198
|
-
persistence?: PersistenceAdapter;
|
|
199
321
|
/**
|
|
200
|
-
* Durable
|
|
322
|
+
* Durable outbox seam for offline writes. When supplied (the `@lunora/db`
|
|
323
|
+
* path wires `createExecutorOutboxSink`), offline mutations are delegated to
|
|
324
|
+
* the sink and the built-in {@link PersistenceAdapter}-backed `OfflineQueue`
|
|
325
|
+
* is bypassed, so a db app has exactly one durable write path. Omit for the
|
|
326
|
+
* standalone client, which keeps using {@link LunoraClientOptions.persistence}.
|
|
327
|
+
*/
|
|
328
|
+
outbox?: OutboxSink;
|
|
329
|
+
/**
|
|
330
|
+
* Durable store for the offline mutation queue. Tri-state — an explicit
|
|
331
|
+
* {@link PersistenceAdapter} is used as-is; `false` opts out (the queue stays
|
|
332
|
+
* in memory, lost on reload); omitted (the default) auto-probes a durable
|
|
333
|
+
* IndexedDB store when the `indexedDB` global is present (browsers), otherwise
|
|
334
|
+
* in-memory, so SSR/Node/React-Native keep the in-memory behaviour and only
|
|
335
|
+
* environments that can persist do. Pass `createAsyncStoragePersistence()` on
|
|
336
|
+
* React Native.
|
|
337
|
+
*/
|
|
338
|
+
persistence?: false | PersistenceAdapter;
|
|
339
|
+
/**
|
|
340
|
+
* App/schema version stamped onto every persisted queued write and cached
|
|
341
|
+
* read. Bump it on a breaking change to a function signature or query shape:
|
|
342
|
+
* on the next boot, persisted writes / cached reads stamped with a different
|
|
343
|
+
* version are dropped (and purged) rather than replayed / hydrated against the
|
|
344
|
+
* new schema. Omit to disable version gating (records are never invalidated by
|
|
345
|
+
* version).
|
|
346
|
+
*
|
|
347
|
+
* **Adoption is itself an invalidation event:** records written before you set
|
|
348
|
+
* `persistenceVersion` carry no version, so the first boot after enabling it
|
|
349
|
+
* purges all currently-queued offline writes (and cached reads) as stale. Adopt
|
|
350
|
+
* it on a build where that clean slate is acceptable — typically the same
|
|
351
|
+
* breaking deploy you're protecting against — not purely speculatively.
|
|
352
|
+
*/
|
|
353
|
+
persistenceVersion?: string;
|
|
354
|
+
/**
|
|
355
|
+
* Durable store for the read cache (Pillar 2). When active, query results
|
|
201
356
|
* are persisted as their subscriptions advance and hydrated on construction
|
|
202
357
|
* so a reload renders cached data before the socket reconnects, then resumes
|
|
203
|
-
* the live subscription from the persisted cursor.
|
|
204
|
-
*
|
|
358
|
+
* the live subscription from the persisted cursor. Tri-state — an explicit
|
|
359
|
+
* {@link QueryCacheAdapter} is used as-is; `false` opts out (reads stay in
|
|
360
|
+
* memory only); omitted (the default) auto-probes IndexedDB exactly like
|
|
361
|
+
* {@link LunoraClientOptions.persistence}.
|
|
205
362
|
*/
|
|
206
363
|
queryCache?: QueryCacheAdapter | false;
|
|
207
364
|
reconnect?: ReconnectOptions;
|
|
@@ -222,17 +379,47 @@ interface LunoraClientOptions {
|
|
|
222
379
|
/** Wire envelope sent on `POST /_lunora/rpc`. */
|
|
223
380
|
interface RpcEnvelope {
|
|
224
381
|
args?: Record<string, unknown>;
|
|
382
|
+
/**
|
|
383
|
+
* Stable per-client identifier (custom-mutator push path). Pairs with
|
|
384
|
+
* {@link RpcEnvelope.mutationId} to form `idempotencyKey` and scope the
|
|
385
|
+
* server `__client_watermark`. Absent on plain `client.mutation` calls.
|
|
386
|
+
*/
|
|
387
|
+
clientId?: string;
|
|
225
388
|
functionPath: string;
|
|
389
|
+
/**
|
|
390
|
+
* Idempotency key (`${clientId}:${mutationId}`) for the custom-mutator push
|
|
391
|
+
* path, mirrored into the `x-lunora-mutation-id` header. Absent on plain
|
|
392
|
+
* `client.mutation` calls.
|
|
393
|
+
*/
|
|
394
|
+
idempotencyKey?: string;
|
|
395
|
+
/**
|
|
396
|
+
* Monotonic per-client mutation id (custom-mutator push path), backing the
|
|
397
|
+
* server-side per-client watermark: `id <= watermark` is a replay (skipped),
|
|
398
|
+
* `id == watermark + 1` runs authoritatively, `id > watermark + 1` halts the
|
|
399
|
+
* batch so the client resends from `watermark + 1`. Absent on plain
|
|
400
|
+
* `client.mutation` calls.
|
|
401
|
+
*/
|
|
402
|
+
mutationId?: number;
|
|
226
403
|
shardKey?: string;
|
|
227
404
|
}
|
|
228
|
-
/**
|
|
405
|
+
/**
|
|
406
|
+
* Wire response from the shard's `/rpc` endpoint (forwarded by the runtime). A
|
|
407
|
+
* watermarked custom-mutator push additionally carries `lastMutationId` — the
|
|
408
|
+
* highest per-client sequence the DO has applied — which the client uses to keep
|
|
409
|
+
* its `clientSeq` generator monotonic across reloads (see `LunoraClient.callMutator`).
|
|
410
|
+
* A plain mutation on a CDC shard carries `commitCursor` — the cursor the write
|
|
411
|
+
* committed at — which gates the drop of a per-call optimistic layer.
|
|
412
|
+
*/
|
|
229
413
|
type RpcResponseBody = {
|
|
230
|
-
result: unknown;
|
|
231
|
-
} | {
|
|
232
414
|
error: {
|
|
233
415
|
code: string;
|
|
416
|
+
data?: unknown;
|
|
234
417
|
message: string;
|
|
235
418
|
};
|
|
419
|
+
} | {
|
|
420
|
+
commitCursor?: number;
|
|
421
|
+
lastMutationId?: number;
|
|
422
|
+
result: unknown;
|
|
236
423
|
};
|
|
237
424
|
/** Subscription protocol — client → server. */
|
|
238
425
|
interface ClientSubscribeMessage {
|
|
@@ -265,10 +452,51 @@ interface ClientUnsubscribeMessage {
|
|
|
265
452
|
* `onDisconnect` when the socket drops.
|
|
266
453
|
*/
|
|
267
454
|
interface ClientConnectMessage {
|
|
455
|
+
/**
|
|
456
|
+
* Stable per-client id (persisted alongside the outbox). Lets the server
|
|
457
|
+
* scope this connection's `__client_watermark` so custom-mutator pokes can
|
|
458
|
+
* echo the right per-client `lastMutationId`. Omitted by clients that don't
|
|
459
|
+
* use custom mutators.
|
|
460
|
+
*/
|
|
461
|
+
clientId?: string;
|
|
268
462
|
context?: Record<string, unknown>;
|
|
269
463
|
id: string;
|
|
270
464
|
type: "connect";
|
|
271
465
|
}
|
|
466
|
+
/**
|
|
467
|
+
* Subscribe to a declarative **shape** — server-side partial replication scoped
|
|
468
|
+
* by `shardBy` + the shape's predicate + RLS. The client sends the shape *name*
|
|
469
|
+
* + validated `args`; the server resolves the trusted `where` (identity/RLS
|
|
470
|
+
* `baseWhere` the client can't forge) and streams the matching rowset, then live
|
|
471
|
+
* {@link ServerPokePartMessage} diffs. `id` namespaces the subscription and is
|
|
472
|
+
* echoed as `shapeId` on every poke part.
|
|
473
|
+
*/
|
|
474
|
+
interface ClientShapeSubscribeMessage {
|
|
475
|
+
id: string;
|
|
476
|
+
shape: {
|
|
477
|
+
args?: Record<string, unknown>;
|
|
478
|
+
name: string;
|
|
479
|
+
};
|
|
480
|
+
/**
|
|
481
|
+
* Resume from this checkpoint (the `__cdc_log` cursor the client last
|
|
482
|
+
* applied for this shape). When absent or below the server's retained floor
|
|
483
|
+
* (`minCdcSeq`), the server re-seeds with a full insert-poke instead of a
|
|
484
|
+
* delta.
|
|
485
|
+
*/
|
|
486
|
+
sinceCheckpoint?: number;
|
|
487
|
+
/**
|
|
488
|
+
* The CDC epoch {@link ClientShapeSubscribeMessage.sinceCheckpoint} belongs
|
|
489
|
+
* to. A mismatch (forked changelog timeline) forces a full re-seed even when
|
|
490
|
+
* the cursor is numerically in range.
|
|
491
|
+
*/
|
|
492
|
+
sinceEpoch?: string;
|
|
493
|
+
type: "shape_subscribe";
|
|
494
|
+
}
|
|
495
|
+
/** Cancel a shape subscription started with the same `id`. */
|
|
496
|
+
interface ClientShapeUnsubscribeMessage {
|
|
497
|
+
id: string;
|
|
498
|
+
type: "shape_unsubscribe";
|
|
499
|
+
}
|
|
272
500
|
interface ClientAckMessage {
|
|
273
501
|
id: string;
|
|
274
502
|
type: "ack";
|
|
@@ -309,7 +537,7 @@ interface ClientWhisperMessage {
|
|
|
309
537
|
topic: string;
|
|
310
538
|
type: "whisper";
|
|
311
539
|
}
|
|
312
|
-
type ClientMessage = ClientAckMessage | ClientConnectMessage | ClientStreamMessage | ClientSubscribeMessage | ClientUnsubscribeMessage | ClientWhisperMessage | ClientWhisperSubscribeMessage;
|
|
540
|
+
type ClientMessage = ClientAckMessage | ClientConnectMessage | ClientShapeSubscribeMessage | ClientShapeUnsubscribeMessage | ClientStreamMessage | ClientSubscribeMessage | ClientUnsubscribeMessage | ClientWhisperMessage | ClientWhisperSubscribeMessage;
|
|
313
541
|
/** Subscription protocol — server → client. */
|
|
314
542
|
interface ServerDataMessage {
|
|
315
543
|
/**
|
|
@@ -323,6 +551,13 @@ interface ServerDataMessage {
|
|
|
323
551
|
/** The CDC epoch this frame's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
|
|
324
552
|
epoch?: string;
|
|
325
553
|
id: string;
|
|
554
|
+
/**
|
|
555
|
+
* The highest custom-mutator `mutationId` from this client the server has
|
|
556
|
+
* now applied (the per-client `__client_watermark`). Echoed so the client's
|
|
557
|
+
* outbox can drop confirmed pending mutations and let TanStack DB collapse
|
|
558
|
+
* the matching optimistic overlay. Absent on shards without custom mutators.
|
|
559
|
+
*/
|
|
560
|
+
lastMutationId?: number;
|
|
326
561
|
type: "data" | "delta";
|
|
327
562
|
}
|
|
328
563
|
/**
|
|
@@ -336,8 +571,33 @@ interface ServerResumeMessage {
|
|
|
336
571
|
/** The CDC epoch this resume's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
|
|
337
572
|
epoch?: string;
|
|
338
573
|
id: string;
|
|
574
|
+
/** Per-client custom-mutator watermark (see {@link ServerDataMessage.lastMutationId}). */
|
|
575
|
+
lastMutationId?: number;
|
|
339
576
|
type: "resume";
|
|
340
577
|
}
|
|
578
|
+
/**
|
|
579
|
+
* Settled acknowledgement for a **list** subscription: a write touched one of
|
|
580
|
+
* the subscription's read tables but produced a byte-identical result, so the
|
|
581
|
+
* server suppressed the data frame. Sent ONLY to a `@lunora/db` custom-mutator
|
|
582
|
+
* client (one that announced a `clientId`, hence has a server-side
|
|
583
|
+
* `__client_watermark`) so its optimistic list overlay drops even when no data
|
|
584
|
+
* frame arrives. Plain `useQuery` subscribers never receive it, and an older
|
|
585
|
+
* client safely ignores the unknown frame.
|
|
586
|
+
*/
|
|
587
|
+
interface ServerSettledMessage {
|
|
588
|
+
cursor?: number;
|
|
589
|
+
/** The CDC epoch this settled frame's cursor belongs to (see {@link CachedQuery.serverEpoch}). */
|
|
590
|
+
epoch?: string;
|
|
591
|
+
id: string;
|
|
592
|
+
/**
|
|
593
|
+
* The highest custom-mutator `mutationId` from this client the server has
|
|
594
|
+
* now applied (the per-client `__client_watermark`). Forwarded to a
|
|
595
|
+
* collection's `onCheckpoint` so it can drop the overlay for the confirmed
|
|
596
|
+
* write whose result didn't change this list.
|
|
597
|
+
*/
|
|
598
|
+
lastMutationId?: number;
|
|
599
|
+
type: "settled";
|
|
600
|
+
}
|
|
341
601
|
interface ServerErrorMessage {
|
|
342
602
|
error?: unknown;
|
|
343
603
|
id?: string;
|
|
@@ -370,7 +630,65 @@ interface ServerWhisperMessage {
|
|
|
370
630
|
topic: string;
|
|
371
631
|
type: "whisper";
|
|
372
632
|
}
|
|
373
|
-
|
|
633
|
+
/**
|
|
634
|
+
* One row-level change in a shape's replication stream — the wire form of the
|
|
635
|
+
* DO's `__cdc_log` `CdcChange`. `insert`/`update` carry the post-image in
|
|
636
|
+
* `value` (projected to the shape's `columns`); `delete` omits it, identifying
|
|
637
|
+
* the removed row by `key` alone. The client applies these to its local
|
|
638
|
+
* collection; an unknown `key` on a `delete` is a safe no-op (a row the client
|
|
639
|
+
* never had in this shape).
|
|
640
|
+
*/
|
|
641
|
+
interface RowOp {
|
|
642
|
+
/** Row primary key (`_id`). */
|
|
643
|
+
key: string;
|
|
644
|
+
op: "delete" | "insert" | "update";
|
|
645
|
+
/** Logical table the row belongs to. */
|
|
646
|
+
table: string;
|
|
647
|
+
/** Post-image document for insert/update; absent on delete. */
|
|
648
|
+
value?: Record<string, unknown>;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Opens a **poke** — an atomically-applied batch of shape diffs (Zero's poke
|
|
652
|
+
* protocol). A `pokeStart` is followed by zero or more {@link ServerPokePartMessage}
|
|
653
|
+
* frames and closed by exactly one {@link ServerPokeEndMessage}; the client
|
|
654
|
+
* buffers every part and applies them in a single transaction at `pokeEnd`, so a
|
|
655
|
+
* socket that drops mid-poke simply re-seeds on reconnect (no torn view).
|
|
656
|
+
*/
|
|
657
|
+
interface ServerPokeStartMessage {
|
|
658
|
+
/** The checkpoint the client's view is expected to be at before this poke applies (for ordering/gap detection). */
|
|
659
|
+
baseCheckpoint?: number;
|
|
660
|
+
/** CDC epoch this poke belongs to; a mismatch forces the client to re-seed rather than apply. */
|
|
661
|
+
epoch?: string;
|
|
662
|
+
/** Correlates this poke's `pokeStart`/`pokePart`/`pokeEnd` frames. */
|
|
663
|
+
pokeId: string;
|
|
664
|
+
type: "pokeStart";
|
|
665
|
+
}
|
|
666
|
+
/** One shape's slice of an in-flight poke: the row-ops to apply for `shapeId`. */
|
|
667
|
+
interface ServerPokePartMessage {
|
|
668
|
+
/** Per-client custom-mutator watermark carried with this slice (see {@link ServerSettledMessage.lastMutationId}). */
|
|
669
|
+
lastMutationId?: number;
|
|
670
|
+
pokeId: string;
|
|
671
|
+
/** Ordered row-level changes for this shape, applied in sequence at `pokeEnd`. */
|
|
672
|
+
rowsPatch: RowOp[];
|
|
673
|
+
/** The {@link ClientShapeSubscribeMessage.id} these row-ops belong to. */
|
|
674
|
+
shapeId: string;
|
|
675
|
+
type: "pokePart";
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Closes a poke: the client commits the buffered parts atomically and advances
|
|
679
|
+
* its checkpoint to {@link ServerPokeEndMessage.checkpoint} (the `__cdc_log`
|
|
680
|
+
* cursor high-watermark the view now reflects), replayed as `sinceCheckpoint` on
|
|
681
|
+
* the next reconnect.
|
|
682
|
+
*/
|
|
683
|
+
interface ServerPokeEndMessage {
|
|
684
|
+
/** The `__cdc_log` cursor the view is at after applying this poke. */
|
|
685
|
+
checkpoint?: number;
|
|
686
|
+
/** CDC epoch the {@link ServerPokeEndMessage.checkpoint} belongs to. */
|
|
687
|
+
epoch?: string;
|
|
688
|
+
pokeId: string;
|
|
689
|
+
type: "pokeEnd";
|
|
690
|
+
}
|
|
691
|
+
type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerPokeEndMessage | ServerPokePartMessage | ServerPokeStartMessage | ServerResumeMessage | ServerSettledMessage | ServerWhisperMessage;
|
|
374
692
|
/**
|
|
375
693
|
* The authenticated user as exposed client-side, mirroring better-auth's
|
|
376
694
|
* `user` row (the `user` field of the `get-session` response). Kept minimal
|
|
@@ -582,6 +900,13 @@ interface WorkflowInstanceDetail extends WorkflowInstanceSummary {
|
|
|
582
900
|
}
|
|
583
901
|
/** A page of workflow instances. */
|
|
584
902
|
interface WorkflowInstancePage {
|
|
903
|
+
/**
|
|
904
|
+
* Whether workflow inspection is configured on the worker (a Cloudflare
|
|
905
|
+
* account id + API token). `false` when the admin proxy reports it can't
|
|
906
|
+
* inspect instances; omitted (treated as configured) otherwise. Lets a
|
|
907
|
+
* caller render a "set credentials" state without a failed request.
|
|
908
|
+
*/
|
|
909
|
+
configured?: boolean;
|
|
585
910
|
instances: WorkflowInstanceSummary[];
|
|
586
911
|
page: number;
|
|
587
912
|
perPage: number;
|
|
@@ -594,6 +919,23 @@ interface SubscriptionError {
|
|
|
594
919
|
message: string;
|
|
595
920
|
}
|
|
596
921
|
type SubscriptionErrorCallback = (error: SubscriptionError) => void;
|
|
922
|
+
/**
|
|
923
|
+
* One active per-call optimistic transform layered onto a subscription. The
|
|
924
|
+
* displayed value is the authoritative {@link SubscriptionState.serverBase}
|
|
925
|
+
* folded through every layer's `transform`, in order — so an incoming server
|
|
926
|
+
* frame re-folds the still-pending layers onto the new base (rebasing) instead
|
|
927
|
+
* of clobbering them. A layer is dropped — gaplessly — once a `data`/`delta`
|
|
928
|
+
* frame whose `cursor >= commitCursor` arrives (its write is now reflected in
|
|
929
|
+
* `serverBase`); `commitCursor` is the CDC cursor the server echoed on the
|
|
930
|
+
* mutation's response, and stays `undefined` while the write is still queued/
|
|
931
|
+
* in-flight (so the overlay survives unrelated deltas until confirmed).
|
|
932
|
+
*/
|
|
933
|
+
interface OptimisticLayer {
|
|
934
|
+
/** The committed CDC cursor (from the mutation response); `undefined` until confirmed. */
|
|
935
|
+
commitCursor?: number;
|
|
936
|
+
readonly id: symbol;
|
|
937
|
+
readonly transform: (current: unknown) => unknown;
|
|
938
|
+
}
|
|
597
939
|
interface SubscriptionState {
|
|
598
940
|
/** True once the server has acked the subscription on the current socket. */
|
|
599
941
|
acked: boolean;
|
|
@@ -605,13 +947,52 @@ interface SubscriptionState {
|
|
|
605
947
|
*/
|
|
606
948
|
readonly argsKey: string;
|
|
607
949
|
readonly callbacks: Set<SubscriptionCallback>;
|
|
950
|
+
/**
|
|
951
|
+
* Notified when a `settled` frame advances this subscription's watermark — a
|
|
952
|
+
* write touched the subscription's tables but the result was byte-identical,
|
|
953
|
+
* so the server suppressed the data frame. A `@lunora/db` list collection
|
|
954
|
+
* uses this to drop the optimistic overlay for the confirmed write.
|
|
955
|
+
*
|
|
956
|
+
* A SET (not a single slot) because `SubscriptionState` is SHARED across
|
|
957
|
+
* every subscriber to the same `(fn, args, shardKey)`: a `@lunora/db`
|
|
958
|
+
* collection may subscribe to a query a plain `useQuery` already opened, so
|
|
959
|
+
* each subscriber registers its own callback (mirroring `callbacks` /
|
|
960
|
+
* `errorCallbacks`) and a `settled` frame fans out to all of them. Plain
|
|
961
|
+
* `useQuery` consumers register nothing, leaving the set empty.
|
|
962
|
+
*/
|
|
963
|
+
readonly checkpointCallbacks: Set<(watermark: {
|
|
964
|
+
checkpoint?: number;
|
|
965
|
+
mutationId?: number;
|
|
966
|
+
}) => void>;
|
|
608
967
|
/** Notified when the server rejects this subscription (e.g. admin auth). */
|
|
609
968
|
readonly errorCallbacks: Set<SubscriptionErrorCallback>;
|
|
610
969
|
readonly fn: FunctionReference;
|
|
611
970
|
readonly id: string;
|
|
971
|
+
/**
|
|
972
|
+
* The highest custom-mutator `mutationId` from this client the server has
|
|
973
|
+
* applied, captured from the last `settled` frame (the suppressed-list-frame
|
|
974
|
+
* watermark). Forwarded to {@link SubscriptionState.checkpointCallbacks}.
|
|
975
|
+
* Absent until a `settled` frame arrives.
|
|
976
|
+
*/
|
|
977
|
+
lastMutationId?: number;
|
|
612
978
|
/** Last known value, used to short-circuit `useQuery`-style consumers. */
|
|
613
979
|
lastValue: unknown;
|
|
614
980
|
/**
|
|
981
|
+
* Active per-call optimistic layers, in application order (see
|
|
982
|
+
* {@link OptimisticLayer}). Empty for subscriptions with no pending per-call
|
|
983
|
+
* optimistic write — the common case, where `lastValue` tracks `serverBase`
|
|
984
|
+
* exactly and behaviour is identical to a plain server-value assignment.
|
|
985
|
+
*/
|
|
986
|
+
optimisticLayers: OptimisticLayer[];
|
|
987
|
+
/**
|
|
988
|
+
* The authoritative server value the optimistic layers fold onto — the value
|
|
989
|
+
* with NO optimistic overlay. Tracks `lastValue` exactly whenever no layers
|
|
990
|
+
* are active; diverges only while a per-call optimistic write is pending. A
|
|
991
|
+
* server frame updates this (and re-folds the layers); the durable read cache
|
|
992
|
+
* persists this, never the optimistic overlay.
|
|
993
|
+
*/
|
|
994
|
+
serverBase: unknown;
|
|
995
|
+
/**
|
|
615
996
|
* The `__cdc_log` high-watermark (`cursor`) the `lastValue` reflects,
|
|
616
997
|
* captured from the last `data`/`delta`/`resume` frame. Persisted to the
|
|
617
998
|
* durable read cache and replayed as `sinceSeq` on reconnect so the server
|
|
@@ -627,18 +1008,15 @@ interface SubscriptionState {
|
|
|
627
1008
|
* until the first epoch-stamped frame arrives.
|
|
628
1009
|
*/
|
|
629
1010
|
serverEpoch?: string;
|
|
630
|
-
/**
|
|
631
|
-
* Monotonic counter incremented on every server-pushed delta or data.
|
|
632
|
-
* Used by optimistic-update rollback to detect whether the server has
|
|
633
|
-
* already moved past the value we'd otherwise restore.
|
|
634
|
-
*/
|
|
635
|
-
serverVersion: number;
|
|
636
1011
|
readonly shardKey?: string;
|
|
637
1012
|
}
|
|
638
1013
|
/**
|
|
639
1014
|
* Active subscription registry. The client keys subscriptions by
|
|
640
|
-
* `(functionPath,
|
|
641
|
-
* single server-side registration.
|
|
1015
|
+
* `(functionPath, stableStringify(args), shardKey)` so duplicate calls share a
|
|
1016
|
+
* single server-side registration. Args are stably encoded (keys sorted at every
|
|
1017
|
+
* depth) so two structurally-equal arg records constructed with a different key
|
|
1018
|
+
* order (`{ a, b }` vs `{ b, a }`) collapse to the same key instead of leaking a
|
|
1019
|
+
* duplicate subscription.
|
|
642
1020
|
*/
|
|
643
1021
|
declare class SubscriptionRegistry {
|
|
644
1022
|
static key(functionPath: string, args: Record<string, unknown>, shardKey?: string): string;
|
|
@@ -656,10 +1034,10 @@ declare class SubscriptionRegistry {
|
|
|
656
1034
|
* many subscribed queries at once (Convex's `OptimisticLocalStore` model).
|
|
657
1035
|
*
|
|
658
1036
|
* `getQuery` reads the current value (server value or any still-pending
|
|
659
|
-
* optimistic override) of a subscribed query; `setQuery`
|
|
660
|
-
*
|
|
661
|
-
*
|
|
662
|
-
*
|
|
1037
|
+
* optimistic override) of a subscribed query; `setQuery` registers a constant
|
|
1038
|
+
* optimistic layer on top. The whole batch rebases onto incoming deltas and
|
|
1039
|
+
* settles together — confirmed on the mutation's commit cursor, or rolled back
|
|
1040
|
+
* on failure — the same per-subscription layer machinery the single-query
|
|
663
1041
|
* per-call `optimistic` transform uses, generalized to N queries.
|
|
664
1042
|
*/
|
|
665
1043
|
interface OptimisticLocalStore {
|
|
@@ -689,30 +1067,20 @@ interface OptimisticLocalStore {
|
|
|
689
1067
|
/** A mutation's multi-query optimistic update: read/write the cache via `localStore`. */
|
|
690
1068
|
type OptimisticUpdate<Args> = (localStore: OptimisticLocalStore, args: Args) => void;
|
|
691
1069
|
/**
|
|
692
|
-
* Build an {@link OptimisticLocalStore} bound to a subscription registry
|
|
693
|
-
* mutation's shard key
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
1070
|
+
* Build an {@link OptimisticLocalStore} bound to a subscription registry and the
|
|
1071
|
+
* mutation's shard key. Each `setQuery(value)` registers a constant-value layer
|
|
1072
|
+
* on its target subscription (via `applyOptimisticLayer`): the predicted value
|
|
1073
|
+
* survives incoming server deltas (re-clamped, masking concurrent changes to that
|
|
1074
|
+
* query — not merged) and drops gaplessly on the mutation's commit cursor, like
|
|
1075
|
+
* the single-query per-call `optimistic` path. Returns the store plus the ordered
|
|
1076
|
+
* `confirm` (success) and `rollback` (failure) closures every `setQuery` produced,
|
|
1077
|
+
* so the caller settles the whole batch when the mutation does.
|
|
697
1078
|
*/
|
|
698
|
-
declare const createLocalStore: (subscriptions: SubscriptionRegistry, shardKey: string | undefined
|
|
1079
|
+
declare const createLocalStore: (subscriptions: SubscriptionRegistry, shardKey: string | undefined) => {
|
|
1080
|
+
confirms: ((commitCursor: number | undefined) => void)[];
|
|
699
1081
|
rollbacks: (() => void)[];
|
|
700
1082
|
store: OptimisticLocalStore;
|
|
701
1083
|
};
|
|
702
|
-
/**
|
|
703
|
-
* Bounded async-iterator queue backing `LunoraClient.stream`.
|
|
704
|
-
*
|
|
705
|
-
* The server pushes one server `chunk` message per yielded value while the
|
|
706
|
-
* client iterates with `for await (const chunk of stream)`. A producer that
|
|
707
|
-
* outruns its consumer would otherwise OOM the page, so the buffer is bounded
|
|
708
|
-
* — exceeding {@link DEFAULT_MAX_BUFFER} surfaces a `STREAM_BACKPRESSURE`
|
|
709
|
-
* error to the iterator (and to the server-side cancel path).
|
|
710
|
-
*
|
|
711
|
-
* The queue is closed exactly once via {@link StreamHandle.complete} (success)
|
|
712
|
-
* or {@link StreamHandle.fail} (transport / server error). Subsequent calls
|
|
713
|
-
* are silent no-ops so a duplicate `complete` frame after a cancel doesn't
|
|
714
|
-
* crash the page.
|
|
715
|
-
*/
|
|
716
1084
|
declare const DEFAULT_MAX_BUFFER = 1024;
|
|
717
1085
|
interface StreamHandle<T = unknown> {
|
|
718
1086
|
/** Mark the stream complete (no more chunks); resolves any pending consumer to `done:true`. */
|
|
@@ -753,6 +1121,41 @@ declare const createStream: <T>(options: {
|
|
|
753
1121
|
*/
|
|
754
1122
|
type ConnectionStatus = "connected" | "connecting" | "idle" | "offline";
|
|
755
1123
|
/**
|
|
1124
|
+
* Terminal verdict for a mutation that passed through the offline queue,
|
|
1125
|
+
* delivered to {@link LunoraClient.onMutationSettled}.
|
|
1126
|
+
*
|
|
1127
|
+
* Unlike the Promise returned by {@link LunoraClient.mutation} — which only the
|
|
1128
|
+
* original caller can await, and which no longer exists after a reload — this
|
|
1129
|
+
* fires for *every* queued write the server (or the queue) reaches a verdict on,
|
|
1130
|
+
* including writes restored from durable storage in a later session. It is the
|
|
1131
|
+
* channel a UI uses to tell the user "your queued change couldn't be saved"
|
|
1132
|
+
* instead of silently dropping a rolled-back optimistic row.
|
|
1133
|
+
*
|
|
1134
|
+
* `status: "rejected"` carries the failure `code` (e.g. `CONFLICT`,
|
|
1135
|
+
* `OFFLINE_QUEUE_OVERFLOW`, `OFFLINE_IDENTITY_CHANGED`) and the `error`.
|
|
1136
|
+
* `hadAwaiter` is `false` for a write whose original `mutation()` Promise is
|
|
1137
|
+
* gone (a hydrated/post-reload replay or an eviction), so a listener can tell
|
|
1138
|
+
* "the caller already saw this" apart from "nothing else will report this".
|
|
1139
|
+
*/
|
|
1140
|
+
interface MutationSettledEvent {
|
|
1141
|
+
/** The write's args, so a listener can describe or re-offer the change. */
|
|
1142
|
+
readonly args: Record<string, unknown>;
|
|
1143
|
+
/** Server/queue error code on `rejected` (e.g. `CONFLICT`), when present. */
|
|
1144
|
+
readonly code?: string;
|
|
1145
|
+
/** The rejection error on `status: "rejected"`. */
|
|
1146
|
+
readonly error?: unknown;
|
|
1147
|
+
/** The `<file>:<function>` reference of the mutation. */
|
|
1148
|
+
readonly functionPath: string;
|
|
1149
|
+
/** Whether a live caller was still awaiting this write's `mutation()` Promise. */
|
|
1150
|
+
readonly hadAwaiter: boolean;
|
|
1151
|
+
/** The write's stable id (idempotency key / queue id). */
|
|
1152
|
+
readonly id: string;
|
|
1153
|
+
/** Shard the write targeted, if any. */
|
|
1154
|
+
readonly shardKey?: string;
|
|
1155
|
+
/** Terminal outcome. */
|
|
1156
|
+
readonly status: "committed" | "rejected";
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
756
1159
|
* Per-call options for {@link LunoraClient.mutation} — the optimistic-update
|
|
757
1160
|
* machinery plus `shardKey`. Exported (at the end of this file) so the framework
|
|
758
1161
|
* adapters (`@lunora/react`, `/solid`, `/svelte`, `/vue`) can type their
|
|
@@ -760,6 +1163,13 @@ type ConnectionStatus = "connected" | "connecting" | "idle" | "offline";
|
|
|
760
1163
|
* re-declaring it.
|
|
761
1164
|
*/
|
|
762
1165
|
interface MutationCallOptions<TCurrent = unknown, TValue = unknown, TArgs = unknown> {
|
|
1166
|
+
/**
|
|
1167
|
+
* Override the auto-generated idempotency key (`x-lunora-mutation-id`). Lets a
|
|
1168
|
+
* durable outbox replay a committed-but-unacked write under its *original* key
|
|
1169
|
+
* so the server dedups it instead of applying it twice. Omit for normal calls —
|
|
1170
|
+
* each then gets a fresh key.
|
|
1171
|
+
*/
|
|
1172
|
+
mutationId?: string;
|
|
763
1173
|
optimistic?: (current: TCurrent | undefined) => TValue;
|
|
764
1174
|
/**
|
|
765
1175
|
* Convex-parity multi-query optimistic update. Receives an
|
|
@@ -770,6 +1180,42 @@ interface MutationCallOptions<TCurrent = unknown, TValue = unknown, TArgs = unkn
|
|
|
770
1180
|
optimisticUpdate?: OptimisticUpdate<TArgs>;
|
|
771
1181
|
shardKey?: string;
|
|
772
1182
|
}
|
|
1183
|
+
/** Callback a shape subscription invokes with its materialized rowset on every applied poke. */
|
|
1184
|
+
type ShapeCallback = (rows: Record<string, unknown>[]) => void;
|
|
1185
|
+
/**
|
|
1186
|
+
* The high-water marks a shape poke has now synced to the client: `checkpoint`
|
|
1187
|
+
* is the op-log cursor and `mutationId` the highest custom-mutator id the server
|
|
1188
|
+
* echoed for this client. A `@lunora/db` collection feeds these into its
|
|
1189
|
+
* checkpoint registry to drop optimistic overlays once the server's authoritative
|
|
1190
|
+
* rows have landed.
|
|
1191
|
+
*/
|
|
1192
|
+
interface SyncWatermark {
|
|
1193
|
+
checkpoint?: number;
|
|
1194
|
+
mutationId?: number;
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* An `Error` carrying the server's machine-readable `code` and (for a
|
|
1198
|
+
* `LunoraError`) structured `data`, plus an optional actionable `hint` (Markdown)
|
|
1199
|
+
* and `docsUrl` resolved from the central error catalog. The client's public
|
|
1200
|
+
* error contract for RPC/batch failures — a UI can render `hint`/`docsUrl` to
|
|
1201
|
+
* tell the user how to fix the error. The `(string & {})` arm keeps
|
|
1202
|
+
* forward-compat/unknown server codes assignable without losing autocomplete on
|
|
1203
|
+
* the known {@link LunoraErrorCode} union.
|
|
1204
|
+
*/
|
|
1205
|
+
type LunoraClientError = Error & {
|
|
1206
|
+
code?: LunoraErrorCode | (string & {});
|
|
1207
|
+
data?: unknown;
|
|
1208
|
+
docsUrl?: string;
|
|
1209
|
+
hint?: string | string[];
|
|
1210
|
+
};
|
|
1211
|
+
/** One demuxed result slot of a {@link LunoraClient.batch} call (plan 088). */
|
|
1212
|
+
type BatchSlot = {
|
|
1213
|
+
error: LunoraClientError;
|
|
1214
|
+
ok: false;
|
|
1215
|
+
} | {
|
|
1216
|
+
ok: true;
|
|
1217
|
+
value: unknown;
|
|
1218
|
+
};
|
|
773
1219
|
/**
|
|
774
1220
|
* Lunora browser/edge client. Talks RPC over HTTP and real-time deltas over
|
|
775
1221
|
* a single multiplexed WebSocket.
|
|
@@ -778,6 +1224,8 @@ interface MutationCallOptions<TCurrent = unknown, TValue = unknown, TArgs = unkn
|
|
|
778
1224
|
* see the package README for the wire protocol.
|
|
779
1225
|
*/
|
|
780
1226
|
declare class LunoraClient {
|
|
1227
|
+
/** 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. */
|
|
1228
|
+
private static readonly MAX_POKE_BUFFERS;
|
|
781
1229
|
readonly url: string;
|
|
782
1230
|
readonly wsUrl: string;
|
|
783
1231
|
private wsToken;
|
|
@@ -787,11 +1235,36 @@ declare class LunoraClient {
|
|
|
787
1235
|
private readonly WebSocketImpl;
|
|
788
1236
|
private readonly bookmark;
|
|
789
1237
|
private readonly reconnectOptions;
|
|
1238
|
+
/** WS connect timeout (ms); `0` disables it. See {@link LunoraClientOptions.connectTimeoutMs}. */
|
|
1239
|
+
private readonly connectTimeoutMs;
|
|
790
1240
|
/** Keepalive cadence (ms); `0` disables the heartbeat. See {@link LunoraClientOptions.heartbeatIntervalMs}. */
|
|
791
1241
|
private readonly heartbeatIntervalMs;
|
|
792
1242
|
private readonly offlineQueue;
|
|
1243
|
+
/**
|
|
1244
|
+
* Durable outbox seam (the `@lunora/db` `createExecutorOutboxSink`). When
|
|
1245
|
+
* set, offline writes are delegated here and the built-in {@link OfflineQueue}
|
|
1246
|
+
* is bypassed, so a db app has exactly one durable write path.
|
|
1247
|
+
*/
|
|
1248
|
+
private readonly outbox;
|
|
1249
|
+
/** Stable per-client id stamped onto every `OutboxMutation` (custom-mutator watermark). */
|
|
1250
|
+
private readonly clientId;
|
|
1251
|
+
/**
|
|
1252
|
+
* Highest custom-mutator watermark the server has echoed for this client,
|
|
1253
|
+
* keyed by shard bucket (`shardKey ?? ""`) since the DO tracks one
|
|
1254
|
+
* `__client_watermark` per shard. `callMutator` bumps it from every
|
|
1255
|
+
* ack; the `@lunora/db` mutator runtime seeds its `clientSeq` generator from
|
|
1256
|
+
* it so a reload (which resets the in-memory counter) never reissues a stale
|
|
1257
|
+
* sequence the server would silently swallow as a replay.
|
|
1258
|
+
*/
|
|
1259
|
+
private readonly clientWatermarks;
|
|
1260
|
+
/** Monotonic per-client mutation counter backing the server `__client_watermark`. */
|
|
1261
|
+
private outboxMutationCounter;
|
|
793
1262
|
private readonly onPersistenceError;
|
|
794
1263
|
private readonly persistence;
|
|
1264
|
+
/** App/schema version stamped on persisted writes + cached reads; mismatches are purged. */
|
|
1265
|
+
private readonly persistenceVersion;
|
|
1266
|
+
/** Releases the multi-tab outbox-leader Web Lock on close (see `hydrateAsOutboxLeader`). */
|
|
1267
|
+
private outboxLeaderRelease;
|
|
795
1268
|
/** Durable read cache (Pillar 2); `undefined` when `queryCache` is omitted or `false`. */
|
|
796
1269
|
private readonly queryCache;
|
|
797
1270
|
/**
|
|
@@ -835,6 +1308,14 @@ declare class LunoraClient {
|
|
|
835
1308
|
private readonly connectionContextHolders;
|
|
836
1309
|
private authToken;
|
|
837
1310
|
/**
|
|
1311
|
+
* Optional STABLE identity subject (a user id), the basis of the offline-queue
|
|
1312
|
+
* identity stamp when supplied. Keeps a same-user token *refresh* from looking
|
|
1313
|
+
* like an identity change (which would discard queued writes). `undefined` =
|
|
1314
|
+
* not supplied, so identity falls back to a hash of the raw token. See
|
|
1315
|
+
* `setAuthToken` / `identityFingerprint`.
|
|
1316
|
+
*/
|
|
1317
|
+
private authSubject;
|
|
1318
|
+
/**
|
|
838
1319
|
* Identity stamp recorded against each queued offline mutation, keyed by
|
|
839
1320
|
* the queue-assigned mutation id. Captured at enqueue from the auth token
|
|
840
1321
|
* in effect at the time, and re-checked at flush so a queued write can
|
|
@@ -849,6 +1330,10 @@ declare class LunoraClient {
|
|
|
849
1330
|
private readonly statusListeners;
|
|
850
1331
|
/** Subscribers notified when the server drops a socket for an expired token (see `onTokenExpired`). */
|
|
851
1332
|
private readonly tokenExpiredListeners;
|
|
1333
|
+
/** Subscribers to offline-queued mutation verdicts (see `onMutationSettled`). */
|
|
1334
|
+
private readonly mutationSettledListeners;
|
|
1335
|
+
/** Subscribers to the offline-queue pending-count (see `onPendingChange`). */
|
|
1336
|
+
private readonly pendingChangeListeners;
|
|
852
1337
|
/**
|
|
853
1338
|
* Whisper-topic handlers, keyed by `connectionKey(shardKey)` → topic → set
|
|
854
1339
|
* of callbacks. Membership doubles as the resubscribe set replayed on every
|
|
@@ -866,20 +1351,78 @@ declare class LunoraClient {
|
|
|
866
1351
|
* calls `.cancel()` or the iterator is garbage-collected.
|
|
867
1352
|
*/
|
|
868
1353
|
private readonly streams;
|
|
1354
|
+
/** Live shape subscriptions (partial replication), keyed by their wire id. */
|
|
1355
|
+
private readonly shapeSubscriptions;
|
|
1356
|
+
/** In-flight pokes being assembled between `pokeStart` and `pokeEnd`, keyed by `pokeId`. */
|
|
1357
|
+
private readonly pokeBuffers;
|
|
1358
|
+
private nextShapeId;
|
|
869
1359
|
constructor(options: LunoraClientOptions);
|
|
870
1360
|
/**
|
|
871
1361
|
* Set (or clear) the bearer token sent on every HTTP RPC. Notifies any
|
|
872
1362
|
* {@link onAuthTokenChange} listeners so React hooks like `useAuth` stay in
|
|
873
1363
|
* sync across all mounted instances.
|
|
874
1364
|
*
|
|
1365
|
+
* Pass a STABLE `subject` (the user id) to key the offline-queue identity on
|
|
1366
|
+
* it instead of the token bytes, so a token *refresh* (same user, new JWT)
|
|
1367
|
+
* doesn't read as an identity change and discard queued writes. The subject is
|
|
1368
|
+
* **sticky**: a later call that omits it (or passes `undefined`) keeps the
|
|
1369
|
+
* established subject — so `setAuthToken(refreshedToken)` after a prior
|
|
1370
|
+
* `setAuthToken(token, user.id)` retains the identity. Pass `null` to clear it
|
|
1371
|
+
* (an explicit sign-out). Establishing the subject for the first time on an
|
|
1372
|
+
* UNCHANGED token (e.g. the user id resolves a tick after the token was set)
|
|
1373
|
+
* re-stamps any in-flight queued writes rather than dropping them — same
|
|
1374
|
+
* credential, just a more stable label. A real user switch (the token AND
|
|
1375
|
+
* subject both change) still drops the previous user's writes.
|
|
1376
|
+
*
|
|
875
1377
|
* Does NOT update the WebSocket auth — the WS token is fixed at upgrade
|
|
876
1378
|
* time and lives in the URL. To refresh live WS auth, call
|
|
877
1379
|
* {@link setWsToken} explicitly, which closes existing shard sockets to
|
|
878
1380
|
* force a reconnect with the new credential.
|
|
879
1381
|
*/
|
|
880
|
-
setAuthToken(token: string | null): void;
|
|
1382
|
+
setAuthToken(token: string | null, subject?: string | null): void;
|
|
881
1383
|
getAuthToken(): string | null;
|
|
882
1384
|
/**
|
|
1385
|
+
* The current identity fingerprint (the same stamp queued offline writes
|
|
1386
|
+
* carry). Exposed so a durable {@link OutboxSink}'s replay handler — which
|
|
1387
|
+
* owns its own at-least-once replay outside the built-in `OfflineQueue` —
|
|
1388
|
+
* can drop a persisted write whose captured `identity` no longer matches the
|
|
1389
|
+
* signed-in user, the guard the queue path applies in `flushOfflineQueue`.
|
|
1390
|
+
*/
|
|
1391
|
+
currentIdentity(): string | null;
|
|
1392
|
+
/** This client's stable identifier — the watermark key the server's custom-mutator protocol advances per `clientSeq`. */
|
|
1393
|
+
clientIdentifier(): string;
|
|
1394
|
+
/**
|
|
1395
|
+
* The highest custom-mutator watermark the server has echoed for this client
|
|
1396
|
+
* on the given shard (0 if none yet). The `@lunora/db` mutator runtime seeds
|
|
1397
|
+
* its `clientSeq` generator from this so a reload never reissues a sequence
|
|
1398
|
+
* the server has already applied (which it would swallow as a replay, silently
|
|
1399
|
+
* dropping the write).
|
|
1400
|
+
*/
|
|
1401
|
+
confirmedMutationWatermark(shardKey?: string): number;
|
|
1402
|
+
/**
|
|
1403
|
+
* Push a custom mutator to its authoritative server impl over the watermark
|
|
1404
|
+
* protocol (Phase 4): the request carries `x-lunora-client-id` + a monotonic
|
|
1405
|
+
* `x-lunora-client-seq`, so the DO runs it exactly once and advances this
|
|
1406
|
+
* client's `__client_watermark`.
|
|
1407
|
+
*
|
|
1408
|
+
* Returns the server `result` plus `applied`: `true` when the DO ran this push
|
|
1409
|
+
* as the next-in-order mutation, `false` when it was a replay ack (`clientSeq`
|
|
1410
|
+
* was at or below the stored watermark — e.g. a stale sequence after a reload).
|
|
1411
|
+
* A `false` verdict tells the caller to reissue above the now-known watermark
|
|
1412
|
+
* (echoed into {@link confirmedMutationWatermark}) rather than treat the benign
|
|
1413
|
+
* ack as a confirmed write. Every ack — applied or not — bumps the watermark.
|
|
1414
|
+
*
|
|
1415
|
+
* This is the online transport for `@lunora/db`'s client-mutator runtime; the
|
|
1416
|
+
* optimistic overlay + durable-outbox concerns live in that runtime, not here.
|
|
1417
|
+
*/
|
|
1418
|
+
callMutator(functionPath: string, args: Record<string, unknown>, options?: {
|
|
1419
|
+
clientSeq?: number;
|
|
1420
|
+
shardKey?: string;
|
|
1421
|
+
}): Promise<{
|
|
1422
|
+
applied: boolean;
|
|
1423
|
+
result: unknown;
|
|
1424
|
+
}>;
|
|
1425
|
+
/**
|
|
883
1426
|
* Subscribe to auth-token changes. Returns an unsubscribe function. The
|
|
884
1427
|
* listener is NOT invoked on registration — use {@link getAuthToken} for
|
|
885
1428
|
* the current value.
|
|
@@ -990,10 +1533,55 @@ declare class LunoraClient {
|
|
|
990
1533
|
* unsubscribe function.
|
|
991
1534
|
*/
|
|
992
1535
|
onConnectionStatus(listener: (status: ConnectionStatus) => void): Unsubscribe;
|
|
1536
|
+
/**
|
|
1537
|
+
* Number of offline writes waiting in the built-in queue to be sent — the
|
|
1538
|
+
* depth for a "N changes waiting to sync" indicator. Counts writes that are
|
|
1539
|
+
* queued (offline / mid-reconnect), not ones already in flight on the wire.
|
|
1540
|
+
* A `@lunora/db` app whose writes ride the unified outbox should read
|
|
1541
|
+
* `LunoraDb.pendingCount()` instead (this counts only the built-in queue).
|
|
1542
|
+
*/
|
|
1543
|
+
pendingCount(): number;
|
|
1544
|
+
/**
|
|
1545
|
+
* Subscribe to changes in {@link pendingCount}. Invokes `listener` immediately
|
|
1546
|
+
* with the current count, then whenever the queue depth changes (a write is
|
|
1547
|
+
* enqueued, flushed, or discarded). Returns an unsubscribe function.
|
|
1548
|
+
*/
|
|
1549
|
+
onPendingChange(listener: (pending: number) => void): Unsubscribe;
|
|
1550
|
+
/**
|
|
1551
|
+
* Subscribe to terminal verdicts for offline-queued mutations. The listener
|
|
1552
|
+
* fires once per queued write that commits or is rejected — including a write
|
|
1553
|
+
* restored from durable storage after a reload, whose original `mutation()`
|
|
1554
|
+
* Promise no longer exists (`hadAwaiter: false`), and a write the queue
|
|
1555
|
+
* evicts on overflow or discards on an identity change. This is the durable
|
|
1556
|
+
* channel for surfacing a rolled-back optimistic write to the UI; an online
|
|
1557
|
+
* mutation that never queued still surfaces through the Promise `mutation()`
|
|
1558
|
+
* returns. The listener is NOT invoked on registration. Returns an
|
|
1559
|
+
* unsubscribe function. See {@link MutationSettledEvent}.
|
|
1560
|
+
*/
|
|
1561
|
+
onMutationSettled(listener: (event: MutationSettledEvent) => void): Unsubscribe;
|
|
993
1562
|
query<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: {
|
|
994
1563
|
shardKey?: string;
|
|
995
1564
|
}): Promise<ReturnOf<F>>;
|
|
996
1565
|
/**
|
|
1566
|
+
* Batch several independent calls into ONE round trip (plan 088). Each call is
|
|
1567
|
+
* dispatched server-side exactly as an individual RPC — per-shard
|
|
1568
|
+
* authorization, `(identity, mutationId)` idempotency, and custom-mutator
|
|
1569
|
+
* watermark ordering are all preserved — and the worker splits the batch by
|
|
1570
|
+
* shard so calls to different shards fan out to their own DOs. Results are
|
|
1571
|
+
* demuxed back in input order; a failing call does NOT fail the batch (its
|
|
1572
|
+
* slot carries `{ ok: false, error }`, with `.code`/`.data` reconstructed like
|
|
1573
|
+
* a single call). Args/results ride the value codec (bytes/bigint survive).
|
|
1574
|
+
*
|
|
1575
|
+
* No promise pipelining and no capability passing — a call's args cannot
|
|
1576
|
+
* reference another call's result (see plan 088 §fence; capabilities are
|
|
1577
|
+
* incompatible with DO hibernation).
|
|
1578
|
+
*/
|
|
1579
|
+
batch(calls: ReadonlyArray<{
|
|
1580
|
+
args?: Record<string, unknown>;
|
|
1581
|
+
fn: FunctionReference;
|
|
1582
|
+
shardKey?: string;
|
|
1583
|
+
}>): Promise<BatchSlot[]>;
|
|
1584
|
+
/**
|
|
997
1585
|
* Invoke a mutation. Errors propagate as rejections.
|
|
998
1586
|
*
|
|
999
1587
|
* Offline-queue semantics: a mutation is queued (and replayed on reconnect)
|
|
@@ -1069,8 +1657,12 @@ declare class LunoraClient {
|
|
|
1069
1657
|
* List a workflow's instances via the admin Workflows proxy
|
|
1070
1658
|
* (`/_lunora/admin/workflows/instances`) — the Cloudflare control-plane data
|
|
1071
1659
|
* the `Workflow` binding can't expose. Requires the worker to be built with a
|
|
1072
|
-
* `workflowsClient` (Cloudflare account id + API token)
|
|
1073
|
-
*
|
|
1660
|
+
* `workflowsClient` (Cloudflare account id + API token). When one isn't
|
|
1661
|
+
* configured this does NOT reject: the proxy returns a `200 { configured:
|
|
1662
|
+
* false }` sentinel, so the result resolves with `configured === false` and an
|
|
1663
|
+
* empty `instances` list — callers should branch on that flag rather than
|
|
1664
|
+
* try/catch. (The instance-detail / status endpoints still reject with 501.)
|
|
1665
|
+
* `name` is the deployed workflow name.
|
|
1074
1666
|
*/
|
|
1075
1667
|
listWorkflowInstances(options: {
|
|
1076
1668
|
name: string;
|
|
@@ -1269,6 +1861,55 @@ declare class LunoraClient {
|
|
|
1269
1861
|
topK?: number;
|
|
1270
1862
|
}): Promise<VectorQueryMatch[]>;
|
|
1271
1863
|
/**
|
|
1864
|
+
* List the worker's registered Workers KV namespaces (binding names). Hits
|
|
1865
|
+
* the admin-gated `GET /_lunora/admin/kv/namespaces` endpoint — the worker
|
|
1866
|
+
* must be built with a `kvIntrospector` and `adminToken`. Powers the
|
|
1867
|
+
* studio's KV browser.
|
|
1868
|
+
*/
|
|
1869
|
+
listKvNamespaces(): Promise<KvNamespaceSummary[]>;
|
|
1870
|
+
/**
|
|
1871
|
+
* List keys in a KV namespace, optionally filtered by `prefix` and
|
|
1872
|
+
* paginated via `cursor`. Hits the admin-gated
|
|
1873
|
+
* `GET /_lunora/admin/kv/keys` endpoint.
|
|
1874
|
+
*/
|
|
1875
|
+
listKvKeys(options: {
|
|
1876
|
+
cursor?: string;
|
|
1877
|
+
limit?: number;
|
|
1878
|
+
namespace: string;
|
|
1879
|
+
prefix?: string;
|
|
1880
|
+
}): Promise<KvKeyListResult>;
|
|
1881
|
+
/**
|
|
1882
|
+
* Read a KV value (as text) and its metadata. Hits the admin-gated
|
|
1883
|
+
* `GET /_lunora/admin/kv/value` endpoint. Returns `{ value: null, metadata: null }`
|
|
1884
|
+
* when the key is absent.
|
|
1885
|
+
*/
|
|
1886
|
+
getKvValue(options: {
|
|
1887
|
+
key: string;
|
|
1888
|
+
namespace: string;
|
|
1889
|
+
}): Promise<KvValueResult>;
|
|
1890
|
+
/**
|
|
1891
|
+
* Write a string value to a KV namespace. Accepts an absolute `expiration`
|
|
1892
|
+
* (Unix seconds) or a relative `expirationTtl`, plus optional `metadata` —
|
|
1893
|
+
* re-send the loaded values on edit so a save preserves rather than clears
|
|
1894
|
+
* them. Hits the admin-gated `PUT /_lunora/admin/kv/value` endpoint.
|
|
1895
|
+
*/
|
|
1896
|
+
putKvValue(options: {
|
|
1897
|
+
expiration?: number;
|
|
1898
|
+
expirationTtl?: number;
|
|
1899
|
+
key: string;
|
|
1900
|
+
metadata?: unknown;
|
|
1901
|
+
namespace: string;
|
|
1902
|
+
value: string;
|
|
1903
|
+
}): Promise<void>;
|
|
1904
|
+
/**
|
|
1905
|
+
* Delete a key from a KV namespace. No-op when the key is absent. Hits the
|
|
1906
|
+
* admin-gated `DELETE /_lunora/admin/kv/value` endpoint.
|
|
1907
|
+
*/
|
|
1908
|
+
deleteKvKey(options: {
|
|
1909
|
+
key: string;
|
|
1910
|
+
namespace: string;
|
|
1911
|
+
}): Promise<void>;
|
|
1912
|
+
/**
|
|
1272
1913
|
* List authenticated users, paged and optionally searched / filtered / sorted.
|
|
1273
1914
|
* Hits the admin-gated `GET /_lunora/admin/auth/users` endpoint — the worker
|
|
1274
1915
|
* must be built with an `authAdmin` and `adminToken`. Powers the studio's
|
|
@@ -1393,6 +2034,107 @@ declare class LunoraClient {
|
|
|
1393
2034
|
cancelAuthOrgInvitation(input: {
|
|
1394
2035
|
invitationId: string;
|
|
1395
2036
|
}): Promise<void>;
|
|
2037
|
+
/**
|
|
2038
|
+
* Report the deployment's auth configuration — enabled plugins, sign-in
|
|
2039
|
+
* methods, user-settable create-user fields, organization sub-features
|
|
2040
|
+
* (teams / roles), and session / rate-limit policy. Drives the config panel
|
|
2041
|
+
* and the dynamic create-user form. Never carries a secret.
|
|
2042
|
+
*/
|
|
2043
|
+
getAuthConfig(): Promise<AuthConfigInfo>;
|
|
2044
|
+
/** Create an organization; optionally seed an `owner` member for `ownerId`. */
|
|
2045
|
+
createAuthOrganization(input: {
|
|
2046
|
+
logo?: string;
|
|
2047
|
+
metadata?: Record<string, unknown>;
|
|
2048
|
+
name: string;
|
|
2049
|
+
ownerId?: string;
|
|
2050
|
+
slug?: string;
|
|
2051
|
+
}): Promise<Record<string, unknown>>;
|
|
2052
|
+
/** Update an organization's name/slug/logo/metadata. */
|
|
2053
|
+
updateAuthOrganization(input: {
|
|
2054
|
+
logo?: string;
|
|
2055
|
+
metadata?: Record<string, unknown>;
|
|
2056
|
+
name?: string;
|
|
2057
|
+
organizationId: string;
|
|
2058
|
+
slug?: string;
|
|
2059
|
+
}): Promise<Record<string, unknown>>;
|
|
2060
|
+
/** Delete an organization and cascade its members, invitations, teams, and custom roles. */
|
|
2061
|
+
deleteAuthOrganization(input: {
|
|
2062
|
+
organizationId: string;
|
|
2063
|
+
}): Promise<void>;
|
|
2064
|
+
/** Directly add an existing user to an organization (no invitation/acceptance). */
|
|
2065
|
+
addAuthOrgMember(input: {
|
|
2066
|
+
organizationId: string;
|
|
2067
|
+
role?: string;
|
|
2068
|
+
userId: string;
|
|
2069
|
+
}): Promise<Record<string, unknown>>;
|
|
2070
|
+
/** Create a pending email invitation to an organization. */
|
|
2071
|
+
inviteAuthOrgMember(input: {
|
|
2072
|
+
email: string;
|
|
2073
|
+
inviterId?: string;
|
|
2074
|
+
organizationId: string;
|
|
2075
|
+
role?: string;
|
|
2076
|
+
}): Promise<Record<string, unknown>>;
|
|
2077
|
+
/** Change a member's role. */
|
|
2078
|
+
setAuthOrgMemberRole(input: {
|
|
2079
|
+
memberId: string;
|
|
2080
|
+
role: string | string[];
|
|
2081
|
+
}): Promise<Record<string, unknown>>;
|
|
2082
|
+
/** List an organization's teams (requires the organization plugin with teams enabled). */
|
|
2083
|
+
listAuthOrgTeams(input: {
|
|
2084
|
+
limit?: number;
|
|
2085
|
+
offset?: number;
|
|
2086
|
+
organizationId: string;
|
|
2087
|
+
}): Promise<AuthPage<Record<string, unknown>>>;
|
|
2088
|
+
/** Create a team under an organization. */
|
|
2089
|
+
createAuthOrgTeam(input: {
|
|
2090
|
+
name: string;
|
|
2091
|
+
organizationId: string;
|
|
2092
|
+
}): Promise<Record<string, unknown>>;
|
|
2093
|
+
/** Rename a team. */
|
|
2094
|
+
updateAuthOrgTeam(input: {
|
|
2095
|
+
name: string;
|
|
2096
|
+
teamId: string;
|
|
2097
|
+
}): Promise<Record<string, unknown>>;
|
|
2098
|
+
/** Delete a team and its memberships. */
|
|
2099
|
+
removeAuthOrgTeam(input: {
|
|
2100
|
+
teamId: string;
|
|
2101
|
+
}): Promise<void>;
|
|
2102
|
+
/** List a team's members. */
|
|
2103
|
+
listAuthOrgTeamMembers(input: {
|
|
2104
|
+
limit?: number;
|
|
2105
|
+
offset?: number;
|
|
2106
|
+
teamId: string;
|
|
2107
|
+
}): Promise<AuthPage<Record<string, unknown>>>;
|
|
2108
|
+
/** Add a user to a team. */
|
|
2109
|
+
addAuthOrgTeamMember(input: {
|
|
2110
|
+
teamId: string;
|
|
2111
|
+
userId: string;
|
|
2112
|
+
}): Promise<Record<string, unknown>>;
|
|
2113
|
+
/** Remove a member from a team. */
|
|
2114
|
+
removeAuthOrgTeamMember(input: {
|
|
2115
|
+
teamMemberId: string;
|
|
2116
|
+
}): Promise<void>;
|
|
2117
|
+
/** List an organization's custom roles (requires the organization plugin with dynamic access control). */
|
|
2118
|
+
listAuthOrgRoles(input: {
|
|
2119
|
+
limit?: number;
|
|
2120
|
+
offset?: number;
|
|
2121
|
+
organizationId: string;
|
|
2122
|
+
}): Promise<AuthPage<Record<string, unknown>>>;
|
|
2123
|
+
/** Create a custom org role with a permission grant (a `resource -> actions[]` map). */
|
|
2124
|
+
createAuthOrgRole(input: {
|
|
2125
|
+
organizationId: string;
|
|
2126
|
+
permission: Record<string, string[]>;
|
|
2127
|
+
role: string;
|
|
2128
|
+
}): Promise<Record<string, unknown>>;
|
|
2129
|
+
/** Replace a custom org role's permission grant. */
|
|
2130
|
+
updateAuthOrgRole(input: {
|
|
2131
|
+
permission: Record<string, string[]>;
|
|
2132
|
+
roleId: string;
|
|
2133
|
+
}): Promise<Record<string, unknown>>;
|
|
2134
|
+
/** Delete a custom org role. */
|
|
2135
|
+
deleteAuthOrgRole(input: {
|
|
2136
|
+
roleId: string;
|
|
2137
|
+
}): Promise<void>;
|
|
1396
2138
|
/** List auth sessions, paged and optionally filtered to one user. */
|
|
1397
2139
|
listAuthSessions(options?: {
|
|
1398
2140
|
limit?: number;
|
|
@@ -1400,6 +2142,27 @@ declare class LunoraClient {
|
|
|
1400
2142
|
userId?: string;
|
|
1401
2143
|
}): Promise<AuthPage<AuthSession>>;
|
|
1402
2144
|
subscribe<F extends FunctionReference>(function_: F, args: ArgsOf<F>, callback: (data: ReturnOf<F>) => void, options?: {
|
|
2145
|
+
onCheckpoint?: (watermark: SyncWatermark) => void;
|
|
2146
|
+
onError?: SubscriptionErrorCallback;
|
|
2147
|
+
shardKey?: string;
|
|
2148
|
+
}): Unsubscribe;
|
|
2149
|
+
/**
|
|
2150
|
+
* Subscribe to a declarative **shape** — server-side partial replication
|
|
2151
|
+
* scoped by `shardBy` + the shape's predicate + RLS. The parallel to
|
|
2152
|
+
* {@link subscribe} for the poke protocol: the client sends the shape *name* +
|
|
2153
|
+
* validated `args` (never a `where` the client could forge), the server seeds
|
|
2154
|
+
* the current membership as an insert-poke and streams live membership diffs.
|
|
2155
|
+
* Each applied poke materializes the shape's rowset and invokes `callback`.
|
|
2156
|
+
*
|
|
2157
|
+
* Unlike {@link subscribe}, shape subscriptions are NOT deduped by
|
|
2158
|
+
* (name, args): the server resolves them under the socket's verified identity,
|
|
2159
|
+
* so every call gets its own id + view. The returned function unsubscribes.
|
|
2160
|
+
*/
|
|
2161
|
+
subscribeShape(shape: {
|
|
2162
|
+
args?: Record<string, unknown>;
|
|
2163
|
+
name: string;
|
|
2164
|
+
}, callback: ShapeCallback, options?: {
|
|
2165
|
+
onCheckpoint?: (watermark: SyncWatermark) => void;
|
|
1403
2166
|
onError?: SubscriptionErrorCallback;
|
|
1404
2167
|
shardKey?: string;
|
|
1405
2168
|
}): Unsubscribe;
|
|
@@ -1427,12 +2190,35 @@ declare class LunoraClient {
|
|
|
1427
2190
|
}): StreamIterable<ReturnOf<F>>;
|
|
1428
2191
|
close(): void;
|
|
1429
2192
|
/**
|
|
2193
|
+
* Persist a mutation that can't go out on the wire right now (offline, or
|
|
2194
|
+
* mid-reconnect after a prior connect). The optimistic update has already
|
|
2195
|
+
* been applied by `mutation`; this only chooses the durable write path and
|
|
2196
|
+
* rolls the optimistic write back if persistence is rejected.
|
|
2197
|
+
*
|
|
2198
|
+
* Two paths: when an `outbox` sink is wired (the `@lunora/db` executor) it
|
|
2199
|
+
* owns persistence + at-least-once replay, so we delegate and return
|
|
2200
|
+
* optimistically (confirmation rides the synced view). Otherwise the
|
|
2201
|
+
* built-in `OfflineQueue` resolves/rejects the returned promise on replay.
|
|
2202
|
+
*/
|
|
2203
|
+
private enqueueOfflineMutation;
|
|
2204
|
+
/**
|
|
1430
2205
|
* Restore offline mutations persisted in a prior session and open a socket
|
|
1431
2206
|
* for each shard they target so they flush once the WS reconnects. Failures
|
|
1432
2207
|
* are swallowed — a broken durable store must not stop the client booting.
|
|
1433
2208
|
*/
|
|
1434
2209
|
private hydratePersistedQueue;
|
|
1435
2210
|
/**
|
|
2211
|
+
* Re-queue the durable offline writes — but only as the multi-tab LEADER. The
|
|
2212
|
+
* persisted queue is shared across a profile's tabs; without coordination
|
|
2213
|
+
* every tab would re-queue and replay the same writes (correct only because
|
|
2214
|
+
* the server dedups by idempotency key, but wasteful + racy). A Web Lock makes
|
|
2215
|
+
* exactly one tab hydrate; it holds the lock for its lifetime, so when it
|
|
2216
|
+
* closes another tab acquires the lock and takes over. Falls back to
|
|
2217
|
+
* unconditional hydration where Web Locks are unavailable (React Native, older
|
|
2218
|
+
* browsers, SSR) — single-context there, so no coordination is needed.
|
|
2219
|
+
*/
|
|
2220
|
+
private hydrateAsOutboxLeader;
|
|
2221
|
+
/**
|
|
1436
2222
|
* Load every cached query into {@link hydratedQueryCache} so the next
|
|
1437
2223
|
* `subscribe()` for each key seeds its initial value off disk. A
|
|
1438
2224
|
* subscription created before this resolves simply misses the cache (it
|
|
@@ -1462,21 +2248,37 @@ declare class LunoraClient {
|
|
|
1462
2248
|
/** Recompute the aggregate status and notify listeners if it changed. */
|
|
1463
2249
|
private emitConnectionStatus;
|
|
1464
2250
|
/**
|
|
1465
|
-
*
|
|
1466
|
-
*
|
|
1467
|
-
*
|
|
1468
|
-
*
|
|
1469
|
-
|
|
2251
|
+
* Build a {@link MutationSettledEvent} from a queued entry and emit it on the
|
|
2252
|
+
* {@link onMutationSettled} channel. `item.id` is always assigned by the time
|
|
2253
|
+
* a write settles (`enqueue`/`hydrate` guarantee it), so the `?? ""` fallback
|
|
2254
|
+
* is unreachable — present only to satisfy the optional queue-id type.
|
|
2255
|
+
*/
|
|
2256
|
+
private emitItemSettled;
|
|
2257
|
+
/**
|
|
2258
|
+
* Apply an optimistic update to the subscription that matches the mutation's
|
|
2259
|
+
* `(functionRef, args, shardKey)` triple, returning the rollback callbacks to
|
|
2260
|
+
* invoke if the mutation later fails.
|
|
2261
|
+
*
|
|
2262
|
+
* The registry is already indexed by exactly this triple via
|
|
2263
|
+
* `SubscriptionRegistry.key`, so at most one subscription can match. A direct
|
|
2264
|
+
* O(1) keyed lookup replaces the former O(N) linear scan over all subscriptions.
|
|
2265
|
+
*
|
|
2266
|
+
* `shardKey` normalization: both `undefined` and `""` map to the empty string
|
|
2267
|
+
* inside `SubscriptionRegistry.key` (via `?? ""`), so a mutation fired without
|
|
2268
|
+
* a shardKey correctly matches a subscription registered without one regardless
|
|
2269
|
+
* of whether the caller passed `undefined` or omitted the field.
|
|
1470
2270
|
*/
|
|
1471
2271
|
private applyOptimisticUpdates;
|
|
1472
2272
|
/**
|
|
1473
2273
|
* Run a Convex-parity `optimisticUpdate` callback against a localStore bound
|
|
1474
|
-
* to the live subscription registry
|
|
1475
|
-
*
|
|
1476
|
-
*
|
|
1477
|
-
*
|
|
1478
|
-
*
|
|
1479
|
-
*
|
|
2274
|
+
* to the live subscription registry. Each `setQuery` registers a constant
|
|
2275
|
+
* optimistic LAYER on its target subscription (via the same engine the
|
|
2276
|
+
* per-call `optimistic` path uses), so the multi-query patch rebases onto
|
|
2277
|
+
* incoming deltas and drops gaplessly on its commit cursor — its `confirm` /
|
|
2278
|
+
* `rollback` closures are appended to the mutation's settle lists. A throwing
|
|
2279
|
+
* callback unwinds its own partial writes — LIFO over just the rollbacks it
|
|
2280
|
+
* produced — and is swallowed, so a buggy optimistic update can never fail the
|
|
2281
|
+
* mutation or leave a partial patch live.
|
|
1480
2282
|
*/
|
|
1481
2283
|
private applyOptimisticUpdate;
|
|
1482
2284
|
private getConnection;
|
|
@@ -1523,6 +2325,13 @@ declare class LunoraClient {
|
|
|
1523
2325
|
* to the lifecycle dispatch.
|
|
1524
2326
|
*/
|
|
1525
2327
|
private sendConnectEnvelope;
|
|
2328
|
+
/**
|
|
2329
|
+
* Re-send every shape subscription bound to `shardKey` over its (now open)
|
|
2330
|
+
* socket. Each frame carries the shape's last applied checkpoint, so the
|
|
2331
|
+
* server resumes from it — or re-seeds when the cursor fell below CDC
|
|
2332
|
+
* retention or the epoch forked.
|
|
2333
|
+
*/
|
|
2334
|
+
private resendShapeSubscriptions;
|
|
1526
2335
|
private ensureSocket;
|
|
1527
2336
|
private handleDisconnect;
|
|
1528
2337
|
/**
|
|
@@ -1538,8 +2347,14 @@ declare class LunoraClient {
|
|
|
1538
2347
|
/** Mark every subscription bound to `shardKey` as needing a fresh ack. */
|
|
1539
2348
|
private markShardPendingAck;
|
|
1540
2349
|
private sendSubscribeIfOpen;
|
|
2350
|
+
private sendShapeSubscribeIfOpen;
|
|
1541
2351
|
private handleServerMessage;
|
|
1542
2352
|
private handleErrorMessage;
|
|
2353
|
+
private handlePokeStart;
|
|
2354
|
+
private handlePokePart;
|
|
2355
|
+
private handlePokeEnd;
|
|
2356
|
+
/** Materialize a shape's keyed view to an array and invoke its callbacks. */
|
|
2357
|
+
private emitShapeRows;
|
|
1543
2358
|
private handleDataMessage;
|
|
1544
2359
|
/**
|
|
1545
2360
|
* Handle a `resume` frame (Pillar 1b): the server proved nothing the
|
|
@@ -1551,6 +2366,25 @@ declare class LunoraClient {
|
|
|
1551
2366
|
*/
|
|
1552
2367
|
private handleResumeMessage;
|
|
1553
2368
|
/**
|
|
2369
|
+
* Handle a `settled` frame: a write touched one of this subscription's read
|
|
2370
|
+
* tables but produced a byte-identical result, so the server suppressed the
|
|
2371
|
+
* data frame. Like {@link handleResumeMessage} the value didn't change — we
|
|
2372
|
+
* advance the resume position and re-persist — but we ALSO surface the echoed
|
|
2373
|
+
* custom-mutator watermark via `onCheckpoint` so a `@lunora/db` list
|
|
2374
|
+
* collection drops the optimistic overlay for the confirmed write (otherwise
|
|
2375
|
+
* its checkpoint gate, fed only by data frames, would hang forever). Sent
|
|
2376
|
+
* only to custom-mutator clients; plain `useQuery` subscribers leave
|
|
2377
|
+
* `onCheckpoint` unset and this is a near no-op.
|
|
2378
|
+
*/
|
|
2379
|
+
private handleSettledMessage;
|
|
2380
|
+
/**
|
|
2381
|
+
* Mark `state` acked and, when the frame carries a newer cursor/epoch than
|
|
2382
|
+
* the cached position, advance the resume watermark and re-persist. Shared by
|
|
2383
|
+
* the `resume` and `settled` frame handlers — both acknowledge "nothing the
|
|
2384
|
+
* client must re-render changed, but the resume position may have moved".
|
|
2385
|
+
*/
|
|
2386
|
+
private ackAndAdvanceCursor;
|
|
2387
|
+
/**
|
|
1554
2388
|
* Resolve the value to publish for a `data`/`delta` frame.
|
|
1555
2389
|
*
|
|
1556
2390
|
* A `data` frame is an authoritative snapshot (the server re-execution path)
|
|
@@ -1578,6 +2412,34 @@ declare class LunoraClient {
|
|
|
1578
2412
|
*/
|
|
1579
2413
|
private identityFingerprint;
|
|
1580
2414
|
/**
|
|
2415
|
+
* Stable token-hash fingerprint of a bearer token (the `<len>:<fnv>:<djb2>`
|
|
2416
|
+
* format a token-stamped queued write carries). Extracted so the replay gate
|
|
2417
|
+
* can recompute the hash of the current credential and recognise a write
|
|
2418
|
+
* stamped under it — even after the fingerprint was relabelled to a subject.
|
|
2419
|
+
*
|
|
2420
|
+
* Two independent 32-bit passes (FNV-1a + djb2) give a ~64-bit digest, so
|
|
2421
|
+
* two distinct equal-length tokens are astronomically unlikely to share a
|
|
2422
|
+
* fingerprint. A single 32-bit hash collides ~1-in-4e9 per equal-length
|
|
2423
|
+
* pair — enough that, on a shared device, user B could hydrate A's cached
|
|
2424
|
+
* reads. Different algorithms (not the same FNV with a different seed, which
|
|
2425
|
+
* would be affine-related) keep the two passes genuinely independent.
|
|
2426
|
+
* Still synchronous (no crypto) and stable across surrogate pairs.
|
|
2427
|
+
*/
|
|
2428
|
+
private hashToken;
|
|
2429
|
+
/**
|
|
2430
|
+
* True when `stamped` is a token-hash of the SAME credential still held now,
|
|
2431
|
+
* even though the live identity has since been relabelled to a subject. Covers
|
|
2432
|
+
* `setAuthToken(token, userId)` where the subject resolved a tick after the
|
|
2433
|
+
* token was set: a write persisted (or requeued) under the token hash must
|
|
2434
|
+
* still replay — the credential never changed, only its label — instead of
|
|
2435
|
+
* being dropped as an identity mismatch. This is the durable counterpart to
|
|
2436
|
+
* {@link restampQueuedIdentity}, which only relabels the in-memory live stamp
|
|
2437
|
+
* (consumed on the first flush) and never touches `item.identity` or the
|
|
2438
|
+
* persisted record, so a reload or a transient-failure requeue would otherwise
|
|
2439
|
+
* fall back to the stale token-hash and wrongly reject the same user's write.
|
|
2440
|
+
*/
|
|
2441
|
+
private isSameCredentialUnderTokenHash;
|
|
2442
|
+
/**
|
|
1581
2443
|
* Drain every in-memory offline write and reject it because the auth
|
|
1582
2444
|
* identity changed. Durable entries are also dropped from persistence so a
|
|
1583
2445
|
* later `hydrate` can't resurrect another user's writes. Stamps are cleared
|
|
@@ -1586,6 +2448,15 @@ declare class LunoraClient {
|
|
|
1586
2448
|
*/
|
|
1587
2449
|
private rejectQueuedForIdentityChange;
|
|
1588
2450
|
/**
|
|
2451
|
+
* Migrate every live identity stamp from `from` to `to` — used when the auth
|
|
2452
|
+
* identity label changes but the underlying credential (token) does NOT, e.g.
|
|
2453
|
+
* the user id resolves a tick after the token was set. The in-memory
|
|
2454
|
+
* `queuedIdentities` map is the flush-time source of truth, so re-stamping it
|
|
2455
|
+
* keeps the in-flight writes replayable under the new (more stable) identity
|
|
2456
|
+
* instead of the flush guard discarding them as a mismatch.
|
|
2457
|
+
*/
|
|
2458
|
+
private restampQueuedIdentity;
|
|
2459
|
+
/**
|
|
1589
2460
|
* Drop the durable read cache on an identity change so a cached value stamped
|
|
1590
2461
|
* under the previous identity can never hydrate into a new session. Clears
|
|
1591
2462
|
* the in-flight write batch and the not-yet-consumed hydrated entries too;
|
|
@@ -1593,5 +2464,77 @@ declare class LunoraClient {
|
|
|
1593
2464
|
*/
|
|
1594
2465
|
private clearQueryCacheForIdentityChange;
|
|
1595
2466
|
private flushOfflineQueue;
|
|
2467
|
+
/**
|
|
2468
|
+
* Partition already-gated writes into the encodable ones (returned) and reject
|
|
2469
|
+
* the rest terminally. A write whose args can't be wire-encoded (e.g. a RegExp
|
|
2470
|
+
* or class instance in a `v.any()` field) can NEVER replay — the codec failure
|
|
2471
|
+
* is deterministic, not transient. Rejecting here is essential: otherwise
|
|
2472
|
+
* `encodeWire` throws mid-flush, is classified as transient (a codec error has
|
|
2473
|
+
* no `.code`), and re-queues forever — a silent hang where the caller's Promise
|
|
2474
|
+
* never settles and the optimistic write never rolls back. Encoding is cheap;
|
|
2475
|
+
* the flush is the slow reconnect path.
|
|
2476
|
+
*/
|
|
2477
|
+
private encodableOrSettleTerminal;
|
|
2478
|
+
/**
|
|
2479
|
+
* Identity guard for one queued write about to replay: a write stamped under
|
|
2480
|
+
* one identity must never replay under another. The live `queuedIdentities`
|
|
2481
|
+
* map is the source of truth for the current session; a hydrated write whose
|
|
2482
|
+
* id isn't in the map falls back to the stamp persisted with the record
|
|
2483
|
+
* (`item.identity`), so a reload can't replay another user's queued writes.
|
|
2484
|
+
* Only legacy records (persisted before stamps were durable —
|
|
2485
|
+
* `item.identity === undefined`) replay under whatever identity is current.
|
|
2486
|
+
*
|
|
2487
|
+
* `Map.get` returns `undefined` for unstamped/hydrated ids and `item.identity`
|
|
2488
|
+
* is `undefined` for legacy records; a persisted `null` (queued while signed
|
|
2489
|
+
* out) is a real value that must not collapse into `undefined` — hence the
|
|
2490
|
+
* explicit `=== undefined` check rather than `??`. Returns `true` when the
|
|
2491
|
+
* write may replay; otherwise settles it `OFFLINE_IDENTITY_CHANGED` and returns
|
|
2492
|
+
* `false`. Either way the live stamp is consumed.
|
|
2493
|
+
*/
|
|
2494
|
+
private passesReplayIdentityGate;
|
|
2495
|
+
/** 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. */
|
|
2496
|
+
private settleReplaySuccess;
|
|
2497
|
+
/** Settle a write the server reached a coded verdict on: replaying would re-trigger the same failure (a poison-message loop), so drop it. */
|
|
2498
|
+
private settleReplayTerminal;
|
|
2499
|
+
/**
|
|
2500
|
+
* Replay already-identity-gated writes one at a time on the single-call `/rpc`
|
|
2501
|
+
* path, preserving FIFO order (parallel `.then()` chains would race the
|
|
2502
|
+
* ordering callers depend on). Each replays under its stable `mutationId` so
|
|
2503
|
+
* the server dedups a write it already committed (exactly-once). A coded error
|
|
2504
|
+
* is a server verdict (drop it); a codeless (transport/transient) failure stops
|
|
2505
|
+
* the flush and re-queues this write and every unreplayed one for the next
|
|
2506
|
+
* reconnect — their callers stay pending, and the identity guard re-applies on
|
|
2507
|
+
* retry via each record's persisted stamp.
|
|
2508
|
+
*/
|
|
2509
|
+
private replaySequential;
|
|
2510
|
+
/**
|
|
2511
|
+
* Coalesce already-identity-gated writes for a single shard into ONE
|
|
2512
|
+
* `/_lunora/rpc-batch` round trip (plan 088 follow-on). The worker forwards
|
|
2513
|
+
* them to the shard DO, which replays each through its single-call dispatch, so
|
|
2514
|
+
* per-entry `mutationId` idempotency and in-order application are inherited from
|
|
2515
|
+
* the proven path. Per-slot demux mirrors {@link replaySequential}'s
|
|
2516
|
+
* classification: success confirms the optimistic layer against the echoed
|
|
2517
|
+
* `commitCursor`; a coded application verdict is terminal; a transient shard
|
|
2518
|
+
* failure (`SHARD_UNAVAILABLE`/`SHARD_ERROR`), a missing slot, or a whole-batch
|
|
2519
|
+
* transport failure re-queues for the next reconnect (never dropping a durable
|
|
2520
|
+
* write). A whole-batch coded rejection (bad request / authorization denial the
|
|
2521
|
+
* server reached a verdict on) is terminal for every entry.
|
|
2522
|
+
*
|
|
2523
|
+
* Returns the writes that must be re-queued and `stop` — `true` when the whole
|
|
2524
|
+
* chunk failed at the transport level, so the caller leaves later chunks queued
|
|
2525
|
+
* rather than sending on. The caller re-queues once, in order, so requeuing is
|
|
2526
|
+
* NOT done here.
|
|
2527
|
+
*/
|
|
2528
|
+
private replayBatched;
|
|
2529
|
+
/**
|
|
2530
|
+
* Demux a `/_lunora/rpc-batch` reply back onto the queued writes it replayed,
|
|
2531
|
+
* in input order. Each slot's envelope classifies its write the same way
|
|
2532
|
+
* {@link replaySequential} does: a success confirms the optimistic layer
|
|
2533
|
+
* against the echoed `commitCursor`; a coded application verdict is terminal;
|
|
2534
|
+
* a transient shard failure ({@link TRANSIENT_BATCH_ERROR_CODES}) or a slot the
|
|
2535
|
+
* server never returned is returned for the caller to re-queue.
|
|
2536
|
+
* @returns the writes that must be re-queued (transient slots), in input order
|
|
2537
|
+
*/
|
|
2538
|
+
private settleReplayBatchSlots;
|
|
1596
2539
|
}
|
|
1597
|
-
export { ArgsOf as A, BookmarkStorage as B,
|
|
2540
|
+
export { StreamHandle as $, ArgsOf as A, BookmarkStorage as B, CONFLICT_ERROR_CODE as C, DEFAULT_MAX_BUFFER as D, RpcEnvelope as E, FunctionReference as F, GlobalFacetResult as G, RpcResponseBody as H, ScheduleRecord as I, SchedulerPoolStatus as J, SchedulerStatus as K, LunoraClient as L, MutationCallOptions as M, ServerMessage as N, OfflineQueueOptions as O, Preloaded as P, QueryCacheAdapter as Q, ReturnOf as R, SubscriptionError as S, ServerPokeEndMessage as T, User as U, ServerPokePartMessage as V, ServerPokeStartMessage as W, ShardTrafficEntry as X, ShardTrafficResult as Y, StorageListPage as Z, StorageObject as _, Unsubscribe as a, StreamIterable as a0, SubscriptionCallback as a1, SubscriptionRegistry as a2, SubscriptionState as a3, SyncWatermark as a4, WorkflowInstanceAction as a5, WorkflowInstanceDetail as a6, WorkflowInstancePage as a7, WorkflowInstanceStatus as a8, WorkflowInstanceSummary as a9, WorkflowStepDetail as aa, createLocalStore as ab, createStream as ac, getErrorCode as ad, getRetryAfterMs as ae, isConflictError as af, isForbiddenError as ag, isRateLimitedError as ah, isUnauthorizedError as ai, SubscriptionErrorCallback as b, PersistenceAdapter as c, ReconnectOptions as d, BatchSlot as e, CachedQuery as f, ClientMessage as g, ClientShapeSubscribeMessage as h, ClientShapeUnsubscribeMessage as i, ConnectionStatus as j, FunctionArgumentDescriptor as k, FunctionDescriptor as l, GlobalFacetValue as m, GlobalFilterClause as n, GlobalTableInfo as o, GlobalTablePage as p, LunoraClientError as q, LunoraClientOptions as r, LunoraErrorCode as s, MutationSettledEvent as t, OptimisticLocalStore as u, OptimisticUpdate as v, OutboxMutation as w, OutboxSink as x, PersistedMutation as y, RowOp as z };
|