@lunora/client 1.0.0-alpha.66 → 1.0.0-alpha.68
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/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.d.ts +2 -2
- package/dist/index.d.mts +44 -11
- package/dist/index.d.ts +44 -11
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/LunoraClient-D-06gXR2.mjs +1 -0
- package/dist/packem_shared/OfflineQueue-DIhrKdlU.mjs +1 -0
- package/dist/packem_shared/{SubscriptionRegistry-CdHSrDqu.mjs → SubscriptionRegistry-D7n5ZE5F.mjs} +1 -1
- package/dist/packem_shared/applyDelta-DJmlwFnT.mjs +1 -0
- package/dist/packem_shared/{createAsyncStorageQueryCache-Di-S52wy.mjs → createAsyncStorageQueryCache-C0mJLuZK.mjs} +1 -1
- package/dist/packem_shared/createClientQuery-B8Nfj-7o.mjs +1 -0
- package/dist/packem_shared/createLocalStore-DtsP-CpS.mjs +1 -0
- package/dist/packem_shared/{createServerClient-BhfYV6Je.mjs → createServerClient-SWMORQWn.mjs} +1 -1
- package/dist/packem_shared/{createSnapshotPrecondition-BPMQbAbk.mjs → createSnapshotPrecondition-ZyQDet2v.mjs} +1 -1
- package/dist/packem_shared/{delta-merge-BoVuM-rE.mjs → delta-merge-CVSN-uoC.mjs} +1 -1
- package/dist/packem_shared/{local-store-tpI9VFpO.mjs → local-store-BQuKm9n6.mjs} +1 -1
- package/dist/packem_shared/{lunora-client.d-DAbmOzH_.d.mts → lunora-client.d-BQ0ig3ih.d.ts} +162 -2
- package/dist/packem_shared/{lunora-client.d-Dz6Yx4By.d.ts → lunora-client.d-DafpbiD5.d.mts} +162 -2
- package/dist/packem_shared/offline-queue-Bmeyg5fy.mjs +1 -0
- package/dist/packem_shared/{preload.d-BmPMaxYW.d.mts → preload.d-C09vDOAR.d.mts} +2 -2
- package/dist/packem_shared/{preload.d-CN5mOiAj.d.ts → preload.d-DGiAgCkV.d.ts} +2 -2
- package/dist/packem_shared/{types.d-BOB755CV.d.mts → types.d-Bsj5mGrj.d.mts} +31 -6
- package/dist/packem_shared/{types.d-BOB755CV.d.ts → types.d-Bsj5mGrj.d.ts} +31 -6
- package/dist/packem_shared/{wire-codec-D4iww4NV.mjs → wire-codec-PBOTh_2d.mjs} +1 -1
- package/dist/packem_shared/{wire-key-BOdKmpG3.mjs → wire-key-Dl7EFWSD.mjs} +1 -1
- package/dist/query/index.d.mts +3 -3
- package/dist/query/index.d.ts +3 -3
- package/dist/service.d.mts +1 -1
- package/dist/service.d.ts +1 -1
- package/dist/service.mjs +1 -1
- package/dist/ssr/index.d.mts +4 -4
- package/dist/ssr/index.d.ts +4 -4
- package/dist/ssr/index.mjs +1 -1
- package/package.json +2 -2
- package/dist/packem_shared/LunoraClient-BKmMPd4k.mjs +0 -1
- package/dist/packem_shared/OfflineQueue-DG51qWl4.mjs +0 -1
- package/dist/packem_shared/applyDelta-DGqMpi3N.mjs +0 -1
- package/dist/packem_shared/createClientQuery-TKD_52cT.mjs +0 -1
- package/dist/packem_shared/createLocalStore-CZwiN9F4.mjs +0 -1
- package/dist/packem_shared/offline-queue-CzI2lYQ8.mjs +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClientOptions, a as Unsubscribe, U as User, W as WsTokenProvider, S as ShardTrafficResult, b as ScheduleRecord, c as SchedulerStatus, d as WorkflowInstanceStatus, e as WorkflowInstancePage, f as WorkflowInstanceDetail, g as WorkflowInstanceAction, h as FunctionDescriptor, i as StorageListPage, G as GlobalTableInfo, j as GlobalFilterClause, k as GlobalTablePage, l as GlobalFacetResult, H as HttpStreamRef, m as HttpStreamArgsOf, n as HttpStreamChunkOf } from "./types.d-
|
|
1
|
+
import { F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClientOptions, a as Unsubscribe, U as User, W as WsTokenProvider, S as ShardTrafficResult, b as ScheduleRecord, c as SchedulerStatus, d as WorkflowInstanceStatus, e as WorkflowInstancePage, f as WorkflowInstanceDetail, g as WorkflowInstanceAction, h as FunctionDescriptor, i as StorageListPage, G as GlobalTableInfo, j as GlobalFilterClause, k as GlobalTablePage, l as GlobalFacetResult, H as HttpStreamRef, m as HttpStreamArgsOf, n as HttpStreamChunkOf } from "./types.d-Bsj5mGrj.js";
|
|
2
2
|
import { CronJobInfo, VectorIndexSummary, VectorQueryMatch, PipelineLogQuery, PipelineLogPage, KvNamespaceSummary, KvKeyListResult, KvValueResult, AuthUser, AuthPage, AuthImpersonation, AuthCapabilities, AuthConfigInfo, AuthSession } from '@lunora/runtime';
|
|
3
3
|
/**
|
|
4
4
|
* Reactive key-value store for local-only client state.
|
|
@@ -208,6 +208,14 @@ declare class SubscriptionRegistry {
|
|
|
208
208
|
add(state: SubscriptionState): void;
|
|
209
209
|
remove(state: SubscriptionState): void;
|
|
210
210
|
all(): SubscriptionState[];
|
|
211
|
+
/**
|
|
212
|
+
* Drop every registration. Terminal — used by `LunoraClient.close()`, whose
|
|
213
|
+
* whole point is to release the callback closures each {@link SubscriptionState}
|
|
214
|
+
* holds (`callbacks`, `errorCallbacks`, `checkpointCallbacks` — React state
|
|
215
|
+
* setters and `@lunora/db` collection closures), which otherwise outlive the
|
|
216
|
+
* closed client for as long as the client object is reachable.
|
|
217
|
+
*/
|
|
218
|
+
clear(): void;
|
|
211
219
|
}
|
|
212
220
|
/**
|
|
213
221
|
* Read/write handle over the client's live query cache, handed to a mutation's
|
|
@@ -694,6 +702,31 @@ declare class LunoraClient {
|
|
|
694
702
|
*/
|
|
695
703
|
private readonly streams;
|
|
696
704
|
/** Live shape subscriptions (partial replication), keyed by their wire id. */
|
|
705
|
+
/**
|
|
706
|
+
* Teardown callbacks for the admin sockets {@link LunoraClient.subscribeScheduledJobs}
|
|
707
|
+
* opens. Those run their own reconnect loop off a closure-local `closed`
|
|
708
|
+
* flag rather than `this.closed` (they predate `ensureSocket`'s guard), so
|
|
709
|
+
* without this registry a `close()` left every one of them reconnecting on
|
|
710
|
+
* its backoff forever — re-minting an ephemeral admin sub-token on each
|
|
711
|
+
* attempt when a `WsTokenProvider` is wired.
|
|
712
|
+
*/
|
|
713
|
+
private readonly adminSocketTeardowns;
|
|
714
|
+
/**
|
|
715
|
+
* The in-flight offline-queue replay per shard (`connectionKey`), while one
|
|
716
|
+
* is running. Two jobs:
|
|
717
|
+
*
|
|
718
|
+
* 1. It serializes overlapping flushes for the same shard — two reconnect
|
|
719
|
+
* events in quick succession used to drain and replay concurrently.
|
|
720
|
+
* 2. It is the barrier {@link LunoraClient.mutation} waits on before sending a FRESH
|
|
721
|
+
* write directly. The socket's `open` handler flips `wsState` to `"open"`
|
|
722
|
+
* first and calls the flush last, so from that instant `mutation()`'s
|
|
723
|
+
* offline gate is false and a new write raced straight to `/rpc` against
|
|
724
|
+
* the replay of the older, queued write for the same document — the newer
|
|
725
|
+
* one could land first and then be overwritten by the older. Ordering
|
|
726
|
+
* inside the replay (`replaySequential`) never covered this, because the
|
|
727
|
+
* racing write was never in the queue.
|
|
728
|
+
*/
|
|
729
|
+
private readonly offlineFlushes;
|
|
697
730
|
private readonly shapeSubscriptions;
|
|
698
731
|
/**
|
|
699
732
|
* In-flight pokes being assembled between `pokeStart` and `pokeEnd`, keyed by
|
|
@@ -813,6 +846,10 @@ declare class LunoraClient {
|
|
|
813
846
|
* Stored per shard and replayed on every (re)connect. When a socket for the
|
|
814
847
|
* shard is already open, a fresh `connect` envelope is sent immediately so the
|
|
815
848
|
* server sees the new context without waiting for a reconnect.
|
|
849
|
+
*
|
|
850
|
+
* Not available on a `crossTabSync` FOLLOWER tab — the context rides the
|
|
851
|
+
* `connect` envelope of a socket a follower does not own, so it would be
|
|
852
|
+
* stored and never sent. Throws `NOT_IMPLEMENTED` there.
|
|
816
853
|
*/
|
|
817
854
|
setConnectionContext(context: Record<string, unknown> | undefined, options?: {
|
|
818
855
|
shardKey?: string;
|
|
@@ -852,6 +889,11 @@ declare class LunoraClient {
|
|
|
852
889
|
* do not put data on a whisper topic that some shard members shouldn't see,
|
|
853
890
|
* and don't trust a whisper's `data` as authorization. Use a query/mutation
|
|
854
891
|
* (with RLS) for anything privileged; whispers are for transient awareness.
|
|
892
|
+
*
|
|
893
|
+
* Not available on a `crossTabSync` FOLLOWER tab — whisper frames are not
|
|
894
|
+
* relayed over the cross-tab channel, so this throws `NOT_IMPLEMENTED`
|
|
895
|
+
* there rather than registering a handler nothing can ever reach. See
|
|
896
|
+
* {@link LunoraClientOptions.crossTabSync}.
|
|
855
897
|
*/
|
|
856
898
|
whisperSubscribe(topic: string, handler: (data: unknown, from?: string) => void, options?: {
|
|
857
899
|
shardKey?: string;
|
|
@@ -863,6 +905,11 @@ declare class LunoraClient {
|
|
|
863
905
|
* queued), and the server silently drops it if the sender exceeds its
|
|
864
906
|
* whisper rate budget. The sender never receives its own whisper. Omitting
|
|
865
907
|
* `data` delivers JSON `null` to receivers (not `undefined`).
|
|
908
|
+
*
|
|
909
|
+
* That best-effort drop is for a socket that is momentarily down. A
|
|
910
|
+
* `crossTabSync` FOLLOWER tab has no socket and never will (see
|
|
911
|
+
* {@link LunoraClientOptions.crossTabSync}), so every whisper from it would
|
|
912
|
+
* be dropped forever — it throws `NOT_IMPLEMENTED` instead.
|
|
866
913
|
*/
|
|
867
914
|
whisper(topic: string, data?: unknown, options?: {
|
|
868
915
|
shardKey?: string;
|
|
@@ -1069,6 +1116,22 @@ declare class LunoraClient {
|
|
|
1069
1116
|
* Mutations issued before the very first WS connect to a shard fail fast.
|
|
1070
1117
|
* Opt into queueing-before-first-connect via
|
|
1071
1118
|
* `OfflineQueueOptions.queueBeforeFirstConnect`.
|
|
1119
|
+
*
|
|
1120
|
+
* **Return-value caveat — the queued paths do not carry the server's result.**
|
|
1121
|
+
* The declared `Promise<ReturnOf<F>>` only holds when the write goes straight
|
|
1122
|
+
* to the server. Once a write is queued:
|
|
1123
|
+
*
|
|
1124
|
+
* - with a durable `outbox` configured, this resolves **immediately with
|
|
1125
|
+
* `undefined`** (typed as `ReturnOf<F>`) the moment the write is handed to
|
|
1126
|
+
* the outbox — the replay happens later, out of band, with no awaiter;
|
|
1127
|
+
* - with the built-in offline queue, it stays pending until the replay lands
|
|
1128
|
+
* and then resolves with the replayed call's value.
|
|
1129
|
+
*
|
|
1130
|
+
* So `const id = await client.mutation(api.todos.create, …)` is `undefined`
|
|
1131
|
+
* for every write issued while offline under an outbox. Generate ids
|
|
1132
|
+
* client-side (or read them back from a subscription) rather than depending
|
|
1133
|
+
* on a mutation's return value in an offline-capable app —
|
|
1134
|
+
* {@link LunoraClient.importRows} documents the same caveat for its counts.
|
|
1072
1135
|
*/
|
|
1073
1136
|
mutation<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>): Promise<ReturnOf<F>>;
|
|
1074
1137
|
action<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: ActionCallOptions): Promise<ReturnOf<F>>;
|
|
@@ -1309,6 +1372,11 @@ declare class LunoraClient {
|
|
|
1309
1372
|
* `DELETE /_lunora/admin/storage?key=…` endpoint — the worker must be built
|
|
1310
1373
|
* with a `storageDelete` function and `adminToken`. Powers the studio file
|
|
1311
1374
|
* browser's per-row delete; resolves `{ deleted, key }`.
|
|
1375
|
+
*
|
|
1376
|
+
* An absent `deleted` field reads as `false`, matching every sibling admin
|
|
1377
|
+
* verb (`runCronJob`'s `ran`, …): the studio renders this value as the row's
|
|
1378
|
+
* outcome, so defaulting a missing field to success would report a delete
|
|
1379
|
+
* that a mismatched/older worker never performed.
|
|
1312
1380
|
*/
|
|
1313
1381
|
deleteStorageObject(key: string, options?: {
|
|
1314
1382
|
bucket?: string;
|
|
@@ -1700,6 +1768,20 @@ declare class LunoraClient {
|
|
|
1700
1768
|
offset?: number;
|
|
1701
1769
|
userId?: string;
|
|
1702
1770
|
}): Promise<AuthPage<AuthSession>>;
|
|
1771
|
+
/**
|
|
1772
|
+
* Subscribe to a live query. The callback fires with the current value (from
|
|
1773
|
+
* the durable read cache, when one is hydrated) and again on every server
|
|
1774
|
+
* frame; the returned function unsubscribes.
|
|
1775
|
+
*
|
|
1776
|
+
* Subscriptions are deduped by `(functionPath, args, shardKey)` — a second
|
|
1777
|
+
* `subscribe` for the same triple joins the existing registration and shares
|
|
1778
|
+
* its value, cursor and optimistic layers.
|
|
1779
|
+
*
|
|
1780
|
+
* Not available on a `crossTabSync` FOLLOWER tab: the cross-tab channel only
|
|
1781
|
+
* carries the LEADER's own subscriptions outward, so a follower would receive
|
|
1782
|
+
* this query's frames only if the leader happened to hold it too. Throws
|
|
1783
|
+
* `NOT_IMPLEMENTED` there — see {@link LunoraClientOptions.crossTabSync}.
|
|
1784
|
+
*/
|
|
1703
1785
|
subscribe<F extends FunctionReference>(function_: F, args: ArgsOf<F>, callback: (data: ReturnOf<F>) => void, options?: {
|
|
1704
1786
|
onCheckpoint?: (watermark: SyncWatermark) => void;
|
|
1705
1787
|
onError?: SubscriptionErrorCallback;
|
|
@@ -1716,6 +1798,11 @@ declare class LunoraClient {
|
|
|
1716
1798
|
* Unlike {@link subscribe}, shape subscriptions are NOT deduped by
|
|
1717
1799
|
* (name, args): the server resolves them under the socket's verified identity,
|
|
1718
1800
|
* so every call gets its own id + view. The returned function unsubscribes.
|
|
1801
|
+
*
|
|
1802
|
+
* Not available on a `crossTabSync` FOLLOWER tab: shape pokes are not part of
|
|
1803
|
+
* the leader→follower broadcast set, so a follower's shape could never
|
|
1804
|
+
* resolve. Throws `NOT_IMPLEMENTED` there — see
|
|
1805
|
+
* {@link LunoraClientOptions.crossTabSync}.
|
|
1719
1806
|
*/
|
|
1720
1807
|
subscribeShape(shape: {
|
|
1721
1808
|
args?: Record<string, unknown>;
|
|
@@ -1770,6 +1857,53 @@ declare class LunoraClient {
|
|
|
1770
1857
|
close(): void;
|
|
1771
1858
|
/** Guard shared by every public entry point: a closed client accepts no further calls. */
|
|
1772
1859
|
private assertOpen;
|
|
1860
|
+
/**
|
|
1861
|
+
* `true` when this tab is a cross-tab FOLLOWER of a live leader — i.e. it
|
|
1862
|
+
* will not open a socket of its own and another tab is known to hold one.
|
|
1863
|
+
*
|
|
1864
|
+
* Deliberately NOT just `!isLeader()`. Every `crossTabSync` client is a
|
|
1865
|
+
* non-leader for the first `leaderTimeout` of its life, while its
|
|
1866
|
+
* claim-leadership probe is outstanding; a lone tab self-promotes at the end
|
|
1867
|
+
* of that window and `onBecomeLeader` opens the sockets and replays every
|
|
1868
|
+
* registered subscription. That window is a legitimate, self-healing defer,
|
|
1869
|
+
* not a failure. A KNOWN leader on another tab is the state that never heals.
|
|
1870
|
+
*/
|
|
1871
|
+
private followsAnotherTab;
|
|
1872
|
+
/**
|
|
1873
|
+
* Reject a call that needs a socket this tab will never have.
|
|
1874
|
+
*
|
|
1875
|
+
* The cross-tab protocol is one-directional: a leader broadcasts
|
|
1876
|
+
* `subscription-data` / `-error` / `-settled` / `connection-status` to
|
|
1877
|
+
* followers, and a follower has no frame with which to tell the leader what
|
|
1878
|
+
* it needs (see `cross-tab.ts`'s `WsFollowerMessage`, which is exactly
|
|
1879
|
+
* heartbeat / claim-leadership / yield-leadership). `subscribeShape` /
|
|
1880
|
+
* `whisper*` / `setConnectionContext` / `acquireConnectionContext` — none of
|
|
1881
|
+
* which the leader broadcasts at all — therefore never worked on a follower
|
|
1882
|
+
* under any circumstances: each returned a handle that looked live, fired no
|
|
1883
|
+
* callback, raised no error, and reported `connectionStatus() ===
|
|
1884
|
+
* "connected"` (mirrored from the leader).
|
|
1885
|
+
*
|
|
1886
|
+
* `subscribe` is deliberately NOT in that set. A follower's `subscribe`
|
|
1887
|
+
* registers the key the leader's broadcast is matched against, so it is the
|
|
1888
|
+
* mechanism the relay is built on rather than a surface that silently fails.
|
|
1889
|
+
* A follower sees a query only while the leader holds the same
|
|
1890
|
+
* `(fn, args, shardKey)` — that is the documented shape of the option, not a
|
|
1891
|
+
* defect.
|
|
1892
|
+
*
|
|
1893
|
+
* `stream()` reaches the same outcome by a different route: it fails the
|
|
1894
|
+
* handle it returns rather than throwing at the call. See
|
|
1895
|
+
* {@link LunoraClientOptions.crossTabSync} for what the option does and does
|
|
1896
|
+
* not cover.
|
|
1897
|
+
*/
|
|
1898
|
+
private assertLeaderOwnedSurface;
|
|
1899
|
+
/**
|
|
1900
|
+
* Clear every timer a {@link ShardConnection} can have armed.
|
|
1901
|
+
*
|
|
1902
|
+
* One function because both teardown paths must clear all three and a fourth
|
|
1903
|
+
* timer would otherwise have to be remembered in two places — which is how a
|
|
1904
|
+
* leak gets added rather than written.
|
|
1905
|
+
*/
|
|
1906
|
+
private clearConnectionTimers;
|
|
1773
1907
|
/**
|
|
1774
1908
|
* Tear down one {@link ShardConnection}'s live state: clear its reconnect/
|
|
1775
1909
|
* connect timers, stop its heartbeat, and close its socket (if any).
|
|
@@ -1777,6 +1911,15 @@ declare class LunoraClient {
|
|
|
1777
1911
|
* handler (demoted, but still alive) so a demoted leader can't leak a
|
|
1778
1912
|
* pending `reconnectTimer` or an open socket's `heartbeatTimer` the way
|
|
1779
1913
|
* an inline `conn.socket?.close()` — which skips both — used to.
|
|
1914
|
+
*
|
|
1915
|
+
* Settles this shard's in-flight streams first. The teardown clears
|
|
1916
|
+
* `conn.socket` BEFORE the real `close` event fires, so that event trips
|
|
1917
|
+
* `openManagedSocket`'s identity guard (`conn.socket !== socket`) and
|
|
1918
|
+
* returns — meaning `handleDisconnect`, the only other place that settles a
|
|
1919
|
+
* shard's streams, never runs for this connection again. `close()` already
|
|
1920
|
+
* failed and cleared `this.streams` before it gets here, so this is a no-op
|
|
1921
|
+
* on that path; the cross-tab demotion path is the one where a consumer's
|
|
1922
|
+
* `for await` used to block forever with no error and no completion.
|
|
1780
1923
|
*/
|
|
1781
1924
|
private teardownConnection;
|
|
1782
1925
|
/**
|
|
@@ -2155,12 +2298,21 @@ declare class LunoraClient {
|
|
|
2155
2298
|
*/
|
|
2156
2299
|
private rejectQueuedForIdentityChange;
|
|
2157
2300
|
/**
|
|
2158
|
-
* Migrate every
|
|
2301
|
+
* Migrate every identity stamp from `from` to `to` — used when the auth
|
|
2159
2302
|
* identity label changes but the underlying credential (token) does NOT, e.g.
|
|
2160
2303
|
* the user id resolves a tick after the token was set. The in-memory
|
|
2161
2304
|
* `queuedIdentities` map is the flush-time source of truth, so re-stamping it
|
|
2162
2305
|
* keeps the in-flight writes replayable under the new (more stable) identity
|
|
2163
2306
|
* instead of the flush guard discarding them as a mismatch.
|
|
2307
|
+
*
|
|
2308
|
+
* That map alone was not enough: it is consumed and DELETED on the first
|
|
2309
|
+
* flush attempt (`passesReplayIdentityGate`), while the queue entry and its
|
|
2310
|
+
* persisted record keep the original stamp. So a reload, or a requeue after a
|
|
2311
|
+
* transient failure, fell back to the old token hash — and once the token had
|
|
2312
|
+
* been refreshed, `isSameCredentialUnderTokenHash` no longer recognised it
|
|
2313
|
+
* and the write was rejected `OFFLINE_IDENTITY_CHANGED` for the very user
|
|
2314
|
+
* `setAuthToken`'s sticky-`subject` contract promises to protect. The queue's
|
|
2315
|
+
* own re-stamp covers both the entry and its durable record.
|
|
2164
2316
|
*/
|
|
2165
2317
|
private restampQueuedIdentity;
|
|
2166
2318
|
/**
|
|
@@ -2197,7 +2349,15 @@ declare class LunoraClient {
|
|
|
2197
2349
|
* nothing), so over-inclusion here is harmless.
|
|
2198
2350
|
*/
|
|
2199
2351
|
private flushAllOfflineQueues;
|
|
2352
|
+
/**
|
|
2353
|
+
* Replay a shard's queued writes, serialized per shard and published as
|
|
2354
|
+
* {@link offlineFlushes} so a concurrent `mutation()` can wait behind it.
|
|
2355
|
+
* Never rejects: every entry's outcome is settled individually inside
|
|
2356
|
+
* {@link drainOfflineQueue}, and a poisoned chain would strand every later
|
|
2357
|
+
* flush AND every write waiting on the barrier.
|
|
2358
|
+
*/
|
|
2200
2359
|
private flushOfflineQueue;
|
|
2360
|
+
private drainOfflineQueue;
|
|
2201
2361
|
/**
|
|
2202
2362
|
* Partition already-gated writes into the encodable ones (returned) and reject
|
|
2203
2363
|
* the rest terminally. A write whose args can't be wire-encoded (e.g. a RegExp
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClientOptions, a as Unsubscribe, U as User, W as WsTokenProvider, S as ShardTrafficResult, b as ScheduleRecord, c as SchedulerStatus, d as WorkflowInstanceStatus, e as WorkflowInstancePage, f as WorkflowInstanceDetail, g as WorkflowInstanceAction, h as FunctionDescriptor, i as StorageListPage, G as GlobalTableInfo, j as GlobalFilterClause, k as GlobalTablePage, l as GlobalFacetResult, H as HttpStreamRef, m as HttpStreamArgsOf, n as HttpStreamChunkOf } from "./types.d-
|
|
1
|
+
import { F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClientOptions, a as Unsubscribe, U as User, W as WsTokenProvider, S as ShardTrafficResult, b as ScheduleRecord, c as SchedulerStatus, d as WorkflowInstanceStatus, e as WorkflowInstancePage, f as WorkflowInstanceDetail, g as WorkflowInstanceAction, h as FunctionDescriptor, i as StorageListPage, G as GlobalTableInfo, j as GlobalFilterClause, k as GlobalTablePage, l as GlobalFacetResult, H as HttpStreamRef, m as HttpStreamArgsOf, n as HttpStreamChunkOf } from "./types.d-Bsj5mGrj.mjs";
|
|
2
2
|
import { CronJobInfo, VectorIndexSummary, VectorQueryMatch, PipelineLogQuery, PipelineLogPage, KvNamespaceSummary, KvKeyListResult, KvValueResult, AuthUser, AuthPage, AuthImpersonation, AuthCapabilities, AuthConfigInfo, AuthSession } from '@lunora/runtime';
|
|
3
3
|
/**
|
|
4
4
|
* Reactive key-value store for local-only client state.
|
|
@@ -208,6 +208,14 @@ declare class SubscriptionRegistry {
|
|
|
208
208
|
add(state: SubscriptionState): void;
|
|
209
209
|
remove(state: SubscriptionState): void;
|
|
210
210
|
all(): SubscriptionState[];
|
|
211
|
+
/**
|
|
212
|
+
* Drop every registration. Terminal — used by `LunoraClient.close()`, whose
|
|
213
|
+
* whole point is to release the callback closures each {@link SubscriptionState}
|
|
214
|
+
* holds (`callbacks`, `errorCallbacks`, `checkpointCallbacks` — React state
|
|
215
|
+
* setters and `@lunora/db` collection closures), which otherwise outlive the
|
|
216
|
+
* closed client for as long as the client object is reachable.
|
|
217
|
+
*/
|
|
218
|
+
clear(): void;
|
|
211
219
|
}
|
|
212
220
|
/**
|
|
213
221
|
* Read/write handle over the client's live query cache, handed to a mutation's
|
|
@@ -694,6 +702,31 @@ declare class LunoraClient {
|
|
|
694
702
|
*/
|
|
695
703
|
private readonly streams;
|
|
696
704
|
/** Live shape subscriptions (partial replication), keyed by their wire id. */
|
|
705
|
+
/**
|
|
706
|
+
* Teardown callbacks for the admin sockets {@link LunoraClient.subscribeScheduledJobs}
|
|
707
|
+
* opens. Those run their own reconnect loop off a closure-local `closed`
|
|
708
|
+
* flag rather than `this.closed` (they predate `ensureSocket`'s guard), so
|
|
709
|
+
* without this registry a `close()` left every one of them reconnecting on
|
|
710
|
+
* its backoff forever — re-minting an ephemeral admin sub-token on each
|
|
711
|
+
* attempt when a `WsTokenProvider` is wired.
|
|
712
|
+
*/
|
|
713
|
+
private readonly adminSocketTeardowns;
|
|
714
|
+
/**
|
|
715
|
+
* The in-flight offline-queue replay per shard (`connectionKey`), while one
|
|
716
|
+
* is running. Two jobs:
|
|
717
|
+
*
|
|
718
|
+
* 1. It serializes overlapping flushes for the same shard — two reconnect
|
|
719
|
+
* events in quick succession used to drain and replay concurrently.
|
|
720
|
+
* 2. It is the barrier {@link LunoraClient.mutation} waits on before sending a FRESH
|
|
721
|
+
* write directly. The socket's `open` handler flips `wsState` to `"open"`
|
|
722
|
+
* first and calls the flush last, so from that instant `mutation()`'s
|
|
723
|
+
* offline gate is false and a new write raced straight to `/rpc` against
|
|
724
|
+
* the replay of the older, queued write for the same document — the newer
|
|
725
|
+
* one could land first and then be overwritten by the older. Ordering
|
|
726
|
+
* inside the replay (`replaySequential`) never covered this, because the
|
|
727
|
+
* racing write was never in the queue.
|
|
728
|
+
*/
|
|
729
|
+
private readonly offlineFlushes;
|
|
697
730
|
private readonly shapeSubscriptions;
|
|
698
731
|
/**
|
|
699
732
|
* In-flight pokes being assembled between `pokeStart` and `pokeEnd`, keyed by
|
|
@@ -813,6 +846,10 @@ declare class LunoraClient {
|
|
|
813
846
|
* Stored per shard and replayed on every (re)connect. When a socket for the
|
|
814
847
|
* shard is already open, a fresh `connect` envelope is sent immediately so the
|
|
815
848
|
* server sees the new context without waiting for a reconnect.
|
|
849
|
+
*
|
|
850
|
+
* Not available on a `crossTabSync` FOLLOWER tab — the context rides the
|
|
851
|
+
* `connect` envelope of a socket a follower does not own, so it would be
|
|
852
|
+
* stored and never sent. Throws `NOT_IMPLEMENTED` there.
|
|
816
853
|
*/
|
|
817
854
|
setConnectionContext(context: Record<string, unknown> | undefined, options?: {
|
|
818
855
|
shardKey?: string;
|
|
@@ -852,6 +889,11 @@ declare class LunoraClient {
|
|
|
852
889
|
* do not put data on a whisper topic that some shard members shouldn't see,
|
|
853
890
|
* and don't trust a whisper's `data` as authorization. Use a query/mutation
|
|
854
891
|
* (with RLS) for anything privileged; whispers are for transient awareness.
|
|
892
|
+
*
|
|
893
|
+
* Not available on a `crossTabSync` FOLLOWER tab — whisper frames are not
|
|
894
|
+
* relayed over the cross-tab channel, so this throws `NOT_IMPLEMENTED`
|
|
895
|
+
* there rather than registering a handler nothing can ever reach. See
|
|
896
|
+
* {@link LunoraClientOptions.crossTabSync}.
|
|
855
897
|
*/
|
|
856
898
|
whisperSubscribe(topic: string, handler: (data: unknown, from?: string) => void, options?: {
|
|
857
899
|
shardKey?: string;
|
|
@@ -863,6 +905,11 @@ declare class LunoraClient {
|
|
|
863
905
|
* queued), and the server silently drops it if the sender exceeds its
|
|
864
906
|
* whisper rate budget. The sender never receives its own whisper. Omitting
|
|
865
907
|
* `data` delivers JSON `null` to receivers (not `undefined`).
|
|
908
|
+
*
|
|
909
|
+
* That best-effort drop is for a socket that is momentarily down. A
|
|
910
|
+
* `crossTabSync` FOLLOWER tab has no socket and never will (see
|
|
911
|
+
* {@link LunoraClientOptions.crossTabSync}), so every whisper from it would
|
|
912
|
+
* be dropped forever — it throws `NOT_IMPLEMENTED` instead.
|
|
866
913
|
*/
|
|
867
914
|
whisper(topic: string, data?: unknown, options?: {
|
|
868
915
|
shardKey?: string;
|
|
@@ -1069,6 +1116,22 @@ declare class LunoraClient {
|
|
|
1069
1116
|
* Mutations issued before the very first WS connect to a shard fail fast.
|
|
1070
1117
|
* Opt into queueing-before-first-connect via
|
|
1071
1118
|
* `OfflineQueueOptions.queueBeforeFirstConnect`.
|
|
1119
|
+
*
|
|
1120
|
+
* **Return-value caveat — the queued paths do not carry the server's result.**
|
|
1121
|
+
* The declared `Promise<ReturnOf<F>>` only holds when the write goes straight
|
|
1122
|
+
* to the server. Once a write is queued:
|
|
1123
|
+
*
|
|
1124
|
+
* - with a durable `outbox` configured, this resolves **immediately with
|
|
1125
|
+
* `undefined`** (typed as `ReturnOf<F>`) the moment the write is handed to
|
|
1126
|
+
* the outbox — the replay happens later, out of band, with no awaiter;
|
|
1127
|
+
* - with the built-in offline queue, it stays pending until the replay lands
|
|
1128
|
+
* and then resolves with the replayed call's value.
|
|
1129
|
+
*
|
|
1130
|
+
* So `const id = await client.mutation(api.todos.create, …)` is `undefined`
|
|
1131
|
+
* for every write issued while offline under an outbox. Generate ids
|
|
1132
|
+
* client-side (or read them back from a subscription) rather than depending
|
|
1133
|
+
* on a mutation's return value in an offline-capable app —
|
|
1134
|
+
* {@link LunoraClient.importRows} documents the same caveat for its counts.
|
|
1072
1135
|
*/
|
|
1073
1136
|
mutation<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>): Promise<ReturnOf<F>>;
|
|
1074
1137
|
action<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: ActionCallOptions): Promise<ReturnOf<F>>;
|
|
@@ -1309,6 +1372,11 @@ declare class LunoraClient {
|
|
|
1309
1372
|
* `DELETE /_lunora/admin/storage?key=…` endpoint — the worker must be built
|
|
1310
1373
|
* with a `storageDelete` function and `adminToken`. Powers the studio file
|
|
1311
1374
|
* browser's per-row delete; resolves `{ deleted, key }`.
|
|
1375
|
+
*
|
|
1376
|
+
* An absent `deleted` field reads as `false`, matching every sibling admin
|
|
1377
|
+
* verb (`runCronJob`'s `ran`, …): the studio renders this value as the row's
|
|
1378
|
+
* outcome, so defaulting a missing field to success would report a delete
|
|
1379
|
+
* that a mismatched/older worker never performed.
|
|
1312
1380
|
*/
|
|
1313
1381
|
deleteStorageObject(key: string, options?: {
|
|
1314
1382
|
bucket?: string;
|
|
@@ -1700,6 +1768,20 @@ declare class LunoraClient {
|
|
|
1700
1768
|
offset?: number;
|
|
1701
1769
|
userId?: string;
|
|
1702
1770
|
}): Promise<AuthPage<AuthSession>>;
|
|
1771
|
+
/**
|
|
1772
|
+
* Subscribe to a live query. The callback fires with the current value (from
|
|
1773
|
+
* the durable read cache, when one is hydrated) and again on every server
|
|
1774
|
+
* frame; the returned function unsubscribes.
|
|
1775
|
+
*
|
|
1776
|
+
* Subscriptions are deduped by `(functionPath, args, shardKey)` — a second
|
|
1777
|
+
* `subscribe` for the same triple joins the existing registration and shares
|
|
1778
|
+
* its value, cursor and optimistic layers.
|
|
1779
|
+
*
|
|
1780
|
+
* Not available on a `crossTabSync` FOLLOWER tab: the cross-tab channel only
|
|
1781
|
+
* carries the LEADER's own subscriptions outward, so a follower would receive
|
|
1782
|
+
* this query's frames only if the leader happened to hold it too. Throws
|
|
1783
|
+
* `NOT_IMPLEMENTED` there — see {@link LunoraClientOptions.crossTabSync}.
|
|
1784
|
+
*/
|
|
1703
1785
|
subscribe<F extends FunctionReference>(function_: F, args: ArgsOf<F>, callback: (data: ReturnOf<F>) => void, options?: {
|
|
1704
1786
|
onCheckpoint?: (watermark: SyncWatermark) => void;
|
|
1705
1787
|
onError?: SubscriptionErrorCallback;
|
|
@@ -1716,6 +1798,11 @@ declare class LunoraClient {
|
|
|
1716
1798
|
* Unlike {@link subscribe}, shape subscriptions are NOT deduped by
|
|
1717
1799
|
* (name, args): the server resolves them under the socket's verified identity,
|
|
1718
1800
|
* so every call gets its own id + view. The returned function unsubscribes.
|
|
1801
|
+
*
|
|
1802
|
+
* Not available on a `crossTabSync` FOLLOWER tab: shape pokes are not part of
|
|
1803
|
+
* the leader→follower broadcast set, so a follower's shape could never
|
|
1804
|
+
* resolve. Throws `NOT_IMPLEMENTED` there — see
|
|
1805
|
+
* {@link LunoraClientOptions.crossTabSync}.
|
|
1719
1806
|
*/
|
|
1720
1807
|
subscribeShape(shape: {
|
|
1721
1808
|
args?: Record<string, unknown>;
|
|
@@ -1770,6 +1857,53 @@ declare class LunoraClient {
|
|
|
1770
1857
|
close(): void;
|
|
1771
1858
|
/** Guard shared by every public entry point: a closed client accepts no further calls. */
|
|
1772
1859
|
private assertOpen;
|
|
1860
|
+
/**
|
|
1861
|
+
* `true` when this tab is a cross-tab FOLLOWER of a live leader — i.e. it
|
|
1862
|
+
* will not open a socket of its own and another tab is known to hold one.
|
|
1863
|
+
*
|
|
1864
|
+
* Deliberately NOT just `!isLeader()`. Every `crossTabSync` client is a
|
|
1865
|
+
* non-leader for the first `leaderTimeout` of its life, while its
|
|
1866
|
+
* claim-leadership probe is outstanding; a lone tab self-promotes at the end
|
|
1867
|
+
* of that window and `onBecomeLeader` opens the sockets and replays every
|
|
1868
|
+
* registered subscription. That window is a legitimate, self-healing defer,
|
|
1869
|
+
* not a failure. A KNOWN leader on another tab is the state that never heals.
|
|
1870
|
+
*/
|
|
1871
|
+
private followsAnotherTab;
|
|
1872
|
+
/**
|
|
1873
|
+
* Reject a call that needs a socket this tab will never have.
|
|
1874
|
+
*
|
|
1875
|
+
* The cross-tab protocol is one-directional: a leader broadcasts
|
|
1876
|
+
* `subscription-data` / `-error` / `-settled` / `connection-status` to
|
|
1877
|
+
* followers, and a follower has no frame with which to tell the leader what
|
|
1878
|
+
* it needs (see `cross-tab.ts`'s `WsFollowerMessage`, which is exactly
|
|
1879
|
+
* heartbeat / claim-leadership / yield-leadership). `subscribeShape` /
|
|
1880
|
+
* `whisper*` / `setConnectionContext` / `acquireConnectionContext` — none of
|
|
1881
|
+
* which the leader broadcasts at all — therefore never worked on a follower
|
|
1882
|
+
* under any circumstances: each returned a handle that looked live, fired no
|
|
1883
|
+
* callback, raised no error, and reported `connectionStatus() ===
|
|
1884
|
+
* "connected"` (mirrored from the leader).
|
|
1885
|
+
*
|
|
1886
|
+
* `subscribe` is deliberately NOT in that set. A follower's `subscribe`
|
|
1887
|
+
* registers the key the leader's broadcast is matched against, so it is the
|
|
1888
|
+
* mechanism the relay is built on rather than a surface that silently fails.
|
|
1889
|
+
* A follower sees a query only while the leader holds the same
|
|
1890
|
+
* `(fn, args, shardKey)` — that is the documented shape of the option, not a
|
|
1891
|
+
* defect.
|
|
1892
|
+
*
|
|
1893
|
+
* `stream()` reaches the same outcome by a different route: it fails the
|
|
1894
|
+
* handle it returns rather than throwing at the call. See
|
|
1895
|
+
* {@link LunoraClientOptions.crossTabSync} for what the option does and does
|
|
1896
|
+
* not cover.
|
|
1897
|
+
*/
|
|
1898
|
+
private assertLeaderOwnedSurface;
|
|
1899
|
+
/**
|
|
1900
|
+
* Clear every timer a {@link ShardConnection} can have armed.
|
|
1901
|
+
*
|
|
1902
|
+
* One function because both teardown paths must clear all three and a fourth
|
|
1903
|
+
* timer would otherwise have to be remembered in two places — which is how a
|
|
1904
|
+
* leak gets added rather than written.
|
|
1905
|
+
*/
|
|
1906
|
+
private clearConnectionTimers;
|
|
1773
1907
|
/**
|
|
1774
1908
|
* Tear down one {@link ShardConnection}'s live state: clear its reconnect/
|
|
1775
1909
|
* connect timers, stop its heartbeat, and close its socket (if any).
|
|
@@ -1777,6 +1911,15 @@ declare class LunoraClient {
|
|
|
1777
1911
|
* handler (demoted, but still alive) so a demoted leader can't leak a
|
|
1778
1912
|
* pending `reconnectTimer` or an open socket's `heartbeatTimer` the way
|
|
1779
1913
|
* an inline `conn.socket?.close()` — which skips both — used to.
|
|
1914
|
+
*
|
|
1915
|
+
* Settles this shard's in-flight streams first. The teardown clears
|
|
1916
|
+
* `conn.socket` BEFORE the real `close` event fires, so that event trips
|
|
1917
|
+
* `openManagedSocket`'s identity guard (`conn.socket !== socket`) and
|
|
1918
|
+
* returns — meaning `handleDisconnect`, the only other place that settles a
|
|
1919
|
+
* shard's streams, never runs for this connection again. `close()` already
|
|
1920
|
+
* failed and cleared `this.streams` before it gets here, so this is a no-op
|
|
1921
|
+
* on that path; the cross-tab demotion path is the one where a consumer's
|
|
1922
|
+
* `for await` used to block forever with no error and no completion.
|
|
1780
1923
|
*/
|
|
1781
1924
|
private teardownConnection;
|
|
1782
1925
|
/**
|
|
@@ -2155,12 +2298,21 @@ declare class LunoraClient {
|
|
|
2155
2298
|
*/
|
|
2156
2299
|
private rejectQueuedForIdentityChange;
|
|
2157
2300
|
/**
|
|
2158
|
-
* Migrate every
|
|
2301
|
+
* Migrate every identity stamp from `from` to `to` — used when the auth
|
|
2159
2302
|
* identity label changes but the underlying credential (token) does NOT, e.g.
|
|
2160
2303
|
* the user id resolves a tick after the token was set. The in-memory
|
|
2161
2304
|
* `queuedIdentities` map is the flush-time source of truth, so re-stamping it
|
|
2162
2305
|
* keeps the in-flight writes replayable under the new (more stable) identity
|
|
2163
2306
|
* instead of the flush guard discarding them as a mismatch.
|
|
2307
|
+
*
|
|
2308
|
+
* That map alone was not enough: it is consumed and DELETED on the first
|
|
2309
|
+
* flush attempt (`passesReplayIdentityGate`), while the queue entry and its
|
|
2310
|
+
* persisted record keep the original stamp. So a reload, or a requeue after a
|
|
2311
|
+
* transient failure, fell back to the old token hash — and once the token had
|
|
2312
|
+
* been refreshed, `isSameCredentialUnderTokenHash` no longer recognised it
|
|
2313
|
+
* and the write was rejected `OFFLINE_IDENTITY_CHANGED` for the very user
|
|
2314
|
+
* `setAuthToken`'s sticky-`subject` contract promises to protect. The queue's
|
|
2315
|
+
* own re-stamp covers both the entry and its durable record.
|
|
2164
2316
|
*/
|
|
2165
2317
|
private restampQueuedIdentity;
|
|
2166
2318
|
/**
|
|
@@ -2197,7 +2349,15 @@ declare class LunoraClient {
|
|
|
2197
2349
|
* nothing), so over-inclusion here is harmless.
|
|
2198
2350
|
*/
|
|
2199
2351
|
private flushAllOfflineQueues;
|
|
2352
|
+
/**
|
|
2353
|
+
* Replay a shard's queued writes, serialized per shard and published as
|
|
2354
|
+
* {@link offlineFlushes} so a concurrent `mutation()` can wait behind it.
|
|
2355
|
+
* Never rejects: every entry's outcome is settled individually inside
|
|
2356
|
+
* {@link drainOfflineQueue}, and a poisoned chain would strand every later
|
|
2357
|
+
* flush AND every write waiting on the barrier.
|
|
2358
|
+
*/
|
|
2200
2359
|
private flushOfflineQueue;
|
|
2360
|
+
private drainOfflineQueue;
|
|
2201
2361
|
/**
|
|
2202
2362
|
* Partition already-gated writes into the encodable ones (returned) and reject
|
|
2203
2363
|
* the rest terminally. A write whose args can't be wire-encoded (e.g. a RegExp
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let h=0;const a=()=>{if(typeof crypto<"u"&&typeof crypto.randomUUID=="function")return crypto.randomUUID();h+=1;const r=typeof crypto<"u"&&typeof crypto.getRandomValues=="function"?Array.from(crypto.getRandomValues(new Uint8Array(8)),t=>t.toString(16).padStart(2,"0")).join(""):Math.random().toString(16).slice(2,12);return`id_${Date.now().toString(36)}_${h.toString(36)}_${r}`},f=(r,t)=>r!==void 0&&t!==r,u=a,o=(r,t,e,i)=>{if(r){r({error:e,mutationId:i,operation:t});return}console.warn(`[lunora] offline-queue persistence ${t} failed`,e)};class m{queueBeforeFirstConnect;maxItems;onPersistenceError;persistence;onEvict;onSizeChange;version;items=[];constructor(t={},e={}){this.maxItems=t.maxItems??1e3,this.queueBeforeFirstConnect=t.queueBeforeFirstConnect??!1,this.onPersistenceError=t.onPersistenceError,this.persistence=e.persistence,this.onEvict=e.onEvict,this.onSizeChange=e.onSizeChange,this.version=e.version}get size(){return this.items.length}enqueue(t){const e=t;e.id??=u(),this.items.push(e),this.persistence?.append({args:e.args,clientId:e.clientId,functionPath:e.functionPath,id:e.id,identity:e.identity,shardKey:e.shardKey,...this.version===void 0?{}:{version:this.version}}).catch(i=>{o(this.onPersistenceError,"append",i,e.id)}),this.evictOverflow(),this.notifySize()}async hydrate(){if(!this.persistence)return[];let t;try{t=await this.persistence.load()}catch(s){throw o(this.onPersistenceError,"load",s),s}const e=[],i=new Set(this.items.map(s=>s.id));for(const s of t)if(!i.has(s.id)){if(f(this.version,s.version)){this.persistence.remove(s.id).catch(d=>{o(this.onPersistenceError,"remove",d,s.id)});continue}i.add(s.id),e.push({args:s.args,clientId:s.clientId,functionPath:s.functionPath,id:s.id,identity:s.identity,reject:()=>{},resolve:()=>{},shardKey:s.shardKey})}this.items.unshift(...e),this.evictOverflow(),this.notifySize();const n=new Set(this.items),c=new Set;for(const s of e)n.has(s)&&c.add(s.shardKey);return[...c]}restampIdentity(t,e){for(const i of this.items){if(i.identity!==t)continue;i.identity=e;const{id:n}=i;if(!this.persistence||n===void 0)continue;const c={args:i.args,clientId:i.clientId,functionPath:i.functionPath,id:n,identity:e,shardKey:i.shardKey,...this.version===void 0?{}:{version:this.version}};this.persistence.remove(n).then(async()=>this.persistence?.append(c)).catch(s=>{o(this.onPersistenceError,"append",s,n)})}}drain(t){if(!t){const n=[...this.items];return this.items.length=0,this.notifySize(),n}const e=[],i=[];for(const n of this.items)(t(n)?e:i).push(n);return this.items.length=0,this.items.push(...i),this.notifySize(),e}requeue(t){t.length!==0&&(this.items.unshift(...t),this.notifySize())}drainConflict(){const t=[],e=[];for(const i of this.items)if(i.precondition!==void 0&&!i.precondition()){const n=new Error("offline mutation skipped: precondition failed before replay");n.code="OFFLINE_PRECONDITION_FAILED",i.reject(n),t.push(i)}else e.push(i);return t.length>0&&(this.items.length=0,this.items.push(...e),this.notifySize()),t}clear(){for(const t of this.items){const e=new Error("CLIENT_CLOSED");e.code="CLIENT_CLOSED",t.reject(e)}this.items.length=0,this.notifySize()}evictOverflow(){for(;this.items.length>this.maxItems;){const t=this.items.shift();if(t){t.id&&this.persistence?.remove(t.id).catch(i=>{o(this.onPersistenceError,"remove",i,t.id)});const e=new Error("offline queue overflow");e.code="OFFLINE_QUEUE_OVERFLOW",t.reject(e),this.onEvict?.(t,e)}}}notifySize(){this.onSizeChange?.(this.items.length)}}export{m as O,f as i,u as n,o as r};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { L as LunoraClient } from "./lunora-client.d-
|
|
2
|
-
import { F as FunctionReference, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./types.d-
|
|
1
|
+
import { L as LunoraClient } from "./lunora-client.d-DafpbiD5.mjs";
|
|
2
|
+
import { F as FunctionReference, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./types.d-Bsj5mGrj.mjs";
|
|
3
3
|
/**
|
|
4
4
|
* Run a query once on the server (during SSR) and capture its result in a
|
|
5
5
|
* serializable {@link Preloaded} token. Embed the token in the rendered HTML and
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { L as LunoraClient } from "./lunora-client.d-
|
|
2
|
-
import { F as FunctionReference, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./types.d-
|
|
1
|
+
import { L as LunoraClient } from "./lunora-client.d-BQ0ig3ih.js";
|
|
2
|
+
import { F as FunctionReference, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./types.d-Bsj5mGrj.js";
|
|
3
3
|
/**
|
|
4
4
|
* Run a query once on the server (during SSR) and capture its result in a
|
|
5
5
|
* serializable {@link Preloaded} token. Embed the token in the rendered HTML and
|
|
@@ -322,13 +322,38 @@ interface LunoraClientOptions {
|
|
|
322
322
|
*/
|
|
323
323
|
connectTimeoutMs?: number;
|
|
324
324
|
/**
|
|
325
|
-
* When `true`, tabs sharing the same origin
|
|
326
|
-
* so only one tab
|
|
327
|
-
*
|
|
325
|
+
* When `true`, tabs sharing the same origin (and the same signed-in identity)
|
|
326
|
+
* coordinate via BroadcastChannel so only one tab — the "leader" — opens
|
|
327
|
+
* WebSocket connections to the server. Reduces simultaneous WS connections,
|
|
328
|
+
* bandwidth, and cross-tab state drift. Requires `BroadcastChannel`
|
|
329
|
+
* (browser-only); silently ignored otherwise. Defaults to `false`.
|
|
328
330
|
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
331
|
+
* **The channel is one-directional: leader → follower.** The leader
|
|
332
|
+
* broadcasts the values, errors, checkpoints and connection status of the
|
|
333
|
+
* subscriptions *it* holds; there is no frame with which a follower can ask
|
|
334
|
+
* the leader for anything.
|
|
335
|
+
*
|
|
336
|
+
* `subscribe` works on a follower and is how the relay delivers: the
|
|
337
|
+
* registration is what the leader's broadcast key is matched against, so a
|
|
338
|
+
* follower sees a value while the leader independently holds the same
|
|
339
|
+
* `(fn, args, shardKey)`.
|
|
340
|
+
*
|
|
341
|
+
* Nothing else is served, because the leader broadcasts nothing for it and a
|
|
342
|
+
* follower cannot ask. `subscribeShape` and `acquireConnectionContext` are
|
|
343
|
+
* inert on a follower — framework code (`@lunora/db`'s shape sync, every
|
|
344
|
+
* `usePresence` adapter) calls them from an effect the app cannot opt out
|
|
345
|
+
* of, so throwing would unwind the tab rather than degrade one feature.
|
|
346
|
+
* `whisper`, `whisperSubscribe`, `setConnectionContext` and `stream` are
|
|
347
|
+
* only ever called by app code, which can handle a failure, so those throw
|
|
348
|
+
* `NOT_IMPLEMENTED` rather than returning a handle that never fires. (The
|
|
349
|
+
* brief window every tab spends claiming leadership at startup is not a
|
|
350
|
+
* follower state: a lone tab self-promotes and its registered subscriptions
|
|
351
|
+
* are sent then.) HTTP surfaces — `query`, `mutation`, `action`, the offline
|
|
352
|
+
* queue's replay — are unaffected on every tab.
|
|
353
|
+
*
|
|
354
|
+
* So: enable this when your tabs run the SAME app views over plain
|
|
355
|
+
* `subscribe`, and leave it off if tabs can sit on different routes or you
|
|
356
|
+
* use shapes, whispers, streams, or connection context.
|
|
332
357
|
*/
|
|
333
358
|
crossTabSync?: boolean;
|
|
334
359
|
fetch?: typeof fetch;
|
|
@@ -322,13 +322,38 @@ interface LunoraClientOptions {
|
|
|
322
322
|
*/
|
|
323
323
|
connectTimeoutMs?: number;
|
|
324
324
|
/**
|
|
325
|
-
* When `true`, tabs sharing the same origin
|
|
326
|
-
* so only one tab
|
|
327
|
-
*
|
|
325
|
+
* When `true`, tabs sharing the same origin (and the same signed-in identity)
|
|
326
|
+
* coordinate via BroadcastChannel so only one tab — the "leader" — opens
|
|
327
|
+
* WebSocket connections to the server. Reduces simultaneous WS connections,
|
|
328
|
+
* bandwidth, and cross-tab state drift. Requires `BroadcastChannel`
|
|
329
|
+
* (browser-only); silently ignored otherwise. Defaults to `false`.
|
|
328
330
|
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
331
|
+
* **The channel is one-directional: leader → follower.** The leader
|
|
332
|
+
* broadcasts the values, errors, checkpoints and connection status of the
|
|
333
|
+
* subscriptions *it* holds; there is no frame with which a follower can ask
|
|
334
|
+
* the leader for anything.
|
|
335
|
+
*
|
|
336
|
+
* `subscribe` works on a follower and is how the relay delivers: the
|
|
337
|
+
* registration is what the leader's broadcast key is matched against, so a
|
|
338
|
+
* follower sees a value while the leader independently holds the same
|
|
339
|
+
* `(fn, args, shardKey)`.
|
|
340
|
+
*
|
|
341
|
+
* Nothing else is served, because the leader broadcasts nothing for it and a
|
|
342
|
+
* follower cannot ask. `subscribeShape` and `acquireConnectionContext` are
|
|
343
|
+
* inert on a follower — framework code (`@lunora/db`'s shape sync, every
|
|
344
|
+
* `usePresence` adapter) calls them from an effect the app cannot opt out
|
|
345
|
+
* of, so throwing would unwind the tab rather than degrade one feature.
|
|
346
|
+
* `whisper`, `whisperSubscribe`, `setConnectionContext` and `stream` are
|
|
347
|
+
* only ever called by app code, which can handle a failure, so those throw
|
|
348
|
+
* `NOT_IMPLEMENTED` rather than returning a handle that never fires. (The
|
|
349
|
+
* brief window every tab spends claiming leadership at startup is not a
|
|
350
|
+
* follower state: a lone tab self-promotes and its registered subscriptions
|
|
351
|
+
* are sent then.) HTTP surfaces — `query`, `mutation`, `action`, the offline
|
|
352
|
+
* queue's replay — are unaffected on every tab.
|
|
353
|
+
*
|
|
354
|
+
* So: enable this when your tabs run the SAME app views over plain
|
|
355
|
+
* `subscribe`, and leave it off if tabs can sit on different routes or you
|
|
356
|
+
* use shapes, whispers, streams, or connection context.
|
|
332
357
|
*/
|
|
333
358
|
crossTabSync?: boolean;
|
|
334
359
|
fetch?: typeof fetch;
|