mirror of
https://github.com/vercel/workflow.git
synced 2026-09-14 19:59:43 +08:00
5dbeecbb82
* docs: document run idempotency * docs: address idempotency review feedback * docs: make hook tokens the idempotency pattern * docs: address toolbar idempotency feedback * docs: clarify idempotency page description * docs: scope idempotency descriptions * docs: move step idempotency example under section * docs: simplify idempotency guidance * docs: simplify idempotency cookbook * docs: add empty changeset Signed-off-by: Nathan Rajlich <n@n8.io> * docs: address idempotency review feedback * feat: add hook ready promise * docs: mention conflicting hook run id * test: cover hook ready continuation scheduling * feat: replace hook.ready with hook.hasConflict (Promise<boolean>) - hook.hasConflict resolves true when the token is owned by another active hook, false once registration is committed — no throw, so workflows can branch on conflicts early. Awaiting it suspends the workflow to commit the hook registration (createHook alone does not). - Chain the already-created fast-path through promiseQueue so resolution order matches event-log order (review feedback). - Skip inline step execution when a suspension has an awaited hook creation so the hasConflict continuation can advance independently of step execution (review feedback). - Update unit tests, e2e tests, workbench workflows, and v4/v5 docs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: fix inconsistent hasConflict bullet in create-webhook reference State both resolution values explicitly (true = token already owned, false = registered) instead of a parenthetical that only described the false case. * docs: require docs preview links in PR descriptions for docs changes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: restore SWC Plugin heading in AGENTS.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: adopt hook.hasConflict in run idempotency docs - Primary claim pattern is now `if (await hook.hasConflict)` instead of try/catch on HookConflictError; payload awaits still reject with HookConflictError (with conflictingRunId) when the owner's run ID is needed. - Route example returns the active owner via resumeHook()'s runId instead of threading conflictingRunId through the workflow result. - Update claim-pattern prose across start(), getHookByToken(), world storage, scheduling, workflow composition, and cookbook idempotency pages (v4 + v5). - Add @skip-typecheck marker to the cross-block route sample, fixing a pre-existing docs typecheck failure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: move resume-or-start guidance into a dedicated resumeHook example The early callout was too vague and out of place at the top of the API reference. Replace it with a 'Resume or Start' example section that explains the flow, shows the resume-first/start-then-retry route, and links to the run idempotency pattern. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: detect the concurrent-start race via runId comparison instead of awaiting returnValue The 'Resume or Start' example returned the just-started run's runId with reused: false even when a concurrent request's run won the token race — the payload had reached the actual owner, so the response pointed callers at a run that exits as a duplicate. The foundations route handled the race correctly but by awaiting run.returnValue, blocking the HTTP response on full workflow completion. resumeHook() always resolves against the actual active owner, so comparing the resumed hook's runId with the started run's runId detects the race in both examples — race-correct and non-blocking. * feat: replace hook.hasConflict with hook.getConflict (Promise<Run | null>) hasConflict's boolean didn't expose WHICH run owns the token, so the duplicate run couldn't act on the conflict. getConflict resolves with null once registration commits, or with a Run handle for the conflicting run — letting the workflow return/log the owner's runId, inspect its status, await its result, or cancel it and continue, all in code. The workflow-mode create-hook module exposes the bundle's compiled Run class (durable step-proxy methods) on a well-known symbol so the host- side hook consumer can construct the conflicting run inside the VM. Contexts without the class (plain unit tests) fall back to a { runId } object, which is also the documented v4 shape (no native Run serialization in v4). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: adopt hook.getConflict and add conflict-handling strategy guide Run idempotency docs now use getConflict (resolves with the conflicting Run in v5, { runId } in v4) and document code-driven conflict strategies in place of static ID-reuse policies: reject the duplicate, adopt the owner's result, inspect before deciding, signal the owner via resumeHook, and supersede via cancel-and-reclaim. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: never resolve getConflict with a non-Run fallback shape getConflict's contract is Promise<Run | null>. In the degenerate cases where a real Run cannot be constructed — a hook_conflict event persisted by an old world without conflictingRunId, or a context that never loaded the workflow-mode create-hook module — reject with HookConflictError instead of resolving with a { runId }-shaped impostor. Test harnesses now register the Run class on the (VM) globalThis like real bundles do. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor: make getConflict a method — hook.getConflict() A property getter that triggers registration/suspension reads as passive state; a method makes the side effect explicit. Update implementation, types, tests, e2e workflows, docs, and changeset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: getConflict is a method — hook.getConflict() Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: typecheck every sample — drop skip-typecheck escape hatches Route examples typecheck as-is since the runId-comparison rewrite; strategy fragments are now complete self-contained workflows; the publishing-libraries cross-block dependency uses the declare @setup convention. 934 samples typechecked, none skipped by this PR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * review: guard Run class registration, fix anchors, clarify changeset - Only register WORKFLOW_RUN_CLASS when the workflow runtime is present (WORKFLOW_CREATE_HOOK installed on globalThis), so host imports of the workflow-mode module neither mutate the host global nor expose the non-step-proxy host Run. - Drop #run-idempotency link fragments — that section lands in the stacked docs PR (#2011), which restores the anchored links. - Note in docs that getConflict() rejects with HookConflictError for legacy hook_conflict events lacking the owner's run ID. - Changeset now calls out the hasConflict -> getConflict() replacement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: restore run-idempotency anchors now that the section exists here Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: describe fixed conflict policies generically, without naming other systems Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Signed-off-by: Nathan Rajlich <n@n8.io> Co-authored-by: Nathan Rajlich <n@n8.io> Co-authored-by: Peter Wielander <mittgfu@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
8 B
8 B