# @zapier/zapier-durable

## 0.14.3

### Patch Changes

- 39a3e75: Requests to the durable server are now retried when the connection fails, the
  same way a 5xx response is. Before, a reset or refused connection, or a DNS
  failure, paused the execution on the first attempt until it was next resumed.
  `RequestOutcomeUnknownError` is now thrown only once every attempt has failed,
  and its message names the underlying cause, such as `read ECONNRESET`.

  A step that succeeds is no longer recorded as failed when the connection drops
  partway through the server's reply to saving its result. Before, the saved
  result was overwritten with a failure and the execution failed. Now the
  execution pauses, and when it resumes it replays from what the server saved.

## 0.14.2

### Patch Changes

- 374f7c4: When a step fails with a user-fixable app error and the run is held, the held status now includes a detail naming what needs attention, when the error identifies one: the connection id for authentication failures (for example an expired connection), the app key for a restricted app, or the action key for a banned action.

## 0.14.1

### Patch Changes

- 1c1a95d: The CLI commands this package documents — `run-durable`, `get-durable-run`, `list-durable-runs`, and `cancel-durable-run` — are now reached with `zapier-sdk --stability experimental` instead of `--experimental`. The old flag still works and prints a deprecation notice. This release only corrects the README; the package's own API is unchanged.

## 0.14.0

### Minor Changes

- 5a053c4: Added `TickReleasedError`. When one branch of a `Promise.all` fails the run
  (for example, a step exhausts its attempts), the other branches can no longer
  start new steps. Such a step now throws `TickReleasedError` instead of running
  a side effect whose result could never be saved.
- 5a053c4: Added `RequestOutcomeUnknownError`, thrown when a durable write fails in a way
  that leaves its outcome on the server unknown.

  A tick now stops, writing nothing more and sending no release, when it cannot
  know or cannot make its writes. The next tick replays what the server saved.
  - A 409 on a retried checkpoint or release throws `LeaseConflictError`.
    Before, it counted as success, so user code could see a result that was
    never saved, and a tick could report done while the run was still running.
  - A request that fails on a network error, or runs out of retries on a timeout
    or 5xx, throws `RequestOutcomeUnknownError`. Before, it counted as a failure,
    so a step that succeeded could be saved as failed over its saved result, and
    a lost completion could be re-sent as a failed release.
  - User code that catches either error cannot keep writing or release. Before,
    a caught `LeaseConflictError` on an expired lease could let the run complete
    without the rejected step.

### Patch Changes

- 5a053c4: A step that follows `ctx.createCallback` no longer risks running its side
  effect twice. Before, when another `Promise.all` branch was already waiting
  (for example on `ctx.wait` or a step retry), the execution could suspend
  before the callback finished saving. The step after it then ran but could not
  be saved, so the next resumption ran it again.

## 0.13.1

### Patch Changes

- b601ab5: Durable results decode faster on Node.js runtimes with native zstd support. Browser and older-runtime behavior is unchanged.

## 0.13.0

### Minor Changes

- 150099e: `ctx.wait` sleeps in-process when the time remaining at first await is at or below `ZAPIER_DURABLE_WAIT_INPROCESS_THRESHOLD_SECONDS` (default: `0`, disabled). The wait is journaled as pending at the call and completed when it resolves; waits above the threshold suspend and resume as before. The per-tick in-process budget is configurable via `ZAPIER_DURABLE_WAIT_INPROCESS_BUDGET_SECONDS` (default: `120`).

## 0.12.7

### Patch Changes

- 79b9be9: _This release contains no user-facing changes._

## 0.12.6

### Patch Changes

- fd9a104: A step that fails because the account's task quota is exhausted is now held (paused until tasks are available) instead of exhausting its retries.

## 0.12.5

### Patch Changes

- 3c18ae1: `RestClient` can now be constructed when the package is loaded with `require()`. The CommonJS build previously threw a `TypeError` at construction. The ESM build is unaffected.

## 0.12.4

### Patch Changes

