Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 40 additions & 29 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,46 @@ interfaces from `effect/unstable/*`, whose API can move between releases. Each r
of this package states the one `effect` version it is built and tested against, and
tracking a new `effect` release is a new release of this package.

## Unreleased / planned — 0.5.0

The removal PR is this checklist. Everything below was deprecated in 0.4.0
with its replacement named in the JSDoc and reported by the
`prefer-definition` lint rule; nothing else changes.

- REMOVE the `typed-activity` module and its package export
(`TypedActivity.make` → `defineActivity`; `PayloadOf`/`SuccessOf`/`ErrorOf`/
`AnyTypedActivity`/`TypedActivity`/`TypedActivityOptions`/
`DEFAULT_ACTIVITY_OPTIONS` → `definition`; `codecsFor`/`ACTIVITY_EXIT_TYPE`/
`TypedActivityCodecs` → `wire`).
- REMOVE the `versioning` module and its package export (`match` → `versioned`,
`version` → `version` from `definition`). Move `deprecateVersion` /
`deprecatePatch` / `patched` (the Temporal-only retirement step) to `bundle`.
- REMOVE from `engine-sandbox`: `callActivity`, `takeMailbox`, `pollMailbox`,
`takeUpdate`, `setStateCell`, `sleepUntil`, `continueAsNew`, and the
`UpdateRequest<S, E, P>` alias. The Temporal `WorkflowOps` runtime keeps
their bodies as private functions. `callRawActivity`, `offerMailbox`
(workflow → workflow), `callNexusWorkflowOperation`, `SandboxRun`, and
`workflowBundle` stay.
- REMOVE `make` from `mailbox`, `update`, `state-cell` (→ `defineMailbox`,
`defineUpdate`, `defineState`) and drop those three package exports — the
modules become internal wire homes (`MAILBOX_SIGNAL`, `WORKFLOW_UPDATE`,
`STATE_CELL_QUERY`, codecs) consumed by the engine halves and `testing`.
- DELETE the deprecation entries from `prefer-definition` once the symbols are
gone (the rule stays, empty tables are fine, so a future deprecation has a
home).
- KEEP `histories/definition-order-0.3.0` in the replay drill; record a
0.4.0 history alongside it.
## 0.5.0 (2026-09-10)

The deletion release: everything deprecated in 0.4.0 is gone, and the API
converges on `definition` + `bundle` + `activities` + `client` /
`engine-client` + `testing` (+ `wire` and the engine-level corners of
`engine-sandbox`). No wire change — see the replay drill below.

