diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 87a38b79..f1350623 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -36,6 +36,10 @@ durable execution with minimal operational complexity.
discovery paths, and optional ignore patterns for CLI commands. It typically
imports the shared `backend` from `openworkflow/client.*` so app code and CLI
use the same connection.
+- **Signal**: A named, point-in-time message sent to all workflows currently
+ waiting on that signal name. Signals carry an optional JSON payload. When
+ sent, a row is written to `workflow_signals` for each waiting workflow and
+ the workflow is woken. If no workflow is waiting, the signal is dropped.
- **`availableAt`**: A critical timestamp on a workflow run that controls its
visibility to workers. It is used for scheduling, heartbeating, crash
recovery, and durable timers.
@@ -105,6 +109,7 @@ of coordination. There is no separate orchestrator server.
| |
| - workflow_runs |
| - step_attempts |
+ | - workflow_signals |
+------------------------------+
```
@@ -122,9 +127,10 @@ of coordination. There is no separate orchestrator server.
`npx @openworkflow/cli worker start` with
auto-discovery of workflow files.
- **Backend**: The source of truth. It stores workflow runs and step attempts.
- The `workflow_runs` table serves as the job queue for the workers, while the
- `step_attempts` table serves as a record of started and completed work,
- enabling memoization.
+ The `workflow_runs` table serves as the job queue for the workers, the
+ `step_attempts` table serves as a record of started and completed work
+ enabling memoization, and the `workflow_signals` table records signal
+ deliveries so a waking workflow can read the payload.
### 2.3. Basic Execution Flow
@@ -149,7 +155,7 @@ of coordination. There is no separate orchestrator server.
`step_attempt` record with status `running`, executes the step function, and
then updates the `step_attempt` to `completed` upon completion. The Worker
continues executing inline until the workflow code completes or encounters a
- sleep.
+ durable wait such as sleep, child-workflow waiting, or signal waiting.
6. **State Update**: The Worker updates the Backend with each `step_attempt` as
it is created and completed, and updates the status of the `workflow_run`
(e.g., `completed`, `running` for parked waits).
@@ -234,10 +240,34 @@ target workflow name in `spec`) and `options.timeout` controls the wait timeout
(default 1y). When the timeout is reached, the parent step fails but the child
workflow continues running independently.
-All step APIs (`step.run`, `step.sleep`, and `step.runWorkflow`) share the same
-collision logic for durable keys. If duplicate base names are encountered in one
-execution pass, OpenWorkflow auto-indexes them as `name`, `name:1`, `name:2`,
-and so on so each step call maps to a distinct step attempt.
+**`step.sendSignal(options)`**: Sends a named signal to all workflows currently
+waiting on it. The send is recorded as a step attempt so it won't repeat on
+replay. If no workflow is waiting, the signal is silently dropped.
+
+```ts
+await step.sendSignal({
+ signal: `approval:${orderId}`,
+ data: { approved: true },
+});
+```
+
+**`step.waitForSignal(options)`**: Parks the workflow until a matching signal
+arrives or the timeout expires. When a signal is sent targeting this signal
+name, a delivery row is written to `workflow_signals` and the workflow is woken.
+Returns `{ data }` on delivery, or `null` on timeout.
+
+```ts
+const result = await step.waitForSignal({
+ signal: `approval:${orderId}`,
+ timeout: "7d",
+});
+```
+
+All step APIs (`step.run`, `step.sleep`, `step.runWorkflow`, `step.sendSignal`,
+and `step.waitForSignal`) share the same collision logic for durable keys. If
+duplicate base names are encountered in one execution pass, OpenWorkflow
+auto-indexes them as `name`, `name:1`, `name:2`, and so on so each step call
+maps to a distinct step attempt.
## 4. Error Handling & Retries
diff --git a/packages/dashboard/src/routes/runs/$runId.tsx b/packages/dashboard/src/routes/runs/$runId.tsx
index 4952f4cd..10beee0f 100644
--- a/packages/dashboard/src/routes/runs/$runId.tsx
+++ b/packages/dashboard/src/routes/runs/$runId.tsx
@@ -352,8 +352,7 @@ function RunDetailsPage() {
const config = STEP_STATUS_CONFIG[step.status];
const StatusIcon = config.icon;
const iconColor = config.color;
- const stepTypeLabel =
- step.kind === "function" ? "function" : step.kind;
+ const stepTypeLabel = step.kind.replaceAll("-", " ");
const stepDuration = computeDuration(
step.startedAt,
step.finishedAt,
diff --git a/packages/docs/docs.json b/packages/docs/docs.json
index 13eb3f90..4d34e3bf 100644
--- a/packages/docs/docs.json
+++ b/packages/docs/docs.json
@@ -42,6 +42,7 @@
"docs/parallel-steps",
"docs/dynamic-steps",
"docs/child-workflows",
+ "docs/signals",
"docs/retries",
"docs/type-safety",
"docs/versioning",
diff --git a/packages/docs/docs/overview.mdx b/packages/docs/docs/overview.mdx
index 75ac999c..08edaf17 100644
--- a/packages/docs/docs/overview.mdx
+++ b/packages/docs/docs/overview.mdx
@@ -37,6 +37,8 @@ work.
- **Memoized steps** prevent duplicate side effects on retries
- **Durable sleep** (`step.sleep`) pauses runs without holding worker capacity
+- **Signals** (`step.sendSignal`, `step.waitForSignal`) enable runtime
+ communication to & between workflows
- **Heartbeats + leases** (`availableAt`) allow automatic crash recovery
- **Database as source of truth** avoids a separate orchestration service
diff --git a/packages/docs/docs/roadmap.mdx b/packages/docs/docs/roadmap.mdx
index f97bc187..6a51854c 100644
--- a/packages/docs/docs/roadmap.mdx
+++ b/packages/docs/docs/roadmap.mdx
@@ -20,10 +20,10 @@ description: What's coming next for OpenWorkflow
- ✅ Idempotency keys
- ✅ Prometheus `/metrics` endpoint
- ✅ Child workflows (`step.runWorkflow`)
+- ✅ Signals (`step.sendSignal`, `step.waitForSignal`)
## Coming Soon
-- Signals
- Cron / scheduling
- Rollback / compensation functions
- Priority and concurrency controls
diff --git a/packages/docs/docs/signals.mdx b/packages/docs/docs/signals.mdx
new file mode 100644
index 00000000..976a77e3
--- /dev/null
+++ b/packages/docs/docs/signals.mdx
@@ -0,0 +1,346 @@
+---
+title: Signals
+description: Send data between workflows at runtime without polling
+---
+
+Signals let workflows communicate at runtime. A workflow can pause and wait for
+a signal, and another workflow (or your application code) can send that signal
+to wake it up with data attached.
+
+This is useful any time a workflow needs to wait for something that isn't on a
+timer: a human approval, a webhook callback, a payment confirmation, or a
+coordination message from another workflow.
+
+## Basic Usage
+
+### Waiting for a Signal
+
+Use `step.waitForSignal()` inside a workflow to pause until a matching signal
+arrives:
+
+```ts
+import { defineWorkflow } from "openworkflow";
+
+export const approvalWorkflow = defineWorkflow(
+ { name: "approval-workflow" },
+ async ({ input, step }) => {
+ await step.run({ name: "request-approval" }, async () => {
+ await slack.send({
+ channel: "#approvals",
+ text: `Please approve order ${input.orderId}`,
+ });
+ });
+
+ // wait until someone sends the "approval" signal
+ const decision = await step.waitForSignal<{ approved: boolean }>({
+ signal: `approval:${input.orderId}`,
+ timeout: "7d",
+ });
+
+ if (decision?.data.approved) {
+ await step.run({ name: "process-order" }, async () => {
+ await orders.process(input.orderId);
+ });
+ }
+ },
+);
+```
+
+### Sending a Signal
+
+Send a signal from another workflow using `step.sendSignal()`:
+
+```ts
+export const reviewWorkflow = defineWorkflow(
+ { name: "review-workflow" },
+ async ({ input, step }) => {
+ const verdict = await step.run({ name: "run-review" }, async () => {
+ return await reviewService.evaluate(input.orderId);
+ });
+
+ await step.sendSignal({
+ signal: `approval:${input.orderId}`,
+ data: { approved: verdict.passed },
+ });
+ },
+);
+```
+
+Or send a signal from your application code using the client:
+
+```ts
+import { ow } from "./openworkflow/client";
+
+// from an API route, webhook handler, etc.
+await ow.sendSignal({
+ signal: `approval:${orderId}`,
+ data: { approved: true },
+});
+```
+
+
+ Signals are not buffered. If you send a signal before any workflow is waiting
+ for it, the signal is lost.
+
+
+## Signal Names
+
+Signal names are arbitrary strings scoped to the backend namespace. Use
+descriptive, unique names — often including an entity ID — to avoid collisions:
+
+```ts
+// Good - scoped to a specific entity
+await step.waitForSignal({ signal: `payment:${invoiceId}` });
+await step.waitForSignal({ signal: `approval:order:${orderId}` });
+
+// Bad - too generic, could collide across workflows
+await step.waitForSignal({ signal: "done" });
+```
+
+## Step Names
+
+Like other step types, signal steps need unique names within a workflow. If you
+don't provide one, OpenWorkflow uses the signal name as the step name.
+
+```ts
+// Implicit — step name defaults to signal name
+await step.waitForSignal({ signal: `payment:${invoiceId}` });
+
+// Explicit step name
+await step.waitForSignal({
+ name: "wait-for-payment",
+ signal: `payment:${invoiceId}`,
+});
+```
+
+## Timeout
+
+`step.waitForSignal` accepts an optional `timeout`. If the signal doesn't
+arrive before the timeout, the step resolves with `null` instead of blocking
+forever.
+
+```ts
+const result = await step.waitForSignal({
+ signal: `approval:${orderId}`,
+ timeout: "24h",
+});
+
+if (result === null) {
+ // timed out — no signal arrived within 24 hours
+ await step.run({ name: "escalate" }, async () => {
+ await alerts.send("Approval timed out");
+ });
+}
+```
+
+`timeout` accepts a [duration string](/docs/sleeping#duration-formats), a
+number of milliseconds, or a `Date`.
+
+If no timeout is specified, the wait defaults to **1 year**.
+
+## Schema Validation
+
+Validate signal payloads at receive time using any
+[Standard Schema](/docs/standard-schema) compatible validator:
+
+```ts
+import { z } from "zod";
+
+const approvalSchema = z.object({
+ approved: z.boolean(),
+ reviewedBy: z.string(),
+});
+
+const decision = await step.waitForSignal({
+ signal: `approval:${orderId}`,
+ schema: approvalSchema,
+ timeout: "7d",
+});
+
+// `decision` is typed as { data: { approved: boolean; reviewedBy: string } } | null
+```
+
+If the signal data doesn't match the schema, the step fails permanently (no
+retries) to surface the contract violation immediately.
+
+## Idempotency
+
+When sending signals from the client, you can provide an idempotency key to
+safely retry without delivering the signal twice:
+
+```ts
+await ow.sendSignal({
+ signal: `payment:${invoiceId}`,
+ data: { amount: 99.99 },
+ idempotencyKey: `payment-confirmed:${invoiceId}`,
+});
+```
+
+If a signal with the same idempotency key has already been sent and delivered
+to at least one waiter, the call returns the original result without
+re-delivering.
+
+## Fan-Out: One Signal, Many Waiters
+
+A single `sendSignal` call delivers to **every** workflow currently waiting on
+that signal name. This makes fan-out coordination straightforward:
+
+```ts
+// Multiple workflows waiting on the same signal
+const workflowA = defineWorkflow({ name: "listener-a" }, async ({ step }) => {
+ const config = await step.waitForSignal({ signal: "config-updated" });
+ // handle update
+});
+
+const workflowB = defineWorkflow({ name: "listener-b" }, async ({ step }) => {
+ const config = await step.waitForSignal({ signal: "config-updated" });
+ // handle update
+});
+
+// One signal wakes both workflows
+await ow.sendSignal({
+ signal: "config-updated",
+ data: { version: 42 },
+});
+```
+
+## Sending Signals from Workflows
+
+Use `step.sendSignal()` inside a workflow to send signals durably. The send is
+recorded as a step attempt, so it won't be repeated on replay:
+
+```ts
+const result = await step.sendSignal({
+ signal: `approval:${orderId}`,
+ data: { approved: true },
+});
+
+// result.workflowRunIds contains IDs of workflows that received the signal
+```
+
+## Common Patterns
+
+### Human-in-the-Loop Approval
+
+```ts
+export const purchaseWorkflow = defineWorkflow(
+ { name: "purchase" },
+ async ({ input, step }) => {
+ await step.run({ name: "send-approval-request" }, async () => {
+ await email.send({
+ to: input.managerEmail,
+ subject: `Approve purchase: $${input.amount}`,
+ body: `Click to approve: ${approvalUrl(input.purchaseId)}`,
+ });
+ });
+
+ const approval = await step.waitForSignal<{ approved: boolean }>({
+ signal: `purchase-approval:${input.purchaseId}`,
+ timeout: "3d",
+ });
+
+ if (!approval?.data.approved) {
+ await step.run({ name: "notify-rejected" }, async () => {
+ await email.send({
+ to: input.requesterEmail,
+ subject: "Purchase request denied",
+ });
+ });
+ return { status: "rejected" };
+ }
+
+ await step.run({ name: "process-purchase" }, async () => {
+ await purchasing.submit(input.purchaseId);
+ });
+
+ return { status: "approved" };
+ },
+);
+
+// in your API route handler:
+app.post("/approve/:purchaseId", async (req, res) => {
+ await ow.sendSignal({
+ signal: `purchase-approval:${req.params.purchaseId}`,
+ data: { approved: req.body.approved },
+ });
+ res.json({ ok: true });
+});
+```
+
+### Webhook Callback
+
+```ts
+export const paymentWorkflow = defineWorkflow(
+ { name: "payment" },
+ async ({ input, step }) => {
+ const checkout = await step.run({ name: "create-checkout" }, async () => {
+ return await stripe.checkout.sessions.create({
+ metadata: { workflowSignal: `payment:${input.orderId}` },
+ // ...
+ });
+ });
+
+ const payment = await step.waitForSignal({
+ signal: `payment:${input.orderId}`,
+ timeout: "1h",
+ });
+
+ if (!payment) {
+ return { status: "expired" };
+ }
+
+ await step.run({ name: "fulfill-order" }, async () => {
+ await orders.fulfill(input.orderId);
+ });
+
+ return { status: "paid" };
+ },
+);
+
+// in your Stripe webhook handler:
+app.post("/webhooks/stripe", async (req, res) => {
+ const event = req.body;
+ if (event.type === "checkout.session.completed") {
+ await ow.sendSignal({
+ signal: event.data.object.metadata.workflowSignal,
+ data: { sessionId: event.data.object.id },
+ idempotencyKey: event.id,
+ });
+ }
+ res.sendStatus(200);
+});
+```
+
+### Workflow-to-Workflow Coordination
+
+```ts
+const producer = defineWorkflow(
+ { name: "data-producer" },
+ async ({ input, step }) => {
+ const data = await step.run({ name: "generate-data" }, async () => {
+ return await heavyComputation(input);
+ });
+
+ await step.sendSignal({
+ signal: `data-ready:${input.batchId}`,
+ data: { recordCount: data.length },
+ });
+ },
+);
+
+const consumer = defineWorkflow(
+ { name: "data-consumer" },
+ async ({ input, step }) => {
+ const notification = await step.waitForSignal({
+ signal: `data-ready:${input.batchId}`,
+ timeout: "1h",
+ });
+
+ if (notification) {
+ await step.run({ name: "process-data" }, async () => {
+ await processRecords(input.batchId, notification.data.recordCount);
+ });
+ }
+ },
+);
+```
diff --git a/packages/docs/docs/steps.mdx b/packages/docs/docs/steps.mdx
index 5eaa2f80..8d00cbdd 100644
--- a/packages/docs/docs/steps.mdx
+++ b/packages/docs/docs/steps.mdx
@@ -115,7 +115,7 @@ await step.run({ name: "log-event" }, async () => {
## Step Types
-OpenWorkflow provides three step types:
+OpenWorkflow provides five step types:
### `step.run()`
@@ -148,6 +148,31 @@ const childOutput = await step.runWorkflow(
);
```
+### `step.sendSignal()`
+
+Sends a signal to all workflows currently waiting on that signal name:
+
+```ts
+const { workflowRunIds } = await step.sendSignal({
+ signal: `approval:${orderId}`,
+ data: { approved: true },
+});
+```
+
+### `step.waitForSignal()`
+
+Pauses the workflow until a matching signal is sent, or the timeout expires:
+
+```ts
+const result = await step.waitForSignal({
+ signal: `approval:${orderId}`,
+ timeout: "7d",
+});
+// result is { data } or null if timed out
+```
+
+See [Signals](/docs/signals) for details.
+
## Retry Policy (Optional)
Control backoff and retry limits for an individual step:
diff --git a/packages/docs/docs/workflows.mdx b/packages/docs/docs/workflows.mdx
index 5d8ae39e..ca480568 100644
--- a/packages/docs/docs/workflows.mdx
+++ b/packages/docs/docs/workflows.mdx
@@ -213,12 +213,12 @@ create a separate run.
The workflow function receives an object with four properties:
-| Parameter | Type | Description |
-| --------- | --------------------- | --------------------------------------------------------------------- |
-| `input` | Generic | The input data passed when starting the workflow |
-| `step` | `StepApi` | API for defining steps (`step.run`, `step.sleep`, `step.runWorkflow`) |
-| `version` | `string \| null` | The workflow version, if specified |
-| `run` | `WorkflowRunMetadata` | Read-only run metadata snapshot (`run.id`, etc.) |
+| Parameter | Type | Description |
+| --------- | --------------------- | -------------------------------------------------------------------------------------------------------------- |
+| `input` | Generic | The input data passed when starting the workflow |
+| `step` | `StepApi` | API for defining steps (`step.run`, `step.sleep`, `step.runWorkflow`, `step.sendSignal`, `step.waitForSignal`) |
+| `version` | `string \| null` | The workflow version, if specified |
+| `run` | `WorkflowRunMetadata` | Read-only run metadata snapshot (`run.id`, etc.) |
```ts
defineWorkflow({ name: "example" }, async ({ input, step, version, run }) => {
diff --git a/packages/openworkflow/client/client.ts b/packages/openworkflow/client/client.ts
index bb936dce..7f7029d0 100644
--- a/packages/openworkflow/client/client.ts
+++ b/packages/openworkflow/client/client.ts
@@ -1,5 +1,6 @@
-import type { Backend } from "../core/backend.js";
+import type { Backend, SendSignalResult } from "../core/backend.js";
import type { DurationString } from "../core/duration.js";
+import { JsonValue } from "../core/json.js";
import type { StandardSchemaV1 } from "../core/standard-schema.js";
import { calculateDateFromDuration } from "../core/step-attempt.js";
import {
@@ -173,6 +174,33 @@ export class OpenWorkflow {
async cancelWorkflowRun(workflowRunId: string): Promise {
await this.backend.cancelWorkflowRun({ workflowRunId });
}
+
+ /**
+ * Send a signal to all workflows currently waiting on the given signal
+ * string. If no workflow is waiting, the signal is silently dropped.
+ * @param options - Signal options
+ * @returns IDs of workflow runs that received the signal
+ * @example
+ * ```ts
+ * const { workflowRunIds } = await ow.sendSignal({
+ * signal: "approval:order_456",
+ * data: { approved: true },
+ * });
+ * ```
+ */
+ async sendSignal(
+ options: Readonly<{
+ signal: string;
+ data?: JsonValue;
+ idempotencyKey?: string;
+ }>,
+ ): Promise {
+ return this.backend.sendSignal({
+ signal: options.signal,
+ data: options.data ?? null,
+ idempotencyKey: options.idempotencyKey ?? null,
+ });
+ }
}
/**
diff --git a/packages/openworkflow/core/backend.ts b/packages/openworkflow/core/backend.ts
index d79dc5e6..9edf79be 100644
--- a/packages/openworkflow/core/backend.ts
+++ b/packages/openworkflow/core/backend.ts
@@ -68,6 +68,12 @@ export interface Backend {
params: Readonly,
): Promise;
+ // Signals
+ sendSignal(params: Readonly): Promise;
+ getSignalDelivery(
+ params: Readonly,
+ ): Promise;
+
// Lifecycle
stop(): Promise;
}
@@ -173,6 +179,20 @@ export interface SetStepAttemptChildWorkflowRunParams {
childWorkflowRunId: string;
}
+export interface SendSignalParams {
+ signal: string;
+ data: JsonValue | null;
+ idempotencyKey: string | null;
+}
+
+export interface SendSignalResult {
+ workflowRunIds: string[];
+}
+
+export interface GetSignalDeliveryParams {
+ stepAttemptId: string;
+}
+
export interface PaginationOptions {
limit?: number;
after?: string;
diff --git a/packages/openworkflow/core/step-attempt.test.ts b/packages/openworkflow/core/step-attempt.test.ts
index ce5f207f..502693bd 100644
--- a/packages/openworkflow/core/step-attempt.test.ts
+++ b/packages/openworkflow/core/step-attempt.test.ts
@@ -7,6 +7,7 @@ import {
calculateDateFromDuration,
createSleepContext,
createWorkflowContext,
+ createSignalWaitContext,
} from "./step-attempt.js";
import type { StepAttempt, StepAttemptCache } from "./step-attempt.js";
import { describe, expect, test } from "vitest";
@@ -339,6 +340,26 @@ describe("createWorkflowContext", () => {
});
});
+describe("createSignalWaitContext", () => {
+ test("creates signal-wait context with signal and timeout", () => {
+ const timeoutAt = new Date("2025-06-15T10:30:00.000Z");
+ const context = createSignalWaitContext("approval:order_456", timeoutAt);
+
+ expect(context).toEqual({
+ kind: "signal-wait",
+ signal: "approval:order_456",
+ timeoutAt: "2025-06-15T10:30:00.000Z",
+ });
+ });
+
+ test("preserves millisecond precision in timeout", () => {
+ const timeoutAt = new Date("2025-01-01T00:00:00.123Z");
+ const context = createSignalWaitContext("test-signal", timeoutAt);
+
+ expect(context.timeoutAt).toBe("2025-01-01T00:00:00.123Z");
+ });
+});
+
function createMockStepAttempt(
overrides: Partial = {},
): StepAttempt {
diff --git a/packages/openworkflow/core/step-attempt.ts b/packages/openworkflow/core/step-attempt.ts
index 888120ac..3730db67 100644
--- a/packages/openworkflow/core/step-attempt.ts
+++ b/packages/openworkflow/core/step-attempt.ts
@@ -7,7 +7,12 @@ import { err, ok } from "./result.js";
/**
* The kind of step in a workflow.
*/
-export type StepKind = "function" | "sleep" | "workflow";
+export type StepKind =
+ | "function"
+ | "sleep"
+ | "workflow"
+ | "signal-send"
+ | "signal-wait";
/**
* Status of a step attempt through its lifecycle.
@@ -34,12 +39,22 @@ export interface WorkflowStepAttemptContext {
timeoutAt: string | null;
}
+/**
+ * Context for a signal-wait step attempt.
+ */
+export interface SignalWaitStepAttemptContext {
+ kind: "signal-wait";
+ signal: string;
+ timeoutAt: string;
+}
+
/**
* Context for a step attempt.
*/
export type StepAttemptContext =
| SleepStepAttemptContext
- | WorkflowStepAttemptContext;
+ | WorkflowStepAttemptContext
+ | SignalWaitStepAttemptContext;
/**
* StepAttempt represents a single attempt of a step within a workflow.
@@ -171,3 +186,20 @@ export function createWorkflowContext(
timeoutAt: timeoutAt?.toISOString() ?? null,
};
}
+
+/**
+ * Create the context object for a signal-wait step attempt.
+ * @param signal - Signal address string
+ * @param timeoutAt - Wait timeout deadline
+ * @returns The context object for the signal-wait step
+ */
+export function createSignalWaitContext(
+ signal: string,
+ timeoutAt: Readonly,
+): SignalWaitStepAttemptContext {
+ return {
+ kind: "signal-wait",
+ signal,
+ timeoutAt: timeoutAt.toISOString(),
+ };
+}
diff --git a/packages/openworkflow/core/workflow-function.ts b/packages/openworkflow/core/workflow-function.ts
index 01745679..6fc0315b 100644
--- a/packages/openworkflow/core/workflow-function.ts
+++ b/packages/openworkflow/core/workflow-function.ts
@@ -1,7 +1,15 @@
import type { DurationString } from "./duration.js";
+import type { JsonValue } from "./json.js";
+import type { StandardSchemaV1 } from "./standard-schema.js";
import type { RetryPolicy, WorkflowSpec } from "./workflow-definition.js";
import type { WorkflowRun } from "./workflow-run.js";
+/**
+ * Timeout for a step wait. Accepts milliseconds (number), a duration string
+ * (e.g. "5m", "1h"), or an absolute Date.
+ */
+export type StepWaitTimeout = number | string | Date;
+
/**
* Config for an individual step defined with `step.run()`.
*/
@@ -37,13 +45,13 @@ export interface StepRunWorkflowOptions {
/**
* Maximum time to wait for the child workflow to complete.
*/
- timeout?: number | string | Date;
+ timeout?: StepWaitTimeout;
}
/**
* Represents the API for defining steps within a workflow. Used within a
* workflow handler to define steps by calling `step.run()`, `step.sleep()`,
- * and `step.runWorkflow()`.
+ * `step.runWorkflow()`, `step.sendSignal()`, and `step.waitForSignal()`.
*/
export interface StepApi {
run: