@lunora/client 1.0.0-alpha.137 → 1.0.0-alpha.139

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 (36) hide show
  1. package/dist/auth/index.d.mts +1 -1
  2. package/dist/auth/index.d.ts +1 -1
  3. package/dist/auth/index.mjs +1 -1
  4. package/dist/index.d.mts +3 -3
  5. package/dist/index.d.ts +3 -3
  6. package/dist/index.mjs +1 -1
  7. package/dist/packem_shared/LunoraClient-DSEbwI7V.mjs +1 -0
  8. package/dist/packem_shared/{SubscriptionRegistry-CS-AFUI9.mjs → SubscriptionRegistry-C3kLqped.mjs} +1 -1
  9. package/dist/packem_shared/applyDelta-CIjpZ5h9.mjs +1 -0
  10. package/dist/packem_shared/{createAsyncStorageQueryCache-BfZkthAE.mjs → createAsyncStorageQueryCache-Dpszf7UD.mjs} +1 -1
  11. package/dist/packem_shared/createLocalStore-0ARUZ892.mjs +1 -0
  12. package/dist/packem_shared/{createServerClient-CzDIXbC4.mjs → createServerClient-DqNe__QI.mjs} +1 -1
  13. package/dist/packem_shared/{createSnapshotPrecondition-GRVKxPsh.mjs → createSnapshotPrecondition-5GQ_5fFu.mjs} +1 -1
  14. package/dist/packem_shared/{delta-merge-vxvH3PvF.mjs → delta-merge-D_5ChS-C.mjs} +1 -1
  15. package/dist/packem_shared/{deserializePreloaded-C6NTQdFa.mjs → deserializePreloaded-Bffy70Wm.mjs} +1 -1
  16. package/dist/packem_shared/httpStream-v4etRV7m.mjs +9 -0
  17. package/dist/packem_shared/local-store-DDppw8Qr.mjs +1 -0
  18. package/dist/packem_shared/{lunora-client.d-Cqp1YHAJ.d.mts → lunora-client.d-CxU0UvII.d.mts} +235 -28
  19. package/dist/packem_shared/{lunora-client.d-BiwicHKh.d.ts → lunora-client.d-DlMXZMfR.d.ts} +235 -28
  20. package/dist/packem_shared/{preload.d-DE--vVCE.d.ts → preload.d-BNFe_NYM.d.ts} +1 -1
  21. package/dist/packem_shared/{preload.d-91KD5ae3.d.mts → preload.d-C3Nbhx4m.d.mts} +1 -1
  22. package/dist/packem_shared/wire-codec-BeIi1K-T.mjs +1 -0
  23. package/dist/packem_shared/{wire-key-HsUDTlQ7.mjs → wire-key-DCPYV4t8.mjs} +1 -1
  24. package/dist/query/index.d.mts +2 -2
  25. package/dist/query/index.d.ts +2 -2
  26. package/dist/service.mjs +1 -1
  27. package/dist/ssr/index.d.mts +2 -2
  28. package/dist/ssr/index.d.ts +2 -2
  29. package/dist/ssr/index.mjs +1 -1
  30. package/package.json +2 -2
  31. package/dist/packem_shared/LunoraClient-BbpyxE38.mjs +0 -1
  32. package/dist/packem_shared/applyDelta-DwcQmRsV.mjs +0 -1
  33. package/dist/packem_shared/createLocalStore-BFdXRTFz.mjs +0 -1
  34. package/dist/packem_shared/httpStream-CPdblK2G.mjs +0 -11
  35. package/dist/packem_shared/local-store-BjenEm3t.mjs +0 -1
  36. package/dist/packem_shared/wire-codec-Sw1rAu-Z.mjs +0 -1