- BREAKING: REMOVED the `typed-activity` module and its package export.
`TypedActivity.make` → `defineActivity`; the type helpers → `definition`;
`codecsFor` / `ACTIVITY_EXIT_TYPE` / `TypedActivityCodecs` → `wire`.
- BREAKING: REMOVED the `versioning` module and its package export.
`match` → `versioned`, `version` → `version` (both `definition`). The
Temporal-only retirement step moved to `bundle`: `deprecateVersion`,
`deprecatePatch`, `patched`.
- BREAKING: REMOVED from `engine-sandbox`: `callActivity`, `takeMailbox`,
`pollMailbox`, `takeUpdate`, `setStateCell`, `sleepUntil`,
`continueAsNew`, and the `UpdateRequest<S, E, P>` alias. Their bodies are
now module-private machinery behind the Temporal `WorkflowOps` runtime.
`workflowBundle` (import it from `bundle`), `callRawActivity`,
`offerMailbox` (workflow → workflow), `callNexusWorkflowOperation`, and
`SandboxRun` remain.
- BREAKING: REMOVED the `mailbox`, `update`, and `state-cell` package exports
and their `make` constructors (→ `defineMailbox`, `defineUpdate`,
`defineState`). The modules stay in the source tree as internal wire homes
(`MAILBOX_SIGNAL`, `WORKFLOW_UPDATE`, `STATE_CELL_QUERY`, codecs) for the
engine halves and `testing`.
- `prefer-definition` keeps its tables (message now "was removed in 0.5.0 —
use …"), so a stale import gets a pointer instead of a bare module-not-found;
it also reports `workflowBundle` imported from `engine-sandbox` (→ `bundle`).
The 0.4.0 plan said to empty the tables; keeping them is strictly more useful.
- Replay drill: `replay-compat.test.ts` now also replays two histories
recorded on 0.4.0 — `defDispatch` (`versioned` marker + awaited
`executeChild`) and `defGrace` (durable timer racing a mailbox take) —
alongside the 0.3.0 `defOrder` history. The version-chain fixtures
(generations 2 and 3) author with `versioned`; generation 1 stays
pre-versioning code, so the drill still proves adoption is replay-safe.
- The repository has no remaining import of a removed symbol; the examples
and every fixture author against the current surface.

Built and tested against `effect@4.0.0-rc.112` and `@temporalio/*@1.19.0`.

## 0.4.0 (2026-09-10)

Expand Down
2 changes: 1 addition & 1 deletion EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ workflow-semantics content).
| [dsl-interpreter](https://github.com/temporalio/samples-typescript/tree/main/dsl-interpreter) | ✅ | The payload carries a declarative program (sequential steps of single or parallel activity calls) and Effect interprets it in-workflow. Test: [dsl.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/dsl.test.ts). |
| [early-return](https://github.com/temporalio/samples-typescript/tree/main/early-return) | ✅ | A forked fiber serves the confirmation `DurableUpdate` once authorization lands, while the main flow continues to the final result. Test: [early-return.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/early-return.test.ts). |
| [polling](https://github.com/temporalio/samples-typescript/tree/main/polling) | ✅ | The infrequent variant: the poll interval IS the activity retry policy, so the workflow is one `callRawActivity`. Test: [polling.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/polling.test.ts). Frequent polling inside one activity is activity-side; unbounded polling adds `continueAsNew`. |
| [patching-api](https://github.com/temporalio/samples-typescript/tree/main/patching-api) | ✅ | `version(site, names)` / `versioned(site, { v1, v2 })` (definition module): an ordered version chain per code site with typed version literals and union-typed channels; fresh runs take the newest case, replays their own. Tests: [versioning-chain.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/versioning-chain.test.ts) (three generations, append-safety, negative control — on the deprecated `Versioning.match`, kept byte-identical for the replay drill), [definition.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/definition.test.ts) (`versioned` on both engines), [replay-compat.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/replay-compat.test.ts) (a history recorded on 0.3.0 replays through the current bundle). |
| [patching-api](https://github.com/temporalio/samples-typescript/tree/main/patching-api) | ✅ | `version(site, names)` / `versioned(site, { v1, v2 })` (definition module): an ordered version chain per code site with typed version literals and union-typed channels; fresh runs take the newest case, replays their own. Tests: [versioning-chain.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/versioning-chain.test.ts) (three generations, append-safety, negative control — generation 1 is pre-versioning code, generations 2 and 3 adopt and extend `versioned`), [definition.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/definition.test.ts) (`versioned` on both engines), [replay-compat.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/replay-compat.test.ts) (a history recorded on 0.3.0 replays through the current bundle). |

## Scheduling and client features

Expand Down
4 changes: 2 additions & 2 deletions docs/guide/declaring-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ And the operations that need no declaration — all from the same module, all re

**The declaration is the only symbol you ever name.** Every client-side surface — the `WorkflowClient` service, the standalone `engine-client` operations, the [in-memory test world](/guide/testing#the-in-memory-runtime), the fake client, the live harness — takes the declaration directly. Each declaration still carries its underlying primitive (`Approval.deferred`, `Priority.mailbox`, `SetAmount.update`, `Status.cell`; a defined activity *is* its `TypedActivity` projection) for engine-level code, and every surface accepts that too. The decoded types are named with `PayloadOf<typeof Charge>`, `SuccessOf<…>`, `ErrorOf<…>` from this module.

::: warning Deprecated authoring surface
The pre-0.3.0 modules — `/typed-activity`, `/versioning`, the `make` constructors of `/mailbox`, `/update`, `/state-cell`, and the per-primitive calls in `/engine-sandbox` (`callActivity`, `takeMailbox`, `takeUpdate`, `setStateCell`, `sleepUntil`, `continueAsNew`) — are **deprecated in 0.4.0 and removed in 0.5.0**. Each has a replacement here; the [`prefer-definition` lint rule](/guide/lint-rules) names it at every remaining import.
::: info Coming from 0.3.x or earlier
The pre-0.4.0 modules — `/typed-activity`, `/versioning`, the `make` constructors of `/mailbox`, `/update`, `/state-cell`, and the per-primitive calls in `/engine-sandbox` (`callActivity`, `takeMailbox`, `takeUpdate`, `setStateCell`, `sleepUntil`, `continueAsNew`) — were deprecated in 0.4.0 and **removed in 0.5.0**. Each has a replacement in the tables above; the [`prefer-definition` lint rule](/guide/lint-rules) names it at every stale import, so migrating is running the linter.
:::

::: info Schemas must be context-free
Expand Down
3 changes: 1 addition & 2 deletions docs/guide/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ The library is one npm package, `@springbird/effect-temporal`, with tree-shakeab
| Module | Runs in | What it is |
| --- | --- | --- |
| `@springbird/effect-temporal/definition` | everywhere | `define*` capability declarations, timers, `continueAsNew`, `executeChild`, `version`/`versioned`, `evolved`, the `WorkflowOps` seam — engine-free |
| `@springbird/effect-temporal/bundle` | the workflow bundle's entry file | `workflowBundle` — hosts registrations behind the bundle's default export, provides the Temporal `WorkflowOps` |
| `@springbird/effect-temporal/bundle` | the workflow bundle's entry file | `workflowBundle` — hosts registrations behind the bundle's default export, provides the Temporal `WorkflowOps`; `deprecateVersion` for retiring version names |
| `@springbird/effect-temporal/engine-sandbox` | the workflow bundle | engine-level escape hatches: raw activity proxies (`callRawActivity`), workflow → workflow offers, Nexus calls |
| `@springbird/effect-temporal/engine-client` | ordinary Node | the client-side engine + standalone read/signal operations |
| `@springbird/effect-temporal/client` | ordinary Node | `WorkflowClient` — the one client service |
| `@springbird/effect-temporal/activities` | worker registration | activity implementation tables (`handle`, `implementActivities`) + the attach bridge |
| `@springbird/effect-temporal/wire` | engine-level | the wire contract: codecs (`codecsFor`), signal/query names, failure types |
| `@springbird/effect-temporal/typed-activity`, `/versioning`, and the `make` constructors in `/mailbox`, `/update`, `/state-cell` | — | **deprecated** (removed in 0.5.0): re-exports and aliases of the `definition` surface; the `prefer-definition` lint rule reports them |
| `@springbird/effect-temporal/nexus` | worker registration | workflow-backed Nexus operations |
| `@springbird/effect-temporal/testing` | tests | the in-memory `WorkflowOps` runtime, a typed fake Temporal client, a live test harness |
| `@springbird/effect-temporal/lint` | your lint config | oxlint/ESLint rules for the authoring footguns |
Expand Down
6 changes: 3 additions & 3 deletions docs/guide/lint-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The whole Effect program runs inside the Temporal workflow sandbox. `Activity.ma
3. **No module-level mutable state in workflow code** — under the worker's default `reuseV8Context`, module-level variables are shared across every workflow instance on a thread. Keep run state inside the handler.
4. **Never mix the halves** — a module must not import both the sandbox half (`@temporalio/workflow`, `engine-sandbox`) and the client half (`@temporalio/client`, `engine-client`): they can never share a process.
5. **Evaluate versions on the main fiber** — [version / versioned](/guide/versioning) markers evaluated inside forks or races make marker order nondeterministic.
6. **Author with the definition module** — the pre-0.4.0 surface (`typed-activity`, `versioning`, the primitive `make` constructors, the per-primitive `engine-sandbox` calls) is deprecated and removed in 0.5.0; every remaining import is a regression waiting to break.
6. **Author with the definition module** — the pre-0.4.0 surface (`typed-activity`, `versioning`, the primitive `make` constructors, the per-primitive `engine-sandbox` calls) was removed in 0.5.0; a stale import fails to resolve, and this rule tells you what replaced it.

## Setup

Expand Down Expand Up @@ -46,7 +46,7 @@ Two presets ship: `recommended` (all six rules, `prefer-call-temporal-activity`

A file counts as workflow code when it imports `@temporalio/workflow`, the `bundle` module, or the `engine-sandbox` module — the rules are inert elsewhere, so enabling them repo-wide is safe. `no-mixed-halves` applies everywhere by nature. `versioning-on-main-fiber` has one more trigger: importing `version` or `versioned` from the [definition module](/guide/declaring-capabilities) marks the file for that rule (alias-aware), since definition-authored handler modules deliberately import nothing engine-shaped. The other sandbox rules cannot see such modules — a handler that needs them linted can live next to its bundle entry, which imports `bundle`.

`prefer-definition` applies everywhere: it keys off the import source alone (the package specifier or a relative path to one of this package's modules), and its message names the replacement — `defineActivity` for `TypedActivity.make`, `versioned` for `Versioning.match`, the declaration's `.take` for `takeMailbox`, `codecsFor` from `wire`, and so on. Because it is an error in `recommended`, `oxlint` exits non-zero on any file still importing a deprecated symbol — which is what stops a migrated codebase regressing.
`prefer-definition` applies everywhere: it keys off the import source alone (the package specifier or a relative path to one of this package's modules), and its message names the replacement — `defineActivity` for `TypedActivity.make`, `versioned` for `Versioning.match`, the declaration's `.take` for `takeMailbox`, `codecsFor` from `wire`, `bundle` for a `workflowBundle` import from `engine-sandbox`, and so on. Because it is an error in `recommended`, `oxlint` exits non-zero on any file still importing a removed symbol — a migration guide that runs as a lint.

The remaining footguns — drain mailboxes before `continueAsNew`, respond to updates before completion — are runtime-shaped and covered by runtime guards and the guide instead.

Expand All @@ -57,4 +57,4 @@ The remaining footguns — drain mailboxes before `continueAsNew`, respond to up
| `no-mixed-halves` | one module importing both process halves |
| `prefer-call-temporal-activity` | raw `Effect.promise` where a cancellable call belongs |
| `versioning-on-main-fiber` | `version` / `versioned` / `Versioning.*` inside `fork` / `race` / `all` |
| `prefer-definition` | any import of a deprecated symbol (removed in 0.5.0), with its replacement |
| `prefer-definition` | any import of the pre-0.4.0 surface (removed in 0.5.0), with its replacement |
6 changes: 3 additions & 3 deletions docs/guide/versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ The **key order is the chain order**: the first key is the original, unguarded b
### The lifecycle of a name

1. **Append** `"v3"`. Deploy. Fresh runs take v3; in-flight runs keep replaying their recorded name.
2. **Retire** an old name only after every history carrying its marker has closed: remove it from the list and deploy `deprecateVersion(site, name)` in its place for one release. Replaying a *removed* version's history fails loudly rather than silently running the wrong code. (`deprecateVersion` lives in the `versioning` module today — the one non-deprecated reason to import it; it moves to `bundle` in 0.5.0.)
2. **Retire** an old name only after every history carrying its marker has closed: remove it from the list and deploy `deprecateVersion(site, name)` (from `@springbird/effect-temporal/bundle` — it is Temporal-only, and belongs in the bundle, never in a handler) in its place for one release. Replaying a *removed* version's history fails loudly rather than silently running the wrong code. The raw primitives, `patched(id)` and `deprecatePatch(id)`, are exported from `bundle` too for one-off guards.

::: warning `Versioning.match` is deprecated
The pre-0.4.0 `versioning` module's `match(site, [{ version, run }])` is the Temporal-only ancestor of `versioned` — same markers, same semantics, but it imports `@temporalio/workflow` and so cannot run in the in-memory runtime. It is deprecated and removed in 0.5.0; `versioned(site, { v1: run1, v2: run2 })` is the drop-in replacement, and the `prefer-definition` lint rule points at it.
::: details Coming from `Versioning.match`
The pre-0.4.0 `versioning` module's `match(site, [{ version, run }])` was the Temporal-only ancestor of `versioned` — same markers, same semantics. It was removed in 0.5.0; `versioned(site, { v1: run1, v2: run2 })` is the drop-in replacement (each `{ version, run }` case becomes a `version: run` key), and histories recorded under `match` replay through `versioned` unchanged — the marker ids are the same.
:::

### Rules
Expand Down
22 changes: 1 addition & 21 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@springbird/effect-temporal",
"version": "0.4.0",
"version": "0.5.0",
"description": "Run `effect/unstable/workflow` programs (Workflow / Activity / DurableClock / DurableDeferred) on a Temporal engine, plus durable mailboxes, updates, queryable state, versioning, schedules, and Nexus operations.",
"license": "MIT",
"type": "module",
Expand Down Expand Up @@ -43,35 +43,15 @@
"default": "./dist/engine-sandbox.js"
},
"./lint": "./dist/lint.js",
"./mailbox": {
"types": "./dist/mailbox.d.ts",
"default": "./dist/mailbox.js"
},
"./nexus": {
"types": "./dist/nexus.d.ts",
"default": "./dist/nexus.js"
},
"./oxlint-presets/*.json": "./oxlint-presets/*.json",
"./state-cell": {
"types": "./dist/state-cell.d.ts",
"default": "./dist/state-cell.js"
},
"./testing": {
"types": "./dist/testing.d.ts",
"default": "./dist/testing.js"
},
"./typed-activity": {
"types": "./dist/typed-activity.d.ts",
"default": "./dist/typed-activity.js"
},
"./update": {
"types": "./dist/update.d.ts",
"default": "./dist/update.js"
},
"./versioning": {
"types": "./dist/versioning.d.ts",
"default": "./dist/versioning.js"
},
"./wire": {
"types": "./dist/wire.d.ts",
"default": "./dist/wire.js"
Expand Down
3 changes: 2 additions & 1 deletion src/__tests__/fixtures/batch-workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import * as Activity from "effect/unstable/workflow/Activity";
import { proxyActivities } from "@temporalio/workflow";
import { callRawActivity, continueAsNew, offerMailbox } from "../../engine-sandbox.js";
import { callRawActivity, offerMailbox } from "../../engine-sandbox.js";
import { workflowBundle } from "../../bundle.js";
import { continueAsNew } from "../../definition.js";
import { BatchDemo, CompletionReports, RecordDemo } from "./batch-demo.js";

const acts = proxyActivities<{ processRecord(index: string): Promise<string> }>({
Expand Down
Loading
Loading