@lunora/client 1.0.0-alpha.70 → 1.0.0-alpha.72
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/README.md +1 -1
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.d.ts +2 -2
- package/dist/index.d.mts +46 -23
- package/dist/index.d.ts +46 -23
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/CONFLICT_ERROR_CODE-BOeeP_Wl.mjs +1 -0
- package/dist/packem_shared/LunoraClient-U7lWMpjV.mjs +1 -0
- package/dist/packem_shared/OfflineQueue-ZapYrAVi.mjs +1 -0
- package/dist/packem_shared/SubscriptionRegistry-BQPU90ST.mjs +1 -0
- package/dist/packem_shared/applyDelta-BiegnbZR.mjs +1 -0
- package/dist/packem_shared/createAsyncStoragePersistence-CeUW9NTj.mjs +1 -0
- package/dist/packem_shared/{createAsyncStorageQueryCache-C0mJLuZK.mjs → createAsyncStorageQueryCache-MoM77Nwg.mjs} +1 -1
- package/dist/packem_shared/createInMemoryPersistence-Cz2W6XVj.mjs +1 -0
- package/dist/packem_shared/createLocalStore-v6oiATqG.mjs +1 -0
- package/dist/packem_shared/{createServerClient-l9OHvYqk.mjs → createServerClient-rFEbvMRr.mjs} +1 -1
- package/dist/packem_shared/{createSnapshotPrecondition-ZyQDet2v.mjs → createSnapshotPrecondition-C4bSgHkg.mjs} +1 -1
- package/dist/packem_shared/{delta-merge-CVSN-uoC.mjs → delta-merge-BPM4sFID.mjs} +1 -1
- package/dist/packem_shared/{local-store-BQuKm9n6.mjs → local-store-CNgeGW02.mjs} +1 -1
- package/dist/packem_shared/{lunora-client.d-DWDMVBKE.d.mts → lunora-client.d-6hU156nD.d.mts} +170 -24
- package/dist/packem_shared/{lunora-client.d-CLwpIj61.d.ts → lunora-client.d-B0t5v2n9.d.ts} +170 -24
- package/dist/packem_shared/offline-queue-xntsWiKt.mjs +1 -0
- package/dist/packem_shared/{preload.d-ClHaOfOQ.d.ts → preload.d-Dl-ja0VQ.d.ts} +1 -1
- package/dist/packem_shared/{preload.d-BAazUwhn.d.mts → preload.d-M7qP6TwL.d.mts} +1 -1
- package/dist/packem_shared/wire-codec-BfIZrTJe.mjs +1 -0
- package/dist/packem_shared/{wire-key-Dl7EFWSD.mjs → wire-key-C9UKT-C3.mjs} +1 -1
- package/dist/query/index.d.mts +2 -2
- package/dist/query/index.d.ts +2 -2
- package/dist/service.mjs +1 -1
- package/dist/ssr/index.d.mts +2 -2
- package/dist/ssr/index.d.ts +2 -2
- package/dist/ssr/index.mjs +1 -1
- package/package.json +2 -2
- package/dist/packem_shared/CONFLICT_ERROR_CODE-CDDneB-G.mjs +0 -1
- package/dist/packem_shared/LunoraClient-BYXxJohQ.mjs +0 -1
- package/dist/packem_shared/OfflineQueue-B6IWyX5W.mjs +0 -1
- package/dist/packem_shared/SubscriptionRegistry-D7n5ZE5F.mjs +0 -1
- package/dist/packem_shared/applyDelta-DJmlwFnT.mjs +0 -1
- package/dist/packem_shared/createAsyncStoragePersistence-CNdM5o1u.mjs +0 -1
- package/dist/packem_shared/createInMemoryPersistence-C0qm7c47.mjs +0 -1
- package/dist/packem_shared/createLocalStore-DtsP-CpS.mjs +0 -1
- package/dist/packem_shared/offline-queue-8mpNPwG8.mjs +0 -1
- package/dist/packem_shared/wire-codec-PBOTh_2d.mjs +0 -1
package/dist/packem_shared/{lunora-client.d-DWDMVBKE.d.mts → lunora-client.d-6hU156nD.d.mts}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { LunoraErrorCodeInput, LunoraErrorCode } from '@lunora/errors';
|
|
1
2
|
import { F as FunctionReference, A as ArgsOf, R as ReturnOf } from "./function-reference.d-Br_hsKje.mjs";
|
|
2
|
-
import { LunoraErrorCode } from '@lunora/errors';
|
|
3
3
|
import { CronJobInfo, VectorIndexSummary, VectorQueryMatch, PipelineLogQuery, PipelineLogPage, KvNamespaceSummary, KvKeyListResult, KvValueResult, AuthUser, AuthPage, AuthImpersonation, AuthCapabilities, AuthConfigInfo, AuthSession } from '@lunora/runtime';
|
|
4
4
|
/**
|
|
5
5
|
* Reactive key-value store for local-only client state.
|
|
@@ -113,7 +113,7 @@ interface ReconnectOptions {
|
|
|
113
113
|
maxDelayMs?: number;
|
|
114
114
|
}
|
|
115
115
|
/** Which durable-storage operation failed, passed to {@link OfflineQueueOptions.onPersistenceError}. */
|
|
116
|
-
type PersistenceOperation = "append" | "clear" | "load" | "remove";
|
|
116
|
+
type PersistenceOperation = "append" | "clear" | "load" | "remove" | "replace";
|
|
117
117
|
/** Context handed to a persistence-error handler. */
|
|
118
118
|
interface PersistenceErrorContext {
|
|
119
119
|
readonly error: unknown;
|
|
@@ -195,6 +195,22 @@ interface PersistenceAdapter {
|
|
|
195
195
|
load: () => Promise<PersistedMutation[]>;
|
|
196
196
|
/** Remove a mutation by id once it has been replayed (resolved or rejected). */
|
|
197
197
|
remove: (id: string) => Promise<void>;
|
|
198
|
+
/**
|
|
199
|
+
* Overwrite an already-persisted mutation IN PLACE, keeping its position in
|
|
200
|
+
* FIFO order. Used when a queued write's identity stamp is rewritten after a
|
|
201
|
+
* sign-in / sign-out.
|
|
202
|
+
*
|
|
203
|
+
* Must be atomic: a `remove` + `append` pair has a window where a process
|
|
204
|
+
* stop leaves the mutation in no durable store at all, and the in-memory
|
|
205
|
+
* entry has already advanced, so a reload loses the write outright. It also
|
|
206
|
+
* moved the record to the BACK of the queue, replaying it out of the order
|
|
207
|
+
* it was issued in. Implementations do the whole swap under one transaction
|
|
208
|
+
* (or one serialized blob write).
|
|
209
|
+
*
|
|
210
|
+
* A mutation whose id is not present is left alone — the record was drained
|
|
211
|
+
* concurrently and re-inserting it would replay a settled write.
|
|
212
|
+
*/
|
|
213
|
+
replace: (mutation: PersistedMutation) => Promise<void>;
|
|
198
214
|
}
|
|
199
215
|
/**
|
|
200
216
|
* One write handed to an {@link OutboxSink}. Mirrors {@link PersistedMutation}
|
|
@@ -378,10 +394,17 @@ interface LunoraClientOptions {
|
|
|
378
394
|
*/
|
|
379
395
|
heartbeatIntervalMs?: number;
|
|
380
396
|
/**
|
|
381
|
-
* When `true` and a `queryCache` is active,
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
397
|
+
* When `true` and a `queryCache` is active, React's `useQuery` holds its
|
|
398
|
+
* TanStack query disabled until the durable cache has finished loading
|
|
399
|
+
* (`whenReady()`), so its first enabled render can seed the cached value
|
|
400
|
+
* instead of issuing an HTTP read that the cache would immediately
|
|
401
|
+
* overwrite. Defaults to `false`.
|
|
402
|
+
*
|
|
403
|
+
* It is ONLY React's `useQuery` that defers — the Vue, Svelte, Solid and
|
|
404
|
+
* Angular hooks subscribe at mount regardless of this flag, and so does
|
|
405
|
+
* React's own subscription registry. They do not need the gate: a
|
|
406
|
+
* subscription opened before the load completes is seeded by the load
|
|
407
|
+
* itself, so a cached value reaches the first subscriber either way.
|
|
385
408
|
*
|
|
386
409
|
* Requires `queryCache` to be set (not `false`); silently ignored otherwise.
|
|
387
410
|
*/
|
|
@@ -1085,7 +1108,16 @@ interface WorkflowInstancePage {
|
|
|
1085
1108
|
type SubscriptionCallback = (data: unknown) => void;
|
|
1086
1109
|
/** A subscription-scoped error the server pushed for this subscription id. */
|
|
1087
1110
|
interface SubscriptionError {
|
|
1088
|
-
|
|
1111
|
+
/**
|
|
1112
|
+
* The coded reason, when the frame carried one. `LunoraErrorCodeInput`, not
|
|
1113
|
+
* `LunoraErrorCode`: the catalog codes autocomplete for a consumer branching
|
|
1114
|
+
* on this (the shard sends `BAD_SUBSCRIPTION_ARGS`, `TOO_MANY_SUBSCRIPTIONS`,
|
|
1115
|
+
* `SUBSCRIPTION_PERSIST_FAILED`, …; the client itself adds
|
|
1116
|
+
* `WIRE_DECODE_FAILED` for a frame `decodeWire` refuses), but this value is
|
|
1117
|
+
* read verbatim off the wire and nothing validates it against the catalog,
|
|
1118
|
+
* so narrowing it to `LunoraErrorCode` would be a lie a newer server tells.
|
|
1119
|
+
*/
|
|
1120
|
+
code?: LunoraErrorCodeInput;
|
|
1089
1121
|
message: string;
|
|
1090
1122
|
}
|
|
1091
1123
|
type SubscriptionErrorCallback = (error: SubscriptionError) => void;
|
|
@@ -1180,6 +1212,19 @@ interface SubscriptionState {
|
|
|
1180
1212
|
*/
|
|
1181
1213
|
serverEpoch?: string;
|
|
1182
1214
|
readonly shardKey?: string;
|
|
1215
|
+
/**
|
|
1216
|
+
* The wire-encoded form of `args`, computed once at `subscribe` time (so an
|
|
1217
|
+
* unsupported value fails loud at the call site, not inside a reconnect's
|
|
1218
|
+
* open handler). Sent on every `subscribe` frame — identical to `args` for
|
|
1219
|
+
* pure JSON, tagged tokens for `bigint`/`Date`/bytes/… (the shard
|
|
1220
|
+
* `decodeWire`s them at its subscribe entry point).
|
|
1221
|
+
*
|
|
1222
|
+
* A SNAPSHOT, not a view: `args` is the caller's own object, retained by
|
|
1223
|
+
* reference and never copied, so a caller that mutates it after subscribing
|
|
1224
|
+
* would otherwise poison every later resubscribe. `encodeWire` rebuilds
|
|
1225
|
+
* every container, so this copy is immune to that.
|
|
1226
|
+
*/
|
|
1227
|
+
readonly wireArgs: Record<string, unknown>;
|
|
1183
1228
|
}
|
|
1184
1229
|
/**
|
|
1185
1230
|
* Active subscription registry. The client keys subscriptions by
|
|
@@ -1193,6 +1238,14 @@ interface SubscriptionState {
|
|
|
1193
1238
|
*/
|
|
1194
1239
|
declare class SubscriptionRegistry {
|
|
1195
1240
|
static key(functionPath: string, args: Record<string, unknown>, shardKey?: string): string;
|
|
1241
|
+
/**
|
|
1242
|
+
* The registry key of an already-registered state, from its cached
|
|
1243
|
+
* {@link SubscriptionState.argsKey}. Re-deriving it from `state.args` would
|
|
1244
|
+
* re-read the caller's own (mutable) args object, so a caller that mutated
|
|
1245
|
+
* its args after subscribing would compute a DIFFERENT key on unsubscribe
|
|
1246
|
+
* and leak the registration forever.
|
|
1247
|
+
*/
|
|
1248
|
+
static keyOf(state: SubscriptionState): string;
|
|
1196
1249
|
private readonly byKey;
|
|
1197
1250
|
private readonly byId;
|
|
1198
1251
|
get(key: string): SubscriptionState | undefined;
|
|
@@ -1719,6 +1772,26 @@ declare class LunoraClient {
|
|
|
1719
1772
|
* racing write was never in the queue.
|
|
1720
1773
|
*/
|
|
1721
1774
|
private readonly offlineFlushes;
|
|
1775
|
+
/**
|
|
1776
|
+
* Per-shard replay backoff, keyed by {@link connectionKey} exactly as the
|
|
1777
|
+
* flushes and their timers are: `delayMs` is the longest hint the CURRENT
|
|
1778
|
+
* flush of that key was given (written by
|
|
1779
|
+
* {@link LunoraClient.noteReplayRetryDelay}, consumed once by
|
|
1780
|
+
* {@link LunoraClient.scheduleRateLimitedRetry} at the end of the drain), and
|
|
1781
|
+
* `attempts` counts its consecutive failed flushes, which is what the
|
|
1782
|
+
* hintless backoff ramps on.
|
|
1783
|
+
*
|
|
1784
|
+
* Keyed, not a single field: flushes are per shard key and run concurrently,
|
|
1785
|
+
* so one field means one limited shard sets the wait for every other shard
|
|
1786
|
+
* and two flushes overwrite — then consume — each other's delay, leaving one
|
|
1787
|
+
* of them with nothing scheduled at all. Evicted by
|
|
1788
|
+
* {@link LunoraClient.scheduleRateLimitedRetry} the moment a key has nothing
|
|
1789
|
+
* left to retry, so an app that shards per document does not accumulate an
|
|
1790
|
+
* entry per document it ever wrote.
|
|
1791
|
+
*/
|
|
1792
|
+
private readonly replayRetryState;
|
|
1793
|
+
/** Pending rate-limit retry flushes, keyed by {@link connectionKey}, so `close()` can cancel them. */
|
|
1794
|
+
private readonly replayRetryTimers;
|
|
1722
1795
|
private readonly shapeSubscriptions;
|
|
1723
1796
|
/**
|
|
1724
1797
|
* In-flight pokes being assembled between `pokeStart` and `pokeEnd`, keyed by
|
|
@@ -2782,10 +2855,12 @@ declare class LunoraClient {
|
|
|
2782
2855
|
* `subscribe` for the same triple joins the existing registration and shares
|
|
2783
2856
|
* its value, cursor and optimistic layers.
|
|
2784
2857
|
*
|
|
2785
|
-
*
|
|
2786
|
-
*
|
|
2787
|
-
*
|
|
2788
|
-
*
|
|
2858
|
+
* Available on a `crossTabSync` FOLLOWER tab, unlike the other socket-backed
|
|
2859
|
+
* surfaces: registering here is exactly what lets the cross-tab relay deliver
|
|
2860
|
+
* the leader's broadcasts (see the note in the body). The caveat is that the
|
|
2861
|
+
* channel only carries the LEADER's own subscriptions outward, so a follower
|
|
2862
|
+
* sees this query's frames only while the leader holds it too — see
|
|
2863
|
+
* {@link LunoraClientOptions.crossTabSync}.
|
|
2789
2864
|
*/
|
|
2790
2865
|
subscribe<F extends FunctionReference>(function_: F, args: ArgsOf<F>, callback: (data: ReturnOf<F>) => void, options?: {
|
|
2791
2866
|
onCheckpoint?: (watermark: SyncWatermark) => void;
|
|
@@ -2967,13 +3042,35 @@ declare class LunoraClient {
|
|
|
2967
3042
|
*/
|
|
2968
3043
|
private hydrateAsOutboxLeader;
|
|
2969
3044
|
/**
|
|
2970
|
-
* Load every cached query
|
|
2971
|
-
*
|
|
2972
|
-
*
|
|
2973
|
-
*
|
|
2974
|
-
*
|
|
3045
|
+
* Load every cached query so a `subscribe()` for the key seeds its initial
|
|
3046
|
+
* value off disk.
|
|
3047
|
+
*
|
|
3048
|
+
* The load is asynchronous but every framework adapter subscribes
|
|
3049
|
+
* SYNCHRONOUSLY at mount, so the subscriptions that most want the cache
|
|
3050
|
+
* already exist by the time it lands. Those are seeded here and their entry
|
|
3051
|
+
* is dropped: an entry that stayed in {@link hydratedQueryCache} behind a
|
|
3052
|
+
* live subscription would be consumed by the NEXT subscribe of the same key
|
|
3053
|
+
* (a remount after navigating away) and replay the previous session's value
|
|
3054
|
+
* over whatever the socket had since delivered. A cache entry therefore
|
|
3055
|
+
* never outlives a live subscription for its key — it is either handed to
|
|
3056
|
+
* that subscription or discarded.
|
|
3057
|
+
*
|
|
3058
|
+
* Only keys with no live subscription are held for a later `subscribe()`;
|
|
3059
|
+
* the identity gate applies to both paths, so a signed-out cache never leaks
|
|
3060
|
+
* into a new session.
|
|
2975
3061
|
*/
|
|
2976
3062
|
private hydrateQueryCache;
|
|
3063
|
+
/**
|
|
3064
|
+
* Hand a loaded read-cache entry to a subscription that was opened before
|
|
3065
|
+
* the load resolved — the same value, cursor and epoch {@link subscribe}
|
|
3066
|
+
* would have taken from {@link takeHydratedCache} had the load finished
|
|
3067
|
+
* first, so the cursor still rides the `subscribe` frame as `sinceSeq` (that
|
|
3068
|
+
* frame only goes out once the socket opens, well after this microtask).
|
|
3069
|
+
*
|
|
3070
|
+
* A no-op once the socket has delivered anything for this key: a live value
|
|
3071
|
+
* always beats the cache. Identity-gated exactly like `takeHydratedCache`.
|
|
3072
|
+
*/
|
|
3073
|
+
private seedSubscriptionFromCache;
|
|
2977
3074
|
/**
|
|
2978
3075
|
* Consume the hydrated read-cache entry for a key (if any), gated on
|
|
2979
3076
|
* identity. The entry is removed whether or not it matches — the cache only
|
|
@@ -3363,6 +3460,29 @@ declare class LunoraClient {
|
|
|
3363
3460
|
*/
|
|
3364
3461
|
private flushOfflineQueue;
|
|
3365
3462
|
private drainOfflineQueue;
|
|
3463
|
+
/**
|
|
3464
|
+
* Remember the longest delay this shard's flush was told (or worked out) to
|
|
3465
|
+
* wait, so the drain can honour it before trying again
|
|
3466
|
+
* ({@link LunoraClient.replayRetryState}). Counts the attempt either way:
|
|
3467
|
+
* that is what a hintless refusal backs off on.
|
|
3468
|
+
*/
|
|
3469
|
+
private noteReplayRetryDelay;
|
|
3470
|
+
/**
|
|
3471
|
+
* Consume this shard's retry delay and re-flush it once the delay has
|
|
3472
|
+
* elapsed.
|
|
3473
|
+
*
|
|
3474
|
+
* Only a failure the server or an edge ANSWERED schedules anything (see
|
|
3475
|
+
* {@link replayRetryDelayMs}): a `fetch` that never landed is already covered
|
|
3476
|
+
* by the reconnect that will flush the queue, whereas a refused flush happens
|
|
3477
|
+
* over a socket that stays open — so without this the writes sit queued
|
|
3478
|
+
* indefinitely. One pending timer per shard; a second delay replaces it
|
|
3479
|
+
* rather than stacking flushes.
|
|
3480
|
+
*
|
|
3481
|
+
* Nothing left to retry on this key (drained, closed, or no delay) drops its
|
|
3482
|
+
* backoff state, which is both the reset after progress and what bounds the
|
|
3483
|
+
* map.
|
|
3484
|
+
*/
|
|
3485
|
+
private scheduleRateLimitedRetry;
|
|
3366
3486
|
/**
|
|
3367
3487
|
* Partition already-gated writes into the encodable ones (returned) and reject
|
|
3368
3488
|
* the rest terminally. A write whose args can't be wire-encoded (e.g. a RegExp
|
|
@@ -3430,11 +3550,14 @@ declare class LunoraClient {
|
|
|
3430
3550
|
* Replay already-identity-gated writes one at a time on the single-call `/rpc`
|
|
3431
3551
|
* path, preserving FIFO order (parallel `.then()` chains would race the
|
|
3432
3552
|
* ordering callers depend on). Each replays under its stable `mutationId` so
|
|
3433
|
-
* the server dedups a write it already committed (exactly-once). A
|
|
3434
|
-
*
|
|
3435
|
-
*
|
|
3436
|
-
*
|
|
3437
|
-
*
|
|
3553
|
+
* the server dedups a write it already committed (exactly-once). A server verdict
|
|
3554
|
+
* drops the write; a transient failure ({@link isTransientReplayFailure} — a
|
|
3555
|
+
* transport error, a shard the worker couldn't reach, a rate-limit refusal, or a
|
|
3556
|
+
* non-2xx carrying no `{ error }` envelope) stops the flush and re-queues this
|
|
3557
|
+
* write and every unreplayed one for the next reconnect — their callers stay
|
|
3558
|
+
* pending, and the identity guard re-applies on retry via each record's persisted
|
|
3559
|
+
* stamp. The batch path classifies a slot by the same rule, so a durable write's
|
|
3560
|
+
* fate never depends on how many siblings happened to be queued alongside it.
|
|
3438
3561
|
*/
|
|
3439
3562
|
private replaySequential;
|
|
3440
3563
|
/**
|
|
@@ -3444,24 +3567,47 @@ declare class LunoraClient {
|
|
|
3444
3567
|
* per-entry `mutationId` idempotency and in-order application are inherited from
|
|
3445
3568
|
* the proven path. Per-slot demux mirrors {@link replaySequential}'s
|
|
3446
3569
|
* classification: success confirms the optimistic layer against the echoed
|
|
3447
|
-
* `commitCursor`; a coded application verdict is terminal; a
|
|
3448
|
-
*
|
|
3570
|
+
* `commitCursor`; a coded application verdict is terminal; a {@link
|
|
3571
|
+
* TRANSIENT_REPLAY_ERROR_CODES} code, a missing slot, or a whole-batch
|
|
3449
3572
|
* transport failure re-queues for the next reconnect (never dropping a durable
|
|
3450
3573
|
* write). A whole-batch coded rejection (bad request / authorization denial the
|
|
3451
3574
|
* server reached a verdict on) is terminal for every entry.
|
|
3452
3575
|
*
|
|
3576
|
+
* The body is also held under {@link MAX_BATCH_BODY_BYTES}: the worker caps a
|
|
3577
|
+
* batch body at 1 MiB and answers `413`, which is ONE refusal covering every
|
|
3578
|
+
* write in the chunk. An over-budget chunk is halved before it is sent, and a
|
|
3579
|
+
* `413` for a chunk of more than one write halves it and retries rather than
|
|
3580
|
+
* settling the whole chunk terminally — so a backlog of large writes still
|
|
3581
|
+
* commits, one bisection deeper.
|
|
3582
|
+
*
|
|
3453
3583
|
* Returns the writes that must be re-queued and `stop` — `true` when the whole
|
|
3454
3584
|
* chunk failed at the transport level, so the caller leaves later chunks queued
|
|
3455
3585
|
* rather than sending on. The caller re-queues once, in order, so requeuing is
|
|
3456
3586
|
* NOT done here.
|
|
3457
3587
|
*/
|
|
3458
3588
|
private replayBatched;
|
|
3589
|
+
/**
|
|
3590
|
+
* Classify a `/_lunora/rpc-batch` reply that carried no per-slot results — one
|
|
3591
|
+
* outcome covering every entry in the chunk. A {@link TRANSIENT_REPLAY_ERROR_CODES}
|
|
3592
|
+
* code (an unreachable shard, a rate-limit refusal) or a
|
|
3593
|
+
* {@link TransportError} (an edge reply with no verdict in it) leaves every
|
|
3594
|
+
* write durable for the next attempt; anything else is a verdict reached on
|
|
3595
|
+
* the request itself, and settles all of them.
|
|
3596
|
+
*/
|
|
3597
|
+
private settleWholeBatchError;
|
|
3598
|
+
/**
|
|
3599
|
+
* Split an over-large batch chunk in half and replay each half, preserving
|
|
3600
|
+
* FIFO order. Recurses through {@link replayBatched}, so a chunk keeps halving
|
|
3601
|
+
* until it fits (or reaches one write, which is then the server's verdict to
|
|
3602
|
+
* give). A `stop` on the first half leaves the second unsent and queued.
|
|
3603
|
+
*/
|
|
3604
|
+
private replayBatchedHalves;
|
|
3459
3605
|
/**
|
|
3460
3606
|
* Demux a `/_lunora/rpc-batch` reply back onto the queued writes it replayed,
|
|
3461
3607
|
* in input order. Each slot's envelope classifies its write the same way
|
|
3462
3608
|
* {@link replaySequential} does: a success confirms the optimistic layer
|
|
3463
3609
|
* against the echoed `commitCursor`; a coded application verdict is terminal;
|
|
3464
|
-
* a transient
|
|
3610
|
+
* a transient failure ({@link TRANSIENT_REPLAY_ERROR_CODES}) or a slot the
|
|
3465
3611
|
* server never returned is returned for the caller to re-queue.
|
|
3466
3612
|
* @returns the writes that must be re-queued (transient slots), in input order
|
|
3467
3613
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { LunoraErrorCodeInput, LunoraErrorCode } from '@lunora/errors';
|
|
1
2
|
import { F as FunctionReference, A as ArgsOf, R as ReturnOf } from "./function-reference.d-Br_hsKje.js";
|
|
2
|
-
import { LunoraErrorCode } from '@lunora/errors';
|
|
3
3
|
import { CronJobInfo, VectorIndexSummary, VectorQueryMatch, PipelineLogQuery, PipelineLogPage, KvNamespaceSummary, KvKeyListResult, KvValueResult, AuthUser, AuthPage, AuthImpersonation, AuthCapabilities, AuthConfigInfo, AuthSession } from '@lunora/runtime';
|
|
4
4
|
/**
|
|
5
5
|
* Reactive key-value store for local-only client state.
|
|
@@ -113,7 +113,7 @@ interface ReconnectOptions {
|
|
|
113
113
|
maxDelayMs?: number;
|
|
114
114
|
}
|
|
115
115
|
/** Which durable-storage operation failed, passed to {@link OfflineQueueOptions.onPersistenceError}. */
|
|
116
|
-
type PersistenceOperation = "append" | "clear" | "load" | "remove";
|
|
116
|
+
type PersistenceOperation = "append" | "clear" | "load" | "remove" | "replace";
|
|
117
117
|
/** Context handed to a persistence-error handler. */
|
|
118
118
|
interface PersistenceErrorContext {
|
|
119
119
|
readonly error: unknown;
|
|
@@ -195,6 +195,22 @@ interface PersistenceAdapter {
|
|
|
195
195
|
load: () => Promise<PersistedMutation[]>;
|
|
196
196
|
/** Remove a mutation by id once it has been replayed (resolved or rejected). */
|
|
197
197
|
remove: (id: string) => Promise<void>;
|
|
198
|
+
/**
|
|
199
|
+
* Overwrite an already-persisted mutation IN PLACE, keeping its position in
|
|
200
|
+
* FIFO order. Used when a queued write's identity stamp is rewritten after a
|
|
201
|
+
* sign-in / sign-out.
|
|
202
|
+
*
|
|
203
|
+
* Must be atomic: a `remove` + `append` pair has a window where a process
|
|
204
|
+
* stop leaves the mutation in no durable store at all, and the in-memory
|
|
205
|
+
* entry has already advanced, so a reload loses the write outright. It also
|
|
206
|
+
* moved the record to the BACK of the queue, replaying it out of the order
|
|
207
|
+
* it was issued in. Implementations do the whole swap under one transaction
|
|
208
|
+
* (or one serialized blob write).
|
|
209
|
+
*
|
|
210
|
+
* A mutation whose id is not present is left alone — the record was drained
|
|
211
|
+
* concurrently and re-inserting it would replay a settled write.
|
|
212
|
+
*/
|
|
213
|
+
replace: (mutation: PersistedMutation) => Promise<void>;
|
|
198
214
|
}
|
|
199
215
|
/**
|
|
200
216
|
* One write handed to an {@link OutboxSink}. Mirrors {@link PersistedMutation}
|
|
@@ -378,10 +394,17 @@ interface LunoraClientOptions {
|
|
|
378
394
|
*/
|
|
379
395
|
heartbeatIntervalMs?: number;
|
|
380
396
|
/**
|
|
381
|
-
* When `true` and a `queryCache` is active,
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
397
|
+
* When `true` and a `queryCache` is active, React's `useQuery` holds its
|
|
398
|
+
* TanStack query disabled until the durable cache has finished loading
|
|
399
|
+
* (`whenReady()`), so its first enabled render can seed the cached value
|
|
400
|
+
* instead of issuing an HTTP read that the cache would immediately
|
|
401
|
+
* overwrite. Defaults to `false`.
|
|
402
|
+
*
|
|
403
|
+
* It is ONLY React's `useQuery` that defers — the Vue, Svelte, Solid and
|
|
404
|
+
* Angular hooks subscribe at mount regardless of this flag, and so does
|
|
405
|
+
* React's own subscription registry. They do not need the gate: a
|
|
406
|
+
* subscription opened before the load completes is seeded by the load
|
|
407
|
+
* itself, so a cached value reaches the first subscriber either way.
|
|
385
408
|
*
|
|
386
409
|
* Requires `queryCache` to be set (not `false`); silently ignored otherwise.
|
|
387
410
|
*/
|
|
@@ -1085,7 +1108,16 @@ interface WorkflowInstancePage {
|
|
|
1085
1108
|
type SubscriptionCallback = (data: unknown) => void;
|
|
1086
1109
|
/** A subscription-scoped error the server pushed for this subscription id. */
|
|
1087
1110
|
interface SubscriptionError {
|
|
1088
|
-
|
|
1111
|
+
/**
|
|
1112
|
+
* The coded reason, when the frame carried one. `LunoraErrorCodeInput`, not
|
|
1113
|
+
* `LunoraErrorCode`: the catalog codes autocomplete for a consumer branching
|
|
1114
|
+
* on this (the shard sends `BAD_SUBSCRIPTION_ARGS`, `TOO_MANY_SUBSCRIPTIONS`,
|
|
1115
|
+
* `SUBSCRIPTION_PERSIST_FAILED`, …; the client itself adds
|
|
1116
|
+
* `WIRE_DECODE_FAILED` for a frame `decodeWire` refuses), but this value is
|
|
1117
|
+
* read verbatim off the wire and nothing validates it against the catalog,
|
|
1118
|
+
* so narrowing it to `LunoraErrorCode` would be a lie a newer server tells.
|
|
1119
|
+
*/
|
|
1120
|
+
code?: LunoraErrorCodeInput;
|
|
1089
1121
|
message: string;
|
|
1090
1122
|
}
|
|
1091
1123
|
type SubscriptionErrorCallback = (error: SubscriptionError) => void;
|
|
@@ -1180,6 +1212,19 @@ interface SubscriptionState {
|
|
|
1180
1212
|
*/
|
|
1181
1213
|
serverEpoch?: string;
|
|
1182
1214
|
readonly shardKey?: string;
|
|
1215
|
+
/**
|
|
1216
|
+
* The wire-encoded form of `args`, computed once at `subscribe` time (so an
|
|
1217
|
+
* unsupported value fails loud at the call site, not inside a reconnect's
|
|
1218
|
+
* open handler). Sent on every `subscribe` frame — identical to `args` for
|
|
1219
|
+
* pure JSON, tagged tokens for `bigint`/`Date`/bytes/… (the shard
|
|
1220
|
+
* `decodeWire`s them at its subscribe entry point).
|
|
1221
|
+
*
|
|
1222
|
+
* A SNAPSHOT, not a view: `args` is the caller's own object, retained by
|
|
1223
|
+
* reference and never copied, so a caller that mutates it after subscribing
|
|
1224
|
+
* would otherwise poison every later resubscribe. `encodeWire` rebuilds
|
|
1225
|
+
* every container, so this copy is immune to that.
|
|
1226
|
+
*/
|
|
1227
|
+
readonly wireArgs: Record<string, unknown>;
|
|
1183
1228
|
}
|
|
1184
1229
|
/**
|
|
1185
1230
|
* Active subscription registry. The client keys subscriptions by
|
|
@@ -1193,6 +1238,14 @@ interface SubscriptionState {
|
|
|
1193
1238
|
*/
|
|
1194
1239
|
declare class SubscriptionRegistry {
|
|
1195
1240
|
static key(functionPath: string, args: Record<string, unknown>, shardKey?: string): string;
|
|
1241
|
+
/**
|
|
1242
|
+
* The registry key of an already-registered state, from its cached
|
|
1243
|
+
* {@link SubscriptionState.argsKey}. Re-deriving it from `state.args` would
|
|
1244
|
+
* re-read the caller's own (mutable) args object, so a caller that mutated
|
|
1245
|
+
* its args after subscribing would compute a DIFFERENT key on unsubscribe
|
|
1246
|
+
* and leak the registration forever.
|
|
1247
|
+
*/
|
|
1248
|
+
static keyOf(state: SubscriptionState): string;
|
|
1196
1249
|
private readonly byKey;
|
|
1197
1250
|
private readonly byId;
|
|
1198
1251
|
get(key: string): SubscriptionState | undefined;
|
|
@@ -1719,6 +1772,26 @@ declare class LunoraClient {
|
|
|
1719
1772
|
* racing write was never in the queue.
|
|
1720
1773
|
*/
|
|
1721
1774
|
private readonly offlineFlushes;
|
|
1775
|
+
/**
|
|
1776
|
+
* Per-shard replay backoff, keyed by {@link connectionKey} exactly as the
|
|
1777
|
+
* flushes and their timers are: `delayMs` is the longest hint the CURRENT
|
|
1778
|
+
* flush of that key was given (written by
|
|
1779
|
+
* {@link LunoraClient.noteReplayRetryDelay}, consumed once by
|
|
1780
|
+
* {@link LunoraClient.scheduleRateLimitedRetry} at the end of the drain), and
|
|
1781
|
+
* `attempts` counts its consecutive failed flushes, which is what the
|
|
1782
|
+
* hintless backoff ramps on.
|
|
1783
|
+
*
|
|
1784
|
+
* Keyed, not a single field: flushes are per shard key and run concurrently,
|
|
1785
|
+
* so one field means one limited shard sets the wait for every other shard
|
|
1786
|
+
* and two flushes overwrite — then consume — each other's delay, leaving one
|
|
1787
|
+
* of them with nothing scheduled at all. Evicted by
|
|
1788
|
+
* {@link LunoraClient.scheduleRateLimitedRetry} the moment a key has nothing
|
|
1789
|
+
* left to retry, so an app that shards per document does not accumulate an
|
|
1790
|
+
* entry per document it ever wrote.
|
|
1791
|
+
*/
|
|
1792
|
+
private readonly replayRetryState;
|
|
1793
|
+
/** Pending rate-limit retry flushes, keyed by {@link connectionKey}, so `close()` can cancel them. */
|
|
1794
|
+
private readonly replayRetryTimers;
|
|
1722
1795
|
private readonly shapeSubscriptions;
|
|
1723
1796
|
/**
|
|
1724
1797
|
* In-flight pokes being assembled between `pokeStart` and `pokeEnd`, keyed by
|
|
@@ -2782,10 +2855,12 @@ declare class LunoraClient {
|
|
|
2782
2855
|
* `subscribe` for the same triple joins the existing registration and shares
|
|
2783
2856
|
* its value, cursor and optimistic layers.
|
|
2784
2857
|
*
|
|
2785
|
-
*
|
|
2786
|
-
*
|
|
2787
|
-
*
|
|
2788
|
-
*
|
|
2858
|
+
* Available on a `crossTabSync` FOLLOWER tab, unlike the other socket-backed
|
|
2859
|
+
* surfaces: registering here is exactly what lets the cross-tab relay deliver
|
|
2860
|
+
* the leader's broadcasts (see the note in the body). The caveat is that the
|
|
2861
|
+
* channel only carries the LEADER's own subscriptions outward, so a follower
|
|
2862
|
+
* sees this query's frames only while the leader holds it too — see
|
|
2863
|
+
* {@link LunoraClientOptions.crossTabSync}.
|
|
2789
2864
|
*/
|
|
2790
2865
|
subscribe<F extends FunctionReference>(function_: F, args: ArgsOf<F>, callback: (data: ReturnOf<F>) => void, options?: {
|
|
2791
2866
|
onCheckpoint?: (watermark: SyncWatermark) => void;
|
|
@@ -2967,13 +3042,35 @@ declare class LunoraClient {
|
|
|
2967
3042
|
*/
|
|
2968
3043
|
private hydrateAsOutboxLeader;
|
|
2969
3044
|
/**
|
|
2970
|
-
* Load every cached query
|
|
2971
|
-
*
|
|
2972
|
-
*
|
|
2973
|
-
*
|
|
2974
|
-
*
|
|
3045
|
+
* Load every cached query so a `subscribe()` for the key seeds its initial
|
|
3046
|
+
* value off disk.
|
|
3047
|
+
*
|
|
3048
|
+
* The load is asynchronous but every framework adapter subscribes
|
|
3049
|
+
* SYNCHRONOUSLY at mount, so the subscriptions that most want the cache
|
|
3050
|
+
* already exist by the time it lands. Those are seeded here and their entry
|
|
3051
|
+
* is dropped: an entry that stayed in {@link hydratedQueryCache} behind a
|
|
3052
|
+
* live subscription would be consumed by the NEXT subscribe of the same key
|
|
3053
|
+
* (a remount after navigating away) and replay the previous session's value
|
|
3054
|
+
* over whatever the socket had since delivered. A cache entry therefore
|
|
3055
|
+
* never outlives a live subscription for its key — it is either handed to
|
|
3056
|
+
* that subscription or discarded.
|
|
3057
|
+
*
|
|
3058
|
+
* Only keys with no live subscription are held for a later `subscribe()`;
|
|
3059
|
+
* the identity gate applies to both paths, so a signed-out cache never leaks
|
|
3060
|
+
* into a new session.
|
|
2975
3061
|
*/
|
|
2976
3062
|
private hydrateQueryCache;
|
|
3063
|
+
/**
|
|
3064
|
+
* Hand a loaded read-cache entry to a subscription that was opened before
|
|
3065
|
+
* the load resolved — the same value, cursor and epoch {@link subscribe}
|
|
3066
|
+
* would have taken from {@link takeHydratedCache} had the load finished
|
|
3067
|
+
* first, so the cursor still rides the `subscribe` frame as `sinceSeq` (that
|
|
3068
|
+
* frame only goes out once the socket opens, well after this microtask).
|
|
3069
|
+
*
|
|
3070
|
+
* A no-op once the socket has delivered anything for this key: a live value
|
|
3071
|
+
* always beats the cache. Identity-gated exactly like `takeHydratedCache`.
|
|
3072
|
+
*/
|
|
3073
|
+
private seedSubscriptionFromCache;
|
|
2977
3074
|
/**
|
|
2978
3075
|
* Consume the hydrated read-cache entry for a key (if any), gated on
|
|
2979
3076
|
* identity. The entry is removed whether or not it matches — the cache only
|
|
@@ -3363,6 +3460,29 @@ declare class LunoraClient {
|
|
|
3363
3460
|
*/
|
|
3364
3461
|
private flushOfflineQueue;
|
|
3365
3462
|
private drainOfflineQueue;
|
|
3463
|
+
/**
|
|
3464
|
+
* Remember the longest delay this shard's flush was told (or worked out) to
|
|
3465
|
+
* wait, so the drain can honour it before trying again
|
|
3466
|
+
* ({@link LunoraClient.replayRetryState}). Counts the attempt either way:
|
|
3467
|
+
* that is what a hintless refusal backs off on.
|
|
3468
|
+
*/
|
|
3469
|
+
private noteReplayRetryDelay;
|
|
3470
|
+
/**
|
|
3471
|
+
* Consume this shard's retry delay and re-flush it once the delay has
|
|
3472
|
+
* elapsed.
|
|
3473
|
+
*
|
|
3474
|
+
* Only a failure the server or an edge ANSWERED schedules anything (see
|
|
3475
|
+
* {@link replayRetryDelayMs}): a `fetch` that never landed is already covered
|
|
3476
|
+
* by the reconnect that will flush the queue, whereas a refused flush happens
|
|
3477
|
+
* over a socket that stays open — so without this the writes sit queued
|
|
3478
|
+
* indefinitely. One pending timer per shard; a second delay replaces it
|
|
3479
|
+
* rather than stacking flushes.
|
|
3480
|
+
*
|
|
3481
|
+
* Nothing left to retry on this key (drained, closed, or no delay) drops its
|
|
3482
|
+
* backoff state, which is both the reset after progress and what bounds the
|
|
3483
|
+
* map.
|
|
3484
|
+
*/
|
|
3485
|
+
private scheduleRateLimitedRetry;
|
|
3366
3486
|
/**
|
|
3367
3487
|
* Partition already-gated writes into the encodable ones (returned) and reject
|
|
3368
3488
|
* the rest terminally. A write whose args can't be wire-encoded (e.g. a RegExp
|
|
@@ -3430,11 +3550,14 @@ declare class LunoraClient {
|
|
|
3430
3550
|
* Replay already-identity-gated writes one at a time on the single-call `/rpc`
|
|
3431
3551
|
* path, preserving FIFO order (parallel `.then()` chains would race the
|
|
3432
3552
|
* ordering callers depend on). Each replays under its stable `mutationId` so
|
|
3433
|
-
* the server dedups a write it already committed (exactly-once). A
|
|
3434
|
-
*
|
|
3435
|
-
*
|
|
3436
|
-
*
|
|
3437
|
-
*
|
|
3553
|
+
* the server dedups a write it already committed (exactly-once). A server verdict
|
|
3554
|
+
* drops the write; a transient failure ({@link isTransientReplayFailure} — a
|
|
3555
|
+
* transport error, a shard the worker couldn't reach, a rate-limit refusal, or a
|
|
3556
|
+
* non-2xx carrying no `{ error }` envelope) stops the flush and re-queues this
|
|
3557
|
+
* write and every unreplayed one for the next reconnect — their callers stay
|
|
3558
|
+
* pending, and the identity guard re-applies on retry via each record's persisted
|
|
3559
|
+
* stamp. The batch path classifies a slot by the same rule, so a durable write's
|
|
3560
|
+
* fate never depends on how many siblings happened to be queued alongside it.
|
|
3438
3561
|
*/
|
|
3439
3562
|
private replaySequential;
|
|
3440
3563
|
/**
|
|
@@ -3444,24 +3567,47 @@ declare class LunoraClient {
|
|
|
3444
3567
|
* per-entry `mutationId` idempotency and in-order application are inherited from
|
|
3445
3568
|
* the proven path. Per-slot demux mirrors {@link replaySequential}'s
|
|
3446
3569
|
* classification: success confirms the optimistic layer against the echoed
|
|
3447
|
-
* `commitCursor`; a coded application verdict is terminal; a
|
|
3448
|
-
*
|
|
3570
|
+
* `commitCursor`; a coded application verdict is terminal; a {@link
|
|
3571
|
+
* TRANSIENT_REPLAY_ERROR_CODES} code, a missing slot, or a whole-batch
|
|
3449
3572
|
* transport failure re-queues for the next reconnect (never dropping a durable
|
|
3450
3573
|
* write). A whole-batch coded rejection (bad request / authorization denial the
|
|
3451
3574
|
* server reached a verdict on) is terminal for every entry.
|
|
3452
3575
|
*
|
|
3576
|
+
* The body is also held under {@link MAX_BATCH_BODY_BYTES}: the worker caps a
|
|
3577
|
+
* batch body at 1 MiB and answers `413`, which is ONE refusal covering every
|
|
3578
|
+
* write in the chunk. An over-budget chunk is halved before it is sent, and a
|
|
3579
|
+
* `413` for a chunk of more than one write halves it and retries rather than
|
|
3580
|
+
* settling the whole chunk terminally — so a backlog of large writes still
|
|
3581
|
+
* commits, one bisection deeper.
|
|
3582
|
+
*
|
|
3453
3583
|
* Returns the writes that must be re-queued and `stop` — `true` when the whole
|
|
3454
3584
|
* chunk failed at the transport level, so the caller leaves later chunks queued
|
|
3455
3585
|
* rather than sending on. The caller re-queues once, in order, so requeuing is
|
|
3456
3586
|
* NOT done here.
|
|
3457
3587
|
*/
|
|
3458
3588
|
private replayBatched;
|
|
3589
|
+
/**
|
|
3590
|
+
* Classify a `/_lunora/rpc-batch` reply that carried no per-slot results — one
|
|
3591
|
+
* outcome covering every entry in the chunk. A {@link TRANSIENT_REPLAY_ERROR_CODES}
|
|
3592
|
+
* code (an unreachable shard, a rate-limit refusal) or a
|
|
3593
|
+
* {@link TransportError} (an edge reply with no verdict in it) leaves every
|
|
3594
|
+
* write durable for the next attempt; anything else is a verdict reached on
|
|
3595
|
+
* the request itself, and settles all of them.
|
|
3596
|
+
*/
|
|
3597
|
+
private settleWholeBatchError;
|
|
3598
|
+
/**
|
|
3599
|
+
* Split an over-large batch chunk in half and replay each half, preserving
|
|
3600
|
+
* FIFO order. Recurses through {@link replayBatched}, so a chunk keeps halving
|
|
3601
|
+
* until it fits (or reaches one write, which is then the server's verdict to
|
|
3602
|
+
* give). A `stop` on the first half leaves the second unsent and queued.
|
|
3603
|
+
*/
|
|
3604
|
+
private replayBatchedHalves;
|
|
3459
3605
|
/**
|
|
3460
3606
|
* Demux a `/_lunora/rpc-batch` reply back onto the queued writes it replayed,
|
|
3461
3607
|
* in input order. Each slot's envelope classifies its write the same way
|
|
3462
3608
|
* {@link replaySequential} does: a success confirms the optimistic layer
|
|
3463
3609
|
* against the echoed `commitCursor`; a coded application verdict is terminal;
|
|
3464
|
-
* a transient
|
|
3610
|
+
* a transient failure ({@link TRANSIENT_REPLAY_ERROR_CODES}) or a slot the
|
|
3465
3611
|
* server never returned is returned for the caller to re-queue.
|
|
3466
3612
|
* @returns the writes that must be re-queued (transient slots), in input order
|
|
3467
3613
|
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let h=0;const d=()=>{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=d,o=(r,t,e,i)=>{if(r)try{r({error:e,mutationId:i,operation:t});return}catch{}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(a=>{o(this.onPersistenceError,"remove",a,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.rewriteStamp(n,c).catch(s=>{o(this.onPersistenceError,"replace",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()}async rewriteStamp(t,e){const i=this.persistence;if(i)try{await i.replace(e)}catch(n){o(this.onPersistenceError,"replace",n,t)}}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,4 +1,4 @@
|
|
|
1
|
-
import { L as LunoraClient, P as Preloaded } from "./lunora-client.d-
|
|
1
|
+
import { L as LunoraClient, P as Preloaded } from "./lunora-client.d-B0t5v2n9.js";
|
|
2
2
|
import { F as FunctionReference, A as ArgsOf, R as ReturnOf } from "./function-reference.d-Br_hsKje.js";
|
|
3
3
|
/**
|
|
4
4
|
* Run a query once on the server (during SSR) and capture its result in a
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as LunoraClient, P as Preloaded } from "./lunora-client.d-
|
|
1
|
+
import { L as LunoraClient, P as Preloaded } from "./lunora-client.d-6hU156nD.mjs";
|
|
2
2
|
import { F as FunctionReference, A as ArgsOf, R as ReturnOf } from "./function-reference.d-Br_hsKje.mjs";
|
|
3
3
|
/**
|
|
4
4
|
* Run a query once on the server (during SSR) and capture its result in a
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const m=e=>{let t="";for(let o=0;o<e.length;o+=32768)t+=String.fromCharCode(...e.subarray(o,o+32768));return btoa(t)},p=e=>{const t=atob(e),a=new Uint8Array(t.length);for(let o=0;o<t.length;o+=1)a[o]=t.codePointAt(o)??0;return a},i="$lunora.wire$";const w="__proto__",g={BigInt64Array,BigUint64Array,Float32Array,Float64Array,Int8Array,Int16Array,Int32Array,Uint8Array,Uint8ClampedArray,Uint16Array,Uint32Array},A={Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError},l=e=>{if(e===null||typeof e!="object")return!1;const t=Object.getPrototypeOf(e);return t===null||t===Object.prototype},u=(e,t=0)=>{if(t>64)throw new RangeError("wire-codec: value nesting exceeds the 64-level limit");if(e===void 0)return[i,"undefined"];if(e===null)return null;const a=typeof e;if(a==="bigint")return[i,"bigint",e.toString()];if(a==="number"){const r=e;return Number.isNaN(r)?[i,"nan"]:r===1/0?[i,"inf"]:r===-1/0?[i,"-inf"]:r}if(a!=="object")return e;if(e instanceof Date)return[i,"date",u(e.getTime(),t+1)];if(e instanceof Error){const r=e,n={};for(const f of Object.keys(r))r[f]!==void 0&&(n[f]=u(r[f],t+1));const c=[i,"error",r.name,r.message,n];return r.cause!==void 0&&c.push(u(r.cause,t+1)),c}if(e instanceof URL)return[i,"url",e.href];if(e instanceof Map)return[i,"map",[...e.entries()].map(([r,n])=>[u(r,t+1),u(n,t+1)])];if(e instanceof Set)return[i,"set",[...e].map(r=>u(r,t+1))];if(e instanceof ArrayBuffer)return[i,"bytes",m(new Uint8Array(e)),"ArrayBuffer"];if(ArrayBuffer.isView(e)){const r=e,n=r.constructor.name,c=new Uint8Array(r.buffer,r.byteOffset,r.byteLength);return n==="Uint8Array"?[i,"bytes",m(c)]:[i,"bytes",m(c),n]}if(Array.isArray(e)){const r=e.map(n=>u(n,t+1));return r.length>0&&r[0]===i?[i,"arr",r]:r}if(!l(e)){const r=e.constructor?.name??"value";throw new TypeError(`wire-codec: cannot encode a ${r} over the Lunora wire — only plain objects, arrays, and the supported built-ins (Date, Error, URL, Map, Set, ArrayBuffer/typed arrays, bigint) round-trip`)}const o=e,s={};for(const r of Object.keys(o)){const n=o[r];if(n===void 0)continue;const c=u(n,t+1);r===w?Object.defineProperty(s,r,{configurable:!0,enumerable:!0,value:c,writable:!0}):s[r]=c}return s},y=(e,t=0)=>{if(t>64)throw new RangeError("wire-codec: value nesting exceeds the 64-level limit");if(e===null||typeof e!="object")return e;if(Array.isArray(e)){if(e[0]===i)switch(e[1]){case"-inf":return-1/0;case"arr":return e[2].map(r=>y(r,t+1));case"bigint":{const r=e[2];if(typeof r!="string"||r.length>1024||!/^-?\d+$/.test(r))throw new RangeError("wire-codec: invalid or over-long bigint (max 1024 digits)");return BigInt(r)}case"date":{if(e.length<3)throw new TypeError("wire-codec: malformed date — missing payload");return new Date(y(e[2],t+1))}case"map":{const r=e[2];return new Map(r.map(n=>{if(!Array.isArray(n)||n.length!==2)throw new TypeError("wire-codec: malformed map entry — expected a [key, value] pair");return[y(n[0],t+1),y(n[1],t+1)]}))}case"set":return new Set(e[2].map(r=>y(r,t+1)));case"url":return new URL(e[2]);case"error":{const r=e[2],n=e[3],c=(Object.hasOwn(A,r)?A[r]:void 0)??Error,f=new c(n);f.name!==r&&Object.defineProperty(f,"name",{configurable:!0,value:r,writable:!0});const b=y(e[4],t+1);if(b===null||typeof b!="object"||Array.isArray(b))throw new TypeError("wire-codec: malformed error — props must be an object");for(const d of Object.keys(b))d===w?Object.defineProperty(f,d,{configurable:!0,enumerable:!0,value:b[d],writable:!0}):f[d]=b[d];return e.length>5&&Object.defineProperty(f,"cause",{configurable:!0,value:y(e[5],t+1),writable:!0}),f}case"bytes":{const r=p(e[2]),n=e[3]??"Uint8Array";if(n==="ArrayBuffer")return r.buffer.byteLength===r.byteLength?r.buffer:r.slice().buffer;const c=Object.hasOwn(g,n)?g[n]:void 0;return c?new c(r.slice().buffer):r}case"inf":return 1/0;case"nan":return Number.NaN;case"undefined":return;default:return e.map(r=>y(r,t+1))}return e.map(s=>y(s,t+1))}const a=e,o={};for(const s of Object.keys(a)){const r=y(a[s],t+1);s===w?Object.defineProperty(o,s,{configurable:!0,enumerable:!0,value:r,writable:!0}):o[s]=r}return o};export{y as d,u as e,l as i};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{e as a}from"./wire-codec-
|
|
1
|
+
import{e as a}from"./wire-codec-BfIZrTJe.mjs";const b=/["\\\u0000-\u001F\uD800-\uDFFF]/,y=t=>b.test(t)?JSON.stringify(t):`"${t}"`,i=t=>{if(t===void 0)return"null";if(typeof t=="bigint")throw new TypeError("stableStringify: cannot use a bigint in a stable JSON cache key — pass it as a string, or use stableWireKey");if(typeof t=="number"){if(Number.isNaN(t))return"nan";if(t===1/0)return"inf";if(t===-1/0)return"-inf";if(Object.is(t,-0))return"-0"}if(typeof t=="string")return y(t);if(t===null||typeof t!="object")return JSON.stringify(t);if(Array.isArray(t)){let r="[";for(let e=0;e<t.length;e++)e>0&&(r+=","),r+=i(t[e]);return r+"]"}const o=Object.getPrototypeOf(t);if(o!==null&&o!==Object.prototype){const r=t.constructor?.name??"value";throw new TypeError(`stableStringify: cannot use a ${r} in a stable JSON cache key — only plain objects, arrays, and JSON primitives are supported (wire-typed values key via stableWireKey)`)}const s=t,c=Object.keys(s).sort();let n="{",f=!0;for(const r of c){const e=s[r];e!==void 0&&(f?f=!1:n+=",",n+=y(r),n+=":",n+=i(e))}return n+"}"},u=t=>i(a(t));export{u as s};
|