@lunora/client 1.0.0-alpha.71 → 1.0.0-alpha.73
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-CQC4SiA4.mjs +1 -0
- package/dist/packem_shared/OfflineQueue-ZapYrAVi.mjs +1 -0
- package/dist/packem_shared/{SubscriptionRegistry-BQPU90ST.mjs → SubscriptionRegistry-BQTQZYoI.mjs} +1 -1
- package/dist/packem_shared/applyDelta-Bz-B1XB9.mjs +1 -0
- package/dist/packem_shared/createAsyncStoragePersistence-CeUW9NTj.mjs +1 -0
- package/dist/packem_shared/{createAsyncStorageQueryCache-MoM77Nwg.mjs → createAsyncStorageQueryCache-VgJNCeEn.mjs} +1 -1
- package/dist/packem_shared/createInMemoryPersistence-Cz2W6XVj.mjs +1 -0
- package/dist/packem_shared/createLocalStore-CJ0VRKPe.mjs +1 -0
- package/dist/packem_shared/{createServerClient-BevCicNQ.mjs → createServerClient-DsyHLN1F.mjs} +1 -1
- package/dist/packem_shared/{createSnapshotPrecondition-C4bSgHkg.mjs → createSnapshotPrecondition-B7kQcZUF.mjs} +1 -1
- package/dist/packem_shared/{delta-merge-BPM4sFID.mjs → delta-merge-Ds6tHkFX.mjs} +1 -1
- package/dist/packem_shared/{local-store-CNgeGW02.mjs → local-store-irMi9H2G.mjs} +1 -1
- package/dist/packem_shared/{lunora-client.d-BSNUC85h.d.mts → lunora-client.d-6hU156nD.d.mts} +149 -24
- package/dist/packem_shared/{lunora-client.d-BkDgDzTg.d.ts → lunora-client.d-B0t5v2n9.d.ts} +149 -24
- package/dist/packem_shared/offline-queue-xntsWiKt.mjs +1 -0
- package/dist/packem_shared/{preload.d-Do17SOqk.d.ts → preload.d-Dl-ja0VQ.d.ts} +1 -1
- package/dist/packem_shared/{preload.d-XsWPDlX6.d.mts → preload.d-M7qP6TwL.d.mts} +1 -1
- package/dist/packem_shared/wire-codec-BOMWQpoF.mjs +1 -0
- package/dist/packem_shared/{wire-key-C9UKT-C3.mjs → wire-key-CmT1XyQI.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-B9UMg5lY.mjs +0 -1
- package/dist/packem_shared/OfflineQueue-B6IWyX5W.mjs +0 -1
- package/dist/packem_shared/applyDelta-BiegnbZR.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-v6oiATqG.mjs +0 -1
- package/dist/packem_shared/offline-queue-8mpNPwG8.mjs +0 -1
- package/dist/packem_shared/wire-codec-BfIZrTJe.mjs +0 -1
package/dist/packem_shared/{lunora-client.d-BSNUC85h.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;
|
|
@@ -1740,6 +1772,26 @@ declare class LunoraClient {
|
|
|
1740
1772
|
* racing write was never in the queue.
|
|
1741
1773
|
*/
|
|
1742
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;
|
|
1743
1795
|
private readonly shapeSubscriptions;
|
|
1744
1796
|
/**
|
|
1745
1797
|
* In-flight pokes being assembled between `pokeStart` and `pokeEnd`, keyed by
|
|
@@ -2803,10 +2855,12 @@ declare class LunoraClient {
|
|
|
2803
2855
|
* `subscribe` for the same triple joins the existing registration and shares
|
|
2804
2856
|
* its value, cursor and optimistic layers.
|
|
2805
2857
|
*
|
|
2806
|
-
*
|
|
2807
|
-
*
|
|
2808
|
-
*
|
|
2809
|
-
*
|
|
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}.
|
|
2810
2864
|
*/
|
|
2811
2865
|
subscribe<F extends FunctionReference>(function_: F, args: ArgsOf<F>, callback: (data: ReturnOf<F>) => void, options?: {
|
|
2812
2866
|
onCheckpoint?: (watermark: SyncWatermark) => void;
|
|
@@ -2988,13 +3042,35 @@ declare class LunoraClient {
|
|
|
2988
3042
|
*/
|
|
2989
3043
|
private hydrateAsOutboxLeader;
|
|
2990
3044
|
/**
|
|
2991
|
-
* Load every cached query
|
|
2992
|
-
*
|
|
2993
|
-
*
|
|
2994
|
-
*
|
|
2995
|
-
*
|
|
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.
|
|
2996
3061
|
*/
|
|
2997
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;
|
|
2998
3074
|
/**
|
|
2999
3075
|
* Consume the hydrated read-cache entry for a key (if any), gated on
|
|
3000
3076
|
* identity. The entry is removed whether or not it matches — the cache only
|
|
@@ -3384,6 +3460,29 @@ declare class LunoraClient {
|
|
|
3384
3460
|
*/
|
|
3385
3461
|
private flushOfflineQueue;
|
|
3386
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;
|
|
3387
3486
|
/**
|
|
3388
3487
|
* Partition already-gated writes into the encodable ones (returned) and reject
|
|
3389
3488
|
* the rest terminally. A write whose args can't be wire-encoded (e.g. a RegExp
|
|
@@ -3451,11 +3550,14 @@ declare class LunoraClient {
|
|
|
3451
3550
|
* Replay already-identity-gated writes one at a time on the single-call `/rpc`
|
|
3452
3551
|
* path, preserving FIFO order (parallel `.then()` chains would race the
|
|
3453
3552
|
* ordering callers depend on). Each replays under its stable `mutationId` so
|
|
3454
|
-
* the server dedups a write it already committed (exactly-once). A
|
|
3455
|
-
*
|
|
3456
|
-
*
|
|
3457
|
-
*
|
|
3458
|
-
*
|
|
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.
|
|
3459
3561
|
*/
|
|
3460
3562
|
private replaySequential;
|
|
3461
3563
|
/**
|
|
@@ -3465,24 +3567,47 @@ declare class LunoraClient {
|
|
|
3465
3567
|
* per-entry `mutationId` idempotency and in-order application are inherited from
|
|
3466
3568
|
* the proven path. Per-slot demux mirrors {@link replaySequential}'s
|
|
3467
3569
|
* classification: success confirms the optimistic layer against the echoed
|
|
3468
|
-
* `commitCursor`; a coded application verdict is terminal; a
|
|
3469
|
-
*
|
|
3570
|
+
* `commitCursor`; a coded application verdict is terminal; a {@link
|
|
3571
|
+
* TRANSIENT_REPLAY_ERROR_CODES} code, a missing slot, or a whole-batch
|
|
3470
3572
|
* transport failure re-queues for the next reconnect (never dropping a durable
|
|
3471
3573
|
* write). A whole-batch coded rejection (bad request / authorization denial the
|
|
3472
3574
|
* server reached a verdict on) is terminal for every entry.
|
|
3473
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
|
+
*
|
|
3474
3583
|
* Returns the writes that must be re-queued and `stop` — `true` when the whole
|
|
3475
3584
|
* chunk failed at the transport level, so the caller leaves later chunks queued
|
|
3476
3585
|
* rather than sending on. The caller re-queues once, in order, so requeuing is
|
|
3477
3586
|
* NOT done here.
|
|
3478
3587
|
*/
|
|
3479
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;
|
|
3480
3605
|
/**
|
|
3481
3606
|
* Demux a `/_lunora/rpc-batch` reply back onto the queued writes it replayed,
|
|
3482
3607
|
* in input order. Each slot's envelope classifies its write the same way
|
|
3483
3608
|
* {@link replaySequential} does: a success confirms the optimistic layer
|
|
3484
3609
|
* against the echoed `commitCursor`; a coded application verdict is terminal;
|
|
3485
|
-
* a transient
|
|
3610
|
+
* a transient failure ({@link TRANSIENT_REPLAY_ERROR_CODES}) or a slot the
|
|
3486
3611
|
* server never returned is returned for the caller to re-queue.
|
|
3487
3612
|
* @returns the writes that must be re-queued (transient slots), in input order
|
|
3488
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;
|
|
@@ -1740,6 +1772,26 @@ declare class LunoraClient {
|
|
|
1740
1772
|
* racing write was never in the queue.
|
|
1741
1773
|
*/
|
|
1742
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;
|
|
1743
1795
|
private readonly shapeSubscriptions;
|
|
1744
1796
|
/**
|
|
1745
1797
|
* In-flight pokes being assembled between `pokeStart` and `pokeEnd`, keyed by
|
|
@@ -2803,10 +2855,12 @@ declare class LunoraClient {
|
|
|
2803
2855
|
* `subscribe` for the same triple joins the existing registration and shares
|
|
2804
2856
|
* its value, cursor and optimistic layers.
|
|
2805
2857
|
*
|
|
2806
|
-
*
|
|
2807
|
-
*
|
|
2808
|
-
*
|
|
2809
|
-
*
|
|
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}.
|
|
2810
2864
|
*/
|
|
2811
2865
|
subscribe<F extends FunctionReference>(function_: F, args: ArgsOf<F>, callback: (data: ReturnOf<F>) => void, options?: {
|
|
2812
2866
|
onCheckpoint?: (watermark: SyncWatermark) => void;
|
|
@@ -2988,13 +3042,35 @@ declare class LunoraClient {
|
|
|
2988
3042
|
*/
|
|
2989
3043
|
private hydrateAsOutboxLeader;
|
|
2990
3044
|
/**
|
|
2991
|
-
* Load every cached query
|
|
2992
|
-
*
|
|
2993
|
-
*
|
|
2994
|
-
*
|
|
2995
|
-
*
|
|
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.
|
|
2996
3061
|
*/
|
|
2997
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;
|
|
2998
3074
|
/**
|
|
2999
3075
|
* Consume the hydrated read-cache entry for a key (if any), gated on
|
|
3000
3076
|
* identity. The entry is removed whether or not it matches — the cache only
|
|
@@ -3384,6 +3460,29 @@ declare class LunoraClient {
|
|
|
3384
3460
|
*/
|
|
3385
3461
|
private flushOfflineQueue;
|
|
3386
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;
|
|
3387
3486
|
/**
|
|
3388
3487
|
* Partition already-gated writes into the encodable ones (returned) and reject
|
|
3389
3488
|
* the rest terminally. A write whose args can't be wire-encoded (e.g. a RegExp
|
|
@@ -3451,11 +3550,14 @@ declare class LunoraClient {
|
|
|
3451
3550
|
* Replay already-identity-gated writes one at a time on the single-call `/rpc`
|
|
3452
3551
|
* path, preserving FIFO order (parallel `.then()` chains would race the
|
|
3453
3552
|
* ordering callers depend on). Each replays under its stable `mutationId` so
|
|
3454
|
-
* the server dedups a write it already committed (exactly-once). A
|
|
3455
|
-
*
|
|
3456
|
-
*
|
|
3457
|
-
*
|
|
3458
|
-
*
|
|
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.
|
|
3459
3561
|
*/
|
|
3460
3562
|
private replaySequential;
|
|
3461
3563
|
/**
|
|
@@ -3465,24 +3567,47 @@ declare class LunoraClient {
|
|
|
3465
3567
|
* per-entry `mutationId` idempotency and in-order application are inherited from
|
|
3466
3568
|
* the proven path. Per-slot demux mirrors {@link replaySequential}'s
|
|
3467
3569
|
* classification: success confirms the optimistic layer against the echoed
|
|
3468
|
-
* `commitCursor`; a coded application verdict is terminal; a
|
|
3469
|
-
*
|
|
3570
|
+
* `commitCursor`; a coded application verdict is terminal; a {@link
|
|
3571
|
+
* TRANSIENT_REPLAY_ERROR_CODES} code, a missing slot, or a whole-batch
|
|
3470
3572
|
* transport failure re-queues for the next reconnect (never dropping a durable
|
|
3471
3573
|
* write). A whole-batch coded rejection (bad request / authorization denial the
|
|
3472
3574
|
* server reached a verdict on) is terminal for every entry.
|
|
3473
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
|
+
*
|
|
3474
3583
|
* Returns the writes that must be re-queued and `stop` — `true` when the whole
|
|
3475
3584
|
* chunk failed at the transport level, so the caller leaves later chunks queued
|
|
3476
3585
|
* rather than sending on. The caller re-queues once, in order, so requeuing is
|
|
3477
3586
|
* NOT done here.
|
|
3478
3587
|
*/
|
|
3479
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;
|
|
3480
3605
|
/**
|
|
3481
3606
|
* Demux a `/_lunora/rpc-batch` reply back onto the queued writes it replayed,
|
|
3482
3607
|
* in input order. Each slot's envelope classifies its write the same way
|
|
3483
3608
|
* {@link replaySequential} does: a success confirms the optimistic layer
|
|
3484
3609
|
* against the echoed `commitCursor`; a coded application verdict is terminal;
|
|
3485
|
-
* a transient
|
|
3610
|
+
* a transient failure ({@link TRANSIENT_REPLAY_ERROR_CODES}) or a slot the
|
|
3486
3611
|
* server never returned is returned for the caller to re-queue.
|
|
3487
3612
|
* @returns the writes that must be re-queued (transient slots), in input order
|
|
3488
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 d=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 s of Object.keys(r))r[s]!==void 0&&(n[s]=u(r[s],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",d(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",d(c)]:[i,"bytes",d(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,f={};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(f,r,{configurable:!0,enumerable:!0,value:c,writable:!0}):f[r]=c}return f},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":{const r=y(e[2],t+1);if(typeof r!="number")throw new TypeError("wire-codec: malformed date — epoch must be a number");return new Date(r)}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":{const r=e[2];if(typeof r!="string")throw new TypeError("wire-codec: malformed url — href must be a string");return new URL(r)}case"error":{const r=e[2],n=e[3],c=(Object.hasOwn(A,r)?A[r]:void 0)??Error,s=new c(n);s.name!==r&&Object.defineProperty(s,"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 m of Object.keys(b))m===w?Object.defineProperty(s,m,{configurable:!0,enumerable:!0,value:b[m],writable:!0}):s[m]=b[m];return e.length>5&&Object.defineProperty(s,"cause",{configurable:!0,value:y(e[5],t+1),writable:!0}),s}case"bytes":{const r=e[2];if(typeof r!="string")throw new TypeError("wire-codec: malformed bytes — payload must be a base64 string");const n=p(r),c=e[3]??"Uint8Array";if(c==="ArrayBuffer")return n.buffer.byteLength===n.byteLength?n.buffer:n.slice().buffer;const s=Object.hasOwn(g,c)?g[c]:void 0;return s?new s(n.slice().buffer):n}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(f=>y(f,t+1))}const a=e,o={};for(const f of Object.keys(a)){const r=y(a[f],t+1);f===w?Object.defineProperty(o,f,{configurable:!0,enumerable:!0,value:r,writable:!0}):o[f]=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-BOMWQpoF.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};
|
package/dist/query/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as SubscriptionError, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-
|
|
1
|
+
import { S as SubscriptionError, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-6hU156nD.mjs";
|
|
2
2
|
export type {
|
|
3
3
|
/**
|
|
4
4
|
* `@lunora/client/query` — the framework-neutral live-query state machine shared
|
|
@@ -10,7 +10,7 @@ export type {
|
|
|
10
10
|
* framework — each adapter supplies thin sinks that write into its own ref /
|
|
11
11
|
* store / signal / cache.
|
|
12
12
|
*/
|
|
13
|
-
b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-
|
|
13
|
+
b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-6hU156nD.mjs";
|
|
14
14
|
import { F as FunctionReference, A as ArgsOf, R as ReturnOf } from "../packem_shared/function-reference.d-Br_hsKje.mjs";
|
|
15
15
|
import '@lunora/errors';
|
|
16
16
|
import '@lunora/runtime';
|
package/dist/query/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as SubscriptionError, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-
|
|
1
|
+
import { S as SubscriptionError, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-B0t5v2n9.js";
|
|
2
2
|
export type {
|
|
3
3
|
/**
|
|
4
4
|
* `@lunora/client/query` — the framework-neutral live-query state machine shared
|
|
@@ -10,7 +10,7 @@ export type {
|
|
|
10
10
|
* framework — each adapter supplies thin sinks that write into its own ref /
|
|
11
11
|
* store / signal / cache.
|
|
12
12
|
*/
|
|
13
|
-
b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-
|
|
13
|
+
b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-B0t5v2n9.js";
|
|
14
14
|
import { F as FunctionReference, A as ArgsOf, R as ReturnOf } from "../packem_shared/function-reference.d-Br_hsKje.js";
|
|
15
15
|
import '@lunora/errors';
|
|
16
16
|
import '@lunora/runtime';
|
package/dist/service.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraError as u}from"@lunora/errors";import{e as w,d as f}from"./packem_shared/wire-codec-
|
|
1
|
+
import{LunoraError as u}from"@lunora/errors";import{e as w,d as f}from"./packem_shared/wire-codec-BOMWQpoF.mjs";const v="/_lunora/rpc",$="https://lunora.internal",N=e=>{const t=new Error(e.message??"request failed");return t.code=e.code,e.data!==void 0&&(t.data=f(e.data)),e.hint!==void 0&&(t.hint=e.hint),e.docsUrl!==void 0&&(t.docsUrl=e.docsUrl),t},h=async(e,t,r,a,l)=>{const s=r.__lunoraRef;let d;try{d=JSON.stringify({args:w(a??{}),functionPath:s,...l?.shardKey===void 0?{}:{shardKey:l.shardKey}})}catch(o){const p=o instanceof Error?o.message:String(o);throw new TypeError(`@lunora/client/service: cannot encode args for '${s}' — ${p}`,o instanceof Error?{cause:o}:void 0)}const n=await e.fetch(new Request(`${$}${v}`,{body:d,headers:{"content-type":"application/json"},method:"POST"}));let i;try{i=await n.json()}catch{throw new u("INTERNAL",`@lunora/client/service: ${t} "${s}" got a non-JSON response (status ${n.status.toString()}). Check the service binding points at the Lunora Worker that declares this function.`,{status:n.status})}if(i===null||typeof i!="object")throw new u("INTERNAL",`@lunora/client/service: ${t} "${s}" returned a JSON body that is not an object (status ${n.status.toString()}). Check the service binding points at the Lunora Worker that declares this function.`,{status:n.status});const c=i;if("error"in c)throw N(c.error);if(!n.ok)throw new u("INTERNAL",`@lunora/client/service: ${t} "${s}" failed with status ${n.status.toString()} and no error envelope.`,{status:n.status});return f(c.result)},E=e=>({action:async(t,r,a)=>await h(e,"action",t,r,a),mutation:async(t,r,a)=>await h(e,"mutation",t,r,a),query:async(t,r,a)=>await h(e,"query",t,r,a)});export{v as RPC_PATH,E as createServiceClient};
|
package/dist/ssr/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-
|
|
1
|
+
import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-6hU156nD.mjs";
|
|
2
2
|
export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/function-reference.d-Br_hsKje.mjs";
|
|
3
3
|
export {
|
|
4
4
|
/**
|
|
@@ -20,7 +20,7 @@ p as preloadQuery,
|
|
|
20
20
|
* WebSocket or touches a UI framework, so it is safe to import from any SSR
|
|
21
21
|
* loader.
|
|
22
22
|
*/
|
|
23
|
-
a as preloadedQueryResult } from "../packem_shared/preload.d-
|
|
23
|
+
a as preloadedQueryResult } from "../packem_shared/preload.d-M7qP6TwL.mjs";
|
|
24
24
|
import '@lunora/errors';
|
|
25
25
|
import '@lunora/runtime';
|
|
26
26
|
/**
|