@lunora/client 1.0.0-alpha.35 → 1.0.0-alpha.36

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.
@@ -0,0 +1 @@
1
+ let d=0;const n="lunora-bridge",o=1e3,h=3e3;class c{bc;tabId;heartbeatInterval;leaderTimeout;knownLeader=void 0;leader=!1;running=!1;promotionPending=!1;lastHeartbeat=0;heartbeatTimer=void 0;leaderCheckTimer=void 0;onBecomeLeader;onStopBeingLeader;onConnectionStatus;onSubscriptionData;onSubscriptionError;onSubscriptionSettled;constructor(e={}){if(d+=1,this.tabId=`tab_${String(d)}_${crypto.randomUUID()}`,this.heartbeatInterval=e.heartbeatInterval??o,this.leaderTimeout=e.leaderTimeout??h,this.onBecomeLeader=e.onBecomeLeader,this.onStopBeingLeader=e.onStopBeingLeader,this.onConnectionStatus=e.onConnectionStatus,this.onSubscriptionData=e.onSubscriptionData,this.onSubscriptionError=e.onSubscriptionError,this.onSubscriptionSettled=e.onSubscriptionSettled,typeof BroadcastChannel>"u"){this.bc=void 0;return}this.bc=new BroadcastChannel(e.channelName??n),this.bc.addEventListener("message",t=>{this.handleMessage(t.data)})}start(){if(!this.running){if(this.running=!0,this.bc===void 0){this.becomeLeader();return}this.broadcast({type:"claim-leadership",tabId:this.tabId,ts:Date.now()}),setTimeout(()=>{this.running&&this.knownLeader===void 0&&this.becomeLeader()},this.leaderTimeout),this.leaderCheckTimer=setInterval(()=>{this.checkLeaderHealth()},this.leaderTimeout),this.heartbeatTimer=setInterval(()=>{this.sendHeartbeat()},this.heartbeatInterval)}}stop(){this.running=!1,this.leader&&(this.broadcast({type:"yield-leadership",tabId:this.tabId}),this.leader=!1,this.knownLeader=void 0,this.onStopBeingLeader?.()),this.heartbeatTimer!==void 0&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=void 0),this.leaderCheckTimer!==void 0&&(clearInterval(this.leaderCheckTimer),this.leaderCheckTimer=void 0),this.bc?.close()}isLeader(){return this.leader}promoteImmediately(){this.running&&this.becomeLeader()}get leaderTabId(){return this.knownLeader}get id(){return this.tabId}get isRunning(){return this.running}broadcastSubscriptionData(e,t,a,i,s){this.leader&&this.broadcast({type:"subscription-data",tabId:this.tabId,key:e,data:t,...a===void 0?{}:{cursor:a},...i===void 0?{}:{epoch:i},...s===void 0?{}:{identity:s}})}broadcastSubscriptionError(e,t){this.leader&&this.broadcast({type:"subscription-error",tabId:this.tabId,key:e,error:t})}broadcastSubscriptionSettled(e,t,a,i,s,r){this.leader&&this.broadcast({type:"subscription-settled",tabId:this.tabId,key:e,...t===void 0?{}:{cursor:t},...a===void 0?{}:{epoch:a},...i===void 0?{}:{lastMutationId:i},...s===void 0?{}:{clientId:s},...r===void 0?{}:{identity:r}})}broadcastConnectionStatus(e,t){this.leader&&this.broadcast({type:"connection-status",tabId:this.tabId,status:e,...t===void 0?{}:{identity:t}})}broadcast(e){this.bc?.postMessage(e)}handleMessage(e){switch(e.type){case"claim-leadership":{this.handleClaimLeadership(e);break}case"connection-status":{if(this.leader||e.tabId===this.tabId)break;this.onConnectionStatus?.(e.status,e.identity);break}case"heartbeat":{this.handleHeartbeat(e);break}case"subscription-data":{if(this.leader||e.tabId===this.tabId)break;this.onSubscriptionData?.(e.key,e.data,e.cursor,e.epoch,e.identity);break}case"subscription-error":{if(this.leader||e.tabId===this.tabId)break;this.onSubscriptionError?.(e.key,e.error);break}case"subscription-settled":{if(this.leader||e.tabId===this.tabId)break;this.onSubscriptionSettled?.(e.key,e.cursor,e.epoch,e.lastMutationId,e.clientId,e.identity);break}case"yield-leadership":{this.knownLeader===e.tabId&&(this.knownLeader=void 0,this.running&&!this.leader&&this.claimAndPromote());break}}}handleClaimLeadership(e){if(e.tabId!==this.tabId){if(this.leader){this.broadcast({type:"heartbeat",tabId:this.tabId,ts:Date.now()});return}this.knownLeader===void 0&&e.tabId<this.tabId&&(this.knownLeader=e.tabId,this.lastHeartbeat=e.ts)}}handleHeartbeat(e){if(e.tabId!==this.tabId){if(this.leader){this.resolveLeaderVsLeaderTieBreak(e);return}this.lastHeartbeat=e.ts,this.knownLeader=e.tabId}}resolveLeaderVsLeaderTieBreak(e){e.tabId<this.tabId?(this.leader=!1,this.knownLeader=e.tabId,this.lastHeartbeat=e.ts,this.onStopBeingLeader?.()):this.broadcast({type:"heartbeat",tabId:this.tabId,ts:Date.now()})}becomeLeader(){this.leader||(this.leader=!0,this.knownLeader=this.tabId,this.lastHeartbeat=Date.now(),this.onBecomeLeader?.(),this.broadcast({type:"heartbeat",tabId:this.tabId,ts:Date.now()}))}sendHeartbeat(){this.leader&&this.broadcast({type:"heartbeat",tabId:this.tabId,ts:Date.now()})}checkLeaderHealth(){if(!(!this.running||this.leader)){if(this.knownLeader===void 0){this.claimAndPromote();return}Date.now()-this.lastHeartbeat>this.leaderTimeout&&(this.knownLeader=void 0,this.claimAndPromote())}}claimAndPromote(){this.promotionPending||(this.promotionPending=!0,this.broadcast({type:"claim-leadership",tabId:this.tabId,ts:Date.now()}),setTimeout(()=>{this.promotionPending=!1,this.running&&this.knownLeader===void 0&&this.becomeLeader()},this.leaderTimeout))}}export{c as TabCoordinator};
@@ -1 +1 @@
1
- import{LunoraClient as r}from"./LunoraClient-DRo2LNe9.mjs";const o=t=>{const e=new r({fetch:t.fetch,url:t.url});return t.token!==void 0&&e.setAuthToken(t.token),e};export{o as createServerClient};
1
+ import{LunoraClient as r}from"./LunoraClient-NUIJBf6Z.mjs";const o=t=>{const e=new r({fetch:t.fetch,url:t.url});return t.token!==void 0&&e.setAuthToken(t.token),e};export{o as createServerClient};
@@ -1492,6 +1492,24 @@ declare class LunoraClient {
1492
1492
  * Not `readonly` — `close()` clears it (mirrors `outboxLeaderRelease`).
1493
1493
  */
1494
1494
  private tabCoordinator;
1495
+ /**
1496
+ * The leader's last-broadcast aggregate {@link ConnectionStatus}, mirrored
1497
+ * on a follower tab — which owns no `ShardConnection` of its own to
1498
+ * compute a status from (see `computeStatus`). `undefined` until the
1499
+ * leader's first broadcast (falls back to `"idle"`), and reset back to
1500
+ * `undefined` whenever this tab stops being a follower of the CURRENT
1501
+ * leader (becomes leader itself, or the leader changes), so a stale
1502
+ * mirror from a previous leader never survives a leadership change.
1503
+ */
1504
+ private leaderStatus;
1505
+ /**
1506
+ * Sticky "has the mirrored leader status ever reported `connected`" flag —
1507
+ * the follower's counterpart to {@link ShardConnection.wasEverConnected},
1508
+ * since a follower has no `ShardConnection` of its own. Feeds the
1509
+ * offline-queue gate (see `mutation`) exactly like the real per-shard flag
1510
+ * does on the leader/single-tab path.
1511
+ */
1512
+ private leaderWasEverConnected;
1495
1513
  /** One {@link ShardConnection} per shard key (keyed by `shardKey ?? ""`). */
1496
1514
  private readonly connections;
1497
1515
  /** Default `connect`-envelope context applied to a shard with no explicit override. */
@@ -1534,6 +1552,17 @@ declare class LunoraClient {
1534
1552
  * See `identityFingerprint` for the fingerprint shape.
1535
1553
  */
1536
1554
  private readonly queuedIdentities;
1555
+ /**
1556
+ * Distinct shard keys with a mutation currently sitting in `offlineQueue`
1557
+ * — fresh writes queued this session (`enqueueOfflineMutation`) or writes
1558
+ * restored from durable storage (`hydratePersistedQueue`). A follower tab
1559
+ * has no per-shard `ShardConnection` to iterate when its mirrored leader
1560
+ * status turns `"connected"` (see the `onConnectionStatus` coordinator
1561
+ * option), so this is what that flush walks instead. Entries are never
1562
+ * removed — flushing an already-empty shard is a cheap no-op, and the set
1563
+ * is bounded by the app's own distinct shard-key cardinality.
1564
+ */
1565
+ private readonly queuedOfflineShardKeys;
1537
1566
  private closed;
1538
1567
  /** Subscribers to auth-token changes (see `onAuthTokenChange`). */
1539
1568
  private readonly authTokenListeners;
@@ -2624,6 +2653,25 @@ declare class LunoraClient {
2624
2653
  signal?: AbortSignal;
2625
2654
  }): StreamIterable<HttpStreamChunkOf<Ref>>;
2626
2655
  close(): void;
2656
+ /**
2657
+ * Tear down one {@link ShardConnection}'s live state: clear its reconnect/
2658
+ * connect timers, stop its heartbeat, and close its socket (if any).
2659
+ * Shared by `close()` (terminal) and the cross-tab `onStopBeingLeader`
2660
+ * handler (demoted, but still alive) so a demoted leader can't leak a
2661
+ * pending `reconnectTimer` or an open socket's `heartbeatTimer` the way
2662
+ * an inline `conn.socket?.close()` — which skips both — used to.
2663
+ */
2664
+ private teardownConnection;
2665
+ /**
2666
+ * Build (but do not start) this client's `TabCoordinator`. Extracted out of
2667
+ * the constructor so `setAuthToken` can rebuild it on an identity change —
2668
+ * the default channel name embeds the identity fingerprint (see below), so
2669
+ * a new identity needs a new coordinator on a new channel. The callback
2670
+ * bodies are the drift-sensitive region (a hand-merged identity guard on
2671
+ * the shard message listener sits ahead of an extracted `lastFrameAt`
2672
+ * stamp elsewhere in this file) — moved verbatim, not reflowed.
2673
+ */
2674
+ private createTabCoordinator;
2627
2675
  /**
2628
2676
  * Persist a mutation that can't go out on the wire right now (offline, or
2629
2677
  * mid-reconnect after a prior connect). The optimistic update has already
@@ -2717,6 +2765,19 @@ declare class LunoraClient {
2717
2765
  */
2718
2766
  private applyOptimisticUpdate;
2719
2767
  private getConnection;
2768
+ /**
2769
+ * The `(wsState, hasSocket, wasEverConnected)` triple `mutation()`'s
2770
+ * offline-queue gate reads. On the leader/single-tab path this is exactly
2771
+ * the real `ShardConnection`'s state (byte-identical to the pre-cross-tab
2772
+ * behavior). A follower has no `ShardConnection` of its own (see
2773
+ * `ensureSocket`), so it derives the same triple from the mirrored
2774
+ * `leaderStatus`/`leaderWasEverConnected` instead: `"connected"` maps to
2775
+ * `"open"` (queue-eligible once `wasEverConnected`), `"connecting"` stays
2776
+ * `"connecting"` (the mid-reconnect queue branch), anything else is
2777
+ * `"idle"`. `hasSocket` is always `false` for a follower — it never has
2778
+ * one.
2779
+ */
2780
+ private connectionGateState;
2720
2781
  private getOrCreateConnection;
2721
2782
  private wsUrlFor;
2722
2783
  /**
@@ -2778,15 +2839,46 @@ declare class LunoraClient {
2778
2839
  * a silent tokenless socket the admin gate would reject.
2779
2840
  */
2780
2841
  private openSocketWithProvidedToken;
2842
+ /**
2843
+ * Construct one WebSocket connection attempt and wire the shared
2844
+ * lifecycle guarantees around it — the fail-fast connect-timeout, the
2845
+ * identity guard that stops a superseded attempt's late `open`/`message`/
2846
+ * `close`/`error` from touching a connection a newer attempt already
2847
+ * owns, and (once open) the keepalive heartbeat with its half-open
2848
+ * watchdog (plan 217). One call opens ONE attempt; the caller owns
2849
+ * reconnect scheduling from `onClose` — mirrors the shard's existing
2850
+ * `ensureSocket` / `handleDisconnect` split, now shared with
2851
+ * `subscribeScheduledJobs` so it stops re-living the bug that split
2852
+ * already fixed once (CLIENT-05).
2853
+ *
2854
+ * The identity guard is `conn.socket !== socket`, re-checked before every
2855
+ * action below. `conn.socket` is reassigned to a new attempt's socket
2856
+ * synchronously — right here, before `open` ever fires — so an older
2857
+ * attempt's guard trips the instant it's superseded, even if its
2858
+ * underlying socket only fires its real `close`/`error` much later. This
2859
+ * ordering is load-bearing: preserve it exactly.
2860
+ */
2861
+ private openManagedSocket;
2781
2862
  /** Construct the shard socket and wire its lifecycle handlers. The connection must already be in the `connecting` state. */
2782
2863
  private openSocket;
2783
2864
  private handleDisconnect;
2784
2865
  /**
2785
- * Begin the keepalive heartbeat on an open connection. Each tick sends a
2786
- * {@link WS_KEEPALIVE_PING} text frame the server answers from its
2787
- * hibernation auto-response without waking the DO. A no-op when the
2788
- * heartbeat is disabled (an interval of zero or less); idempotent — any
2789
- * existing timer is cleared first so a reconnect can't leak intervals.
2866
+ * Begin the keepalive heartbeat on an open connection attempt the only
2867
+ * caller is {@link openManagedSocket}'s own `open` handler, so both the
2868
+ * shard socket and `subscribeScheduledJobs` share this one implementation
2869
+ * instead of each hand-rolling their own (plan 217, generalized).
2870
+ *
2871
+ * Each tick first checks the half-open watchdog (see
2872
+ * {@link ManagedSocketState.lastFrameAt}): if no frame at all has arrived
2873
+ * within `heartbeatIntervalMs * 2.5`, the far end has gone quiet without
2874
+ * the socket ever firing `close` — force it closed and report it through
2875
+ * `onWatchdogTrip` (the caller's `onClose`) so the normal reconnect/backoff
2876
+ * takes over instead of every live query on it silently staling forever.
2877
+ * Otherwise it sends a {@link WS_KEEPALIVE_PING} text frame the server
2878
+ * answers from its hibernation auto-response without waking the DO. A
2879
+ * no-op when the heartbeat is disabled (an interval of zero or less);
2880
+ * idempotent — any existing timer is cleared first so a reconnect can't
2881
+ * leak intervals.
2790
2882
  */
2791
2883
  private startHeartbeat;
2792
2884
  /** Clear a connection's keepalive timer, if any. Safe to call repeatedly. */
@@ -2848,6 +2940,23 @@ declare class LunoraClient {
2848
2940
  private dispatchWhisper;
2849
2941
  /** Notify every {@link onTokenExpired} listener (best-effort, listener throws swallowed). */
2850
2942
  private notifyTokenExpired;
2943
+ /**
2944
+ * CLIENT-04: `type: "complete"` today is sent ONLY by `@lunora/do`'s
2945
+ * `handleStream` (see `shard-do.ts`), gated to the `stream` envelope type
2946
+ * and minting only `stream_*` ids — the `subscribe` path never sends it, so
2947
+ * a live SUBSCRIPTION provably never receives `complete` from the current
2948
+ * server. But `ServerCompleteMessage` is a generic `id`-keyed frame and
2949
+ * `ShardDO` is user-subclassable, so this stays defensive rather than
2950
+ * assuming a `sub_*` id can never reach here: unlike the historical
2951
+ * `subscriptions.remove(state)`, which dropped the state out of
2952
+ * `subscriptions.all()` — the set the reconnect resubscribe loop walks
2953
+ * (`ensureSocket`'s `open` handler) — and so froze the query forever across
2954
+ * every future reconnect, this fans a cancellation error to any listener
2955
+ * and marks the registration un-acked instead. Non-destructive: the state
2956
+ * stays in the registry, so the very next reconnect resubscribes it. The
2957
+ * two id-spaces don't overlap (`sub_*` vs `stream_*`), so the stream and
2958
+ * subscription lookups below are mutually exclusive.
2959
+ */
2851
2960
  private handleCompleteMessage;
2852
2961
  private unpersist;
2853
2962
  /**
@@ -2910,6 +3019,18 @@ declare class LunoraClient {
2910
3019
  * the durable `clear()` is best-effort.
2911
3020
  */
2912
3021
  private clearQueryCacheForIdentityChange;
3022
+ /**
3023
+ * Flush every shard with a mutation currently queued in `offlineQueue`
3024
+ * (see `queuedOfflineShardKeys`). Used on a FOLLOWER tab when the
3025
+ * mirrored leader status transitions to `"connected"` — a follower has no
3026
+ * per-shard `ShardConnection` reconnect event to hang the usual
3027
+ * single-shard `flushOfflineQueue(shardKey)` call off of (see the
3028
+ * `handleConnect` call site), so this walks every shard that might have
3029
+ * something queued instead. Flushing an already-empty shard is a cheap
3030
+ * no-op (`flushOfflineQueue` returns immediately once `drain` yields
3031
+ * nothing), so over-inclusion here is harmless.
3032
+ */
3033
+ private flushAllOfflineQueues;
2913
3034
  private flushOfflineQueue;
2914
3035
  /**
2915
3036
  * Partition already-gated writes into the encodable ones (returned) and reject
@@ -2984,4 +3105,4 @@ declare class LunoraClient {
2984
3105
  */
2985
3106
  private settleReplayBatchSlots;
2986
3107
  }
2987
- export { SchedulerStatus as $, ArgsOf as A, BookmarkStorage as B, CONFLICT_ERROR_CODE as C, DEFAULT_MAX_BUFFER as D, LunoraErrorCode as E, FunctionReference as F, GlobalFacetResult as G, HttpStreamRef as H, MutationSettledEvent as I, OptimisticLocalStore as J, OptimisticUpdate as K, LunoraClient as L, MutationCallOptions as M, OutboxMutation as N, OfflineQueueOptions as O, Preloaded as P, QueryCacheAdapter as Q, ReturnOf as R, SubscriptionError as S, OutboxSink as T, User as U, PersistedMutation as V, RowOp as W, RpcEnvelope as X, RpcResponseBody as Y, ScheduleRecord as Z, SchedulerPoolStatus as _, Unsubscribe as a, ServerMessage as a0, ServerPokeEndMessage as a1, ServerPokePartMessage as a2, ServerPokeStartMessage as a3, ShardTrafficEntry as a4, ShardTrafficResult as a5, StorageListPage as a6, StorageObject as a7, StreamHandle as a8, SubscriptionCallback as a9, SubscriptionRegistry as aa, SubscriptionState as ab, SyncWatermark as ac, WorkflowInstanceAction as ad, WorkflowInstanceDetail as ae, WorkflowInstancePage as af, WorkflowInstanceStatus as ag, WorkflowInstanceSummary as ah, WorkflowStepDetail as ai, WsTokenProvider as aj, createClientQuery as ak, createLocalStore as al, createStream as am, getErrorCode as an, getRetryAfterMs as ao, isConflictError as ap, isForbiddenError as aq, isRateLimitedError as ar, isUnauthorizedError as as, SubscriptionErrorCallback as b, PersistenceAdapter as c, HttpStreamArgsOf as d, HttpStreamChunkOf as e, StreamIterable as f, ReconnectOptions as g, BatchSlot as h, CachedQuery as i, ClientDebugShard as j, ClientDebugSnapshot as k, ClientDebugSubscription as l, ClientMessage as m, ClientQueryRef as n, ClientShapeSubscribeMessage as o, ClientShapeUnsubscribeMessage as p, ConnectionStatus as q, FunctionArgumentDescriptor as r, FunctionDescriptor as s, GlobalFacetValue as t, GlobalFilterClause as u, GlobalTableInfo as v, GlobalTablePage as w, HttpStreamCallArgs as x, LunoraClientError as y, LunoraClientOptions as z };
3108
+ export { SchedulerStatus as $, ArgsOf as A, BookmarkStorage as B, ConnectionStatus as C, DEFAULT_MAX_BUFFER as D, LunoraErrorCode as E, FunctionReference as F, GlobalFacetResult as G, HttpStreamRef as H, MutationSettledEvent as I, OptimisticLocalStore as J, OptimisticUpdate as K, LunoraClient as L, MutationCallOptions as M, OutboxMutation as N, OfflineQueueOptions as O, Preloaded as P, QueryCacheAdapter as Q, ReturnOf as R, SubscriptionError as S, OutboxSink as T, User as U, PersistedMutation as V, RowOp as W, RpcEnvelope as X, RpcResponseBody as Y, ScheduleRecord as Z, SchedulerPoolStatus as _, Unsubscribe as a, ServerMessage as a0, ServerPokeEndMessage as a1, ServerPokePartMessage as a2, ServerPokeStartMessage as a3, ShardTrafficEntry as a4, ShardTrafficResult as a5, StorageListPage as a6, StorageObject as a7, StreamHandle as a8, SubscriptionCallback as a9, SubscriptionRegistry as aa, SubscriptionState as ab, SyncWatermark as ac, WorkflowInstanceAction as ad, WorkflowInstanceDetail as ae, WorkflowInstancePage as af, WorkflowInstanceStatus as ag, WorkflowInstanceSummary as ah, WorkflowStepDetail as ai, WsTokenProvider as aj, createClientQuery as ak, createLocalStore as al, createStream as am, getErrorCode as an, getRetryAfterMs as ao, isConflictError as ap, isForbiddenError as aq, isRateLimitedError as ar, isUnauthorizedError as as, SubscriptionErrorCallback as b, PersistenceAdapter as c, HttpStreamArgsOf as d, HttpStreamChunkOf as e, StreamIterable as f, ReconnectOptions as g, BatchSlot as h, CONFLICT_ERROR_CODE as i, CachedQuery as j, ClientDebugShard as k, ClientDebugSnapshot as l, ClientDebugSubscription as m, ClientMessage as n, ClientQueryRef as o, ClientShapeSubscribeMessage as p, ClientShapeUnsubscribeMessage as q, FunctionArgumentDescriptor as r, FunctionDescriptor as s, GlobalFacetValue as t, GlobalFilterClause as u, GlobalTableInfo as v, GlobalTablePage as w, HttpStreamCallArgs as x, LunoraClientError as y, LunoraClientOptions as z };
@@ -1492,6 +1492,24 @@ declare class LunoraClient {
1492
1492
  * Not `readonly` — `close()` clears it (mirrors `outboxLeaderRelease`).
1493
1493
  */
1494
1494
  private tabCoordinator;
1495
+ /**
1496
+ * The leader's last-broadcast aggregate {@link ConnectionStatus}, mirrored
1497
+ * on a follower tab — which owns no `ShardConnection` of its own to
1498
+ * compute a status from (see `computeStatus`). `undefined` until the
1499
+ * leader's first broadcast (falls back to `"idle"`), and reset back to
1500
+ * `undefined` whenever this tab stops being a follower of the CURRENT
1501
+ * leader (becomes leader itself, or the leader changes), so a stale
1502
+ * mirror from a previous leader never survives a leadership change.
1503
+ */
1504
+ private leaderStatus;
1505
+ /**
1506
+ * Sticky "has the mirrored leader status ever reported `connected`" flag —
1507
+ * the follower's counterpart to {@link ShardConnection.wasEverConnected},
1508
+ * since a follower has no `ShardConnection` of its own. Feeds the
1509
+ * offline-queue gate (see `mutation`) exactly like the real per-shard flag
1510
+ * does on the leader/single-tab path.
1511
+ */
1512
+ private leaderWasEverConnected;
1495
1513
  /** One {@link ShardConnection} per shard key (keyed by `shardKey ?? ""`). */
1496
1514
  private readonly connections;
1497
1515
  /** Default `connect`-envelope context applied to a shard with no explicit override. */
@@ -1534,6 +1552,17 @@ declare class LunoraClient {
1534
1552
  * See `identityFingerprint` for the fingerprint shape.
1535
1553
  */
1536
1554
  private readonly queuedIdentities;
1555
+ /**
1556
+ * Distinct shard keys with a mutation currently sitting in `offlineQueue`
1557
+ * — fresh writes queued this session (`enqueueOfflineMutation`) or writes
1558
+ * restored from durable storage (`hydratePersistedQueue`). A follower tab
1559
+ * has no per-shard `ShardConnection` to iterate when its mirrored leader
1560
+ * status turns `"connected"` (see the `onConnectionStatus` coordinator
1561
+ * option), so this is what that flush walks instead. Entries are never
1562
+ * removed — flushing an already-empty shard is a cheap no-op, and the set
1563
+ * is bounded by the app's own distinct shard-key cardinality.
1564
+ */
1565
+ private readonly queuedOfflineShardKeys;
1537
1566
  private closed;
1538
1567
  /** Subscribers to auth-token changes (see `onAuthTokenChange`). */
1539
1568
  private readonly authTokenListeners;
@@ -2624,6 +2653,25 @@ declare class LunoraClient {
2624
2653
  signal?: AbortSignal;
2625
2654
  }): StreamIterable<HttpStreamChunkOf<Ref>>;
2626
2655
  close(): void;
2656
+ /**
2657
+ * Tear down one {@link ShardConnection}'s live state: clear its reconnect/
2658
+ * connect timers, stop its heartbeat, and close its socket (if any).
2659
+ * Shared by `close()` (terminal) and the cross-tab `onStopBeingLeader`
2660
+ * handler (demoted, but still alive) so a demoted leader can't leak a
2661
+ * pending `reconnectTimer` or an open socket's `heartbeatTimer` the way
2662
+ * an inline `conn.socket?.close()` — which skips both — used to.
2663
+ */
2664
+ private teardownConnection;
2665
+ /**
2666
+ * Build (but do not start) this client's `TabCoordinator`. Extracted out of
2667
+ * the constructor so `setAuthToken` can rebuild it on an identity change —
2668
+ * the default channel name embeds the identity fingerprint (see below), so
2669
+ * a new identity needs a new coordinator on a new channel. The callback
2670
+ * bodies are the drift-sensitive region (a hand-merged identity guard on
2671
+ * the shard message listener sits ahead of an extracted `lastFrameAt`
2672
+ * stamp elsewhere in this file) — moved verbatim, not reflowed.
2673
+ */
2674
+ private createTabCoordinator;
2627
2675
  /**
2628
2676
  * Persist a mutation that can't go out on the wire right now (offline, or
2629
2677
  * mid-reconnect after a prior connect). The optimistic update has already
@@ -2717,6 +2765,19 @@ declare class LunoraClient {
2717
2765
  */
2718
2766
  private applyOptimisticUpdate;
2719
2767
  private getConnection;
2768
+ /**
2769
+ * The `(wsState, hasSocket, wasEverConnected)` triple `mutation()`'s
2770
+ * offline-queue gate reads. On the leader/single-tab path this is exactly
2771
+ * the real `ShardConnection`'s state (byte-identical to the pre-cross-tab
2772
+ * behavior). A follower has no `ShardConnection` of its own (see
2773
+ * `ensureSocket`), so it derives the same triple from the mirrored
2774
+ * `leaderStatus`/`leaderWasEverConnected` instead: `"connected"` maps to
2775
+ * `"open"` (queue-eligible once `wasEverConnected`), `"connecting"` stays
2776
+ * `"connecting"` (the mid-reconnect queue branch), anything else is
2777
+ * `"idle"`. `hasSocket` is always `false` for a follower — it never has
2778
+ * one.
2779
+ */
2780
+ private connectionGateState;
2720
2781
  private getOrCreateConnection;
2721
2782
  private wsUrlFor;
2722
2783
  /**
@@ -2778,15 +2839,46 @@ declare class LunoraClient {
2778
2839
  * a silent tokenless socket the admin gate would reject.
2779
2840
  */
2780
2841
  private openSocketWithProvidedToken;
2842
+ /**
2843
+ * Construct one WebSocket connection attempt and wire the shared
2844
+ * lifecycle guarantees around it — the fail-fast connect-timeout, the
2845
+ * identity guard that stops a superseded attempt's late `open`/`message`/
2846
+ * `close`/`error` from touching a connection a newer attempt already
2847
+ * owns, and (once open) the keepalive heartbeat with its half-open
2848
+ * watchdog (plan 217). One call opens ONE attempt; the caller owns
2849
+ * reconnect scheduling from `onClose` — mirrors the shard's existing
2850
+ * `ensureSocket` / `handleDisconnect` split, now shared with
2851
+ * `subscribeScheduledJobs` so it stops re-living the bug that split
2852
+ * already fixed once (CLIENT-05).
2853
+ *
2854
+ * The identity guard is `conn.socket !== socket`, re-checked before every
2855
+ * action below. `conn.socket` is reassigned to a new attempt's socket
2856
+ * synchronously — right here, before `open` ever fires — so an older
2857
+ * attempt's guard trips the instant it's superseded, even if its
2858
+ * underlying socket only fires its real `close`/`error` much later. This
2859
+ * ordering is load-bearing: preserve it exactly.
2860
+ */
2861
+ private openManagedSocket;
2781
2862
  /** Construct the shard socket and wire its lifecycle handlers. The connection must already be in the `connecting` state. */
2782
2863
  private openSocket;
2783
2864
  private handleDisconnect;
2784
2865
  /**
2785
- * Begin the keepalive heartbeat on an open connection. Each tick sends a
2786
- * {@link WS_KEEPALIVE_PING} text frame the server answers from its
2787
- * hibernation auto-response without waking the DO. A no-op when the
2788
- * heartbeat is disabled (an interval of zero or less); idempotent — any
2789
- * existing timer is cleared first so a reconnect can't leak intervals.
2866
+ * Begin the keepalive heartbeat on an open connection attempt the only
2867
+ * caller is {@link openManagedSocket}'s own `open` handler, so both the
2868
+ * shard socket and `subscribeScheduledJobs` share this one implementation
2869
+ * instead of each hand-rolling their own (plan 217, generalized).
2870
+ *
2871
+ * Each tick first checks the half-open watchdog (see
2872
+ * {@link ManagedSocketState.lastFrameAt}): if no frame at all has arrived
2873
+ * within `heartbeatIntervalMs * 2.5`, the far end has gone quiet without
2874
+ * the socket ever firing `close` — force it closed and report it through
2875
+ * `onWatchdogTrip` (the caller's `onClose`) so the normal reconnect/backoff
2876
+ * takes over instead of every live query on it silently staling forever.
2877
+ * Otherwise it sends a {@link WS_KEEPALIVE_PING} text frame the server
2878
+ * answers from its hibernation auto-response without waking the DO. A
2879
+ * no-op when the heartbeat is disabled (an interval of zero or less);
2880
+ * idempotent — any existing timer is cleared first so a reconnect can't
2881
+ * leak intervals.
2790
2882
  */
2791
2883
  private startHeartbeat;
2792
2884
  /** Clear a connection's keepalive timer, if any. Safe to call repeatedly. */
@@ -2848,6 +2940,23 @@ declare class LunoraClient {
2848
2940
  private dispatchWhisper;
2849
2941
  /** Notify every {@link onTokenExpired} listener (best-effort, listener throws swallowed). */
2850
2942
  private notifyTokenExpired;
2943
+ /**
2944
+ * CLIENT-04: `type: "complete"` today is sent ONLY by `@lunora/do`'s
2945
+ * `handleStream` (see `shard-do.ts`), gated to the `stream` envelope type
2946
+ * and minting only `stream_*` ids — the `subscribe` path never sends it, so
2947
+ * a live SUBSCRIPTION provably never receives `complete` from the current
2948
+ * server. But `ServerCompleteMessage` is a generic `id`-keyed frame and
2949
+ * `ShardDO` is user-subclassable, so this stays defensive rather than
2950
+ * assuming a `sub_*` id can never reach here: unlike the historical
2951
+ * `subscriptions.remove(state)`, which dropped the state out of
2952
+ * `subscriptions.all()` — the set the reconnect resubscribe loop walks
2953
+ * (`ensureSocket`'s `open` handler) — and so froze the query forever across
2954
+ * every future reconnect, this fans a cancellation error to any listener
2955
+ * and marks the registration un-acked instead. Non-destructive: the state
2956
+ * stays in the registry, so the very next reconnect resubscribes it. The
2957
+ * two id-spaces don't overlap (`sub_*` vs `stream_*`), so the stream and
2958
+ * subscription lookups below are mutually exclusive.
2959
+ */
2851
2960
  private handleCompleteMessage;
2852
2961
  private unpersist;
2853
2962
  /**
@@ -2910,6 +3019,18 @@ declare class LunoraClient {
2910
3019
  * the durable `clear()` is best-effort.
2911
3020
  */
2912
3021
  private clearQueryCacheForIdentityChange;
3022
+ /**
3023
+ * Flush every shard with a mutation currently queued in `offlineQueue`
3024
+ * (see `queuedOfflineShardKeys`). Used on a FOLLOWER tab when the
3025
+ * mirrored leader status transitions to `"connected"` — a follower has no
3026
+ * per-shard `ShardConnection` reconnect event to hang the usual
3027
+ * single-shard `flushOfflineQueue(shardKey)` call off of (see the
3028
+ * `handleConnect` call site), so this walks every shard that might have
3029
+ * something queued instead. Flushing an already-empty shard is a cheap
3030
+ * no-op (`flushOfflineQueue` returns immediately once `drain` yields
3031
+ * nothing), so over-inclusion here is harmless.
3032
+ */
3033
+ private flushAllOfflineQueues;
2913
3034
  private flushOfflineQueue;
2914
3035
  /**
2915
3036
  * Partition already-gated writes into the encodable ones (returned) and reject
@@ -2984,4 +3105,4 @@ declare class LunoraClient {
2984
3105
  */
2985
3106
  private settleReplayBatchSlots;
2986
3107
  }
2987
- export { SchedulerStatus as $, ArgsOf as A, BookmarkStorage as B, CONFLICT_ERROR_CODE as C, DEFAULT_MAX_BUFFER as D, LunoraErrorCode as E, FunctionReference as F, GlobalFacetResult as G, HttpStreamRef as H, MutationSettledEvent as I, OptimisticLocalStore as J, OptimisticUpdate as K, LunoraClient as L, MutationCallOptions as M, OutboxMutation as N, OfflineQueueOptions as O, Preloaded as P, QueryCacheAdapter as Q, ReturnOf as R, SubscriptionError as S, OutboxSink as T, User as U, PersistedMutation as V, RowOp as W, RpcEnvelope as X, RpcResponseBody as Y, ScheduleRecord as Z, SchedulerPoolStatus as _, Unsubscribe as a, ServerMessage as a0, ServerPokeEndMessage as a1, ServerPokePartMessage as a2, ServerPokeStartMessage as a3, ShardTrafficEntry as a4, ShardTrafficResult as a5, StorageListPage as a6, StorageObject as a7, StreamHandle as a8, SubscriptionCallback as a9, SubscriptionRegistry as aa, SubscriptionState as ab, SyncWatermark as ac, WorkflowInstanceAction as ad, WorkflowInstanceDetail as ae, WorkflowInstancePage as af, WorkflowInstanceStatus as ag, WorkflowInstanceSummary as ah, WorkflowStepDetail as ai, WsTokenProvider as aj, createClientQuery as ak, createLocalStore as al, createStream as am, getErrorCode as an, getRetryAfterMs as ao, isConflictError as ap, isForbiddenError as aq, isRateLimitedError as ar, isUnauthorizedError as as, SubscriptionErrorCallback as b, PersistenceAdapter as c, HttpStreamArgsOf as d, HttpStreamChunkOf as e, StreamIterable as f, ReconnectOptions as g, BatchSlot as h, CachedQuery as i, ClientDebugShard as j, ClientDebugSnapshot as k, ClientDebugSubscription as l, ClientMessage as m, ClientQueryRef as n, ClientShapeSubscribeMessage as o, ClientShapeUnsubscribeMessage as p, ConnectionStatus as q, FunctionArgumentDescriptor as r, FunctionDescriptor as s, GlobalFacetValue as t, GlobalFilterClause as u, GlobalTableInfo as v, GlobalTablePage as w, HttpStreamCallArgs as x, LunoraClientError as y, LunoraClientOptions as z };
3108
+ export { SchedulerStatus as $, ArgsOf as A, BookmarkStorage as B, ConnectionStatus as C, DEFAULT_MAX_BUFFER as D, LunoraErrorCode as E, FunctionReference as F, GlobalFacetResult as G, HttpStreamRef as H, MutationSettledEvent as I, OptimisticLocalStore as J, OptimisticUpdate as K, LunoraClient as L, MutationCallOptions as M, OutboxMutation as N, OfflineQueueOptions as O, Preloaded as P, QueryCacheAdapter as Q, ReturnOf as R, SubscriptionError as S, OutboxSink as T, User as U, PersistedMutation as V, RowOp as W, RpcEnvelope as X, RpcResponseBody as Y, ScheduleRecord as Z, SchedulerPoolStatus as _, Unsubscribe as a, ServerMessage as a0, ServerPokeEndMessage as a1, ServerPokePartMessage as a2, ServerPokeStartMessage as a3, ShardTrafficEntry as a4, ShardTrafficResult as a5, StorageListPage as a6, StorageObject as a7, StreamHandle as a8, SubscriptionCallback as a9, SubscriptionRegistry as aa, SubscriptionState as ab, SyncWatermark as ac, WorkflowInstanceAction as ad, WorkflowInstanceDetail as ae, WorkflowInstancePage as af, WorkflowInstanceStatus as ag, WorkflowInstanceSummary as ah, WorkflowStepDetail as ai, WsTokenProvider as aj, createClientQuery as ak, createLocalStore as al, createStream as am, getErrorCode as an, getRetryAfterMs as ao, isConflictError as ap, isForbiddenError as aq, isRateLimitedError as ar, isUnauthorizedError as as, SubscriptionErrorCallback as b, PersistenceAdapter as c, HttpStreamArgsOf as d, HttpStreamChunkOf as e, StreamIterable as f, ReconnectOptions as g, BatchSlot as h, CONFLICT_ERROR_CODE as i, CachedQuery as j, ClientDebugShard as k, ClientDebugSnapshot as l, ClientDebugSubscription as m, ClientMessage as n, ClientQueryRef as o, ClientShapeSubscribeMessage as p, ClientShapeUnsubscribeMessage as q, FunctionArgumentDescriptor as r, FunctionDescriptor as s, GlobalFacetValue as t, GlobalFilterClause as u, GlobalTableInfo as v, GlobalTablePage as w, HttpStreamCallArgs as x, LunoraClientError as y, LunoraClientOptions as z };
@@ -0,0 +1 @@
1
+ let c=0;const a=()=>{if(typeof crypto<"u"&&typeof crypto.randomUUID=="function")return crypto.randomUUID();c+=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)}_${c.toString(36)}_${r}`},f=(r,t)=>r!==void 0&&t!==r,d=a,h=(r,t,e,i)=>{if(r){r({error:e,mutationId:i,operation:t});return}console.warn(`[lunora] offline-queue persistence ${t} failed`,e)};class u{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??=d(),this.items.push(e),this.persistence?.append({args:e.args,functionPath:e.functionPath,id:e.id,identity:e.identity,shardKey:e.shardKey,...this.version===void 0?{}:{version:this.version}}).catch(i=>{h(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 h(this.onPersistenceError,"load",s),s}const e=[];for(const s of t)if(!(this.items.some(o=>o.id===s.id)||e.some(o=>o.id===s.id))){if(f(this.version,s.version)){this.persistence.remove(s.id).catch(o=>{h(this.onPersistenceError,"remove",o,s.id)});continue}e.push({args:s.args,functionPath:s.functionPath,id:s.id,identity:s.identity,reject:()=>{},resolve:()=>{},shardKey:s.shardKey})}this.items.unshift(...e),this.evictOverflow(),this.notifySize();const i=new Set(this.items),n=new Set;for(const s of e)i.has(s)&&n.add(s.shardKey);return[...n]}drain(t){if(!t){const n=[...this.items];return this.items.length=0,this.notifySize(),n}const e=[],i=[];for(const n of this.items)(t(n)?e:i).push(n);return this.items.length=0,this.items.push(...i),this.notifySize(),e}requeue(t){t.length!==0&&(this.items.unshift(...t),this.notifySize())}drainConflict(){const t=[],e=[];for(const i of this.items)if(i.precondition!==void 0&&!i.precondition()){const n=new Error("offline mutation skipped: precondition failed before replay");n.code="OFFLINE_PRECONDITION_FAILED",i.reject(n),t.push(i)}else e.push(i);return t.length>0&&(this.items.length=0,this.items.push(...e),this.notifySize()),t}clear(){for(const t of this.items){const e=new Error("CLIENT_CLOSED");e.code="CLIENT_CLOSED",t.reject(e)}this.items.length=0,this.notifySize()}evictOverflow(){for(;this.items.length>this.maxItems;){const t=this.items.shift();if(t){t.id&&this.persistence?.remove(t.id).catch(i=>{h(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{f as d,u as h,h as s,d as u};
@@ -1,4 +1,4 @@
1
- import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-DLcz4GRz.mjs";
1
+ import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-CQ4K1Ls8.mjs";
2
2
  /**
3
3
  * Run a query once on the server (during SSR) and capture its result in a
4
4
  * serializable {@link Preloaded} token. Embed the token in the rendered HTML and
@@ -1,4 +1,4 @@
1
- import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-DLcz4GRz.js";
1
+ import { F as FunctionReference, L as LunoraClient, A as ArgsOf, R as ReturnOf, P as Preloaded } from "./lunora-client.d-CQ4K1Ls8.js";
2
2
  /**
3
3
  * Run a query once on the server (during SSR) and capture its result in a
4
4
  * serializable {@link Preloaded} token. Embed the token in the rendered HTML and
@@ -1,5 +1,5 @@
1
- import { S as SubscriptionError, F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-DLcz4GRz.mjs";
2
- export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-DLcz4GRz.mjs";
1
+ import { S as SubscriptionError, F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-CQ4K1Ls8.mjs";
2
+ export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-CQ4K1Ls8.mjs";
3
3
  import '@lunora/runtime';
4
4
  /**
5
5
  * The sentinel a framework adapter resolves its reactive args to when it wants
@@ -1,5 +1,5 @@
1
- import { S as SubscriptionError, F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-DLcz4GRz.js";
2
- export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-DLcz4GRz.js";
1
+ import { S as SubscriptionError, F as FunctionReference, A as ArgsOf, R as ReturnOf, L as LunoraClient, a as Unsubscribe } from "../packem_shared/lunora-client.d-CQ4K1Ls8.js";
2
+ export type { b as SubscriptionErrorCallback } from "../packem_shared/lunora-client.d-CQ4K1Ls8.js";
3
3
  import '@lunora/runtime';
4
4
  /**
5
5
  * The sentinel a framework adapter resolves its reactive args to when it wants
@@ -1,6 +1,6 @@
1
- import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-DLcz4GRz.mjs";
2
- export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-DLcz4GRz.mjs";
3
- export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-C1M_N5mE.mjs";
1
+ import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-CQ4K1Ls8.mjs";
2
+ export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-CQ4K1Ls8.mjs";
3
+ export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-CrqgaKH9.mjs";
4
4
  import '@lunora/runtime';
5
5
  /**
6
6
  * Structural shape of a better-auth `getSession` call's resolved value.
@@ -1,6 +1,6 @@
1
- import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-DLcz4GRz.js";
2
- export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-DLcz4GRz.js";
3
- export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-hkFC0Uno.js";
1
+ import { P as Preloaded, L as LunoraClient } from "../packem_shared/lunora-client.d-CQ4K1Ls8.js";
2
+ export type { A as ArgsOf, F as FunctionReference, R as ReturnOf } from "../packem_shared/lunora-client.d-CQ4K1Ls8.js";
3
+ export { p as preloadQuery, a as preloadedQueryResult } from "../packem_shared/preload.d-DZf_x6mY.js";
4
4
  import '@lunora/runtime';
5
5
  /**
6
6
  * Structural shape of a better-auth `getSession` call's resolved value.
@@ -1 +1 @@
1
- import{getServerSession as o}from"../packem_shared/getServerSession-CGjgRBAE.mjs";import{deserializePreloaded as l,serializePreloaded as t}from"../packem_shared/deserializePreloaded-CHfOFpka.mjs";import{createServerClient as i}from"../packem_shared/createServerClient-DX7Dk2w2.mjs";import{preloadQuery as s,preloadedQueryResult as f}from"../packem_shared/preloadQuery-DjwSjFwt.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-CGjgRBAE.mjs";import{deserializePreloaded as l,serializePreloaded as t}from"../packem_shared/deserializePreloaded-CHfOFpka.mjs";import{createServerClient as i}from"../packem_shared/createServerClient-Bc6O-zYd.mjs";import{preloadQuery as s,preloadedQueryResult as f}from"../packem_shared/preloadQuery-DjwSjFwt.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.35",
3
+ "version": "1.0.0-alpha.36",
4
4
  "description": "Lunora browser SDK: WebSocket transport, optimistic updates, and an offline mutation queue",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -66,7 +66,7 @@
66
66
  "access": "public"
67
67
  },
68
68
  "dependencies": {
69
- "@lunora/errors": "1.0.0-alpha.10",
69
+ "@lunora/errors": "1.0.0-alpha.12",
70
70
  "@visulima/storage-client": "1.0.0"
71
71
  },
72
72
  "engines": {