@civitai/blocks-react 0.43.1 → 0.44.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/README.md +87 -6
- package/dist/hooks/useAppStorage.d.ts.map +1 -1
- package/dist/hooks/useAppStorage.js +17 -4
- package/dist/hooks/useAppStorage.js.map +1 -1
- package/dist/hooks/useBuzzWorkflow.d.ts +260 -20
- package/dist/hooks/useBuzzWorkflow.d.ts.map +1 -1
- package/dist/hooks/useBuzzWorkflow.js +302 -20
- package/dist/hooks/useBuzzWorkflow.js.map +1 -1
- package/dist/hooks/useCheckpointPicker.d.ts.map +1 -1
- package/dist/hooks/useCheckpointPicker.js +7 -2
- package/dist/hooks/useCheckpointPicker.js.map +1 -1
- package/dist/hooks/useSaveImage.d.ts.map +1 -1
- package/dist/hooks/useSaveImage.js +6 -2
- package/dist/hooks/useSaveImage.js.map +1 -1
- package/dist/hooks/useSharedStorage.d.ts.map +1 -1
- package/dist/hooks/useSharedStorage.js +20 -4
- package/dist/hooks/useSharedStorage.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/liveHost.d.ts.map +1 -1
- package/dist/internal/liveHost.js +17 -0
- package/dist/internal/liveHost.js.map +1 -1
- package/dist/internal/mockHost.d.ts +48 -5
- package/dist/internal/mockHost.d.ts.map +1 -1
- package/dist/internal/mockHost.js +65 -3
- package/dist/internal/mockHost.js.map +1 -1
- package/dist/internal/validate.d.ts +66 -16
- package/dist/internal/validate.d.ts.map +1 -1
- package/dist/internal/validate.js +89 -27
- package/dist/internal/validate.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -237,8 +237,44 @@ try {
|
|
|
237
237
|
showError(estimateFailureMessage(err));
|
|
238
238
|
}
|
|
239
239
|
if (priced) {
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
// 🔴 submit() REJECTS when the reply carries no usable workflow outcome. A
|
|
241
|
+
// priced refusal is different — it RESOLVES.
|
|
242
|
+
try {
|
|
243
|
+
const snap = await submit(body); // status 'submitting' → 'polling'
|
|
244
|
+
if (snap.status === 'failed') {
|
|
245
|
+
// 🔴 A RESOLVED `failed` IS A PRICED SERVER OUTCOME — and only SOME of
|
|
246
|
+
// them are about the viewer's wallet. Affordability (per-call budget, the
|
|
247
|
+
// per-user daily Buzz cap) IS fixable by buying Buzz; the per-app velocity
|
|
248
|
+
// limit, the per-app aggregate daily cap, a fail-closed "temporarily
|
|
249
|
+
// unavailable" deny and a missing price quote are NOT. Selling Buzz for
|
|
250
|
+
// one of those takes money and fixes nothing, so branch before you offer.
|
|
251
|
+
showError(submitOutcomeMessage(snap)); // YOUR app owns this copy
|
|
252
|
+
} else {
|
|
253
|
+
await poll(snap.workflowId); // you loop this on a backoff until terminal
|
|
254
|
+
}
|
|
255
|
+
} catch (err) {
|
|
256
|
+
if (!(err instanceof WorkflowSubmitError)) throw err;
|
|
257
|
+
// Log both for the developer; render neither.
|
|
258
|
+
logForDebugging(err.message, err.snapshot.error);
|
|
259
|
+
// 🔴 TWO SEPARATE QUESTIONS — DO NOT CONJOIN THEM. `code` decides what you may
|
|
260
|
+
// say about MONEY; the id decides only whether there is something to POLL.
|
|
261
|
+
// Folding the id test into the `code` test sends a 'workflow-failed' reply
|
|
262
|
+
// whose id is 'whatif' into the reassuring arm — the exact blind-retry
|
|
263
|
+
// invitation this whole guard exists to remove.
|
|
264
|
+
if (err.code === 'workflow-failed') {
|
|
265
|
+
// 🔴 Spend MAY ALREADY BE COMMITTED. Do not tell the viewer it was free,
|
|
266
|
+
// and do not retry blindly — a retry mints a fresh idempotency key, i.e. a
|
|
267
|
+
// SECOND reservation.
|
|
268
|
+
showError('The generation may have started but did not complete. Check your history.');
|
|
269
|
+
// Only NOW ask about pollability: 'whatif' is a non-workflow sentinel.
|
|
270
|
+
if (err.snapshot.workflowId !== 'whatif') await poll(err.snapshot.workflowId);
|
|
271
|
+
} else {
|
|
272
|
+
// 🔴 'exception' means the host had no workflow to report — USUALLY nothing
|
|
273
|
+
// was queued, but a lost response or an in-progress idempotency conflict
|
|
274
|
+
// reaches this arm too. Retry with the SAME idempotencyKey, not a fresh one.
|
|
275
|
+
showError('Could not start the generation. Please try again.');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
242
278
|
}
|
|
243
279
|
```
|
|
244
280
|
|
|
@@ -252,10 +288,55 @@ if (priced) {
|
|
|
252
288
|
- The hook does **not** auto-poll. After `submit` flips status to `'polling'`,
|
|
253
289
|
the **caller** runs a `useEffect` that calls `poll(workflowId)` on a backoff
|
|
254
290
|
until the snapshot is terminal (`succeeded | failed | canceled | expired`).
|
|
255
|
-
-
|
|
256
|
-
`status: 'failed'
|
|
257
|
-
|
|
258
|
-
|
|
291
|
+
- A **priced** submit refusal comes back as a **resolved** snapshot with
|
|
292
|
+
`status: 'failed'`, an `error` string, **and a numeric `cost.total`** — the
|
|
293
|
+
price the server refused to charge. That is a workflow *outcome*, not an error.
|
|
294
|
+
Check `snap.status`, not just `try/catch`.
|
|
295
|
+
🔴 **Not all of them are affordability.** Only the per-call `buzzBudget` gate
|
|
296
|
+
and the per-user daily Buzz cap are about the wallet. The per-app **velocity**
|
|
297
|
+
limit, the per-app **aggregate daily** cap, a fail-closed **"temporarily
|
|
298
|
+
unavailable"** deny and a **missing price quote** are priced outcomes too, and
|
|
299
|
+
buying Buzz fixes none of them. Branch before you offer a top-up.
|
|
300
|
+
- **`submit` REJECTS when the reply carries no usable workflow outcome**
|
|
301
|
+
(`@civitai/blocks-react@0.44.0+`). Every failure-shaped reply reports
|
|
302
|
+
`status: 'failed'`, so `status` cannot tell them apart — **`cost` presence
|
|
303
|
+
decides resolve-vs-reject, and `workflowId` decides which rejection**:
|
|
304
|
+
- **priced refusal** → carries `cost`. **Resolves**, as above.
|
|
305
|
+
- **a reply the host built itself** (`failureSnapshot(err)`, which stamps the
|
|
306
|
+
literal `workflowId: 'failed'` — from a `catch` or a short-circuit such as
|
|
307
|
+
the moderator-review nack) → no `cost`. **Rejects** with
|
|
308
|
+
`err.code === 'exception'`, which means *the host had no workflow to report*.
|
|
309
|
+
🔴 **Not the same as "nothing happened."** Usually nothing was queued or
|
|
310
|
+
charged and a retry is fine, but a **lost response**, an **in-progress
|
|
311
|
+
idempotency conflict**, or a **transient 5xx/408/429/401** also land here,
|
|
312
|
+
and a workflow may have been created and charged. Prefer reusing the same
|
|
313
|
+
`idempotencyKey` on retry, and don't render "nothing was charged" as fact.
|
|
314
|
+
- **a failed, unpriced reply whose id is NOT that sentinel** (normally a
|
|
315
|
+
genuine orchestrator id) → no `cost`. **Rejects** with
|
|
316
|
+
`err.code === 'workflow-failed'`.
|
|
317
|
+
🔴 **Money may already be committed.** Server-side, *any* resolved submit
|
|
318
|
+
keeps its Buzz reservation "regardless of snapshot status", with no refund on
|
|
319
|
+
a non-throwing failed snapshot. So do not tell the viewer it was free, and do
|
|
320
|
+
not retry blindly — `submit()` mints a fresh `idempotencyKey` per call, so an
|
|
321
|
+
automatic retry is a second reservation. Read `err.snapshot.workflowId` (a
|
|
322
|
+
usually-pollable id) and `watch`/`poll` it to learn the workflow's actual
|
|
323
|
+
fate — guarding with `err.snapshot.workflowId !== 'whatif'` first, since the
|
|
324
|
+
server treats both `'failed'` and `'whatif'` as non-workflow sentinels.
|
|
325
|
+
|
|
326
|
+
Before that version everything resolved, so a block branching on
|
|
327
|
+
`snap.status === 'failed'` could not tell "you can't afford this" from "the
|
|
328
|
+
request failed", and one gating a money control on
|
|
329
|
+
`typeof snap.cost?.total === 'number'` saw the same dead-control shape
|
|
330
|
+
civitai/civitai#4159 describes. An ordinary in-flight reply
|
|
331
|
+
(`{ status: 'pending' }`) is cost-less too and still resolves — and so are
|
|
332
|
+
cost-less `succeeded` / `canceled` / `expired` replies. Only a *failure-shaped*
|
|
333
|
+
reply with no price rejects. `err.message` is a generic developer-facing
|
|
334
|
+
constant that makes **no claim about money** (the two codes differ on that) and
|
|
335
|
+
the server's words stay on `err.snapshot.error`, exactly as on
|
|
336
|
+
`WorkflowEstimateError` below. Note this also fires in **moderator review
|
|
337
|
+
preview**. To exercise your `catch` locally, set the mock host's
|
|
338
|
+
`generation.failSubmitException: true`.
|
|
339
|
+
- **`estimate` follows the same rule for a different question** (`@civitai/blocks-react@0.43.0+`).
|
|
259
340
|
It **rejects** with a `WorkflowEstimateError` when the reply carries no usable
|
|
260
341
|
price, rather than resolving a snapshot with no `cost`. Two things produce
|
|
261
342
|
that, and `err.code` tells them apart: `'failed'` (the estimate errored
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAppStorage.d.ts","sourceRoot":"","sources":["../../src/hooks/useAppStorage.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACjD;;;;OAIG;IACH,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnF;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC7D;;;OAGG;IACH,IAAI,CAAC,IAAI,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAClC;;;OAGG;IACH,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;CACtC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,IAAI,aAAa,
|
|
1
|
+
{"version":3,"file":"useAppStorage.d.ts","sourceRoot":"","sources":["../../src/hooks/useAppStorage.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACjD;;;;OAIG;IACH,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnF;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC7D;;;OAGG;IACH,IAAI,CAAC,IAAI,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAClC;;;OAGG;IACH,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;CACtC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,IAAI,aAAa,CAsF7C"}
|
|
@@ -35,15 +35,28 @@ export function useAppStorage() {
|
|
|
35
35
|
},
|
|
36
36
|
async set(key, value) {
|
|
37
37
|
const result = await sendTypedRequest(transport, { type: 'APP_STORAGE_SET', payload: { key, value } }, 'APP_STORAGE_SET_RESULT');
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
// A PRESENT `error` is the reject signal, not a TRUTHY one: the reply
|
|
39
|
+
// validator early-accepts anything carrying an `error` key, so
|
|
40
|
+
// `error: ''` reaches here having skipped the success-field checks.
|
|
41
|
+
// `||` (not `??`) so an empty error string still yields readable copy.
|
|
42
|
+
if (!result.ok || result.error !== undefined) {
|
|
43
|
+
throw new Error(result.error || 'storage set failed');
|
|
40
44
|
}
|
|
41
45
|
return { ok: true, sizeBytes: result.sizeBytes };
|
|
42
46
|
},
|
|
43
47
|
async delete(key) {
|
|
44
48
|
const result = await sendTypedRequest(transport, { type: 'APP_STORAGE_DELETE', payload: { key } }, 'APP_STORAGE_DELETE_RESULT');
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
// PRESENT, not truthy — see `set()` above.
|
|
50
|
+
if (!result.ok || result.error !== undefined) {
|
|
51
|
+
throw new Error(result.error || 'storage delete failed');
|
|
52
|
+
}
|
|
53
|
+
// `deleted` is optional on the wire type because an error reply omits
|
|
54
|
+
// it. On the success path the validator requires a boolean, so this is
|
|
55
|
+
// defence in depth rather than a reachable branch through the real
|
|
56
|
+
// transport — but it is what makes the `boolean` we return honest,
|
|
57
|
+
// instead of a cast asserting a guarantee this function cannot see.
|
|
58
|
+
if (typeof result.deleted !== 'boolean') {
|
|
59
|
+
throw new Error('storage delete failed: reply carried no `deleted` flag');
|
|
47
60
|
}
|
|
48
61
|
return { ok: true, deleted: result.deleted };
|
|
49
62
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAppStorage.js","sourceRoot":"","sources":["../../src/hooks/useAppStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AA6D5D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,OAAO,CAAgB,GAAG,EAAE;QACjC,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;QACjC,OAAO;YACL,KAAK,CAAC,GAAG,CAAc,GAAW;gBAChC,MAAM,MAAM,GAAG,MAAM,gBAAgB,CACnC,SAAS,EACT,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,EAC7C,wBAAwB,CACzB,CAAC;gBACF,IAAI,MAAM,CAAC,KAAK;oBAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAChD,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI,CAAa,CAAC;YAC5C,CAAC;YACD,KAAK,CAAC,GAAG,CAAc,GAAW,EAAE,KAAQ;gBAC1C,MAAM,MAAM,GAAG,MAAM,gBAAgB,CACnC,SAAS,EACT,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EACpD,wBAAwB,CACzB,CAAC;gBACF,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"useAppStorage.js","sourceRoot":"","sources":["../../src/hooks/useAppStorage.ts"],"names":[],"mappings":"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"}
|
|
@@ -143,20 +143,13 @@ export interface WatchWorkflowOptions {
|
|
|
143
143
|
* off a third party's default-printed surface. `test/useBuzzWorkflow.test.tsx`
|
|
144
144
|
* pins it with a realistic `Unique constraint failed: Key (email)=(…)` fixture.
|
|
145
145
|
*
|
|
146
|
-
* 🔴
|
|
147
|
-
* `WORKFLOW_SUBMITTED`
|
|
148
|
-
* `status
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* own blast radius on the recovery path (a fix MUST keep the budget-rejection arm
|
|
154
|
-
* RESOLVING, or the top-up flow breaks) and is deliberately NOT attempted here —
|
|
155
|
-
* so `submit` is left resolving BOTH, and the accompanying test pins only the
|
|
156
|
-
* budget-rejection producer, not a claim that submit is correct.
|
|
157
|
-
*
|
|
158
|
-
* 🔴 TRACKED, NOT FORGOTTEN: civitai/civitai-app-starters#251. Read it before
|
|
159
|
-
* touching `submit`'s failure handling.
|
|
146
|
+
* 🔴 `submit` HAS ITS OWN CLASS — {@link WorkflowSubmitError} — BECAUSE ITS
|
|
147
|
+
* DISCRIMINATOR IS DIFFERENT. On `WORKFLOW_SUBMITTED` both producers report
|
|
148
|
+
* `status:'failed'`, so `status` separates nothing there and `cost` presence is
|
|
149
|
+
* what does. Do not merge the two classes or reuse this one for submit: the
|
|
150
|
+
* question each answers is different ("was a usable price returned?" vs "was a
|
|
151
|
+
* workflow actually queued?") and so is the arm that must keep resolving.
|
|
152
|
+
* (civitai/civitai-app-starters#251, the `submit` half of civitai/civitai#4159.)
|
|
160
153
|
*/
|
|
161
154
|
export declare class WorkflowEstimateError extends Error {
|
|
162
155
|
/**
|
|
@@ -199,6 +192,170 @@ export declare class WorkflowEstimateError extends Error {
|
|
|
199
192
|
readonly snapshot: BlockWorkflowSnapshot;
|
|
200
193
|
constructor(snapshot: BlockWorkflowSnapshot, code: 'failed' | 'no-cost');
|
|
201
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Why {@link UseBuzzWorkflowReturn.submit} rejected. **The two differ on whether
|
|
197
|
+
* money may already have moved** — see {@link WorkflowSubmitError.code}.
|
|
198
|
+
*/
|
|
199
|
+
export type WorkflowSubmitErrorCode = 'exception' | 'workflow-failed';
|
|
200
|
+
/**
|
|
201
|
+
* Thrown by {@link UseBuzzWorkflowReturn.submit} when the host's reply carries no
|
|
202
|
+
* usable workflow outcome — either the submit ERRORED before anything was queued,
|
|
203
|
+
* or a workflow-shaped reply came back already failed with no price.
|
|
204
|
+
*
|
|
205
|
+
* 🔴 READ {@link WorkflowSubmitError.code} BEFORE SAYING ANYTHING ABOUT MONEY.
|
|
206
|
+
* The two codes differ on exactly that, and getting it wrong is expensive in both
|
|
207
|
+
* directions — see the per-code notes below.
|
|
208
|
+
*
|
|
209
|
+
* 🔴 WHY THIS EXISTS — civitai/civitai-app-starters#251, the `submit` half of
|
|
210
|
+
* civitai/civitai#4159. A server-side `blocks.submitWorkflow` throw cannot reach
|
|
211
|
+
* the block as a rejection; the reply crosses `postMessage`. The host instead
|
|
212
|
+
* posts a well-formed `WORKFLOW_SUBMITTED` carrying its `failureSnapshot(err)` —
|
|
213
|
+
* `{ workflowId: 'failed', status: 'failed', error: '<server message>' }`, with
|
|
214
|
+
* **no `cost`**. That snapshot is perfectly legal, so `submit()` resolved it and
|
|
215
|
+
* moved the hook to `'done'`, handing the caller a "workflow" that does not
|
|
216
|
+
* exist.
|
|
217
|
+
*
|
|
218
|
+
* 🔴 TWO PRODUCERS, ONE `status` — AND HERE `status` DISCRIMINATES NOTHING. This
|
|
219
|
+
* is what makes submit's version of the defect different from estimate's, and
|
|
220
|
+
* why {@link WorkflowEstimateError}'s guard could not simply be copied:
|
|
221
|
+
*
|
|
222
|
+
* - **budget / spend-cap REJECTION** — a legitimate OUTCOME. The server quotes
|
|
223
|
+
* the price it refused to charge, so the snapshot carries a numeric
|
|
224
|
+
* `cost.total`. Blocks recover from it (open a top-up flow), so it MUST keep
|
|
225
|
+
* RESOLVING. Every such exit on the server attaches a cost: the per-call
|
|
226
|
+
* `buzzBudget` gate, the per-user daily Buzz cap, the per-app aggregate and
|
|
227
|
+
* velocity caps, and the dev-tunnel session cap — on all three body kinds.
|
|
228
|
+
* - **caught server EXCEPTION** — `failureSnapshot(err)`, no `cost`, and the
|
|
229
|
+
* `'failed'` sentinel id. The host had no workflow to report — usually
|
|
230
|
+
* nothing was queued, but a lost response or an in-progress idempotency
|
|
231
|
+
* conflict lands here too. Rejects as `'exception'`.
|
|
232
|
+
* - **a reply that came back failed and unpriced with a NON-sentinel id** —
|
|
233
|
+
* normally a genuine orchestrator id, no `cost` (the server's
|
|
234
|
+
* `snapshotFromWorkflow` omits the key whenever `workflow.cost?.total` is not
|
|
235
|
+
* numeric). Rejects as `'workflow-failed'`, and **money may already be
|
|
236
|
+
* committed** — see that code's note.
|
|
237
|
+
*
|
|
238
|
+
* So the rule is `cost` presence, and ONLY among failure-shaped replies: an
|
|
239
|
+
* ordinary in-flight submit (`{ workflowId:'wf_…', status:'pending' }`) is
|
|
240
|
+
* cost-less too and must keep resolving, which is why the guard is not keyed on
|
|
241
|
+
* `cost` alone either. Both clauses are load-bearing; each has its own control
|
|
242
|
+
* in `test/useBuzzWorkflow.test.tsx`.
|
|
243
|
+
*
|
|
244
|
+
* 🔴 THE GUARD IS DELIBERATELY NOT WIDENED TO ALL TERMINAL STATUSES. A cost-less
|
|
245
|
+
* `succeeded` / `canceled` / `expired` reply is a legitimate outcome and must
|
|
246
|
+
* RESOLVE; only `'failed'` is unusable. Swapping the clause for
|
|
247
|
+
* `TERMINAL_STATUSES.has(...)` is a silent WIDENING that a delete-only mutation
|
|
248
|
+
* sweep cannot see — `test/useBuzzWorkflow.test.tsx` pins each of those three
|
|
249
|
+
* statuses explicitly for exactly that reason.
|
|
250
|
+
*
|
|
251
|
+
* 🔴 THREE FIELDS, THREE AUDIENCES — AND **NONE OF THEM IS VIEWER-FACING COPY**,
|
|
252
|
+
* exactly as on {@link WorkflowEstimateError}. Two apps migrating to `0.43.0`
|
|
253
|
+
* piped that class's `err.message` straight into rendered UI
|
|
254
|
+
* (civitai/civitai-app-starters#253); the same split applies here so the same
|
|
255
|
+
* mistake is not re-enabled on the submit path.
|
|
256
|
+
*
|
|
257
|
+
* - {@link WorkflowSubmitError.snapshot}`.error` — **the DIAGNOSTIC read.** The
|
|
258
|
+
* server's own words, verbatim. **Server-authored and UNSANITISED**:
|
|
259
|
+
* civitai's `errorHandling.ts` documents that raw upstream text —
|
|
260
|
+
* Prisma/`pg` column and constraint names among it — can reach this field.
|
|
261
|
+
* Log it, or show it in a developer-facing error surface. **NEVER render it
|
|
262
|
+
* verbatim into markup**, and think before shipping it to a third-party
|
|
263
|
+
* error tracker.
|
|
264
|
+
* - {@link WorkflowSubmitError.message} — **DEVELOPER-FACING.** A CONSTANT
|
|
265
|
+
* template with only `code` interpolated. It contains no server text, so it
|
|
266
|
+
* is safe to LOG and safe to let a stack trace print — deliberate, because
|
|
267
|
+
* `message` is what an uncaught rejection prints and what a third-party
|
|
268
|
+
* block's error reporter ships upstream by default. **It is NOT intended for
|
|
269
|
+
* display to viewers** and **its exact wording is NOT a contract.**
|
|
270
|
+
* - {@link WorkflowSubmitError.code} — **the BRANCH TARGET**, and the only
|
|
271
|
+
* stable one.
|
|
272
|
+
*
|
|
273
|
+
* 🔴 DO NOT "SIMPLIFY" THIS BY PUTTING `snapshot.error` BACK ON `message`, and do
|
|
274
|
+
* not widen the guard to reject a budget rejection. The first re-opens #253; the
|
|
275
|
+
* second breaks the top-up recovery flow, which is the one thing #251 says must
|
|
276
|
+
* not break.
|
|
277
|
+
*/
|
|
278
|
+
export declare class WorkflowSubmitError extends Error {
|
|
279
|
+
/**
|
|
280
|
+
* WHICH PRODUCER this was, structurally — so a caller branches on an enum
|
|
281
|
+
* rather than on prose. It is the ONLY stable branch target here:
|
|
282
|
+
* `snapshot.error` is server-authored and can change without notice, and
|
|
283
|
+
* {@link WorkflowSubmitError.message} is a generic developer-facing summary
|
|
284
|
+
* whose exact wording is not a contract either.
|
|
285
|
+
*
|
|
286
|
+
* 🔴 THE TWO CODES DIFFER ON WHETHER MONEY MOVED. Do not collapse them, and do
|
|
287
|
+
* not write recovery copy that ignores the distinction.
|
|
288
|
+
*
|
|
289
|
+
* - `'exception'` — the host built this reply itself
|
|
290
|
+
* (`failureSnapshot(err)`, id {@link HOST_SYNTHESISED_WORKFLOW_ID}), from a
|
|
291
|
+
* `catch` OR from a non-catch short-circuit such as the moderator-review
|
|
292
|
+
* nack. **It means the host had no workflow to report — NOT that nothing
|
|
293
|
+
* happened.**
|
|
294
|
+
*
|
|
295
|
+
* 🔴 USUALLY nothing was queued and nothing was charged, and a retry is the
|
|
296
|
+
* sensible recovery. But the same shape is reachable when a workflow MAY have
|
|
297
|
+
* been created and charged, because the failure happened after the request
|
|
298
|
+
* left the block:
|
|
299
|
+
* - a **lost response** — the server's own catch concedes that "the
|
|
300
|
+
* orchestrator externalId dedupe still protects a retry that DID create a
|
|
301
|
+
* workflow server-side despite a lost response". What that path refunds
|
|
302
|
+
* is its own reservation and cap counters; the Buzz debit happens inside
|
|
303
|
+
* the orchestrator's submit.
|
|
304
|
+
* - an **in-progress idempotency CONFLICT** — a concurrent first attempt is
|
|
305
|
+
* still in flight and may be committing right now. Retrying blind is the
|
|
306
|
+
* worst option there.
|
|
307
|
+
* - a **transient transport failure** (5xx / 408 / 429 / 401), which the
|
|
308
|
+
* `dev:live` host collapses into this same shape.
|
|
309
|
+
*
|
|
310
|
+
* So prefer **reusing the same {@link SubmitWorkflowOptions.idempotencyKey}**
|
|
311
|
+
* on retry rather than letting a fresh one be minted, and do not render
|
|
312
|
+
* "nothing was charged" to a viewer as a certainty. There is no workflow id
|
|
313
|
+
* on this arm to poll.
|
|
314
|
+
*
|
|
315
|
+
* - `'workflow-failed'` — the id was NOT the host's sentinel, so the reply came
|
|
316
|
+
* from the server's `snapshotFromWorkflow`: normally a real orchestrator id,
|
|
317
|
+
* already `'failed'` and with no price.
|
|
318
|
+
*
|
|
319
|
+
* 🔴 **DO NOT TELL THE VIEWER NOTHING WAS CHARGED, AND DO NOT BLIND-RETRY.**
|
|
320
|
+
* Server-side, `blocks.submitWorkflow` treats *any resolved* submit as
|
|
321
|
+
* money-COMMITTED: its own comment is "A resolved submit is money-COMMITTED
|
|
322
|
+
* (the reservation is kept regardless of snapshot status)… we do NOT refund
|
|
323
|
+
* on a non-throwing failed snapshot", and `finalizeGenIdempotency` runs on
|
|
324
|
+
* that path. So Buzz may already be spent for this call.
|
|
325
|
+
*
|
|
326
|
+
* A retry is a SECOND reservation unless you reuse the same
|
|
327
|
+
* {@link SubmitWorkflowOptions.idempotencyKey} — `submit()` mints a fresh key
|
|
328
|
+
* per call by default, so an automatic retry double-reserves.
|
|
329
|
+
*
|
|
330
|
+
* 🔴 `err.snapshot.workflowId` IS USUALLY POLLABLE, BUT CHECK IT FIRST — this
|
|
331
|
+
* arm is defined by what the id is NOT. The server emits `workflow.id ??
|
|
332
|
+
* 'whatif'`, and it treats **both** `'failed'` and `'whatif'` as non-workflow
|
|
333
|
+
* sentinels (it skips its own persistence and settle steps on either). So
|
|
334
|
+
* `'whatif'` lands here — correctly, because the cautious money reading still
|
|
335
|
+
* applies — but there is nothing to poll. Guard with
|
|
336
|
+
* `err.snapshot.workflowId !== 'whatif'` before calling
|
|
337
|
+
* {@link UseBuzzWorkflowReturn.watch} / {@link UseBuzzWorkflowReturn.poll} to
|
|
338
|
+
* learn the workflow's actual fate before spending again.
|
|
339
|
+
*
|
|
340
|
+
* A union rather than a boolean so a future producer gets its own code without
|
|
341
|
+
* breaking a caller's `switch`.
|
|
342
|
+
*
|
|
343
|
+
* 🔴 A BUDGET REJECTION NEVER ARRIVES HERE. It resolves, and is read off the
|
|
344
|
+
* returned snapshot as `status === 'failed'` with a numeric `cost.total`.
|
|
345
|
+
*/
|
|
346
|
+
readonly code: WorkflowSubmitErrorCode;
|
|
347
|
+
/**
|
|
348
|
+
* The snapshot the host replied with, VERBATIM — including `snapshot.error`,
|
|
349
|
+
* the server's own words, when there are any.
|
|
350
|
+
*
|
|
351
|
+
* 🔴 `snapshot.error` IS SERVER-AUTHORED AND UNSANITISED. It is where the
|
|
352
|
+
* reason lives, but raw upstream text — Prisma/`pg` column and constraint
|
|
353
|
+
* names among it — can reach that field. Log it, show it in a developer-facing
|
|
354
|
+
* error surface, **NEVER render it verbatim into markup**.
|
|
355
|
+
*/
|
|
356
|
+
readonly snapshot: BlockWorkflowSnapshot;
|
|
357
|
+
constructor(snapshot: BlockWorkflowSnapshot, code: WorkflowSubmitErrorCode);
|
|
358
|
+
}
|
|
202
359
|
/** Optional per-submit controls. */
|
|
203
360
|
export interface SubmitWorkflowOptions {
|
|
204
361
|
/**
|
|
@@ -207,6 +364,11 @@ export interface SubmitWorkflowOptions {
|
|
|
207
364
|
* host+orchestrator collapse it to ONE Buzz charge instead of double-charging.
|
|
208
365
|
* Omit → the hook generates a fresh key per `submit()` call (each call is a new
|
|
209
366
|
* logical submit); pass a stable id (e.g. a grid-cell id) to make a retry safe.
|
|
367
|
+
*
|
|
368
|
+
* 🔴 THIS IS THE FIELD THAT MAKES A RETRY AFTER A `'workflow-failed'` REJECTION
|
|
369
|
+
* SAFE. That code means a workflow probably exists and its spend may already
|
|
370
|
+
* be committed server-side; retrying WITHOUT reusing the key mints a fresh one
|
|
371
|
+
* and therefore a SECOND reservation. See {@link WorkflowSubmitError.code}.
|
|
210
372
|
*/
|
|
211
373
|
idempotencyKey?: string;
|
|
212
374
|
}
|
|
@@ -234,6 +396,43 @@ interface UseBuzzWorkflowReturn {
|
|
|
234
396
|
* estimate's price sitting in `result` for a Confirm gate to read.
|
|
235
397
|
*/
|
|
236
398
|
estimate: (body: WorkflowBody) => Promise<BlockWorkflowSnapshot>;
|
|
399
|
+
/**
|
|
400
|
+
* Queue a workflow. Resolves ONLY with a reply that represents a real workflow
|
|
401
|
+
* OUTCOME — one that was queued, or one the server priced and then refused.
|
|
402
|
+
*
|
|
403
|
+
* 🔴 REJECTS with {@link WorkflowSubmitError} when the reply is failure-shaped
|
|
404
|
+
* and carries no price. Wrap every call in `try/catch`; see that class for why
|
|
405
|
+
* resolving such a reply was the `submit` half of civitai/civitai#4159.
|
|
406
|
+
* **Check `err.code` before saying anything about money** — and note that
|
|
407
|
+
* NEITHER code guarantees nothing was spent. `'exception'` usually means
|
|
408
|
+
* nothing was queued or charged, but a lost response or an in-progress
|
|
409
|
+
* idempotency conflict reaches it too; `'workflow-failed'` means a workflow
|
|
410
|
+
* PROBABLY exists (the `'whatif'` sentinel lands here too and has nothing to
|
|
411
|
+
* poll — guard before polling) and its spend may already be committed. Do not
|
|
412
|
+
* tell the viewer it was free, and on either code prefer reusing the same
|
|
413
|
+
* {@link SubmitWorkflowOptions.idempotencyKey}.
|
|
414
|
+
*
|
|
415
|
+
* 🔴 A BUDGET / SPEND-CAP REJECTION STILL RESOLVES, and that is deliberate. It
|
|
416
|
+
* is a documented outcome, not an error: the server quotes what it refused to
|
|
417
|
+
* charge, so the resolved snapshot has `status === 'failed'` AND a numeric
|
|
418
|
+
* `cost.total`. THAT is the shape to branch on when offering a top-up —
|
|
419
|
+
* `useBuzzPurchase().openPurchaseModal()` — not a `catch`.
|
|
420
|
+
*
|
|
421
|
+
* 🔴 BUT A RESOLVED `'failed'` IS NOT ALWAYS AN AFFORDABILITY PROBLEM, so do not
|
|
422
|
+
* wire every one of them to a purchase modal. The per-app **velocity** limit,
|
|
423
|
+
* the per-app **aggregate daily** cap, a fail-closed "temporarily unavailable"
|
|
424
|
+
* deny and a **missing price quote** are all priced, resolving outcomes that
|
|
425
|
+
* buying Buzz cannot fix.
|
|
426
|
+
*
|
|
427
|
+
* 🔴 THIS ALSO INCLUDES MODERATOR REVIEW PREVIEW. While an app is under review
|
|
428
|
+
* the host short-circuits every workflow request with
|
|
429
|
+
* `failureSnapshot('not available in review preview')`, so `submit()` rejects
|
|
430
|
+
* there where it used to resolve — the correct reading (no workflow was
|
|
431
|
+
* queued), and why the catch is not optional.
|
|
432
|
+
*
|
|
433
|
+
* `result` is updated to the returned snapshot BEFORE any rejection, so a
|
|
434
|
+
* failed submit can never leave a previous submit's workflow in `result`.
|
|
435
|
+
*/
|
|
237
436
|
submit: (body: WorkflowBody, options?: SubmitWorkflowOptions) => Promise<BlockWorkflowSnapshot>;
|
|
238
437
|
/**
|
|
239
438
|
* ONE host round-trip. The low-level pull primitive — you almost certainly
|
|
@@ -282,10 +481,20 @@ interface UseBuzzWorkflowReturn {
|
|
|
282
481
|
* Orchestrates the estimate → confirm → submit → poll dance through the
|
|
283
482
|
* host-mediated `postMessage` path.
|
|
284
483
|
*
|
|
285
|
-
* The host enforces budget rules (`cost_estimate <= token.buzzBudget`)
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
484
|
+
* The host enforces budget rules (`cost_estimate <= token.buzzBudget`) before
|
|
485
|
+
* forwarding to the orchestrator.
|
|
486
|
+
*
|
|
487
|
+
* 🔴 A BUDGET REFUSAL DOES **NOT** REJECT — IT RESOLVES. It comes back as a
|
|
488
|
+
* snapshot with `status: 'failed'`, an `error` string and the `cost` the server
|
|
489
|
+
* declined to charge, and THAT resolved shape is the cue to call
|
|
490
|
+
* `useBuzzPurchase().openPurchaseModal()`. What DOES reject is a submit with no
|
|
491
|
+
* usable outcome — see {@link WorkflowSubmitError}. Routing a rejection into a
|
|
492
|
+
* top-up sells Buzz for a failure Buzz cannot fix.
|
|
493
|
+
*
|
|
494
|
+
* 🔴 NOR IS EVERY RESOLVED `'failed'` AN AFFORDABILITY PROBLEM. The per-app
|
|
495
|
+
* velocity limit, the per-app aggregate daily cap, a fail-closed "temporarily
|
|
496
|
+
* unavailable" deny and a missing price quote are all priced, resolving outcomes
|
|
497
|
+
* too. Branch on the message/your own policy before offering to sell anything.
|
|
289
498
|
*
|
|
290
499
|
* AFTER `submit` FLIPS `status` TO `'polling'`, USE `watch(workflowId)`. It owns
|
|
291
500
|
* the loop, resolves on the terminal snapshot, and pushes every intermediate
|
|
@@ -347,8 +556,39 @@ interface UseBuzzWorkflowReturn {
|
|
|
347
556
|
* logForDebugging(err.message, err.snapshot.error); // developer-facing: LOG only
|
|
348
557
|
* showError(estimateFailureMessage(err)); // viewer-facing: app-owned
|
|
349
558
|
* }
|
|
350
|
-
*
|
|
351
|
-
*
|
|
559
|
+
* // 🔴 submit() REJECTS when the reply carries no usable workflow outcome, so it
|
|
560
|
+
* // needs a catch too — and the two codes differ on whether MONEY MOVED.
|
|
561
|
+
* try {
|
|
562
|
+
* const snap = await submit(body); // status 'submitting' → 'polling'
|
|
563
|
+
* if (snap.status === 'failed') {
|
|
564
|
+
* // RESOLVED + priced = a server outcome (affordability, a cap, a velocity
|
|
565
|
+
* // limit, a transient deny). Only some of those are fixable by buying Buzz.
|
|
566
|
+
* showError(submitOutcomeMessage(snap));
|
|
567
|
+
* } else {
|
|
568
|
+
* const done = await watch(snap.workflowId, { onUpdate: render }); // → terminal
|
|
569
|
+
* if (done.status === 'succeeded') setImages(done.imageUrls ?? []);
|
|
570
|
+
* }
|
|
571
|
+
* } catch (err) {
|
|
572
|
+
* if (!(err instanceof WorkflowSubmitError)) throw err;
|
|
573
|
+
* logForDebugging(err.message, err.snapshot.error); // developer-facing: LOG only
|
|
574
|
+
* // 🔴 TWO SEPARATE QUESTIONS: `code` decides what you may say about MONEY,
|
|
575
|
+
* // the id decides only whether there is anything to POLL. Conjoining them
|
|
576
|
+
* // sends a 'whatif' id into the reassuring arm.
|
|
577
|
+
* if (err.code === 'workflow-failed') {
|
|
578
|
+
* // Spend may already be committed. Do NOT retry blindly (that mints a new
|
|
579
|
+
* // idempotency key = a second reservation) and do NOT claim nothing was
|
|
580
|
+
* // charged.
|
|
581
|
+
* showError('The generation may have started but did not complete.');
|
|
582
|
+
* // 'whatif' is a non-workflow sentinel — nothing behind it to poll.
|
|
583
|
+
* if (err.snapshot.workflowId !== 'whatif') {
|
|
584
|
+
* await watch(err.snapshot.workflowId, { onUpdate: render });
|
|
585
|
+
* }
|
|
586
|
+
* } else {
|
|
587
|
+
* // 'exception' — usually nothing was queued. Retry, but reuse the SAME
|
|
588
|
+
* // idempotencyKey: a lost response can also land here.
|
|
589
|
+
* showError('Could not start the generation. Please try again.');
|
|
590
|
+
* }
|
|
591
|
+
* }
|
|
352
592
|
*/
|
|
353
593
|
export declare function useBuzzWorkflow(): UseBuzzWorkflowReturn;
|
|
354
594
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuzzWorkflow.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AA8BnG;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAW7C,iEAAiE;AACjE,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACrD;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAyBD
|
|
1
|
+
{"version":3,"file":"useBuzzWorkflow.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"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"}
|