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
117 changes: 116 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,122 @@ 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.

## 0.3.0 (unreleased)
## 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.4.0 (2026-09-10)

Closes the gaps between the `definition` module and the legacy authoring
surface: after this release a consumer imports `definition`, `bundle`,
`activities`, `client`/`engine-client`, `testing`, and (engine-level) `wire`
— never `engine-sandbox` except for the engine-level escape hatches,
never `typed-activity`, `versioning`, or `mailbox`.

- NEW: timers on the `WorkflowOps` seam — `sleep({ name, duration })` and
`sleepUntil({ name, timestamp })` from `definition`, requiring only
`WorkflowOps`. On Temporal they dispatch to `DurableClock.sleep` / the
existing `sleepUntil` (same timestamp rule, now the shared
`sleepUntilTarget`: zone-less and unparseable timestamps die). In
`makeTestWorkflowOps` they follow Effect's `Clock`, so a `TestClock` can
`adjust` past them — deliberately not instant, so a mailbox take racing a
grace-period timer is testable in both orders (pinned on both engines).
- NEW: `continueAsNew(workflow, payload, options?)` on the seam and in
`definition`. In memory it interrupts the handler fiber and records the
continuation (`world.continuedAsNew`, `world.continuedAsNewOf(W)`), payload
round-tripped through the workflow's payload schema (a schema-invalid
payload dies, as on the wire).
- NEW: child workflows on the seam — `executeChild(workflow, payload,
{ discard? })` from `definition`. On Temporal it is upstream `execute`
under the sandbox engine (identical commands: `startChild` with the digest
id, `REQUEST_CANCEL`/`ABANDON`, attach-on-taken). `makeTestWorkflowOps`
accepts `workflows: [handleWorkflow(Child, handler)]` and runs children
in-process with schema round-tripping, discard-forks, and attach on a
taken id.
- NEW: `versioned(site, { v1: run1, v2: run2 })` — the run-table form of
`version` (key order is the chain order, oldest first), built on the same
`WorkflowOps.version`; the `versioning-on-main-fiber` lint rule recognises
it (alias-aware) alongside `version`.
- NEW: the activity type helpers live on `definition` — `PayloadOf`,
`SuccessOf`, `ErrorOf`, `AnyTypedActivity`, `TypedActivity`,
`TypedActivityOptions`, `DEFAULT_ACTIVITY_OPTIONS`. `codecsFor`,
`TypedActivityCodecs`, and `ACTIVITY_EXIT_TYPE` moved to `wire` (the
`typed-activity` module re-exports everything, deprecated).
- NEW: every client-side operation takes the declaration directly —
`offerMailbox(Priority, …)`, `executeUpdate(SetAmount, …)`,
`readStateCell(Status, …)`, `deferredState(Approval, …)` in `engine-client`
and on `WorkflowClient`; the underlying primitive (`Priority.mailbox`, …) is
still accepted (`MailboxLike` / `UpdateLike` / `StateCellLike` /
`DeferredLike`). NEW `completeDeferred(Approval, { client, workflowId,
exit })` / `wf.completeDeferred(Approval, workflowId, exit)`: the client
half of `Approval.await` — same done-signal as `DurableDeferred.done`, no
token, no `WorkflowEngine`. The workflow → workflow `offerMailbox`
(engine-sandbox) accepts the declaration too.
- NEW: `makeFakeTemporalClient` gains `offer(Priority, workflowId, payload)`
and `offersTo(Priority)` (decoded through the declaration) — no
`MAILBOX_SIGNAL` import in consumer tests. The live harness client gains
`offer` / `request` / `stateOf` / `resolve`, mirroring the in-memory world.
- NEW: the `bundle` module — `workflowBundle`'s home, "the one file the
Temporal worker points at". `engine-sandbox` still exports it; its other
exports are documented as engine-level (`callRawActivity`, workflow →
workflow `offerMailbox`, `callNexusWorkflowOperation`, `SandboxRun`) or
deprecated (below).
- NEW: `prefer-definition` lint rule, on in the `recommended` preset as an
error: reports any import of a deprecated symbol from this package's
modules (published specifier or relative path) with the replacement in the
message, so `oxlint` fails on regressions.
- NEW: `replay-compat.test.ts` — a history recorded on 0.3.0
(`fixtures/histories/definition-order-0.3.0`) replays through the current
bundle. Wire identity is unchanged: activity types, signal/query/update
names, and patch-marker ids are byte-identical.
- DEPRECATED (removed in 0.5.0; replacement in each JSDoc):
`engine-sandbox`'s `callActivity`, `takeMailbox`, `pollMailbox`,
`takeUpdate`, `setStateCell`, `sleepUntil`, `continueAsNew`, and the
`UpdateRequest<S, E, P>` alias (use `definition`'s
`UpdateRequest<P, S, E>` — one parameter order); `TypedActivity.make`;
`DurableMailbox.make`, `DurableUpdate.make`, `StateCell.make`;
`Versioning.match` / `Versioning.version`; the whole `typed-activity` and
`versioning` modules.
- `defineDeferred` / `defineMailbox` / `defineUpdate` / `defineState` return
named interfaces (`DefinedDeferred`, `DefinedMailbox`, `DefinedUpdate`,
`DefinedState`) — structurally what they returned before.
- The repository authors with the new surface throughout: fixtures and
examples import `workflowBundle` from `bundle`, the examples use `sleep`
and `continueAsNew` from `definition` and drive declarations directly;
the versioning-chain and loop fixtures deliberately stay on the deprecated
calls so the replay drills keep covering them.
- `effect` peer/dev pin: `4.0.0-rc.112` (was `4.0.0-beta.101`); full suite
and both examples green against it. `@temporalio/*` stays `1.19.0`.

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

