statewire 0.12.2 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/statewire.ts CHANGED
@@ -59,10 +59,21 @@ export const useStatewire = <
59
59
  core.getState,
60
60
  ) as TState;
61
61
  const isBusy = useSyncExternalStore(subscribe, core.getBusy, core.getBusy);
62
+ const failed = useSyncExternalStore(
63
+ subscribe,
64
+ core.getFailed,
65
+ core.getFailed,
66
+ );
62
67
  const connection = value.connection ?? LIVE_CONNECTION;
63
68
 
64
- return useMemo(
65
- () => ({
69
+ return useMemo(() => {
70
+ const failures = (): NonNullable<typeof value.failures> => {
71
+ if (!value.failures) {
72
+ throw new Error("statewire: this transport has no failed-send lane");
73
+ }
74
+ return value.failures;
75
+ };
76
+ return {
66
77
  state,
67
78
  getState: core.getState as () => TState,
68
79
  connection,
@@ -76,9 +87,13 @@ export const useStatewire = <
76
87
  }
77
88
  value.setContext(contextValue);
78
89
  },
79
- }),
80
- [state, isBusy, connection, core, value],
81
- );
90
+ failed,
91
+ retryFailed: (entry: StatewireClient.FailedCommand) =>
92
+ failures().retry(entry),
93
+ discardFailed: (entry: StatewireClient.FailedCommand) =>
94
+ failures().discard(entry),
95
+ };
96
+ }, [state, isBusy, failed, connection, core, value]);
82
97
  };
83
98
 
84
99
  export const StatewireResource = <
@@ -121,6 +136,18 @@ export class StatewireClient<
121
136
  this.root.getValue().setContext(value);
122
137
  }
123
138
 
139
+ get failed(): readonly StatewireClient.FailedCommand[] {
140
+ return this.root.getValue().failed;
141
+ }
142
+
143
+ retryFailed(entry: StatewireClient.FailedCommand): Promise<unknown> {
144
+ return this.root.getValue().retryFailed(entry);
145
+ }
146
+
147
+ discardFailed(entry: StatewireClient.FailedCommand): void {
148
+ this.root.getValue().discardFailed(entry);
149
+ }
150
+
124
151
  subscribe(listener: () => void): () => void {
125
152
  return this.root.subscribe(listener);
126
153
  }
@@ -155,6 +182,11 @@ export namespace StatewireClient {
155
182
  ctx: { state: NonNullable<TState> },
156
183
  ) => void;
157
184
  onConnectionChange?: (connection: Connection) => void;
185
+ /** Drops a retained failed entry once a canonical snapshot shows its effect landed; checked against each new snapshot. */
186
+ failureCovered?: (
187
+ state: NonNullable<TState>,
188
+ entry: FailedCommand,
189
+ ) => boolean;
158
190
  };
159
191
 
160
192
  export type Helpers<
@@ -171,6 +203,12 @@ export namespace StatewireClient {
171
203
  isBusy: boolean;
172
204
  /** Replace this client's ambient context on the server; throws on transports without a client context channel. */
173
205
  setContext: (value: Record<string, unknown>) => void;
206
+ /** Non-accepted sends retained in the session record; reload with it. */
207
+ failed: readonly FailedCommand[];
208
+ /** Re-enqueues the entry's command on fresh clocks and removes the entry. */
209
+ retryFailed: (entry: FailedCommand) => Promise<unknown>;
210
+ /** Removes the entry. */
211
+ discardFailed: (entry: FailedCommand) => void;
174
212
  };
175
213
 
176
214
  export type ConnectionState =
@@ -259,6 +297,16 @@ export namespace StatewireClient {
259
297
  }>,
260
298
  ) => "requeue" | "drop";
261
299
 
300
+ /** A non-accepted send retained for the user: the wire statement, its failure, and its original clocks. */
301
+ export type FailedCommand = {
302
+ readonly command: string;
303
+ readonly params: readonly unknown[];
304
+ readonly meta?: unknown;
305
+ readonly failure: SendFailure;
306
+ readonly ts: number;
307
+ readonly seq: number;
308
+ };
309
+
262
310
  /** A command as persisted while un-durable: its lane seq plus the wire statement. */
263
311
  export type SessionCommand = {
264
312
  seq: number;
@@ -275,6 +323,8 @@ export namespace StatewireClient {
275
323
  commands: readonly SessionCommand[];
276
324
  /** Terminal verdicts parked for the sharer that minted the seq (shared storage only). */
277
325
  verdicts?: readonly SessionVerdict[];
326
+ /** Non-accepted sends retained for the user; survive record rebases and resets. */
327
+ failed?: readonly FailedCommand[];
278
328
  };
279
329
 
280
330
  /** A terminal verdict one sharer's connection carried, parked for the sharer awaiting the seq. */
@@ -1,6 +1,9 @@
1
1
  import { useEffect } from "react";
2
2
  import type { Statewire } from "../types";
3
3
  import {
4
+ discardFailed,
5
+ failedCommands,
6
+ retryFailed,
4
7
  setContext,
5
8
  setTransport,
6
9
  useStatewireSession,
@@ -39,5 +42,10 @@ export const useStatewireRemote = <
39
42
  return !s.idle;
40
43
  },
41
44
  setContext: (value) => setContext(s, value),
45
+ failures: {
46
+ list: () => failedCommands(s),
47
+ retry: (entry) => retryFailed(s, entry),
48
+ discard: (entry) => discardFailed(s, entry),
49
+ },
42
50
  };
43
51
  };
package/src/types.ts CHANGED
@@ -59,6 +59,12 @@ export interface Statewire<
59
59
  isBusy?: boolean;
60
60
  /** Replace this client's ambient context on the server; it rides the ctx lane (no verdict). */
61
61
  setContext?: (value: Record<string, unknown>) => void;
62
+ /** The retained failed-send lane; present on transports with a durable session. */
63
+ failures?: {
64
+ list: () => readonly StatewireClient.FailedCommand[];
65
+ retry: (entry: StatewireClient.FailedCommand) => Promise<unknown>;
66
+ discard: (entry: StatewireClient.FailedCommand) => void;
67
+ };
62
68
  }
63
69
 
64
70
  const describe = (failure: StatewireClient.SendFailure): string => {