@voltro/client 0.9.0 → 0.11.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/CHANGELOG.md +394 -7
- package/dist/index.d.ts +223 -7
- package/dist/index.js +408 -308
- package/package.json +2 -2
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,31 @@ 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
|
+
readonly compensationCovered: ReadonlyArray<string>;
|
|
1141
|
+
readonly compensationUncertain: ReadonlyArray<{
|
|
1142
|
+
readonly step: string;
|
|
1143
|
+
readonly coveredBy: string;
|
|
1144
|
+
}>;
|
|
1145
|
+
}) => void;
|
|
1146
|
+
/** Skip compensation entirely. For a sequence whose steps are all idempotent
|
|
1147
|
+
* upserts, undoing is churn rather than cleanup. */
|
|
1148
|
+
readonly compensate?: boolean;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1118
1151
|
/**
|
|
1119
1152
|
* Derive table COLUMNS from a query's output `Schema` — an
|
|
1120
1153
|
* `Schema.Array(Schema.Struct({...}))`. Drills into the array's element and maps
|
|
@@ -1137,6 +1170,52 @@ export declare interface SeqElement<E> {
|
|
|
1137
1170
|
readonly event: E;
|
|
1138
1171
|
}
|
|
1139
1172
|
|
|
1173
|
+
/** Start a sequence. Pure and React-free, so it is testable on its own. */
|
|
1174
|
+
export declare const sequence: () => SequenceBuilder<Record<never, never>>;
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* A sequence under construction. Each `.step()` widens the context type, so a
|
|
1178
|
+
* later step reads earlier results by name with real types — `linkThread` can
|
|
1179
|
+
* see both `ctx.ticket` and `ctx.thread` without threading them by hand.
|
|
1180
|
+
*/
|
|
1181
|
+
export declare interface SequenceBuilder<Ctx> {
|
|
1182
|
+
/** A CONDITIONAL step. `Result | undefined` in the context, because it may not
|
|
1183
|
+
* have run — later steps have to say what they do about that. */
|
|
1184
|
+
step<Name extends string, Result>(name: Name, run: (ctx: Ctx) => Result | Promise<Result>, options: StepOptions<Ctx, Result> & {
|
|
1185
|
+
readonly when: (ctx: Ctx) => boolean;
|
|
1186
|
+
}): SequenceBuilder<Ctx & {
|
|
1187
|
+
readonly [K in Name]: Result | undefined;
|
|
1188
|
+
}>;
|
|
1189
|
+
step<Name extends string, Result>(name: Name, run: (ctx: Ctx) => Result | Promise<Result>, options?: StepOptions<Ctx, Result>): SequenceBuilder<Ctx & {
|
|
1190
|
+
readonly [K in Name]: Result;
|
|
1191
|
+
}>;
|
|
1192
|
+
/* Excluded from this release type: steps */
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
export declare type SequenceResult<Ctx> = {
|
|
1196
|
+
readonly ok: true;
|
|
1197
|
+
readonly data: Ctx;
|
|
1198
|
+
} | {
|
|
1199
|
+
readonly ok: false;
|
|
1200
|
+
/** The step whose `run` threw. Its `undo` was NOT called — see the header. */
|
|
1201
|
+
readonly failedStep: string;
|
|
1202
|
+
/** The original failure. Never replaced by a compensation error. */
|
|
1203
|
+
readonly error: unknown;
|
|
1204
|
+
/** Names of the steps whose `undo` ran, newest first. */
|
|
1205
|
+
readonly compensated: ReadonlyArray<string>;
|
|
1206
|
+
/** Undos that threw. Non-empty means something is left over upstream. */
|
|
1207
|
+
readonly compensationFailures: ReadonlyArray<CompensationFailure>;
|
|
1208
|
+
/** Steps whose `undo` was skipped because a later step's `covers` names
|
|
1209
|
+
* them and that undo SUCCEEDED. They are reversed; nothing to do. */
|
|
1210
|
+
readonly compensationCovered: ReadonlyArray<string>;
|
|
1211
|
+
/** Steps whose covering undo FAILED. Neither reversed nor known to be —
|
|
1212
|
+
* the honest third answer, and the one worth reconciling. */
|
|
1213
|
+
readonly compensationUncertain: ReadonlyArray<{
|
|
1214
|
+
readonly step: string;
|
|
1215
|
+
readonly coveredBy: string;
|
|
1216
|
+
}>;
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1140
1219
|
/** Register (or clear) the app-wide notifier that `notify:` routes to. Call once
|
|
1141
1220
|
* at boot, next to your toast provider. */
|
|
1142
1221
|
export declare const setMutationNotifier: (notifier: MutationNotifier | undefined) => void;
|
|
@@ -1191,6 +1270,58 @@ export declare const stableKey: (key: ReadonlyArray<unknown>) => string;
|
|
|
1191
1270
|
*/
|
|
1192
1271
|
export declare const startMutation: (args: Pick<MutationEvent, "id" | "apiName" | "tag" | "input">) => void;
|
|
1193
1272
|
|
|
1273
|
+
declare interface Step {
|
|
1274
|
+
readonly name: string;
|
|
1275
|
+
readonly run: (ctx: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
1276
|
+
readonly undo: StepUndo<never> | undefined;
|
|
1277
|
+
readonly covers: ReadonlyArray<string>;
|
|
1278
|
+
readonly when: ((ctx: Record<string, unknown>) => boolean) | undefined;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
export declare interface StepOptions<Ctx, Result> {
|
|
1282
|
+
/** How to reverse this step if a LATER step fails. Omit when the step has no
|
|
1283
|
+
* externally visible effect worth reversing.
|
|
1284
|
+
*
|
|
1285
|
+
* **Your undo may be called while an earlier step's undo also runs.** They
|
|
1286
|
+
* are independent by default — see `covers` when one already reverses the
|
|
1287
|
+
* other, and the module header for why the runner cannot infer it. */
|
|
1288
|
+
readonly undo?: StepUndo<Result>;
|
|
1289
|
+
/**
|
|
1290
|
+
* Earlier steps this step's `undo` ALREADY reverses. Their undos are skipped.
|
|
1291
|
+
*
|
|
1292
|
+
* The case that produced this: `deleteJiraDraftTicket` deletes the issue AND
|
|
1293
|
+
* discards the draft, so the earlier `discardDraft` undo ran a second time.
|
|
1294
|
+
* That was harmless only because discarding is idempotent — for a refund or a
|
|
1295
|
+
* cancellation email the double-run is a real defect, and the runner has no
|
|
1296
|
+
* way to know which kind it is looking at.
|
|
1297
|
+
*
|
|
1298
|
+
* .step('draft', createDraft, { undo: discardDraft })
|
|
1299
|
+
* .step('jira', createTicket, { undo: deleteJiraDraftTicket, covers: ['draft'] })
|
|
1300
|
+
*
|
|
1301
|
+
* If the covering undo FAILS, the covered steps are neither run nor claimed:
|
|
1302
|
+
* whether the cascade got that far is genuinely unknown, and guessing either
|
|
1303
|
+
* way is wrong. They come back in `compensationUncertain` so the app can
|
|
1304
|
+
* reconcile instead of finding out later.
|
|
1305
|
+
*/
|
|
1306
|
+
readonly covers?: ReadonlyArray<keyof Ctx & string>;
|
|
1307
|
+
/**
|
|
1308
|
+
* Run this step only when the predicate holds. A skipped step contributes
|
|
1309
|
+
* `undefined` to the context (the type says so) and gets no `undo`.
|
|
1310
|
+
*
|
|
1311
|
+
* Narrow on purpose: this is for a sequence that is linear EXCEPT for one
|
|
1312
|
+
* optional step — "save, then schedule the summary only if the set changed",
|
|
1313
|
+
* `if (assigneeKey) assign else unassign`. Loops and real branches still keep
|
|
1314
|
+
* their `try/catch`; an optional step is a different shape from either, and
|
|
1315
|
+
* without this the whole flow fell back even though the rest was a clean
|
|
1316
|
+
* pipeline.
|
|
1317
|
+
*/
|
|
1318
|
+
readonly when?: (ctx: Ctx) => boolean;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
/** One step's compensation. Receives the step's OWN result, which is the point:
|
|
1322
|
+
* `undo: (created) => remove.run({ id: created.id })`. */
|
|
1323
|
+
export declare type StepUndo<Result> = (result: Result) => unknown | Promise<unknown>;
|
|
1324
|
+
|
|
1194
1325
|
/** Subscribe to client errors. Returns an unsubscribe fn. */
|
|
1195
1326
|
export declare const subscribeClientErrors: (listener: ClientErrorListener) => (() => void);
|
|
1196
1327
|
|
|
@@ -1350,6 +1481,34 @@ export declare interface SubscriptionCacheOptions {
|
|
|
1350
1481
|
readonly errorBus?: RpcErrorBus;
|
|
1351
1482
|
}
|
|
1352
1483
|
|
|
1484
|
+
/**
|
|
1485
|
+
* Deliberately NOT subscribed — `skip: true`.
|
|
1486
|
+
*
|
|
1487
|
+
* This state exists because `loading` was answering two different questions
|
|
1488
|
+
* with one boolean: "waiting for the first snapshot" and "not asking at all".
|
|
1489
|
+
* A skipped subscription used to report `loading: true` forever, so the blessed
|
|
1490
|
+
* pattern
|
|
1491
|
+
*
|
|
1492
|
+
* if (loading) return <Skeleton/>
|
|
1493
|
+
*
|
|
1494
|
+
* rendered a permanent skeleton for a query the app had consciously switched
|
|
1495
|
+
* off — and `skip: !currentUser?.id` / `skip: !open` are among the most common
|
|
1496
|
+
* things anyone writes. Call sites only survived it by carrying a redundant
|
|
1497
|
+
* `if (!currentUser) return null` in front, which is the very check `loading`
|
|
1498
|
+
* was supposed to replace.
|
|
1499
|
+
*
|
|
1500
|
+
* `loading` is `false` here: nothing is in flight. `data` is `undefined`,
|
|
1501
|
+
* which is why this state is only handed to callers that actually pass a
|
|
1502
|
+
* dynamic `skip` — see the overloads. Everyone else keeps the two-state union
|
|
1503
|
+
* where `!loading` still proves `data` is present.
|
|
1504
|
+
*/
|
|
1505
|
+
export declare interface SubscriptionIdle extends SubscriptionMeta {
|
|
1506
|
+
readonly data: undefined;
|
|
1507
|
+
readonly loading: false;
|
|
1508
|
+
readonly isEmpty: false;
|
|
1509
|
+
readonly idle: true;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1353
1512
|
/** The fields that mean the same thing in every subscription state. */
|
|
1354
1513
|
export declare interface SubscriptionMeta {
|
|
1355
1514
|
/** Server revision counter; -1 before first snapshot. */
|
|
@@ -1407,6 +1566,9 @@ export declare interface SubscriptionOptions<T = unknown> {
|
|
|
1407
1566
|
export declare interface SubscriptionPending extends SubscriptionMeta {
|
|
1408
1567
|
readonly data: undefined;
|
|
1409
1568
|
readonly loading: true;
|
|
1569
|
+
/** Always `false` — this subscription IS asking, it just has no answer yet.
|
|
1570
|
+
* See {@link SubscriptionIdle} for the deliberately-not-asking case. */
|
|
1571
|
+
readonly idle: false;
|
|
1410
1572
|
/** Always `false` while pending. "Empty" is a statement about data that
|
|
1411
1573
|
* ARRIVED; before then it would be a guess, and rendering an empty state on
|
|
1412
1574
|
* that guess is the flash-of-empty-state bug. */
|
|
@@ -1417,6 +1579,8 @@ export declare interface SubscriptionPending extends SubscriptionMeta {
|
|
|
1417
1579
|
export declare interface SubscriptionSettled<T> extends SubscriptionMeta {
|
|
1418
1580
|
readonly data: T;
|
|
1419
1581
|
readonly loading: false;
|
|
1582
|
+
/** Always `false` — data arrived. */
|
|
1583
|
+
readonly idle: false;
|
|
1420
1584
|
/** The data that arrived is empty — an empty array, or a null value. */
|
|
1421
1585
|
readonly isEmpty: boolean;
|
|
1422
1586
|
}
|
|
@@ -1451,6 +1615,10 @@ export declare type SubscriptionState<T> = SubscriptionPending | SubscriptionSet
|
|
|
1451
1615
|
export declare interface SubscriptionStateWithFallback<T> extends SubscriptionMeta {
|
|
1452
1616
|
readonly data: T;
|
|
1453
1617
|
readonly loading: boolean;
|
|
1618
|
+
/** True when `skip` is on. `data` is the fallback, and no request is in
|
|
1619
|
+
* flight — distinguishes "showing the fallback because we chose not to ask"
|
|
1620
|
+
* from "…because the answer has not arrived". */
|
|
1621
|
+
readonly idle: boolean;
|
|
1454
1622
|
readonly isEmpty: boolean;
|
|
1455
1623
|
}
|
|
1456
1624
|
|
|
@@ -1854,11 +2022,54 @@ export declare interface UseResourceCanState {
|
|
|
1854
2022
|
|
|
1855
2023
|
export declare const useResumableAgentStream: <E = unknown>(apiName: string, rpcTag: string, options?: ResumableAgentStreamOptions<E>) => ResumableAgentStreamControls<E>;
|
|
1856
2024
|
|
|
2025
|
+
/**
|
|
2026
|
+
* @param options Sequence-wide defaults — most usefully `onError`, which fires
|
|
2027
|
+
* once per failed sequence instead of once per step.
|
|
2028
|
+
*
|
|
2029
|
+
* ```tsx
|
|
2030
|
+
* const seq = useSequence({ onError: (e) => notify.error(String(e)) })
|
|
2031
|
+
*
|
|
2032
|
+
* const attach = async (file: File) => {
|
|
2033
|
+
* const result = await seq.run(
|
|
2034
|
+
* sequence()
|
|
2035
|
+
* .step('upload', () => upload.run({ file }), {
|
|
2036
|
+
* undo: (r) => removeObject.run({ id: r.id }), // the created id, right here
|
|
2037
|
+
* })
|
|
2038
|
+
* .step('attach', (c) => createAttachment.run({ refId: c.upload.id })),
|
|
2039
|
+
* )
|
|
2040
|
+
* if (result.ok) setAttachmentId(result.data.attach.id)
|
|
2041
|
+
* }
|
|
2042
|
+
* ```
|
|
2043
|
+
*
|
|
2044
|
+
* Compensation is best-effort and tab-bound — see `sequence.ts`. When the
|
|
2045
|
+
* rollback has to survive a closed tab, move the sequence into a workflow.
|
|
2046
|
+
*/
|
|
2047
|
+
export declare const useSequence: (options?: RunSequenceOptions) => UseSequenceResult;
|
|
2048
|
+
|
|
2049
|
+
export declare interface UseSequenceResult {
|
|
2050
|
+
/** Run a built sequence. Resolves with the discriminated result; it does not
|
|
2051
|
+
* reject, which is what removes the try/catch. */
|
|
2052
|
+
readonly run: <Ctx>(builder: SequenceBuilder<Ctx>, options?: RunSequenceOptions) => Promise<SequenceResult<Ctx>>;
|
|
2053
|
+
/** True while a sequence is in flight. */
|
|
2054
|
+
readonly pending: boolean;
|
|
2055
|
+
/** The last sequence's original failure, or `undefined` after a success. */
|
|
2056
|
+
readonly error: unknown | undefined;
|
|
2057
|
+
/** The step that failed last time, for a targeted message ("couldn't create
|
|
2058
|
+
* the ticket") rather than a generic one. */
|
|
2059
|
+
readonly failedStep: string | undefined;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
1857
2062
|
export declare function useSubscription<T = unknown>(apiName: string, rpcTag: string, input: Readonly<Record<string, unknown>>, options: SubscriptionOptions<T> & {
|
|
1858
2063
|
readonly fallback: T;
|
|
1859
2064
|
}): SubscriptionStateWithFallback<T>;
|
|
1860
2065
|
|
|
1861
|
-
export declare function useSubscription<T = unknown>(apiName: string, rpcTag: string, input?: Readonly<Record<string, unknown>>, options?: SubscriptionOptions<T>
|
|
2066
|
+
export declare function useSubscription<T = unknown>(apiName: string, rpcTag: string, input?: Readonly<Record<string, unknown>>, options?: SubscriptionOptions<T> & {
|
|
2067
|
+
readonly skip?: false;
|
|
2068
|
+
}): SubscriptionState<T>;
|
|
2069
|
+
|
|
2070
|
+
export declare function useSubscription<T = unknown>(apiName: string, rpcTag: string, input: Readonly<Record<string, unknown>>, options: SubscriptionOptions<T> & {
|
|
2071
|
+
readonly skip?: boolean;
|
|
2072
|
+
}): SubscriptionState<T> | SubscriptionIdle;
|
|
1862
2073
|
|
|
1863
2074
|
/** The column shape a `<DataTable query=…>` will render — from the query's
|
|
1864
2075
|
* output Schema. */
|
|
@@ -2012,11 +2223,11 @@ export declare interface WorkflowClientMessages {
|
|
|
2012
2223
|
}>>;
|
|
2013
2224
|
}
|
|
2014
2225
|
|
|
2015
|
-
export declare type WorkflowDomainEventsState = SubscriptionState<ReadonlyArray<WorkflowDomainEventRow>> & {
|
|
2226
|
+
export declare type WorkflowDomainEventsState = (SubscriptionState<ReadonlyArray<WorkflowDomainEventRow>> | SubscriptionIdle) & {
|
|
2016
2227
|
readonly events: ReadonlyArray<WorkflowDomainEventRow>;
|
|
2017
2228
|
};
|
|
2018
2229
|
|
|
2019
|
-
export declare type WorkflowEventDeliveriesState = SubscriptionState<ReadonlyArray<WorkflowEventDeliveryRow>> & {
|
|
2230
|
+
export declare type WorkflowEventDeliveriesState = (SubscriptionState<ReadonlyArray<WorkflowEventDeliveryRow>> | SubscriptionIdle) & {
|
|
2020
2231
|
readonly deliveries: ReadonlyArray<WorkflowEventDeliveryRow>;
|
|
2021
2232
|
};
|
|
2022
2233
|
|
|
@@ -2029,7 +2240,7 @@ export declare interface WorkflowRunError {
|
|
|
2029
2240
|
|
|
2030
2241
|
export { WorkflowRunEventRow }
|
|
2031
2242
|
|
|
2032
|
-
export declare type WorkflowRunEventsState = SubscriptionState<ReadonlyArray<WorkflowRunEventRow>> & {
|
|
2243
|
+
export declare type WorkflowRunEventsState = (SubscriptionState<ReadonlyArray<WorkflowRunEventRow>> | SubscriptionIdle) & {
|
|
2033
2244
|
readonly events: ReadonlyArray<WorkflowRunEventRow>;
|
|
2034
2245
|
};
|
|
2035
2246
|
|
|
@@ -2041,11 +2252,16 @@ export declare interface WorkflowRunsFilter {
|
|
|
2041
2252
|
readonly limit?: number;
|
|
2042
2253
|
}
|
|
2043
2254
|
|
|
2044
|
-
export declare type WorkflowRunsState = SubscriptionState<ReadonlyArray<WorkflowRunRow>> & {
|
|
2255
|
+
export declare type WorkflowRunsState = (SubscriptionState<ReadonlyArray<WorkflowRunRow>> | SubscriptionIdle) & {
|
|
2045
2256
|
readonly runs: ReadonlyArray<WorkflowRunRow>;
|
|
2046
2257
|
};
|
|
2047
2258
|
|
|
2048
|
-
|
|
2259
|
+
/** `SubscriptionIdle` is in the union because `id === undefined` skips: asking
|
|
2260
|
+
* for "the run with no id" is not a pending request, it is no request. Before
|
|
2261
|
+
* the idle state existed this reported `loading: true` forever, so a caller
|
|
2262
|
+
* following the blessed `if (loading) return <Skeleton/>` rendered a permanent
|
|
2263
|
+
* skeleton whenever nothing was selected. */
|
|
2264
|
+
export declare type WorkflowRunState = (SubscriptionState<ReadonlyArray<WorkflowRunRow>> | SubscriptionIdle) & {
|
|
2049
2265
|
readonly run: WorkflowRunRow | undefined;
|
|
2050
2266
|
};
|
|
2051
2267
|
|
|
@@ -2075,7 +2291,7 @@ export { WorkflowRunStatus }
|
|
|
2075
2291
|
|
|
2076
2292
|
export { WorkflowRunStepRow }
|
|
2077
2293
|
|
|
2078
|
-
export declare type WorkflowRunStepsState = SubscriptionState<ReadonlyArray<WorkflowRunStepRow>> & {
|
|
2294
|
+
export declare type WorkflowRunStepsState = (SubscriptionState<ReadonlyArray<WorkflowRunStepRow>> | SubscriptionIdle) & {
|
|
2079
2295
|
readonly steps: ReadonlyArray<WorkflowRunStepRow>;
|
|
2080
2296
|
};
|
|
2081
2297
|
|