## 0.3.0 (2026-09-10)

- NEW: the `definition` module — declare each capability once and use it
directly inside handlers: `defineActivity` (callable: `yield* Charge({ orderId })`),
Expand Down
21 changes: 12 additions & 9 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,22 @@ workflow-semantics content).

| Sample | Status | With this package |
| ------------------------------------------------------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [timer-progress](https://github.com/temporalio/samples-typescript/tree/main/timer-progress) | ✅ | `DurableClock.sleep`. Test: [primitives.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/primitives.test.ts) (delay skipped by the time-skipping server). |
| [timer-progress](https://github.com/temporalio/samples-typescript/tree/main/timer-progress) | ✅ | `sleep` (definition module; `DurableClock.sleep` underneath on Temporal, an Effect `Clock` in memory). Test: [primitives.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/primitives.test.ts) (delay skipped by the time-skipping server). |
| [sleep-for-days](https://github.com/temporalio/samples-typescript/tree/main/sleep-for-days) | ✅ | Same mechanism; a Temporal timer's duration is unbounded. Test: [primitives.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/primitives.test.ts) pins a 2-minute durable delay under time skipping — the same mechanism at any duration. |
| [timer-examples](https://github.com/temporalio/samples-typescript/tree/main/timer-examples) | ✅ | The order-timeout race is `Effect.raceFirst(activity, DurableClock.sleep)` — pinned by the `timeout-activity` fixture mode and the [primitives.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/primitives.test.ts) test "cancels the server-side activity when the calling FIBER is interrupted", which asserts the timed-out activity is cancelled server-side, not abandoned. The `UpdatableTimer` half races the mailbox's `.take` against the timer. Test: [mailbox.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/mailbox.test.ts). |
| [timer-examples](https://github.com/temporalio/samples-typescript/tree/main/timer-examples) | ✅ | The order-timeout race is `Effect.raceFirst(activity, sleep)` — pinned by the `timeout-activity` fixture mode and the [primitives.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/primitives.test.ts) test "cancels the server-side activity when the calling FIBER is interrupted", which asserts the timed-out activity is cancelled server-side, not abandoned. The `UpdatableTimer` half races the mailbox's `.take` against the timer. Tests: [mailbox.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/mailbox.test.ts), and [definition.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/definition.test.ts) runs the same grace-period race on the in-memory runtime under `TestClock` in both orders AND on Temporal. |

## Workflow composition

| Sample | Status | With this package |
| ------------------------------------------------------------------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [child-workflows](https://github.com/temporalio/samples-typescript/tree/main/child-workflows) | ✅ | `MyChild.execute` in a workflow body; typed results/failures compose, cancellation cascades, discard = fire-and-forget, taken ids attach. Tests: [child.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/child.test.ts). |
| [child-workflows](https://github.com/temporalio/samples-typescript/tree/main/child-workflows) | ✅ | `executeChild(Child, payload)` (definition module; `MyChild.execute` in a body is the same engine path); typed results/failures compose, cancellation cascades, `{ discard: true }` = fire-and-forget, taken ids attach. Tests: [child.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/child.test.ts), [definition.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/definition.test.ts) (same parent handler on the in-memory runtime via `handleWorkflow` bindings and on Temporal). |
| [saga](https://github.com/temporalio/samples-typescript/tree/main/saga) | ✅ | `Workflow.withCompensation`, firing on typed failure and on interrupt. Tests: [primitives.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/primitives.test.ts), [child.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/child.test.ts). |
| [continue-as-new](https://github.com/temporalio/samples-typescript/tree/main/continue-as-new) | ✅ | `continueAsNew(workflow, payload)` (engine-sandbox): ends the run and starts a fresh one with the same workflow id. Test: [continue-as-new.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/continue-as-new.test.ts) — asserts the fresh history, not just the looped result. |
| [continue-as-new](https://github.com/temporalio/samples-typescript/tree/main/continue-as-new) | ✅ | `continueAsNew(workflow, payload)` (definition module): ends the run and starts a fresh one with the same workflow id. Tests: [continue-as-new.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/continue-as-new.test.ts) — asserts the fresh history, not just the looped result; [definition.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/definition.test.ts) — the in-memory runtime records the schema-checked continuation, Temporal rolls the history. |
| [batch-sliding-window](https://github.com/temporalio/samples-typescript/tree/main/batch-sliding-window) | ✅ | Sliding window of discarded children reporting completion via workflow-to-workflow mailbox; the orchestrator continues-as-new mid-batch, draining reports with the mailbox's `.poll` into the carried in-flight set. Test: [batch.test.ts](https://github.com/TeamSpringbird/effect-temporal/blob/main/src/__tests__/batch.test.ts). |
| [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) | ✅ | `Versioning.match`: 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). |
| [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). |

## Scheduling and client features

Expand Down Expand Up @@ -108,10 +108,13 @@ are worth a smoke test once the package is adopted in a real worker.
Every sample with workflow-semantics content is supported (tested) or works
unchanged; the remainder is infrastructure with no package involvement.
Where a sample needed a primitive the upstream Effect API does not define,
the package provides one: `DurableMailbox` (repeated signals, long-lived
state), `DurableUpdate` (request/response with typed channels), `StateCell`
(queryable state), `continueAsNew` (unbounded workflows), schedule and
Nexus bridges, and patch-marker versioning.
the package provides one, declared through the `definition` module:
`defineMailbox` (repeated signals, long-lived state), `defineUpdate`
(request/response with typed channels), `defineState` (queryable state),
`continueAsNew` (unbounded workflows), `executeChild`, `sleep`/`sleepUntil`,
`version`/`versioned` (patch-marker versioning), plus schedule and Nexus
bridges. Every one of them runs on the in-memory test runtime as well as on
Temporal.

New capabilities land with a test mirroring the corresponding sample, in
this package's suite.
Loading
Loading