- 55f5f1d: A step that fails because the connection's credentials could not be refreshed is now held for re-authentication rather than exhausting retries.
- 0f54b24: Fixed the `## CLI` section of the README, which referenced commands that don't exist (`durable-run`, `durable-callback`, `durable-status`). It now points to the real experimental commands (`run-durable`, `get-durable-run`, `list-durable-runs`, `cancel-durable-run`) and the CLI reference docs.

  Also documented previously-undocumented options that already shipped: `defineDurable`'s `outputSchema`, and `ctx.step`'s `maxAttempts` / `retryDelaySeconds`. Corrected the documented test-mode env var name to `ZAPIER_DURABLE_TEST_MAX_RETRY_DELAY_MILLISECONDS` (`_MS` is a deprecated alias).

## 0.12.3

### Patch Changes

- 96db263: A step that fails because the app or action is restricted now classifies as
  `access_restricted_app` or `banned_app_action` and the run is held with
  `app_restriction` or `banned_app_action`, rather than retrying indefinitely.

## 0.12.2

### Patch Changes

- fd979cb: A step that fails because authentication access was revoked now classifies as
  `authentication_revoked` and the run is held for the user to fix, rather than
  retrying indefinitely.

## 0.12.1

### Patch Changes

- d374015: A step that fails because the account is out of tasks now classifies as
  `over_task_limit` and asks the server to hold the run, instead of burning its
  retries and failing. A `402` response carrying the error code
  `insufficient_tasks` now maps to the server's `over_task_limit` cause, which is
  hold-eligible. As with `stale_authentication`, the directive is advisory — a
  server that does not honor it releases the run to retry exactly as before.

## 0.12.0

### Minor Changes

- d711e2f: Added `ErrorMetadata`, `HeldReason`, and `RunStateDirective` type exports. A step failure is now classified and reported as structured error metadata on the checkpoint, and when the failure is hold-eligible (e.g. an expired connection), the runner sends an advisory hold directive on release so the server can park the run until the user reconnects.

## 0.11.1

### Patch Changes

- 67b055c: Network requests made during durable execution are now bounded by a 30-second per-attempt timeout. A stalled request is aborted and retried like any other transient transport failure, each attempt getting a fresh 30-second budget, and the call fails only once every attempt has timed out. The resulting error names the request that stalled. Previously a stalled request could hang indefinitely.

## 0.11.0

### Minor Changes

- 54fc1c0: Added optional `inbox`, `idempotencyKey`, and `leaseToken` arguments to durable calls for retry-safe worker-inbox execution.

  Two new errors make the server's 409 responses distinguishable from transport failures: `ExecutionTerminalError` when checkout hits an already-finished execution (the usual cause is a duplicate event matching an inbox execution that has completed — read the outcome instead of retrying), and `IdempotencyConflictError` when an `idempotencyKey` is reused with a different request body.

  `MemoryClient` and `FilesystemClient` now honor `inbox` + `idempotencyKey`, so ensure-by-key behavior is exercised locally rather than only against the API.

## 0.10.1

### Patch Changes

- 72c1cc7: Replaying a completed step whose result was externalized no longer strands the run when it races with a live blocker (e.g. a pending callback inside `Promise.all`). Previously the hydration round-trip could be interrupted mid-flight, leaving the surrounding promise unresolved.

## 0.10.0

### Minor Changes

- 36b07c5: Renamed the `ZAPIER_DURABLE_TEST_MAX_RETRY_DELAY_MS` environment variable to `ZAPIER_DURABLE_TEST_MAX_RETRY_DELAY_MILLISECONDS` to spell out the unit. The old name still works as a fallback.

## 0.9.1

### Patch Changes

- e1896a3: The default `ZAPIER_DURABLE_API_URL` now points at the Code Substrate runner API. Callers that set `ZAPIER_DURABLE_API_URL` explicitly are unaffected.

## 0.9.0

### Minor Changes

- a25b47a: Surface typed errors for callback delivery. `DurableClient.callback()` now throws
  `CallbackExpiredError` (the callback expired) and `CallbackConflictError` (already
  completed with a different payload / no longer deliverable), so a delivering caller
  can tell "already expired" from "conflicting redelivery" from a transient failure.
  Both extend the durable error hierarchy and are classified terminal by `isSdkError`.
  The now-unproduced `expired` / `already_completed` members are removed from the
  `CallbackResponse` union; `not_found` and `validation_failed` remain returns.

## 0.8.0

### Minor Changes

