@zapier/zapier-durable
v0.5.4
Published
Durable execution for Zapier SDK.
Readme
@zapier/zapier-durable
Durable execution for the Zapier SDK. Pluggable adapter model for different runtimes.
Pre-1.0: This package is published on the
latestdist-tag while the API stabilises. Expect breaking changes between minor versions until 1.0.
Features
- Clean ergonomics: Two consistent call styles everywhere (
(name, run)or({ name, run, ...options })) - Optional schemas: Input/payload validation with Zod, but schemas are always optional (defaults to
any) - Adapter-based: Pluggable for different runtimes
Installation
pnpm add @zapier/zapier-durableQuick Start
import { defineDurable } from "@zapier/zapier-durable";
import { z } from "zod";
const myDurable = defineDurable({
name: "my-durable",
description: "My durable example",
inputSchema: z.object({ userId: z.string() }),
run: async (ctx, input) => {
// Steps are durable - they only run once even if the execution replays
const result = await ctx.step("fetch-data", async () => {
const response = await fetch("https://api.example.com/data");
return response.json();
});
// Create a callback for external approval
const [approvalPromise, callbackUrl] = await ctx.createCallback({
name: "wait-for-approval",
payloadSchema: z.object({ approved: z.boolean() }),
});
// Send the callback URL (wrapped in a step so it only runs once)
await ctx.step("notify-approver", async () => {
await notifyApprover(callbackUrl);
});
// Wait for the callback to be delivered
const approval = await approvalPromise;
if (!approval.approved) {
throw new Error("Not approved");
}
return { approved: true, userId: input.userId };
},
});
const response = await myDurable({ userId: "123" });
// Technically, we'd wait for an approval callback, and resume with the execution ID:
// await myDurable(response.executionId)
// ...but imagine we had instant approval!
if (response.done) {
console.log(response.result);
// { approved: true, userId: "123" }
} else {
console.log(response.error);
// { message: "Not approved", name: "Error", stack?: string }
}CLI
$ npx zapier-sdk durable-run --help
Usage: zapier-sdk durable-run [options] <path>
Run a durable execution from a TypeScript/JavaScript file.
Arguments:
path Path to the durable file
Options:
--execution-id <string> Execution ID to resume (auto-generated if omitted)
--input <string> JSON input for the durable (e.g., '{"userId": "123"}')
--debug Enable verbose debug logging
--json Output raw JSON instead of formatted results
-h, --help display help for command$ npx zapier-sdk durable-callback --help
Usage: zapier-sdk durable-callback [options]
Deliver a payload to a waiting callback.
Options:
--callback-id <string> The callback ID to deliver to
--payload <string> JSON payload to deliver
--debug Enable verbose debug logging
--json Output raw JSON instead of formatted results
-h, --help display help for command$ npx zapier-sdk durable-status --help
Usage: zapier-sdk durable-status [options]
Show the status of a durable execution.
Options:
--execution-id <string> The execution ID to check
--debug Enable verbose debug logging
--json Output raw JSON instead of formatted results
-h, --help display help for commandAPI
defineDurable
Define a durable execution with optional input/output schemas.
// Simple signature
defineDurable(name, run)
// Object signature with options
defineDurable({ name, run, description?, inputSchema?, outputSchema? })Options:
inputSchema?: ZodSchema- Optional Zod schema for input validationdescription?: string- Optional description for the durable
ctx.step
Execute a durable step. Steps are memoized - they only execute once even if the execution replays.
// Simple signature
await ctx.step(name, run)
// Object signature with options
await ctx.step({ name, run, outputSchema? })Options:
outputSchema?: ZodSchema- Optional Zod schema for step output validation
ctx.createCallback
Create a callback that can be awaited later. Returns [promise, callbackUrl].
// Simple signature
const [promise, callbackUrl] = await ctx.createCallback("my-callback");
// Object signature with options
const [promise, callbackUrl] = await ctx.createCallback({
name: "my-callback",
payloadSchema: z.object({ result: z.string() }),
timeoutSeconds: 3600,
});
// Send callbackUrl somewhere, then await the promise
const result = await promise;ctx.wait
Wait for a specified duration.
// Simple signature
await ctx.wait("delay-step", 60);
// Object signature
await ctx.wait({ name: "delay-step", seconds: 60 });Configuration
Configuration is via environment variables:
| Variable | Default | Description |
| ---------------------------------- | ---------------------------------------------------- | ------------------------------------------------------- |
| ZAPIER_DURABLE_ADAPTER | "filesystem" | Adapter type: "filesystem", "zapier", or "memory" |
| ZAPIER_DURABLE_FS_DIR | ~/.config/zapier-sdk/durable | Directory for filesystem adapter |
| ZAPIER_DURABLE_DETERMINISM_GUARD | "on" | Enable determinism checking ("on" or "off") |
| ZAPIER_DURABLE_API_URL | https://sdkapi.zapier.com/api/v0/sdk/sdkdurableapi | Base URL for the API (when adapter=zapier) |
| ZAPIER_DURABLE_API_JWT | (auto for localhost) | JWT token for API authentication |
| ZAPIER_DURABLE_DEBUG | "false" | Enable debug logging ("true" or "false") |
Test Mode (Integration Tests)
For faster integration tests, these env vars cap durations:
| Variable | Default | Description |
| ---------------------------------------- | ------- | -------------------------- |
| ZAPIER_DURABLE_TEST_MAX_WAIT_SECONDS | (none) | Cap ctx.wait() durations |
| ZAPIER_DURABLE_TEST_MAX_RETRY_DELAY_MS | (none) | Cap retry backoff delays |
Example:
ZAPIER_DURABLE_TEST_MAX_WAIT_SECONDS=2 \
ZAPIER_DURABLE_TEST_MAX_RETRY_DELAY_MS=500 \
pnpm test