@@ -252,7 +252,11 @@ interface OutboxMutation {
252
252
  /** Stable per-client id; pairs with {@link OutboxMutation.mutationId} as `idempotencyKey`. */
253
253
  clientId: string;
254
254
  functionPath: string;
255
- /** `${clientId}:${mutationId}` — sent as `x-lunora-mutation-id` so a replay is server-idempotent. */
255
+ /**
256
+ * `${clientId}:${mutationId}`, or the caller's own `mutationId` when it passed
257
+ * one (an `importRows` chunk) — sent as `x-lunora-mutation-id` so a replay is
258
+ * server-idempotent.
259
+ */
256
260
  idempotencyKey: string;
257
261
  /** Issuing identity fingerprint (`null` = signed out); drives the sink's identity guard. */
258
262
  identity: string | null;
@@ -866,6 +870,16 @@ interface ServerWhisperMessage {
866
870
  topic: string;
867
871
  type: "whisper";
868
872
  }
873
+ /**
874
+ * The user the shard authenticated this socket as, sent in reply to every
875
+ * `connect` frame. `subject` is the resolved user id, or `null` when the socket
876
+ * is anonymous. A cookie-session client reads it to learn who it is — a
877
+ * sign-out or another user's sign-in changes nothing else it can see.
878
+ */
879
+ interface ServerIdentityMessage {
880
+ subject: null | string;
881
+ type: "identity";
882
+ }
869
883
  /**
870
884
  * One row-level change in a shape's replication stream — the wire form of the
871
885
  * DO's `__cdc_log` `CdcChange`. `insert`/`update` carry the post-image in
@@ -945,7 +959,7 @@ interface ServerPokeEndMessage {
945
959
  pokeId: string;
946
960
  type: "pokeEnd";
947
961
  }
948
- type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerPokeEndMessage | ServerPokePartMessage | ServerPokeStartMessage | ServerResumeMessage | ServerSettledMessage | ServerWhisperMessage;
962
+ type ServerMessage = ServerAckMessage | ServerChunkMessage | ServerCompleteMessage | ServerDataMessage | ServerErrorMessage | ServerIdentityMessage | ServerPokeEndMessage | ServerPokePartMessage | ServerPokeStartMessage | ServerResumeMessage | ServerSettledMessage | ServerWhisperMessage;
949
963
  /**
950
964
  * The authenticated user as exposed client-side, mirroring better-auth's
951
965
  * `user` row (the `user` field of the `get-session` response). Kept minimal
@@ -1255,6 +1269,12 @@ type SubscriptionErrorCallback = (error: SubscriptionError) => void;
1255
1269
  * in-flight (so the overlay survives unrelated deltas until confirmed).
1256
1270
  */
1257
1271
  interface OptimisticLayer {
1272
+ /**
1273
+ * Where this write's RPC resolved in the process-wide acknowledgement order
1274
+ * (see `acknowledgementMark`); `undefined` until confirmed. A polled snapshot
1275
+ * carries no cursor, so this is what lets it drop the layer instead.
1276
+ */
1277
+ acknowledgedAt?: number;
1258
1278
  /** The committed CDC cursor (from the mutation response); `undefined` until confirmed. */
1259
1279
  commitCursor?: number;
1260
1280
  readonly id: symbol;
@@ -1473,8 +1493,9 @@ type WSState = "idle" | "connecting" | "open" | "closed";
1473
1493
  * is (re)connecting and none is open; `connected` = at least one socket is open;
1474
1494
  * `polling` = no socket would open, so live queries are being refreshed over HTTP
1475
1495
  * instead (see {@link file://./polling-fallback.ts} — live but slower, and shapes
1476
- * / streams / whispers are dark); `offline` = sockets exist but all are down
1477
- * (between reconnect attempts).
1496
+ * / streams / whispers are dark; mutations go over HTTP as they do while
1497
+ * connected); `offline` = sockets exist but all are down (between reconnect
1498
+ * attempts), and a poll could not reach the origin either.
1478
1499
  *
1479
1500
  * `polling` outranks `connecting`: while the fallback is running a reconnect is
1480
1501
  * still armed in the background, and reporting that attempt would flicker the
@@ -1642,6 +1663,16 @@ interface MutationCallOptions<TCurrent = unknown, TValue = unknown, TArgs = unkn
1642
1663
  * Omit for normal calls.
1643
1664
  */
1644
1665
  replayBaseline?: null | number;
1666
+ /**
1667
+ * The identity stamp a durable write was queued under
1668
+ * ({@link import("./types").OutboxMutation.identity}), handed back by the
1669
+ * sink's replay. Under a cookie session the request then names that user,
1670
+ * and the worker refuses it with `IDENTITY_MISMATCH` when the cookie now
1671
+ * belongs to someone else — the check {@link LunoraClient.replayIdentityVerdict}
1672
+ * cannot make, because the client cannot see the cookie. Omit for normal
1673
+ * calls.
1674
+ */
1675
+ replayIdentity?: null | string;
1645
1676
  shardKey?: string;
1646
1677
  }
1647
1678
  /** Callback a shape subscription invokes with its materialized rowset on every applied poke. */
@@ -1904,6 +1935,25 @@ declare class LunoraClient {
1904
1935
  * the token cannot: is this still the current question?
1905
1936
  */
1906
1937
  private sessionProbeGeneration;
1938
+ /**
1939
+ * Monotonic id of every question this client asks the server about who it
1940
+ * is: each `/get-session` probe, and each cookie-authenticated socket upgrade
1941
+ * (answered by the `identity` frame the shard sends on `connect`). Probes
1942
+ * take their `sessionProbeGeneration` from it, so both kinds of
1943
+ * answer share one order.
1944
+ */
1945
+ private identityQuestions;
1946
+ /**
1947
+ * The `identityQuestions` id of the newest answer adopted so far. A
1948
+ * cookie-session answer older than it describes an older cookie — a socket
1949
+ * upgraded before a probe that has since answered — and may not move the
1950
+ * identity back. See `adoptCookieSubject`.
1951
+ */
1952
+ private identityAnsweredAt;
1953
+ /** Unregisters this client from the page's session-change signal (`shared/session-change.ts`); set in the constructor. */
1954
+ private releaseSessionChangeListener;
1955
+ /** Whether the one-time `IDENTITY_MISMATCH` configuration warning has been logged (see `noteIdentityMismatch`). */
1956
+ private warnedIdentityDisagreement;
1907
1957
  /**
1908
1958
  * Whether anything resolves this client's identity at all.
1909
1959
  *
@@ -1952,6 +2002,8 @@ declare class LunoraClient {
1952
2002
  private readonly statusListeners;
1953
2003
  /** Subscribers notified when the server drops a socket for an expired token (see `onTokenExpired`). */
1954
2004
  private readonly tokenExpiredListeners;
2005
+ /** Subscribers notified when the previous identity's session is retired (see `onIdentityChange`). */
2006
+ private readonly identityChangeListeners;
1955
2007
  /** Token hash the last durable-replay auth refusal fired `onTokenExpired` for — see `shouldRequeueReplayFailure`. */
1956
2008
  private authRefusalNotifiedFor;
1957
2009
  /** Subscribers to offline-queued mutation verdicts (see `onMutationSettled`). */
@@ -2189,6 +2241,27 @@ declare class LunoraClient {
2189
2241
  * the current value.
2190
2242
  */
2191
2243
  onAuthTokenChange(listener: (token: string | null) => void): Unsubscribe;
2244
+ /**
2245
+ * Subscribe to a user switch or sign-out: fired after {@link setAuthToken}
2246
+ * retires the previous identity's session, i.e. every live subscription has
2247
+ * just been blanked to `undefined` and its socket closed. Not fired when a
2248
+ * bearer identity is first established from signed-out (nothing is
2249
+ * retired), nor when a subject is attached to the same credential, nor on
2250
+ * a cookie session's first answer after page load.
2251
+ *
2252
+ * A cookie session fires it on every change the server reports — sign-out,
2253
+ * a sign-in after a known sign-out, a different user — whether the answer
2254
+ * came from {@link getCurrentUser} or from a socket's `identity` frame. Its
2255
+ * sign-out changes nothing the client can observe on its own; in a browser
2256
+ * the client also re-resolves whenever `@lunora/auth-ui` or the
2257
+ * `lunoraSessionSync()` better-auth plugin reports a session change.
2258
+ *
2259
+ * For caches layered on top of the client. A subscriber only hears the
2260
+ * blank for queries it still subscribes to, so a cache that keeps values for
2261
+ * unmounted queries must drop them here, or the next user can read the
2262
+ * previous user's rows from it.
2263
+ */
2264
+ onIdentityChange(listener: () => void): Unsubscribe;
2192
2265
  /**
2193
2266
  * Fetch the currently authenticated user from better-auth's `get-session`
2194
2267
  * endpoint, returning the `user` record or `null` when signed out. Sends
@@ -2213,6 +2286,10 @@ declare class LunoraClient {
2213
2286
  * knows the answer — leaving it to each adapter is what left the sticky-subject
2214
2287
  * contract unreached in every shipped one, so a routine JWT refresh read as a
2215
2288
  * user switch and discarded the user's own queued writes and read cache.
2289
+ *
2290
+ * Under a cookie session (no token held) the answer is adopted whatever it
2291
+ * is, "no session" included, and a change from the identity already known
2292
+ * retires the previous session — see {@link onIdentityChange}.
2216
2293
  */
2217
2294
  getCurrentUser(): Promise<User | null>;
2218
2295
  /**
@@ -2533,16 +2610,24 @@ declare class LunoraClient {
2533
2610
  * {@link LunoraClient.importRows} documents the same caveat for its counts.
2534
2611
  */
2535
2612
  mutation<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>): Promise<ReturnOf<F>>;
2613
+ /**
2614
+ * Whether a {@link mutation} on `shardKey` would be queued (offline queue or
2615
+ * durable outbox) rather than fail when the network is down. `false` before
2616
+ * the shard's first connect (unless `queueBeforeFirstConnect` is set) and on
2617
+ * a client with no WebSocket. A caller that wants to hold a write until the
2618
+ * network returns must do so itself when this is `false`.
2619
+ */
2620
+ canQueueOffline(shardKey?: string): boolean;
2536
2621
  action<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: ActionCallOptions): Promise<ReturnOf<F>>;
2537
2622
  /**
2538
2623
  * Bulk-import `rows` through a mutation that accepts a batch, chunked so a large
2539
2624
  * dataset lands in a bounded number of round-trips.
2540
2625
  *
2541
- * **Offline caveat:** each chunk is sent with {@link LunoraClient.mutation}, which
2542
- * resolves once the write is durably queued rather than once the server has applied
2543
- * it. So an import run while offline resolves `{ chunks, imported }` with nothing
2544
- * committed yet — the counts describe what was *handed over*, and the outbox
2545
- * replays them on reconnect. Don't report "migration complete" on this alone.
2626
+ * **Queued chunks:** `imported` counts only rows the server committed. A chunk a
2627
+ * durable outbox took instead (offline, or behind older queued writes) is counted
2628
+ * in `queued`; the outbox replays it later under the same key. The built-in
2629
+ * offline queue holds the call until each chunk commits, so its rows land in
2630
+ * `imported`. Report "migration complete" only when `queued` is `0`.
2546
2631
  *
2547
2632
  * This is the one-shot migration / seed path: "I have 20k rows client-side and a
2548
2633
  * server mutation that inserts many at once". Doing it by hand goes wrong in two
@@ -2550,7 +2635,8 @@ declare class LunoraClient {
2550
2635
  * wait per row (a 200-row import becomes 200 sequential hops), while a single
2551
2636
  * giant call blows the DO's batch limit. So: chunk, send sequentially, and give
2552
2637
  * each chunk a stable idempotency key derived from `importId` + its index, so a
2553
- * resumed or retried import doesn't double-insert the chunks that already landed.
2638
+ * resumed or retried import doesn't double-insert the chunks that already landed
2639
+ * (for an anonymous caller only while the `clientId` is the same; see `importId`).
2554
2640
  *
2555
2641
  * The server mutation is yours (Lunora can't guess the table or the row shape);
2556
2642
  * back it with `ctx.db.insertMany(...)`, or `insertManyUnsafe(...)` for data you
@@ -2581,15 +2667,18 @@ declare class LunoraClient {
2581
2667
  * SILENTLY DROPS those rows. Keep `chunkSize` (and the row order) identical
2582
2668
  * across resumes of the same `importId`. Omit `importId` only for a
2583
2669
  * throwaway import where double-insertion is acceptable.
2670
+ *
2671
+ * CAVEAT — anonymous callers: the server scopes the key to the signed-in
2672
+ * user, or to the client's `clientId` when nobody is signed in. A
2673
+ * `LunoraClient` mints a fresh `clientId` on every construction, so an
2674
+ * anonymous import resumed after a reload is not deduped and inserts the
2675
+ * landed chunks again. Pass a persisted `clientId` to the constructor to
2676
+ * resume anonymously, or run the import signed in.
2584
2677
  */
2585
2678
  importId?: string;
2586
2679
  /**
2587
- * Called after each chunk is accepted — for a progress bar.
2588
- *
2589
- * "Accepted" is not always "committed": while offline (or mid-reconnect) a
2590
- * `mutation` resolves as soon as the write is durably **queued**, so a fully
2591
- * offline import reports completion with nothing yet applied server-side.
2592
- * Gate a migration's "done" state on connectivity, not just on this.
2680
+ * Called after each chunk is accepted — for a progress bar. `done` counts
2681
+ * committed and queued rows alike; the result separates them.
2593
2682
  */
2594
2683
  onProgress?: (progress: {
2595
2684
  done: number;
@@ -2602,6 +2691,7 @@ declare class LunoraClient {
2602
2691
  }): Promise<{
2603
2692
  chunks: number;
2604
2693
  imported: number;
2694
+ queued: number;
2605
2695
  }>;
2606
2696
  /**
2607
2697
  * Read the cross-shard request distribution for a `.shardBy(...)` table —
@@ -3372,6 +3462,14 @@ declare class LunoraClient {
3372
3462
  * stamp elsewhere in this file) — moved verbatim, not reflowed.
3373
3463
  */
3374
3464
  private createTabCoordinator;
3465
+ /** The body of {@link batch}, optionally abortable (the polling fallback's timeout). */
3466
+ private sendBatch;
3467
+ /**
3468
+ * The body of {@link mutation}, also saying whether the write committed. `committed` is
3469
+ * `false` only when a durable outbox took the write, which resolves at once
3470
+ * with no server result.
3471
+ */
3472
+ private runMutation;
3375
3473
  /**
3376
3474
  * Persist a mutation that can't go out on the wire right now (offline, or
3377
3475
  * mid-reconnect after a prior connect). The optimistic update has already
@@ -3574,8 +3672,24 @@ declare class LunoraClient {
3574
3672
  * than being swallowed: the whole point of the fallback is that the app keeps
3575
3673
  * working, and a query that is now failing (a permission change, a bad arg)
3576
3674
  * has to be visible.
3675
+ *
3676
+ * Resolves `false` when the request never reached the origin (`fetch` itself
3677
+ * rejected), which is how the fallback tells a device with no network from a
3678
+ * network that only refuses the upgrade. Any answer, an error envelope
3679
+ * included, resolves `true`.
3680
+ *
3681
+ * Optimistic layers need the same care without a cursor. The snapshot is taken
3682
+ * after the request is sent, so every write whose RPC had resolved before that
3683
+ * moment is already in it. Those layers are dropped against the acknowledgement
3684
+ * mark sampled before the send. A write acknowledged after the send may be
3685
+ * missing from the snapshot, so its layer stays.
3577
3686
  */
3578
3687
  private pollSubscriptions;
3688
+ /**
3689
+ * Whether a request reaches the origin at all: an empty batch, whose answer
3690
+ * (even a refusal) is the proof. `false` only when `fetch` itself rejects.
3691
+ */
3692
+ private probeOrigin;
3579
3693
  /** Recompute the aggregate status and notify listeners if it changed. */
3580
3694
  private emitConnectionStatus;
3581
3695
  /**
@@ -3619,7 +3733,7 @@ declare class LunoraClient {
3619
3733
  */
3620
3734
  private sendOrQueueUnsubscribe;
3621
3735
  /**
3622
- * The `(wsState, hasSocket, wasEverConnected)` triple `mutation()`'s
3736
+ * The `(wsState, hasSocket, wasEverConnected, polling)` state `mutation()`'s
3623
3737
  * offline-queue gate reads. On the leader/single-tab path this is exactly
3624
3738
  * the real `ShardConnection`'s state (byte-identical to the pre-cross-tab
3625
3739
  * behavior). A follower has no `ShardConnection` of its own (see
@@ -3629,6 +3743,11 @@ declare class LunoraClient {
3629
3743
  * `"connecting"` (the mid-reconnect queue branch), anything else is
3630
3744
  * `"idle"`. `hasSocket` is always `false` for a follower — it never has
3631
3745
  * one.
3746
+ *
3747
+ * `polling` is `true` while the HTTP polling fallback is reaching the origin
3748
+ * (the leader's mirrored `"polling"` on a follower). Writes then go straight
3749
+ * over HTTP, as they do while connected, instead of queueing for a socket
3750
+ * that may never open.
3632
3751
  */
3633
3752
  private connectionGateState;
3634
3753
  private getOrCreateConnection;
@@ -3664,7 +3783,8 @@ declare class LunoraClient {
3664
3783
  * once per socket open, so the server's `onConnect` hooks fire symmetrically
3665
3784
  * with `onDisconnect` (which the DO dispatches unconditionally at close for
3666
3785
  * every lifecycle-aware socket). The DO no-ops cheaply when no `onConnect`
3667
- * hooks are registered, so the single frame costs nothing in the common case.
3786
+ * hooks are registered, and answers with the socket's `identity` frame
3787
+ * (see `handleIdentityFrame`).
3668
3788
  *
3669
3789
  * The shard's registered context (or the client-wide default) rides along
3670
3790
  * when one is set — the DO records it on the attachment for replay to
@@ -3806,6 +3926,10 @@ declare class LunoraClient {
3806
3926
  private resnapshotSubscription;
3807
3927
  /** Materialize a shape's keyed view to an array and invoke its callbacks. */
3808
3928
  private emitShapeRows;
3929
+ /**
3930
+ * Apply one `data` frame. `snapshotMark` is set only for a frame synthesized
3931
+ * by a poll, which carries no cursor: see {@link pollSubscriptions}.
3932
+ */
3809
3933
  private handleDataMessage;
3810
3934
  /**
3811
3935
  * Handle a `resume` frame (Pillar 1b): the server proved nothing the
@@ -4002,19 +4126,78 @@ declare class LunoraClient {
4002
4126
  * Key the offline-queue identity on the resolved user id rather than the
4003
4127
  * token bytes, so the NEXT token refresh keeps the same identity.
4004
4128
  *
4005
- * Only a resolved user labels anything. A 401, a network failure or an
4006
- * empty session resolves `null` for reasons that say nothing about who is
4007
- * signed in, and a token that rotated mid-flight belongs to a session this
4008
- * answer predates — both leave the established label alone rather than
4009
- * clearing it (which would look like an identity change and drop the queue).
4129
+ * Under a bearer token only a resolved user labels anything. A 401 or an
4130
+ * empty session resolves `null` for reasons that say nothing about who holds
4131
+ * the token, so the established label is left alone rather than cleared
4132
+ * (which would look like an identity change and drop the queue). A token
4133
+ * that rotated mid-flight belongs to a session this answer predates and is
4134
+ * ignored for the same reason.
4135
+ *
4136
+ * Under a cookie session (no token) the answer is the identity, `null`
4137
+ * included: nothing on the client changes when the user signs out, so "the
4138
+ * server found no session" is the only sign-out this client will ever see.
4139
+ * See {@link adoptCookieSubject}.
4010
4140
  *
4011
- * A superseded probe is the same case, and the one the token check cannot
4012
- * see: on a cookie app both `requestToken`s are `null`, so a slow answer
4013
- * from an OLDER probe passed that check and overwrote the subject a newer
4014
- * one had already established. `requestGeneration` is the question's id —
4015
- * it must still be the current one at the moment of the write.
4141
+ * A superseded probe is ignored too, and the token check cannot see it: on a
4142
+ * cookie app both `requestToken`s are `null`, so a slow answer from an OLDER
4143
+ * probe passed that check and overwrote the subject a newer one had already
4144
+ * established. `requestGeneration` is the question's id — it must still be
4145
+ * the current one at the moment of the write.
4016
4146
  */
4017
4147
  private adoptResolvedSubject;
4148
+ /**
4149
+ * Adopt the user a cookie session resolved to — `null` for nobody — as this
4150
+ * client's identity, from either source the server answers through: a
4151
+ * `/get-session` probe, or a socket's `identity` frame.
4152
+ *
4153
+ * Three states, told apart by `authSubject`: `undefined` is not known yet
4154
+ * (every page load until the first answer), `null` is known to be nobody,
4155
+ * and a string is that user. {@link setAuthToken} retires the previous
4156
+ * session on every change between two KNOWN states — `A → nobody`,
4157
+ * `nobody → B`, `A → B` — and on none out of `undefined`, so the first
4158
+ * answer after a page load never evicts what the page is already showing
4159
+ * for the session it was loaded under.
4160
+ *
4161
+ * `question` orders the answers: one older than the answer in force
4162
+ * describes an older cookie and is dropped. Returns whether it was adopted.
4163
+ */
4164
+ private adoptCookieSubject;
4165
+ /**
4166
+ * A cookie socket's `identity` frame: the user the shard authenticated this
4167
+ * socket as, sent in reply to every `connect`.
4168
+ *
4169
+ * Adopted like a probe's answer when it is the newest one
4170
+ * ({@link adoptCookieSubject}). An OLDER one that disagrees with the
4171
+ * identity in force means this socket was upgraded on a cookie that has
4172
+ * since changed hands, and is still delivering that user's rows: the
4173
+ * session it belongs to is retired, which replaces the socket.
4174
+ *
4175
+ * Ignored for a socket that authenticated with a token rather than the
4176
+ * cookie (see `ShardConnection.identityQuestion`) — its answer is about that
4177
+ * token, not about the cookie session this client's identity tracks.
4178
+ */
4179
+ private handleIdentityFrame;
4180
+ /**
4181
+ * The subject half of {@link setAuthToken}.
4182
+ *
4183
+ * Sticky: only an explicit value (incl. `null` = signed out) changes the
4184
+ * subject; omitting it keeps the established one. Two exceptions, both about
4185
+ * a subject that must not outlive the credential it described:
4186
+ *
4187
+ * - Clearing the token clears a user subject — see `setAuthToken`'s
4188
+ * docblock. A `null` one stays: a cookie session known to be nobody's is
4189
+ * still nobody's.
4190
+ * - A token arriving on a session known to be nobody's drops the `null`,
4191
+ * so the identity falls back to the token itself rather than staying
4192
+ * "nobody" while a credential is held.
4193
+ */
4194
+ private applySubject;
4195
+ /**
4196
+ * A cookie session the server has answered "nobody" for — see
4197
+ * {@link adoptCookieSubject}. Distinct from a subject not known YET
4198
+ * (`undefined`).
4199
+ */
4200
+ private isKnownNobody;
4018
4201
  /**
4019
4202
  * True while an established subject labels a credential it was never checked
4020
4203
  * against — see the `subjectToken` field. Every replay verdict is
@@ -4301,6 +4484,30 @@ declare class LunoraClient {
4301
4484
  * answer.
4302
4485
  */
4303
4486
  private shouldRequeueReplayFailure;
4487
+ /**
4488
+ * The `expectSubject` a replayed write carries: the user its identity stamp
4489
+ * names, so the worker refuses it when the request resolves to anyone else.
4490
+ *
4491
+ * The replay gate compares the stamp with what this client believes, and
4492
+ * under a cookie session that belief can be stale — a sign-out and another
4493
+ * user's sign-in change no token, and the socket's `open` flush runs before
4494
+ * its `identity` frame lands. Only the server sees the cookie the write is
4495
+ * about to ride, so it is the server that has to check.
4496
+ *
4497
+ * Sent on every replay made without a bearer token, whatever resolved the
4498
+ * stamp — a socket's `identity` frame labels the session in apps that never
4499
+ * call `getCurrentUser()` too. An app without auth stamps `null` and its
4500
+ * requests resolve to nobody, so the header always passes there. A bearer
4501
+ * request states its credential explicitly and the gate already matched it;
4502
+ * a token-hash or missing stamp names no user.
4503
+ */
4504
+ private replayExpectation;
4505
+ /**
4506
+ * Whether `failure` is the worker's `IDENTITY_MISMATCH` refusal, and if so,
4507
+ * ask who is signed in now: the answer is what re-runs the replay gate
4508
+ * ({@link setAuthToken}), and nothing else would on a cookie session.
4509
+ */
4510
+ private noteIdentityMismatch;
4304
4511
  /** Settle a write the server reached a coded verdict on: replaying would re-trigger the same failure (a poison-message loop), so drop it. */
4305
4512
  private settleReplayTerminal;
4306
4513
  /**
@@ -1,4 +1,4 @@
1
- import { L as LunoraClient, P as Preloaded } from "./lunora-client.d-BiwicHKh.js";
1
+ import { L as LunoraClient, P as Preloaded } from "./lunora-client.d-DlMXZMfR.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-Cqp1YHAJ.mjs";
1
+ import { L as LunoraClient, P as Preloaded } from "./lunora-client.d-CxU0UvII.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 w=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),i=new Uint8Array(t.length);for(let o=0;o<t.length;o+=1)i[o]=t.codePointAt(o)??0;return i},b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",h=e=>{let t="",i=0;const o=e.length-2;for(;i<o;i+=3){const r=e[i]<<16|e[i+1]<<8|e[i+2];t+=b.charAt(r>>18&63)+b.charAt(r>>12&63)+b.charAt(r>>6&63)+b.charAt(r&63)}const a=e.length-i;if(a===1){const r=e[i]<<16;t+=b.charAt(r>>18&63)+b.charAt(r>>12&63)}else if(a===2){const r=e[i]<<16|e[i+1]<<8;t+=b.charAt(r>>18&63)+b.charAt(r>>12&63)+b.charAt(r>>6&63)}return t},c="$lunora.wire$";const g="__proto__",A={BigInt64Array,BigUint64Array,Float32Array,Float64Array,Int8Array,Int16Array,Int32Array,Uint8Array,Uint8ClampedArray,Uint16Array,Uint32Array},l={Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError},E=e=>{if(e===null||typeof e!="object")return!1;const t=Object.getPrototypeOf(e);return t===null||t===Object.prototype},d=(e,t=0)=>{if(t>64)throw new RangeError("wire-codec: value nesting exceeds the 64-level limit");if(e===void 0)return[c,"undefined"];if(e===null)return null;const i=typeof e;if(i==="bigint")return[c,"bigint",e.toString()];if(i==="number"){const r=e;return Number.isNaN(r)?[c,"nan"]:r===1/0?[c,"inf"]:r===-1/0?[c,"-inf"]:r}if(i!=="object")return e;if(e instanceof Date)return[c,"date",d(e.getTime(),t+1)];if(e instanceof Error){const r=e,n={};for(const s of Object.keys(r)){if(r[s]===void 0)continue;const y=d(r[s],t+1);s===g?Object.defineProperty(n,s,{configurable:!0,enumerable:!0,value:y,writable:!0}):n[s]=y}const f=[c,"error",String(r.name),String(r.message),n];return r.cause!==void 0&&f.push(d(r.cause,t+1)),f}if(e instanceof URL)return[c,"url",e.href];if(e instanceof Map)return[c,"map",[...e.entries()].map(([r,n])=>[d(r,t+1),d(n,t+1)])];if(e instanceof Set)return[c,"set",[...e].map(r=>d(r,t+1))];if(e instanceof ArrayBuffer)return[c,"bytes",w(new Uint8Array(e)),"ArrayBuffer"];if(ArrayBuffer.isView(e)){const r=e,n=r.constructor.name,f=new Uint8Array(r.buffer,r.byteOffset,r.byteLength);return n==="Uint8Array"?[c,"bytes",w(f)]:[c,"bytes",w(f),n]}if(Array.isArray(e)){const r=e.map(n=>d(n,t+1));return r.length>0&&r[0]===c?[c,"arr",r]:r}if(!E(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,a={};for(const r of Object.keys(o)){const n=o[r];if(n===void 0)continue;const f=d(n,t+1);r===g?Object.defineProperty(a,r,{configurable:!0,enumerable:!0,value:f,writable:!0}):a[r]=f}return a},u=(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]===c)switch(e[1]){case"-inf":return-1/0;case"arr":return e[2].map(r=>u(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=u(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[u(n[0],t+1),u(n[1],t+1)]}))}case"set":return new Set(e[2].map(r=>u(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];if(typeof r!="string"||typeof n!="string")throw new TypeError("wire-codec: malformed error — name and message must be strings");const f=(Object.hasOwn(l,r)?l[r]:void 0)??Error,s=new f(n);s.name!==r&&Object.defineProperty(s,"name",{configurable:!0,value:r,writable:!0});const y=u(e[4],t+1);if(y===null||typeof y!="object"||Array.isArray(y))throw new TypeError("wire-codec: malformed error — props must be an object");for(const m of Object.keys(y))m===g?Object.defineProperty(s,m,{configurable:!0,enumerable:!0,value:y[m],writable:!0}):s[m]=y[m];return e.length>5&&Object.defineProperty(s,"cause",{configurable:!0,value:u(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);if(w(n)!==r)throw new TypeError("wire-codec: malformed bytes — payload must be canonical padded base64");const f=e[3]??"Uint8Array";if(f==="ArrayBuffer")return n.buffer.byteLength===n.byteLength?n.buffer:n.slice().buffer;const s=Object.hasOwn(A,f)?A[f]: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=>u(r,t+1))}return e.map(a=>u(a,t+1))}const i=e,o={};for(const a of Object.keys(i)){const r=u(i[a],t+1);a===g?Object.defineProperty(o,a,{configurable:!0,enumerable:!0,value:r,writable:!0}):o[a]=r}return o},T=(e,t,i)=>{try{return d(i)}catch(o){throw new TypeError(`${e}: cannot encode args for '${t}' — ${o instanceof Error?o.message:String(o)}`,o instanceof Error?{cause:o}:void 0)}};export{T as a,u as d,d as e,E as i,h as t};
@@ -1 +1 @@
1
- import{e as a}from"./wire-codec-Sw1rAu-Z.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-BeIi1K-T.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,4 +1,4 @@
1
- import { S as SubscriptionError, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-Cqp1YHAJ.mjs";
1
+ import { S as SubscriptionError, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-CxU0UvII.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-Cqp1YHAJ.mjs";
13
+ b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-CxU0UvII.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';
@@ -1,4 +1,4 @@
1
- import { S as SubscriptionError, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-BiwicHKh.js";
1
+ import { S as SubscriptionError, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-DlMXZMfR.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-BiwicHKh.js";
13
+ b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-DlMXZMfR.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 i}from"@lunora/errors";import{a as p,d}from"./packem_shared/wire-codec-Sw1rAu-Z.mjs";import{e as w}from"./packem_shared/replay-DYdPvn3o.mjs";const v="/_lunora/rpc",N="https://lunora.internal",$=e=>{const t=new Error(e.message??"request failed");return t.code=e.code,e.data!==void 0&&(t.data=d(e.data)),e.hint!==void 0&&(t.hint=e.hint),e.docsUrl!==void 0&&(t.docsUrl=e.docsUrl),t},c=async(e,t,n,s,u)=>{const o=n.__lunoraRef,f=JSON.stringify({args:p("@lunora/client/service",o,s??{}),functionPath:o,...u?.shardKey===void 0?{}:{shardKey:u.shardKey}}),a=await e.fetch(new Request(`${N}${v}`,{body:f,headers:{"content-type":"application/json"},method:"POST"}));let r;try{r=await a.json()}catch{throw new i("INTERNAL",`@lunora/client/service: ${t} "${o}" got a non-JSON response (status ${a.status.toString()}). Check the service binding points at the Lunora Worker that declares this function.`,{status:a.status})}if(r===null||typeof r!="object")throw new i("INTERNAL",`@lunora/client/service: ${t} "${o}" returned a JSON body that is not an object (status ${a.status.toString()}). Check the service binding points at the Lunora Worker that declares this function.`,{status:a.status});const l=r,h=w(l);if(h!==void 0)throw $(h);if(!a.ok)throw new i("INTERNAL",`@lunora/client/service: ${t} "${o}" failed with status ${a.status.toString()} and no error envelope.`,{status:a.status});return d(l.result)},E=e=>({action:async(t,n,s)=>await c(e,"action",t,n,s),mutation:async(t,n,s)=>await c(e,"mutation",t,n,s),query:async(t,n,s)=>await c(e,"query",t,n,s)});export{v as RPC_PATH,E as createServiceClient};
1
+ import{LunoraError as i}from"@lunora/errors";import{a as p,d}from"./packem_shared/wire-codec-BeIi1K-T.mjs";import{e as w}from"./packem_shared/replay-DYdPvn3o.mjs";const v="/_lunora/rpc",N="https://lunora.internal",$=e=>{const t=new Error(e.message??"request failed");return t.code=e.code,e.data!==void 0&&(t.data=d(e.data)),e.hint!==void 0&&(t.hint=e.hint),e.docsUrl!==void 0&&(t.docsUrl=e.docsUrl),t},c=async(e,t,n,s,u)=>{const o=n.__lunoraRef,f=JSON.stringify({args:p("@lunora/client/service",o,s??{}),functionPath:o,...u?.shardKey===void 0?{}:{shardKey:u.shardKey}}),a=await e.fetch(new Request(`${N}${v}`,{body:f,headers:{"content-type":"application/json"},method:"POST"}));let r;try{r=await a.json()}catch{throw new i("INTERNAL",`@lunora/client/service: ${t} "${o}" got a non-JSON response (status ${a.status.toString()}). Check the service binding points at the Lunora Worker that declares this function.`,{status:a.status})}if(r===null||typeof r!="object")throw new i("INTERNAL",`@lunora/client/service: ${t} "${o}" returned a JSON body that is not an object (status ${a.status.toString()}). Check the service binding points at the Lunora Worker that declares this function.`,{status:a.status});const l=r,h=w(l);if(h!==void 0)throw $(h);if(!a.ok)throw new i("INTERNAL",`@lunora/client/service: ${t} "${o}" failed with status ${a.status.toString()} and no error envelope.`,{status:a.status});return d(l.result)},E=e=>({action:async(t,n,s)=>await c(e,"action",t,n,s),mutation:async(t,n,s)=>await c(e,"mutation",t,n,s),query:async(t,n,s)=>await c(e,"query",t,n,s)});export{v as RPC_PATH,E as createServiceClient};
@@ -1,4 +1,4 @@
1
- import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-Cqp1YHAJ.mjs";
1
+ import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-CxU0UvII.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-91KD5ae3.mjs";
23
+ a as preloadedQueryResult } from "../packem_shared/preload.d-C3Nbhx4m.mjs";
24
24
  import '@lunora/errors';
25
25
  import '@lunora/runtime';
26
26
  /**
@@ -1,4 +1,4 @@
1
- import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-BiwicHKh.js";
1
+ import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-DlMXZMfR.js";
2
2
  export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/function-reference.d-Br_hsKje.js";
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-DE--vVCE.js";
23
+ a as preloadedQueryResult } from "../packem_shared/preload.d-BNFe_NYM.js";
24
24
  import '@lunora/errors';
25
25
  import '@lunora/runtime';
26
26
  /**
@@ -1 +1 @@
1
- import{getServerSession as o}from"../packem_shared/getServerSession-BaOoo1p6.mjs";import{deserializePreloaded as l,serializePreloaded as t}from"../packem_shared/deserializePreloaded-C6NTQdFa.mjs";import{createServerClient as i}from"../packem_shared/createServerClient-CzDIXbC4.mjs";import{preloadQuery as s,preloadedQueryResult as f}from"../packem_shared/preloadQuery-uFy24PCR.mjs";export{i as createServerClient,l as deserializePreloaded,o as getServerSession,s as preloadQuery,f as preloadedQueryResult,t as serializePreloaded};
1
+ import{getServerSession as o}from"../packem_shared/getServerSession-BaOoo1p6.mjs";import{deserializePreloaded as l,serializePreloaded as t}from"../packem_shared/deserializePreloaded-Bffy70Wm.mjs";import{createServerClient as i}from"../packem_shared/createServerClient-DqNe__QI.mjs";import{preloadQuery as s,preloadedQueryResult as f}from"../packem_shared/preloadQuery-uFy24PCR.mjs";export{i as createServerClient,l as deserializePreloaded,o as getServerSession,s as preloadQuery,f as preloadedQueryResult,t as serializePreloaded};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/client",
3
- "version": "1.0.0-alpha.137",
3
+ "version": "1.0.0-alpha.139",
4
4
  "description": "Lunora browser SDK: WebSocket transport, optimistic updates, and an offline mutation queue",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -70,7 +70,7 @@
70
70
  "access": "public"
71
71
  },
72
72
  "dependencies": {
73
- "@lunora/errors": "1.0.0-alpha.41",
73
+ "@lunora/errors": "1.0.0-alpha.43",
74
74
  "@visulima/storage-client": "1.0.3"
75
75
  },
76
76
  "peerDependencies": {