- 9056820: Added a `retrying` field to suspended `DurableResult` returns. When a step fails but still has attempts remaining, `executeTick`'s suspended branch now returns `retrying: [{ name, error, attempt, nextRetryAt }]` — one entry per step that burned an attempt this tick, each with the full serialized error chain. Absent on completed and failed returns, and on suspends with no step failure (e.g. a `ctx.wait`), so a host can observe an in-flight retry failure without waiting for the step to exhaust.

## 0.7.0

### Minor Changes

- 99e0034: Capture the full underlying error when a step exhausts its retries. An uncaught `StepExhaustedError` now carries the original failure (name, message, stack, own properties, and any nested cause) through its native `cause`, and the runner reports that full chain when releasing a failed execution, so hosts can surface a complete stack trace.

  Inside the durable context a caught cause exposes only `name` and `message`; other fields read as `undefined` while the workflow runs so catch logic stays deterministic across replays. The full error is available outside the durable context (inside steps, and once the error has propagated out of the workflow) and is journaled so the first exhaustion and every later replay observe an identical cause.

## 0.6.1

### Patch Changes

- a111871: `ctx.wait` now sends a server-authoritative `duration_seconds` instead of a client-computed `resume_at`, eliminating SDK-vs-server clock skew on wait deadlines. Wait durations are validated as whole-number seconds in `[1, 31536000]` and throw `DurableValidationError` (carrying the offending value) on invalid input.

## 0.6.0

### Minor Changes

- 5c8aebd: `createCallback` can now tell you whether a callback was answered before its deadline.

  Pass `timeoutSeconds`, and awaiting the callback resolves a `CallbackResult` you branch on:
  - `{ status: "delivered", value, at }` — the payload arrived in time; `value` is that payload.
  - `{ status: "expired", at }` — the deadline passed first.

  Branch on `result.status` to handle the deadline. Racing durable operations with `Promise.race` is not supported.

  Callbacks created without `timeoutSeconds` are unchanged: awaiting still resolves the payload directly, and they never expire.

  This is a breaking change if you already pass `timeoutSeconds`: awaiting used to resolve the payload directly, and now resolves the `CallbackResult` above.

## 0.5.4

### Patch Changes

- dd3ab5e: Publish `@zapier/zapier-durable` as a public npm package. From this
  version forward, `npm install @zapier/zapier-durable` works without
  `@zapier`-scope authentication.

## 0.5.3

### Patch Changes

- 02ed05d: Public-release prep. Drops the stale Alpha framing in the README and
  replaces it with a generic pre-1.0 disclaimer. The actual flip from
  restricted to public access on the npm registry is sequenced in a
  follow-up.

## 0.5.2

### Patch Changes

- f17a21a: Decompress externalized result bundles via native `DecompressionStream("zstd")` where available (Node 24+, modern browsers since mid-2024), falling back to `fzstd` otherwise. The fallback module is now dynamically imported so the ~30 KB pure-JS decompressor doesn't ship in browsers that have native zstd. Both paths enforce a hard cap on uncompressed output, derived from the server-signed `uncompressed_size` plus a small slack; the native path aborts the stream mid-flight when the cap is exceeded, the fallback length-checks after decompression. Cap violations and decompressor errors surface as the existing `ResultCorruptError(kind: "decompress_error")`.

## 0.5.1

### Patch Changes

- d5e9828: Switch the default API base URL to the public sdkapi proxy at `https://sdkapi.zapier.com/api/v0/sdk/sdkdurableapi`. Affects both `ZAPIER_DURABLE_API_URL` and `RestClient`'s `baseUrl` default. Existing callers that pass an explicit `baseUrl` or set `ZAPIER_DURABLE_API_URL` are unaffected.

  Quiet the `guardedImportsPlugin` esbuild plugin by default — the `[determinism-guard] Shimming:` log is now gated behind a new optional `verbose` parameter, and the generated CJS preload no longer logs `[determinism-guard] Preload installed` on startup.

## 0.5.0

### Minor Changes

- df9121e: Implements the SDK read consumer for externalized step results. `RestClient` advertises the `presigned-result-v1` capability; sentinel-prefixed step results are resolved lazily on first read inside `ctx.step()` via a per-tick `OperationBundleResolver` (fetch the presigned S3 bundle, verify SHA-256 before fzstd decompression, parse the JSON bag). Falls back to `fetchDeferred` on 403, expired URLs, or when the server omits a presigned block. Opt out via `disablePresignedCapability` for restricted-egress environments.

