From 3447629ec1754253fe40c2d56423cb2f0759d66f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 05:46:02 +0000 Subject: [PATCH 1/2] docs(api): narrow the client-SDK flow-rejection example's catch binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `automation.execute` example inside the API-surface tour fence read `err.httpStatus` / `err.code` / `err.details?.errorMessage` / `err.details?.summary` off an untyped `catch` binding. Under `strict` that binding is `unknown`, so a reader copying it got four TS18046 errors — measured, before: 4x "'err' is of type 'unknown'". No guard was in scope where the block sat, and the tour fence's subject is the client surface rather than error handling, so the example moves out of the tour into its own "Flow execution errors" subsection under Error Handling, where it reuses the `isApiError` guard the section already declares. `details` is `unknown` on that shared shape because what rides in it is per-surface; the automation door's own artefacts (`errorMessage`, `summary`) are narrowed one step further, and only on FLOW_FAILED, which is the only arm that carries them. The tour keeps a pointer in its place, and the page's contributor note is updated to say what is now true of the page's three catch bindings. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6 --- content/docs/api/client-sdk.mdx | 73 ++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/content/docs/api/client-sdk.mdx b/content/docs/api/client-sdk.mdx index bc0c02d897..b6692d9303 100644 --- a/content/docs/api/client-sdk.mdx +++ b/content/docs/api/client-sdk.mdx @@ -43,16 +43,21 @@ pnpm add @objectstack/client React Hooks block — the three that stand alone. What is not: every block that continues Quick Start's implied context. Quick Start establishes `client` once and each later block reads it, so a marker there reds with TS2304 - "Cannot find name 'client'". The two Error Handling blocks USED to add + "Cannot find name 'client'". The Error Handling blocks USED to add TS18046 ("'error' is of type 'unknown'") on top of that; they now narrow the `catch` binding through a guard the first block declares, which is what a reader's own strict project needs anyway. That leaves only the page-wide TS2304 convention between them and a marker — still unmarked here because - the marker question is its own card, so nothing compiles these two blocks. - The remaining unnarrowed `catch` is inside the API-surface tour block - (`automation.execute`), which is a fragment for other reasons. - - Measured with all 13 fences marked, then reverted: 114 diagnostics, TS2304 / + the marker question is its own card, so nothing compiles them. + The page's third `catch` — the flow-rejection example — used to sit inside + the API-surface tour fence, where no guard was in scope to narrow it and the + fence's own subject is the client surface, not error handling. It now stands + alone under Error Handling as "Flow execution errors", reads the same guard, + and the tour keeps a pointer in its place. No unnarrowed `catch` binding is + left on this page. + + Measured with all fences marked, then reverted — 13 fences at the time, + before the flow-rejection block was split out of the tour: 114 diagnostics, TS2304 / TS18046 / TS18004 / TS2591, spread over the nine continuation blocks — and ZERO TS2307. Before the carve-out the same sweep produced 128 diagnostics including TS2307 on every SDK import. Not one diagnostic in either sweep was @@ -428,15 +433,10 @@ await client.i18n.getFieldLabels('account', 'zh-CN'); await client.automation.trigger('send_welcome_email', { userId }); // A flow that does not run REJECTS — it does not resolve with an inner -// `{ success: false }`. Branch on `err.code`, not on the resolved value: -try { - await client.automation.execute('order_approval', { params: { orderId } }); -} catch (err) { - err.httpStatus; // 409 | 422 | 400 | 404 - err.code; // 'FLOW_DISABLED' | 'FLOW_NO_START_NODE' | 'FLOW_FAILED' - err.details?.errorMessage; // the flow author's own text, on a FLOW_FAILED - err.details?.summary; // which node failed, on a FLOW_FAILED -} +// `{ success: false }`. Branch on the thrown error's `code`, not on the +// resolved value; the narrowed `catch` that needs is "Flow execution errors" +// under Error Handling, below. +await client.automation.execute('order_approval', { params: { orderId } }); // Screen flows pause for user input instead of completing. `execute()` returns // `{ status: 'paused', runId, screen }`; render the screen, then resume the run @@ -702,6 +702,49 @@ condition. Neither is guaranteed on the wire — as the narrowing example shows, `error.category` and `error.retryable` are present only when the server sent them, and the REST server's per-field validation envelope sends neither. +### Flow execution errors + +`client.automation.execute()` **rejects** when the flow does not run — it does +not resolve with an inner `{ success: false }` — so branch on the thrown error, +not on the resolved value. Its refusal codes are ledger-registered rather than +members of the standard catalog above, which makes them service-specific, not +unofficial. + +Only a run that actually dispatched has artefacts to report, so `details` — +`unknown` on the shared shape, because what rides in it is per-surface — is +narrowed one step further here: + +```typescript +/** + * What the automation door attaches to `details` on a `FLOW_FAILED`: the run's + * own artefacts. A flow that never dispatched (`FLOW_DISABLED`, + * `FLOW_NO_START_NODE`, or an unknown flow's 404) has no author text and no + * node log to point at, which is why both members are optional. + */ +interface FlowFailureDetails { + errorMessage?: string; // the flow author's own text (`flow.errorMessage`) + summary?: unknown; // per-node accounting (spec's `FlowRunSummary`) +} + +function hasFlowFailureDetails( + error: ObjectStackApiError, +): error is ObjectStackApiError & { details: FlowFailureDetails } { + return typeof error.details === 'object' && error.details !== null; +} + +try { + await client.automation.execute('order_approval', { params: { orderId } }); +} catch (err) { + if (!isApiError(err)) throw err; // not a server response — rethrow + console.error(err.httpStatus); // 409 | 422 | 400 | 404 + console.error(err.code); // 'FLOW_DISABLED' | 'FLOW_NO_START_NODE' | 'FLOW_FAILED' + if (err.code === 'FLOW_FAILED' && hasFlowFailureDetails(err)) { + console.error(err.details.errorMessage); // the flow author's own text + console.error(err.details.summary); // which node failed + } +} +``` + --- ## Configuration From 40556bbdf81f97988163d431b339f16fbccd42d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 05:55:11 +0000 Subject: [PATCH 2/2] docs(api): reword the tour's pointer and reflow the contributor note MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up wording pass on the same block — no change to the narrowing. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6 --- content/docs/api/client-sdk.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/docs/api/client-sdk.mdx b/content/docs/api/client-sdk.mdx index b6692d9303..e21dbec146 100644 --- a/content/docs/api/client-sdk.mdx +++ b/content/docs/api/client-sdk.mdx @@ -57,8 +57,8 @@ pnpm add @objectstack/client left on this page. Measured with all fences marked, then reverted — 13 fences at the time, - before the flow-rejection block was split out of the tour: 114 diagnostics, TS2304 / - TS18046 / TS18004 / TS2591, spread over the nine continuation blocks — and + before the flow-rejection block was split out of the tour: 114 diagnostics, + TS2304 / TS18046 / TS18004 / TS2591, spread over the nine continuation blocks — and ZERO TS2307. Before the carve-out the same sweep produced 128 diagnostics including TS2307 on every SDK import. Not one diagnostic in either sweep was a doc-vs-SDK divergence; that is what the three marked blocks now hold. @@ -434,8 +434,8 @@ await client.automation.trigger('send_welcome_email', { userId }); // A flow that does not run REJECTS — it does not resolve with an inner // `{ success: false }`. Branch on the thrown error's `code`, not on the -// resolved value; the narrowed `catch` that needs is "Flow execution errors" -// under Error Handling, below. +// resolved value. The narrowed `catch` this needs under `strict` is its own +// block below: "Flow execution errors", under Error Handling. await client.automation.execute('order_approval', { params: { orderId } }); // Screen flows pause for user input instead of completing. `execute()` returns