@voltro/client 0.9.0 → 0.10.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/dist/index.d.ts CHANGED
@@ -384,6 +384,14 @@ export declare interface ClientTraceEvent {
384
384
 
385
385
  declare type ClientTraceListener = (event: ClientTraceEvent) => void;
386
386
 
387
+ /** A compensation that itself failed. Surfaced rather than swallowed: the app
388
+ * now has an orphan it may need to reconcile, and hiding that is how orphans
389
+ * become permanent. */
390
+ export declare interface CompensationFailure {
391
+ readonly step: string;
392
+ readonly error: unknown;
393
+ }
394
+
387
395
  /** Pure virtualization math: scroll position → the row window to fetch +
388
396
  * the spacer heights. `total` (when known) caps the window + sizes the bottom
389
397
  * spacer; without it, infinite-scroll (bottomSpacer 0). */
@@ -1115,6 +1123,26 @@ export declare class RpcErrorBus {
1115
1123
 
1116
1124
  export declare type RpcErrorListener = (event: RpcError) => void;
1117
1125
 
1126
+ /**
1127
+ * Run a sequence. Resolves with a discriminated result rather than rejecting —
1128
+ * the same "handled" semantics `useMutation`/`useAction` use, so the call site
1129
+ * loses its try/catch without losing the failure.
1130
+ */
1131
+ export declare const runSequence: <Ctx>(builder: SequenceBuilder<Ctx>, options?: RunSequenceOptions) => Promise<SequenceResult<Ctx>>;
1132
+
1133
+ export declare interface RunSequenceOptions {
1134
+ /** Called once, with the original failure. The whole point of the primitive:
1135
+ * one handler for the sequence instead of a try/catch per step. */
1136
+ readonly onError?: (error: unknown, detail: {
1137
+ readonly failedStep: string;
1138
+ readonly compensated: ReadonlyArray<string>;
1139
+ readonly compensationFailures: ReadonlyArray<CompensationFailure>;
1140
+ }) => void;
1141
+ /** Skip compensation entirely. For a sequence whose steps are all idempotent
1142
+ * upserts, undoing is churn rather than cleanup. */
1143
+ readonly compensate?: boolean;
1144
+ }
1145
+
1118
1146
  /**
1119
1147
  * Derive table COLUMNS from a query's output `Schema` — an
1120
1148
  * `Schema.Array(Schema.Struct({...}))`. Drills into the array's element and maps
@@ -1137,6 +1165,36 @@ export declare interface SeqElement<E> {
1137
1165
  readonly event: E;
1138
1166
  }
1139
1167
 
1168
+ /** Start a sequence. Pure and React-free, so it is testable on its own. */
1169
+ export declare const sequence: () => SequenceBuilder<Record<never, never>>;
1170
+
1171
+ /**
1172
+ * A sequence under construction. Each `.step()` widens the context type, so a
1173
+ * later step reads earlier results by name with real types — `linkThread` can
1174
+ * see both `ctx.ticket` and `ctx.thread` without threading them by hand.
1175
+ */
1176
+ export declare interface SequenceBuilder<Ctx> {
1177
+ step<Name extends string, Result>(name: Name, run: (ctx: Ctx) => Result | Promise<Result>, options?: StepOptions<Result>): SequenceBuilder<Ctx & {
1178
+ readonly [K in Name]: Result;
1179
+ }>;
1180
+ /* Excluded from this release type: steps */
1181
+ }
1182
+
1183
+ export declare type SequenceResult<Ctx> = {
1184
+ readonly ok: true;
1185
+ readonly data: Ctx;
1186
+ } | {
1187
+ readonly ok: false;
1188
+ /** The step whose `run` threw. Its `undo` was NOT called — see the header. */
1189
+ readonly failedStep: string;
1190
+ /** The original failure. Never replaced by a compensation error. */
1191
+ readonly error: unknown;
1192
+ /** Names of the steps whose `undo` ran, newest first. */
1193
+ readonly compensated: ReadonlyArray<string>;
1194
+ /** Undos that threw. Non-empty means something is left over upstream. */
1195
+ readonly compensationFailures: ReadonlyArray<CompensationFailure>;
1196
+ };
1197
+
1140
1198
  /** Register (or clear) the app-wide notifier that `notify:` routes to. Call once
1141
1199
  * at boot, next to your toast provider. */
1142
1200
  export declare const setMutationNotifier: (notifier: MutationNotifier | undefined) => void;
@@ -1191,6 +1249,22 @@ export declare const stableKey: (key: ReadonlyArray<unknown>) => string;
1191
1249
  */
1192
1250
  export declare const startMutation: (args: Pick<MutationEvent, "id" | "apiName" | "tag" | "input">) => void;
1193
1251
 
1252
+ declare interface Step {
1253
+ readonly name: string;
1254
+ readonly run: (ctx: Record<string, unknown>) => unknown | Promise<unknown>;
1255
+ readonly undo: StepUndo<never> | undefined;
1256
+ }
1257
+
1258
+ export declare interface StepOptions<Result> {
1259
+ /** How to reverse this step if a LATER step fails. Omit when the step has no
1260
+ * externally visible effect worth reversing. */
1261
+ readonly undo?: StepUndo<Result>;
1262
+ }
1263
+
1264
+ /** One step's compensation. Receives the step's OWN result, which is the point:
1265
+ * `undo: (created) => remove.run({ id: created.id })`. */
1266
+ export declare type StepUndo<Result> = (result: Result) => unknown | Promise<unknown>;
1267
+
1194
1268
  /** Subscribe to client errors. Returns an unsubscribe fn. */
1195
1269
  export declare const subscribeClientErrors: (listener: ClientErrorListener) => (() => void);
1196
1270
 
@@ -1350,6 +1424,34 @@ export declare interface SubscriptionCacheOptions {
1350
1424
  readonly errorBus?: RpcErrorBus;
1351
1425
  }
1352
1426
 
1427
+ /**
1428
+ * Deliberately NOT subscribed — `skip: true`.
1429
+ *
1430
+ * This state exists because `loading` was answering two different questions
1431
+ * with one boolean: "waiting for the first snapshot" and "not asking at all".
1432
+ * A skipped subscription used to report `loading: true` forever, so the blessed
1433
+ * pattern
1434
+ *
1435
+ * if (loading) return <Skeleton/>
1436
+ *
1437
+ * rendered a permanent skeleton for a query the app had consciously switched
1438
+ * off — and `skip: !currentUser?.id` / `skip: !open` are among the most common
1439
+ * things anyone writes. Call sites only survived it by carrying a redundant
1440
+ * `if (!currentUser) return null` in front, which is the very check `loading`
1441
+ * was supposed to replace.
1442
+ *
1443
+ * `loading` is `false` here: nothing is in flight. `data` is `undefined`,
1444
+ * which is why this state is only handed to callers that actually pass a
1445
+ * dynamic `skip` — see the overloads. Everyone else keeps the two-state union
1446
+ * where `!loading` still proves `data` is present.
1447
+ */
1448
+ export declare interface SubscriptionIdle extends SubscriptionMeta {
1449
+ readonly data: undefined;
1450
+ readonly loading: false;
1451
+ readonly isEmpty: false;
1452
+ readonly idle: true;
1453
+ }
1454
+
1353
1455
  /** The fields that mean the same thing in every subscription state. */
1354
1456
  export declare interface SubscriptionMeta {
1355
1457
  /** Server revision counter; -1 before first snapshot. */
@@ -1407,6 +1509,9 @@ export declare interface SubscriptionOptions<T = unknown> {
1407
1509
  export declare interface SubscriptionPending extends SubscriptionMeta {
1408
1510
  readonly data: undefined;
1409
1511
  readonly loading: true;
1512
+ /** Always `false` — this subscription IS asking, it just has no answer yet.
1513
+ * See {@link SubscriptionIdle} for the deliberately-not-asking case. */
1514
+ readonly idle: false;
1410
1515
  /** Always `false` while pending. "Empty" is a statement about data that
1411
1516
  * ARRIVED; before then it would be a guess, and rendering an empty state on
1412
1517
  * that guess is the flash-of-empty-state bug. */
@@ -1417,6 +1522,8 @@ export declare interface SubscriptionPending extends SubscriptionMeta {
1417
1522
  export declare interface SubscriptionSettled<T> extends SubscriptionMeta {
1418
1523
  readonly data: T;
1419
1524
  readonly loading: false;
1525
+ /** Always `false` — data arrived. */
1526
+ readonly idle: false;
1420
1527
  /** The data that arrived is empty — an empty array, or a null value. */
1421
1528
  readonly isEmpty: boolean;
1422
1529
  }
@@ -1451,6 +1558,10 @@ export declare type SubscriptionState<T> = SubscriptionPending | SubscriptionSet
1451
1558
  export declare interface SubscriptionStateWithFallback<T> extends SubscriptionMeta {
1452
1559
  readonly data: T;
1453
1560
  readonly loading: boolean;
1561
+ /** True when `skip` is on. `data` is the fallback, and no request is in
1562
+ * flight — distinguishes "showing the fallback because we chose not to ask"
1563
+ * from "…because the answer has not arrived". */
1564
+ readonly idle: boolean;
1454
1565
  readonly isEmpty: boolean;
1455
1566
  }
1456
1567
 
@@ -1854,11 +1965,54 @@ export declare interface UseResourceCanState {
1854
1965
 
1855
1966
  export declare const useResumableAgentStream: <E = unknown>(apiName: string, rpcTag: string, options?: ResumableAgentStreamOptions<E>) => ResumableAgentStreamControls<E>;
1856
1967
 
1968
+ /**
1969
+ * @param options Sequence-wide defaults — most usefully `onError`, which fires
1970
+ * once per failed sequence instead of once per step.
1971
+ *
1972
+ * ```tsx
1973
+ * const seq = useSequence({ onError: (e) => notify.error(String(e)) })
1974
+ *
1975
+ * const attach = async (file: File) => {
1976
+ * const result = await seq.run(
1977
+ * sequence()
1978
+ * .step('upload', () => upload.run({ file }), {
1979
+ * undo: (r) => removeObject.run({ id: r.id }), // the created id, right here
1980
+ * })
1981
+ * .step('attach', (c) => createAttachment.run({ refId: c.upload.id })),
1982
+ * )
1983
+ * if (result.ok) setAttachmentId(result.data.attach.id)
1984
+ * }
1985
+ * ```
1986
+ *
1987
+ * Compensation is best-effort and tab-bound — see `sequence.ts`. When the
1988
+ * rollback has to survive a closed tab, move the sequence into a workflow.
1989
+ */
1990
+ export declare const useSequence: (options?: RunSequenceOptions) => UseSequenceResult;
1991
+
1992
+ export declare interface UseSequenceResult {
1993
+ /** Run a built sequence. Resolves with the discriminated result; it does not
1994
+ * reject, which is what removes the try/catch. */
1995
+ readonly run: <Ctx>(builder: SequenceBuilder<Ctx>, options?: RunSequenceOptions) => Promise<SequenceResult<Ctx>>;
1996
+ /** True while a sequence is in flight. */
1997
+ readonly pending: boolean;
1998
+ /** The last sequence's original failure, or `undefined` after a success. */
1999
+ readonly error: unknown | undefined;
2000
+ /** The step that failed last time, for a targeted message ("couldn't create
2001
+ * the ticket") rather than a generic one. */
2002
+ readonly failedStep: string | undefined;
2003
+ }
2004
+
1857
2005
  export declare function useSubscription<T = unknown>(apiName: string, rpcTag: string, input: Readonly<Record<string, unknown>>, options: SubscriptionOptions<T> & {
1858
2006
  readonly fallback: T;
1859
2007
  }): SubscriptionStateWithFallback<T>;
1860
2008
 
1861
- export declare function useSubscription<T = unknown>(apiName: string, rpcTag: string, input?: Readonly<Record<string, unknown>>, options?: SubscriptionOptions<T>): SubscriptionState<T>;
2009
+ export declare function useSubscription<T = unknown>(apiName: string, rpcTag: string, input?: Readonly<Record<string, unknown>>, options?: SubscriptionOptions<T> & {
2010
+ readonly skip?: false;
2011
+ }): SubscriptionState<T>;
2012
+
2013
+ export declare function useSubscription<T = unknown>(apiName: string, rpcTag: string, input: Readonly<Record<string, unknown>>, options: SubscriptionOptions<T> & {
2014
+ readonly skip?: boolean;
2015
+ }): SubscriptionState<T> | SubscriptionIdle;
1862
2016
 
1863
2017
  /** The column shape a `<DataTable query=…>` will render — from the query's
1864
2018
  * output Schema. */
@@ -2012,11 +2166,11 @@ export declare interface WorkflowClientMessages {
2012
2166
  }>>;
2013
2167
  }
2014
2168
 
2015
- export declare type WorkflowDomainEventsState = SubscriptionState<ReadonlyArray<WorkflowDomainEventRow>> & {
2169
+ export declare type WorkflowDomainEventsState = (SubscriptionState<ReadonlyArray<WorkflowDomainEventRow>> | SubscriptionIdle) & {
2016
2170
  readonly events: ReadonlyArray<WorkflowDomainEventRow>;
2017
2171
  };
2018
2172
 
2019
- export declare type WorkflowEventDeliveriesState = SubscriptionState<ReadonlyArray<WorkflowEventDeliveryRow>> & {
2173
+ export declare type WorkflowEventDeliveriesState = (SubscriptionState<ReadonlyArray<WorkflowEventDeliveryRow>> | SubscriptionIdle) & {
2020
2174
  readonly deliveries: ReadonlyArray<WorkflowEventDeliveryRow>;
2021
2175
  };
2022
2176
 
@@ -2029,7 +2183,7 @@ export declare interface WorkflowRunError {
2029
2183
 
2030
2184
  export { WorkflowRunEventRow }
2031
2185
 
2032
- export declare type WorkflowRunEventsState = SubscriptionState<ReadonlyArray<WorkflowRunEventRow>> & {
2186
+ export declare type WorkflowRunEventsState = (SubscriptionState<ReadonlyArray<WorkflowRunEventRow>> | SubscriptionIdle) & {
2033
2187
  readonly events: ReadonlyArray<WorkflowRunEventRow>;
2034
2188
  };
2035
2189
 
@@ -2041,11 +2195,16 @@ export declare interface WorkflowRunsFilter {
2041
2195
  readonly limit?: number;
2042
2196
  }
2043
2197
 
2044
- export declare type WorkflowRunsState = SubscriptionState<ReadonlyArray<WorkflowRunRow>> & {
2198
+ export declare type WorkflowRunsState = (SubscriptionState<ReadonlyArray<WorkflowRunRow>> | SubscriptionIdle) & {
2045
2199
  readonly runs: ReadonlyArray<WorkflowRunRow>;
2046
2200
  };
2047
2201
 
2048
- export declare type WorkflowRunState = SubscriptionState<ReadonlyArray<WorkflowRunRow>> & {
2202
+ /** `SubscriptionIdle` is in the union because `id === undefined` skips: asking
2203
+ * for "the run with no id" is not a pending request, it is no request. Before
2204
+ * the idle state existed this reported `loading: true` forever, so a caller
2205
+ * following the blessed `if (loading) return <Skeleton/>` rendered a permanent
2206
+ * skeleton whenever nothing was selected. */
2207
+ export declare type WorkflowRunState = (SubscriptionState<ReadonlyArray<WorkflowRunRow>> | SubscriptionIdle) & {
2049
2208
  readonly run: WorkflowRunRow | undefined;
2050
2209
  };
2051
2210
 
@@ -2075,7 +2234,7 @@ export { WorkflowRunStatus }
2075
2234
 
2076
2235
  export { WorkflowRunStepRow }
2077
2236
 
2078
- export declare type WorkflowRunStepsState = SubscriptionState<ReadonlyArray<WorkflowRunStepRow>> & {
2237
+ export declare type WorkflowRunStepsState = (SubscriptionState<ReadonlyArray<WorkflowRunStepRow>> | SubscriptionIdle) & {
2079
2238
  readonly steps: ReadonlyArray<WorkflowRunStepRow>;
2080
2239
  };
2081
2240