## 0.4.1

### Patch Changes

- b897714: Fix race between step retry checkpoint and dormancy. The step's catch block previously called `tracker.pause(name)` before awaiting the checkpoint HTTP call, which let dormancy fire and the runner's release call null the lease while the checkpoint was still in flight. The checkpoint would then land at the API as 409 "Lease invalid or expired", the failure was never recorded, `retry_count` never advanced, and in some cases runs would hang permanently. Wrap both the `failed` (retry) and `exhausted` checkpoints in a synthetic ACTIVE worker so dormancy cannot fire until the HTTP call settles. The exhausted-checkpoint payload now also includes `retry_count` and `max_attempts`, which were previously omitted and clobbered the column back to 0 on upsert. Reserve the prefix `__zapier_internal__:` for SDK-internal DormancyTracker ids and reject user operation names that start with it (`ReservedOperationNameError`) so synthetic ids cannot collide with user-named operations.

## 0.4.0

### Minor Changes

- cc43359: Wire shape and typed errors for journal externalization. Adds `PresignedBlock` and `DeferredFieldsResponse` types, an optional `presigned?: PresignedBlock` field on `Operation`, and `fetchDeferred(executionId, operationId, leaseToken)` on the `DurableClient` interface. All three client implementations get a stub returning `{ fields: {} }` — real HTTP, sentinel detection, and S3 fetch land in a follow-up. Three new typed errors — `ResultTooLargeError`, `ResultNotFoundError`, `ResultCorruptError` — added to `isSdkError` as terminal. `ResultCorruptError.kind` discriminates `checksum_mismatch | decompress_error | zstd_version_mismatch | envelope_sig_invalid`.

## 0.3.1

### Patch Changes

- 993479b: Move wait expiry evaluation from the SDK runtime to the server at checkout — the server is the authority on operation state. `MemoryClient` and `FilesystemClient` accept an injected `clock` that governs wait auto-completion at checkout and callback expiry / `completed_at`. `DurableCallOptions.clock` is now forwarded into auto-created default clients so the high-level `durable(input, { clock })` API stays on a single time source.

## 0.3.0

### Minor Changes

- 2e7d4b3: Export `executeTick` — a lower-level primitive for running the durable tick lifecycle on a pre-built `DurableContext`. This enables the SDK's `defineDurable` to construct an enriched context (with `zapier`, `connection`, extensions) and delegate execution without reimplementing the tick lifecycle.
  - `executeTick` is generic over `TContext extends DurableContext`, so callers can pass enriched context subtypes without casting
  - `runTick` is refactored to delegate to `executeTick` internally — no behavior change
  - `DurableContext` gains a public `termination` getter exposing the dormancy promise

## 0.2.1

### Patch Changes

- 5e312e1: Fix ESM build: prevent tsc from overwriting tsup's bundled dist with unbundled re-exports that break Node's ESM module resolution

## 0.2.0

### Minor Changes

- a010373: Add subpath entrypoints for runner, node, and testing concerns.

## 0.1.0

### Minor Changes

- 0ec936e: Move @zapier/zapier-durable into the zapier-sdk monorepo and wire up CLI integration (durable-run, durable-callback, durable-status commands).

## 0.1.0-alpha.3

### Minor Changes

- 62ab557: Use positional arguments for CallableDurable: pass an input object to start a new
  execution, or an execution ID string to resume. Call options (client, logging, etc.)
  move to an optional second argument.

## 0.1.0-alpha.2

### Minor Changes

- d1b7bf2: Rename Quiescence to Dormancy. `QuiescenceTracker` is now `DormancyTracker`,
  exported from `dormancy.ts`. Better communicates that the execution has gone
  dormant while waiting on external events.

## 0.1.0-alpha.1

### Minor Changes

- 4bda16d: Replace CallableDurable's positional `(input, options?)` signature with a single
  options object using a discriminated union: `{ input }` to start a new execution,
  or `{ executionId }` to resume an existing one. Input schema validation now only
  runs on the start path, fixing a bug where resume calls would fail validation
  against throwaway data for any workflow with an inputSchema.

## 0.1.0-alpha.0

### Minor Changes

- 8adc9dc: Initial alpha pre-release for prototyping.
