@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.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/index.d.mts +2 -2
  3. package/dist/auth/index.d.ts +2 -2
  4. package/dist/index.d.mts +46 -23
  5. package/dist/index.d.ts +46 -23
  6. package/dist/index.mjs +1 -1
  7. package/dist/packem_shared/CONFLICT_ERROR_CODE-BOeeP_Wl.mjs +1 -0
  8. package/dist/packem_shared/LunoraClient-U7lWMpjV.mjs +1 -0
  9. package/dist/packem_shared/OfflineQueue-ZapYrAVi.mjs +1 -0
  10. package/dist/packem_shared/SubscriptionRegistry-BQPU90ST.mjs +1 -0
  11. package/dist/packem_shared/applyDelta-BiegnbZR.mjs +1 -0
  12. package/dist/packem_shared/createAsyncStoragePersistence-CeUW9NTj.mjs +1 -0
  13. package/dist/packem_shared/{createAsyncStorageQueryCache-C0mJLuZK.mjs → createAsyncStorageQueryCache-MoM77Nwg.mjs} +1 -1
  14. package/dist/packem_shared/createInMemoryPersistence-Cz2W6XVj.mjs +1 -0
  15. package/dist/packem_shared/createLocalStore-v6oiATqG.mjs +1 -0
  16. package/dist/packem_shared/{createServerClient-l9OHvYqk.mjs → createServerClient-rFEbvMRr.mjs} +1 -1
  17. package/dist/packem_shared/{createSnapshotPrecondition-ZyQDet2v.mjs → createSnapshotPrecondition-C4bSgHkg.mjs} +1 -1
  18. package/dist/packem_shared/{delta-merge-CVSN-uoC.mjs → delta-merge-BPM4sFID.mjs} +1 -1
  19. package/dist/packem_shared/{local-store-BQuKm9n6.mjs → local-store-CNgeGW02.mjs} +1 -1
  20. package/dist/packem_shared/{lunora-client.d-DWDMVBKE.d.mts → lunora-client.d-6hU156nD.d.mts} +170 -24
  21. package/dist/packem_shared/{lunora-client.d-CLwpIj61.d.ts → lunora-client.d-B0t5v2n9.d.ts} +170 -24
  22. package/dist/packem_shared/offline-queue-xntsWiKt.mjs +1 -0
  23. package/dist/packem_shared/{preload.d-ClHaOfOQ.d.ts → preload.d-Dl-ja0VQ.d.ts} +1 -1
  24. package/dist/packem_shared/{preload.d-BAazUwhn.d.mts → preload.d-M7qP6TwL.d.mts} +1 -1
  25. package/dist/packem_shared/wire-codec-BfIZrTJe.mjs +1 -0
  26. package/dist/packem_shared/{wire-key-Dl7EFWSD.mjs → wire-key-C9UKT-C3.mjs} +1 -1
  27. package/dist/query/index.d.mts +2 -2
  28. package/dist/query/index.d.ts +2 -2
  29. package/dist/service.mjs +1 -1
  30. package/dist/ssr/index.d.mts +2 -2
  31. package/dist/ssr/index.d.ts +2 -2
  32. package/dist/ssr/index.mjs +1 -1
  33. package/package.json +2 -2
  34. package/dist/packem_shared/CONFLICT_ERROR_CODE-CDDneB-G.mjs +0 -1
  35. package/dist/packem_shared/LunoraClient-BYXxJohQ.mjs +0 -1
  36. package/dist/packem_shared/OfflineQueue-B6IWyX5W.mjs +0 -1
  37. package/dist/packem_shared/SubscriptionRegistry-D7n5ZE5F.mjs +0 -1
  38. package/dist/packem_shared/applyDelta-DJmlwFnT.mjs +0 -1
  39. package/dist/packem_shared/createAsyncStoragePersistence-CNdM5o1u.mjs +0 -1
  40. package/dist/packem_shared/createInMemoryPersistence-C0qm7c47.mjs +0 -1
  41. package/dist/packem_shared/createLocalStore-DtsP-CpS.mjs +0 -1
  42. package/dist/packem_shared/offline-queue-8mpNPwG8.mjs +0 -1
  43. package/dist/packem_shared/wire-codec-PBOTh_2d.mjs +0 -1
@@ -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, framework hooks (React, Vue, …)
382
- * wait for the durable cache to finish hydrating before their first render
383
- * with an enabled subscription, so users see cached data instead of an
384
- * undefined flash before the socket round-trip. Defaults to `false`.
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
- code?: string;
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
- * Not available on a `crossTabSync` FOLLOWER tab: the cross-tab channel only
2786
- * carries the LEADER's own subscriptions outward, so a follower would receive
2787
- * this query's frames only if the leader happened to hold it too. Throws
2788
- * `NOT_IMPLEMENTED` there see {@link LunoraClientOptions.crossTabSync}.
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 into {@link hydratedQueryCache} so the next
2971
- * `subscribe()` for each key seeds its initial value off disk. A
2972
- * subscription created before this resolves simply misses the cache (it
2973
- * gets a live snapshot as before); the gate at seed time also drops any
2974
- * entry whose stamped identity no longer matches the current one.
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 coded error
3434
- * is a server verdict (drop it); a codeless (transport/transient) failure stops
3435
- * the flush and re-queues this write and every unreplayed one for the next
3436
- * reconnect their callers stay pending, and the identity guard re-applies on
3437
- * retry via each record's persisted stamp.
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 transient shard
3448
- * failure (`SHARD_UNAVAILABLE`/`SHARD_ERROR`), a missing slot, or a whole-batch
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 shard failure ({@link TRANSIENT_BATCH_ERROR_CODES}) or a slot the
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, framework hooks (React, Vue, …)
382
- * wait for the durable cache to finish hydrating before their first render
383
- * with an enabled subscription, so users see cached data instead of an
384
- * undefined flash before the socket round-trip. Defaults to `false`.
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
- code?: string;
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
- * Not available on a `crossTabSync` FOLLOWER tab: the cross-tab channel only
2786
- * carries the LEADER's own subscriptions outward, so a follower would receive
2787
- * this query's frames only if the leader happened to hold it too. Throws
2788
- * `NOT_IMPLEMENTED` there see {@link LunoraClientOptions.crossTabSync}.
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 into {@link hydratedQueryCache} so the next
2971
- * `subscribe()` for each key seeds its initial value off disk. A
2972
- * subscription created before this resolves simply misses the cache (it
2973
- * gets a live snapshot as before); the gate at seed time also drops any
2974
- * entry whose stamped identity no longer matches the current one.
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 coded error
3434
- * is a server verdict (drop it); a codeless (transport/transient) failure stops
3435
- * the flush and re-queues this write and every unreplayed one for the next
3436
- * reconnect their callers stay pending, and the identity guard re-applies on
3437
- * retry via each record's persisted stamp.
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 transient shard
3448
- * failure (`SHARD_UNAVAILABLE`/`SHARD_ERROR`), a missing slot, or a whole-batch
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 shard failure ({@link TRANSIENT_BATCH_ERROR_CODES}) or a slot the
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-CLwpIj61.js";
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-DWDMVBKE.mjs";
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-PBOTh_2d.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};
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};