Skip to content

Commit 710bfca

Browse files
btraversclaude
andcommitted
docs: fix beta install path and stale guidance from v8 review
Wave 1 meta/docs items from the v8 review remediation spec: - Item 7: add a beta-install warning and @beta-tagged install commands to the root README, docs/how-to/install.md, and tutorial step 1 — npm's latest dist-tag is 7.0.0 while the docs teach the v8 API. - Item 8: correct AGENTS.md rule 2 and handlers.md — unthrown 5 does export OkAsync/ErrAsync (only lowercase okAsync/errAsync are absent); switch handlers.md's cancellation example from free-function isErr(result) to the method style the codebase uses. - Item 9: add the missing @temporalio/testing ^1 peer to dependencies.md's testing row and key-dependencies table. - Item 10: examples/README.md — drop stale "Promise-based worker" and "Result/Future" wording, list all three example packages; fix the matching one-line comment in order-processing-contract's contract.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 65f4669 commit 710bfca

8 files changed

Lines changed: 57 additions & 32 deletions

File tree

.agents/rules/dependencies.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
| `@temporalio/worker` | Temporal worker SDK — peer dep of `worker` |
99
| `@temporalio/workflow` | Temporal workflow API — peer dep of `worker` |
1010
| `@temporalio/common` | Shared Temporal types — peer dep of `client`/`worker` |
11+
| `@temporalio/testing` | Time-skipping test server (`TestWorkflowEnvironment`) — peer dep of `testing` |
1112
| `@standard-schema/spec` | Standard Schema specification — direct dep |
1213
| `unthrown` | `Result` / `AsyncResult` — peer dep of `client`/`worker` |
1314
| `zod` | Direct dep of `contract` (used internally for the `defineContract` runtime validation pass); user-side schema lib for the others |
@@ -37,12 +38,12 @@ All dependency versions are centralized in `pnpm-workspace.yaml` under the `cata
3738

3839
Anything that appears in a published package's **public type signatures** must be a peer dep, not a regular dep — otherwise downstream consumers can end up with two disjoint nominal types in their typechecker (theirs and ours), even though the runtime classes are compatible.
3940

40-
| Package | Peer dependencies |
41-
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
42-
| client | `@temporalio/client ^1`, `@temporalio/common ^1`, `unthrown ^5` |
43-
| worker | `@temporalio/common ^1`, `@temporalio/worker ^1`, `@temporalio/workflow ^1`, `unthrown ^5` |
44-
| contract | `unthrown ^5` (optional — only needed when using `result-async`) |
45-
| testing | `vitest ^4` (the `globalSetup` hook integrates with vitest's test runner), `@temporalio/client ^1`, `@temporalio/worker ^1` (both exposed by the `it` fixture's public types) |
41+
| Package | Peer dependencies |
42+
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
43+
| client | `@temporalio/client ^1`, `@temporalio/common ^1`, `unthrown ^5` |
44+
| worker | `@temporalio/common ^1`, `@temporalio/worker ^1`, `@temporalio/workflow ^1`, `unthrown ^5` |
45+
| contract | `unthrown ^5` (optional — only needed when using `result-async`) |
46+
| testing | `vitest ^4` (the `globalSetup` hook integrates with vitest's test runner), `@temporalio/client ^1`, `@temporalio/testing ^1`, `@temporalio/worker ^1` (all exposed by the fixtures' public types — e.g. `TestWorkflowEnvironment` in `time-skipping`) |
4647

4748
When you add a peer dep, also add it to `devDependencies` (with the same `"catalog:"` reference) so the local workspace build still resolves it. The workspace has `autoInstallPeers: false`, so peers must be present somewhere on the install side.
4849

.agents/rules/handlers.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ case, the worker package exports a `qualify(type, options?)` helper that builds
2929
that function — `fromPromise(inventoryService.check(orderId), qualify("INVENTORY_CHECK_FAILED"))`
3030
preserves an `Error` rejection's message and `cause`, with `options.nonRetryable`
3131
/ `options.details` / `options.message` (non-`Error` fallback) available. For a
32-
value you already have, lift a sync result with `Ok(value).toAsync()` /
33-
`Err(failure).toAsync()` — unthrown has no `okAsync`/`errAsync`.
32+
value you already have, use `OkAsync(value)` / `ErrAsync(failure)`, or lift an
33+
existing sync `Result` with `Ok(value).toAsync()` / `Err(failure).toAsync()`
34+
unthrown has no lowercase `okAsync`/`errAsync`.
3435

3536
Canonical example: `examples/order-processing-worker/src/application/activities.ts`.
3637

@@ -129,14 +130,12 @@ await workerResult.value.run();
129130
Workflows opt into cancellation control via `context.cancellableScope` / `context.nonCancellableScope`. They fold cancellation into the project's `AsyncResult` shape — callers branch on `Err(WorkflowCancelledError)` instead of catching `CancelledFailure`.
130131

131132
```typescript
132-
import { isErr } from "unthrown";
133-
134133
implementation: async (context, args) => {
135134
const result = await context.cancellableScope(async () => {
136135
return context.activities.processStep(args);
137136
});
138137

139-
if (isErr(result)) {
138+
if (result.isErr()) {
140139
// Workflow was cancelled. Cleanup that must not be cancelled itself
141140
// goes inside `nonCancellableScope`.
142141
await context.nonCancellableScope(async () => {
@@ -151,7 +150,7 @@ implementation: async (context, args) => {
151150

152151
- `cancellableScope<T>(fn)` — returns `AsyncResult<T, WorkflowCancelledError>`. Cancels propagate from outside.
153152
- `nonCancellableScope<T>(fn)` — same shape; _outside_ cancels are ignored. Cancels raised _inside_ still surface as `Err(...)`. Use for graceful-shutdown cleanup.
154-
- Non-cancellation errors thrown by `fn` are _unmodeled_ failures: they ride unthrown's **`defect`** channel (inspectable via `isDefect(result)` / `result.cause`, re-thrown at the edge), not the modeled `err` channel.
153+
- Non-cancellation errors thrown by `fn` are _unmodeled_ failures: they ride unthrown's **`defect`** channel (inspectable via `result.isDefect()` / `result.cause`, re-thrown at the edge), not the modeled `err` channel.
155154

156155
Canonical implementation: `packages/worker/src/cancellation.ts:38` (`cancellableScope`), `:75` (`nonCancellableScope`). Error class: `packages/worker/src/errors.ts:193`.
157156

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This file is the source of truth for agent guidance in this repo. `CLAUDE.md` an
77
## The 6 rules that prevent broken PRs
88

99
1. **Workflow code is deterministic.** No `Date.now()`, `Math.random()`, `setTimeout`, `crypto.randomUUID()`, native I/O, or `process.env` reads inside `declareWorkflow`'s `implementation`. Use `@temporalio/workflow` primitives (`sleep`, `uuid4`, the patched `Date`) or push the side effect into an activity. See [.agents/rules/workflow-determinism.md](.agents/rules/workflow-determinism.md). This is the #1 cause of broken Temporal workflows — read that file before touching workflow code.
10-
2. **Activities and the typed client return `AsyncResult<T, E>` from `unthrown`.** Never throw — wrap technical errors in `ApplicationFailure` and surface them via `Err(...).toAsync()` (or `fromPromise(promise, qualify)`, where `qualify` returns the modeled error `E`). unthrown has no `okAsync`/`errAsync`: lift a sync `Result` with `.toAsync()`. The client uses unthrown's `Result` for sync returns. unthrown adds a third **`defect`** channel for _unanticipated_ failures — a thrown exception the code didn't model surfaces as a defect (inspectable via `result.isDefect()` / `result.cause`, re-thrown at the edge), not a typed `err`. Narrow before reaching `.value`/`.error`/`.cause` — both the `r.isOk()` method and the `isOk(r)` free function are type guards (same for `isErr`/`isDefect`); the codebase uses the methods. Error classes are built with `TaggedError("@temporal-contract/Name", { name: "Name" })<{ ...payload }>` — the `_tag` is package-namespaced to avoid collisions, while `options.name` keeps `Error.name` the bare class name for readable logs. The worker's `ValidationError` subclasses are the exception — they must stay `ApplicationFailure` for Temporal's terminal-failure semantics. There is no `neverthrow`, no `@swan-io/boxed`, and no `@temporal-contract/boxed` package — those were removed.
10+
2. **Activities and the typed client return `AsyncResult<T, E>` from `unthrown`.** Never throw — wrap technical errors in `ApplicationFailure` and surface them via `Err(...).toAsync()` (or `fromPromise(promise, qualify)`, where `qualify` returns the modeled error `E`). unthrown has no lowercase `okAsync`/`errAsync`: use `OkAsync(value)`/`ErrAsync(error)` to construct an `AsyncResult` directly, or lift an existing sync `Result` with `Ok(value).toAsync()`/`Err(error).toAsync()`. The client uses unthrown's `Result` for sync returns. unthrown adds a third **`defect`** channel for _unanticipated_ failures — a thrown exception the code didn't model surfaces as a defect (inspectable via `result.isDefect()` / `result.cause`, re-thrown at the edge), not a typed `err`. Narrow before reaching `.value`/`.error`/`.cause` — both the `r.isOk()` method and the `isOk(r)` free function are type guards (same for `isErr`/`isDefect`); the codebase uses the methods. Error classes are built with `TaggedError("@temporal-contract/Name", { name: "Name" })<{ ...payload }>` — the `_tag` is package-namespaced to avoid collisions, while `options.name` keeps `Error.name` the bare class name for readable logs. The worker's `ValidationError` subclasses are the exception — they must stay `ApplicationFailure` for Temporal's terminal-failure semantics. There is no `neverthrow`, no `@swan-io/boxed`, and no `@temporal-contract/boxed` package — those were removed.
1111
3. **No `any`.** Use `unknown` and narrow. Enforced by oxlint.
1212
4. **`.js` extensions in every import.** TypeScript files import each other as `./foo.js`, never `./foo` or `./foo.ts`. Required by ESM module resolution.
1313
5. **ESM only.** All packages are `"type": "module"`. No CommonJS in source.

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,17 @@ partial state, nothing to unwind.
128128

129129
## Install
130130

131+
> **8.0 is currently a prerelease.** npm's `latest` tag still resolves to 7.x,
132+
> while this README documents the v8 API. Install the `@temporal-contract/*`
133+
> packages with the `beta` tag until 8.0 is stable — a plain
134+
> `pnpm add @temporal-contract/contract` gives you the previous major.
135+
131136
```bash
132-
# Core packages
133-
pnpm add @temporal-contract/contract @temporal-contract/worker @temporal-contract/client
137+
# Core packages (8.0 beta — `latest` still resolves 7.x)
138+
pnpm add @temporal-contract/contract@beta @temporal-contract/worker@beta \
139+
@temporal-contract/client@beta
134140

135-
# Peer dependencies
141+
# Peer dependencies (stable releases)
136142
pnpm add unthrown zod \
137143
@temporalio/client @temporalio/common @temporalio/worker @temporalio/workflow
138144
```

docs/how-to/install.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,45 @@
1111

1212
## Install the packages
1313

14+
::: warning 8.0 is currently a prerelease
15+
The 8.0 line — the API these docs describe — is published under the `beta`
16+
dist-tag, so a plain `npm install @temporal-contract/contract` still resolves
17+
7.x. Install the `@temporal-contract/*` packages with the explicit `@beta`
18+
tag, as the commands below do. The peer dependencies (`unthrown`,
19+
`@temporalio/*`) are stable releases.
20+
:::
21+
1422
Pick the packages for what you are building. Most applications split across
1523
processes, so each process installs only what it uses.
1624

1725
::: code-group
1826

1927
```bash [pnpm]
2028
# Shared — the contract, imported by every side
21-
pnpm add @temporal-contract/contract
29+
pnpm add @temporal-contract/contract@beta
2230

2331
# Worker process
24-
pnpm add @temporal-contract/worker
32+
pnpm add @temporal-contract/worker@beta
2533

2634
# Client process
27-
pnpm add @temporal-contract/client
35+
pnpm add @temporal-contract/client@beta
2836

2937
# Tests
30-
pnpm add -D @temporal-contract/testing
38+
pnpm add -D @temporal-contract/testing@beta
3139
```
3240

3341
```bash [npm]
34-
npm install @temporal-contract/contract
35-
npm install @temporal-contract/worker
36-
npm install @temporal-contract/client
37-
npm install -D @temporal-contract/testing
42+
npm install @temporal-contract/contract@beta
43+
npm install @temporal-contract/worker@beta
44+
npm install @temporal-contract/client@beta
45+
npm install -D @temporal-contract/testing@beta
3846
```
3947

4048
```bash [yarn]
41-
yarn add @temporal-contract/contract
42-
yarn add @temporal-contract/worker
43-
yarn add @temporal-contract/client
44-
yarn add -D @temporal-contract/testing
49+
yarn add @temporal-contract/contract@beta
50+
yarn add @temporal-contract/worker@beta
51+
yarn add @temporal-contract/client@beta
52+
yarn add -D @temporal-contract/testing@beta
4553
```
4654

4755
:::

docs/tutorial/your-first-workflow.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,18 @@ npm pkg set type=module
3737
Install temporal-contract and its peers:
3838

3939
```bash
40-
npm install @temporal-contract/contract @temporal-contract/worker @temporal-contract/client
40+
npm install @temporal-contract/contract@beta @temporal-contract/worker@beta @temporal-contract/client@beta
4141
npm install unthrown zod @temporalio/client @temporalio/common @temporalio/worker @temporalio/workflow
4242
npm install -D typescript @types/node tsx
4343
```
4444

45+
::: warning The `@beta` tag is required
46+
temporal-contract 8.0 — the API this tutorial teaches — is currently a
47+
prerelease published under the `beta` dist-tag. Without `@beta`, npm installs
48+
the 7.x line, and the code in this tutorial will not match. The peers
49+
(`unthrown`, `@temporalio/*`, `zod`) are stable releases.
50+
:::
51+
4552
Create a `tsconfig.json`. The two settings that matter are `module: nodenext`
4653
(temporal-contract is ESM-only) and `strict` (the type inference depends on it):
4754

examples/README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44
55
## Available Examples
66

7+
### [order-processing-contract](./order-processing-contract)
8+
9+
Shared contract package — domain schemas and the workflow/activity definitions imported by both the worker and the client
10+
711
### [order-processing-worker](./order-processing-worker)
812

9-
Standard Promise-based worker with Clean Architecture
13+
Worker with Clean Architecture; activities return `AsyncResult` from unthrown
1014

1115
### [order-processing-client](./order-processing-client)
1216

13-
Standalone client demonstrating interaction with the unified contract
17+
Standalone client demonstrating interaction with the shared contract
1418

1519
**Note**: The client example works with the worker implementation seamlessly through the shared contract (`orderProcessingContract`).
1620

examples/order-processing-contract/src/contract.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
* This contract defines a unified order processing system with:
1717
* - Global activities for logging and notifications
1818
* - A workflow for processing orders with payment, inventory, and shipping
19-
* - Support for both standard Promise-based and Result/Future pattern implementations
19+
* - Activity implementations that return `AsyncResult` values from unthrown
2020
*
2121
* The contract uses domain schemas as the source of truth for business entities.
2222
*/

0 commit comments

Comments
 (0)