- SDKs & APIs
- @ironflow/core
@ironflow/core
Shared types, schemas, constants, and utilities for the Ironflow JavaScript SDK.
Note: You typically don’t need to install this package directly. It’s automatically included as a dependency of
@ironflow/browserand@ironflow/node.
Installation
Section titled “Installation”npm install @ironflow/coreImport type definitions for type-safe development:
import type { // Event types IronflowEvent, EventFilter, EventSourceType,
// Function types IronflowFunction, FunctionConfig, FunctionContext, FunctionHandler, Trigger, RetryConfig, OnExhausted, CircuitBreakerConfig, ThrottleConfig, ConcurrencyConfig, ExecutionMode,
// Step types StepClient, Duration, ParallelOptions,
// Run types Run, RunInfo, RunStatus, ListRunsOptions, ListRunsResult, CancellationEvent, RunEvent, RunVersionFilter,
// Trigger & emit types InvokeResult, InvokeSyncOptions, InvokeSyncResult, EmitOptions, EmitResult, EmitSyncResult,
// Subscription types Subscription, SubscribeOptions, SubscriptionCallbacks, SubscriptionEvent, SubscriptionErrorInfo, EventMetadata, ConnectionState, BufferConfig, AckHandle, AckableSubscription,
// Consumer group types AckMode, BackpressureMode, ConsumerGroupConfig, ConsumerGroup, ConsumerGroupStatus,
// KV types KVBucketConfig, KVBucketInfo, KVEntry, KVPutResult, KVListKeysResult, KVListBucketsResult, KVWatchEvent, KVWatchCallbacks, KVWatchOptions, KVWatcher,
// File storage types FileBucketConfig, FileBucketInfo, FileInfo, FileObject, ListFilesOptions, ListFilesResult, PutFileOptions, GetFileOptions, MoveFileOptions, CopyFileOptions, SignUploadOptions, SignedUrl,
// Config types ConfigResponse, ConfigEntry, ConfigSetResult, ConfigWatchCallbacks,
// Branded ID types RunId, FunctionId, StepId, EventId,
// Logger Logger,
// Server types ServerCapabilities,} from "@ironflow/core";IronflowEvent<T>
Section titled “IronflowEvent<T>”Represents an event in the system.
interface IronflowEvent<T = unknown> { id: string; name: string; version: number; data: T; timestamp: Date; idempotencyKey?: string; source?: string; metadata?: Record<string, unknown>; emittedBy?: string;}emittedBy is the authenticated emitter that the engine recorded, for example apikey:<id> or user:<id>. The engine sets it from the credential of the caller. Function trigger events, stored event reads, entity stream reads, and waiting-step results expose it when the engine reports one; it is undefined when absent.
The metadata field carries user-defined key-value pairs attached at emit time (via the metadata option in client.emit() or the REST API). It is available in function handlers as event.metadata and in subscription events when includeMetadata is enabled.
Represents a workflow run.
interface Run { cancellationCause?: string; cancellationEvent?: CancellationEvent; id: string; functionId: string; eventId: string; status: RunStatus; attempt: number; maxAttempts: number; input?: unknown; output?: unknown; error?: { message: string; code?: string; }; startedAt?: Date; endedAt?: Date; createdAt: Date; updatedAt: Date;}CancellationEvent (eventId, eventName, matchPath, matchValue) is the matched rule and event retained for a cancelled run.
RunStatus
Section titled “RunStatus”type RunStatus = | "running" | "completed" | "failed" | "cancelled" | "paused" | "waiting_for_capacity" | "waiting";FunctionConfig
Section titled “FunctionConfig”Configuration for an Ironflow function.
interface FunctionConfig { id: string; name?: string; description?: string; // Human-readable description shown in the dashboard triggers: Trigger[]; retry?: RetryConfig; timeout?: number; // milliseconds (unset: no per-function budget) concurrency?: ConcurrencyConfig; circuitBreaker?: CircuitBreakerConfig; // Push-endpoint circuit breaker overrides throttle?: ThrottleConfig; // Soft start-rate limit debounce?: DebounceConfig; // Collapse rapid-fire events into a single invocation mode?: ExecutionMode; // "push" | "pull" actorKey?: string; // JSON path for an actor key. Stored and returned by the server; dispatch does not use it for routing yet schema?: ZodType; // Zod schema; validated at runtime before the handler runs secrets?: string[]; // Secret names this function requires stepTimeout?: string; // Default timeout for step.run() calls ("30s", "5m") recording?: boolean; // Enable audit recording recordingProfile?: "all" | "run_lifecycle" | "steps"; // Workflow event families to capture cancelOn?: CancelOnConfig[]; // Auto-cancel running runs when matching events arrive metadata?: Record<string, unknown>; // Custom metadata (e.g., service, team, owner)}A pull run has no function timeout. For a push run, timeout cannot go above the engine pushTimeout ceiling (default 10s).
The nested config types:
interface Trigger { event: string; expression?: string; // CEL filter cron?: string; // schedule, for example "0 9 * * *"}
interface RetryConfig { maxAttempts?: number; // total attempts, the first included. Server default 25 initialDelayMs?: number; // server default 1000 backoffFactor?: number; // server default 2.0 maxDelayMs?: number; // server default 300000 onExhausted?: "pause" | "fail"; // server default "pause"}
// The server raises a window shorter than the longest probe (21.3s with the// default push timeout) to that minimum; see the circuit breakers how-to.interface CircuitBreakerConfig { failureThreshold?: number; // consecutive failed attempts that open the circuit. Server default 5 openDurationMs?: number; // first open window; each failed probe doubles it. Server default 60000 maxOpenDurationMs?: number; // cap for the doubling window. Server default 600000; at least openDurationMs}
interface ThrottleConfig { limit: number; // runs allowed to start per period, at least 1 periodMs: number; // window length, at least 1000 key?: string; // path into the event data; omitted means one lane for the whole function}
interface ConcurrencyConfig { limit: number; key?: string; // Path into the event data, e.g. "customerId" (not "event.data.customerId")}
interface DebounceConfig { periodMs: number; // quiet period, minimum 1000 key?: string; // JSON path; omitted means one global lane maxWaitMs?: number; // fire at least this often; must be >= periodMs}
interface CancelOnConfig { event: string; match: string; // JSON path that must equal the same field on the running run}debounce works only for asynchronous calls. A synchronous call to a debounced function fails with a FailedPrecondition error.
Retry, exhaustion and RetryAfterError
Section titled “Retry, exhaustion and RetryAfterError”The SDK sends only the retry fields you set. The server fills in the rest when each run is created, from spec.engine.retry: 25 attempts (the first included), a 1 second initial delay, a backoff factor of 2.0, a 5 minute maximum delay, and onExhausted: "pause". A function registered by an earlier SDK version keeps the values stored with it until it registers again.
onExhausted decides what happens when a retryable failure has used every attempt:
"pause"holds the run, with its completed steps, until you resume it. The run is not terminal, so its compensations do not run."fail"ends the run as failed and runs its compensations.
A non-retryable error always fails the run at once.
Throw RetryAfterError to choose the delay before the next attempt. The engine waits retryAfterMs instead of the computed backoff, capped at maxDelayMs. It is retryable, so onExhausted applies to it as to any retryable error. A RetryAfterError thrown inside step.run() keeps its delay when the step wraps it in a StepError.
import { RetryAfterError } from "@ironflow/core";
throw new RetryAfterError("rate limited", 30_000);Unlike the Go and Python SDKs, IronflowError here has no retryAfter field, so a Retry-After header on an HTTP error is not forwarded as a run retry hint; only RetryAfterError sets the delay. A zero, negative or non-finite retryAfterMs means no hint and the normal backoff applies.
A handler can read ctx.run.attempt, ctx.run.maxAttempts and ctx.run.onExhausted to see where it is in the budget. maxAttempts is 0 and onExhausted is "" when the engine is too old to send them.
createFunction rejects, before registering, a retry.onExhausted other than "pause" or "fail", a negative circuitBreaker field, a circuitBreaker.maxOpenDurationMs below openDurationMs, a throttle.limit below 1 and a throttle.periodMs below 1000.
A throttle is soft. At most limit new runs start per periodMs (per key value when key is set). Runs over the limit wait in the queue and are never dropped. Retries and resumed runs are not throttled.
StepClient
Section titled “StepClient”Client for recorded execution (durable steps).
interface StepClient { run<T>(name: string, fn: () => Promise<T>, options?: StepRunOptions): Promise<T>; sleep(name: string, duration: Duration): Promise<void>; sleepUntil(name: string, until: Date | string): Promise<void>; waitForEvent<T>(name: string, filter: EventFilter): Promise<IronflowEvent<T>>; parallel<T extends unknown[]>( name: string, branches: { [K in keyof T]: (step: StepClient) => Promise<T[K]> }, options?: ParallelOptions, ): Promise<T>; map<T, R>( name: string, items: T[], fn: (item: T, step: StepClient, index: number) => Promise<R>, options?: ParallelOptions, ): Promise<R[]>; compensate(stepName: string, fn: () => Promise<void>): void; invoke<T = unknown>(functionId: string, input?: unknown, options?: { timeout?: string }): Promise<T>; invokeAsync(functionId: string, input?: unknown): Promise<{ runId: string }>; publish(topic: string, data: unknown, options?: PublishOptions): Promise<PublishResult>;}Logger
Section titled “Logger”Logger interface used throughout the SDK.
interface Logger { debug(message: string, data?: Record<string, unknown>): void; info(message: string, data?: Record<string, unknown>): void; warn(message: string, data?: Record<string, unknown>): void; error(message: string, data?: Record<string, unknown>): void;}KVBucketConfig
Section titled “KVBucketConfig”Configuration for creating a KV bucket.
interface KVBucketConfig { name: string; description?: string; ttlSeconds?: number; maxValueSize?: number; maxBytes?: number; history?: number;}KVEntry
Section titled “KVEntry”A key-value entry returned by get.
interface KVEntry { key: string; value: unknown; revision: number; created_at: Date; operation: string; // "put" | "delete"}KVWatchEvent
Section titled “KVWatchEvent”A change event delivered over WebSocket.
interface KVWatchEvent { type: "kv_update"; key: string; value: string; // the stored bytes, base64-encoded; "" for a delete revision: number; operation: "put" | "delete"; bucket: string;}KVWatchCallbacks
Section titled “KVWatchCallbacks”interface KVWatchCallbacks { onUpdate: (event: KVWatchEvent) => void; onError?: (error: Error) => void; onClose?: () => void;}EmitOptions
Section titled “EmitOptions”Options for emitting events.
interface EmitOptions { version?: number; // Event schema version (default: 1) idempotencyKey?: string; metadata?: Record<string, unknown>; namespace?: string; // default: "default"}EmitSyncResult
Section titled “EmitSyncResult”One run’s outcome from emitSync(). emitSync() returns EmitSyncResult[]: an
event can match several triggers, and every matched run is reported. An event
that matches nothing returns [].
interface EmitSyncResult { runId: string; // ID of the triggered run functionId: string; // ID of the function that handled the event status: RunStatus; // Final run status output: unknown; // Function return value error?: { message: string; code?: string }; durationMs: number; // Wall-clock time from trigger to completion waitTimedOut: boolean; // Wait budget expired; the run is still going}emitSync() never throws on a run outcome — with N results there is no
unambiguous choice of which failure to raise. Inspect status, error and
waitTimedOut per element. Transport, protocol and validation errors still
throw. See ADR 0067.
InvokeSyncOptions and InvokeSyncResult
Section titled “InvokeSyncOptions and InvokeSyncResult”Argument and return type of the ID-keyed invoke() method, which targets one
function and produces exactly one run.
interface InvokeSyncOptions<TInput = unknown> { data: TInput; timeout?: number; // server-side wait budget in ms (default 30000) idempotencyKey?: string; metadata?: Record<string, unknown>;}
type InvokeSyncResult = Omit<EmitSyncResult, "waitTimedOut">;Because it guarantees a single run, invoke() does throw RunFailedError,
RunCancelledError and non-retryable RunWaitTimeoutError — which is why
waitTimedOut is absent from the result.
timeout is a server-side wait budget, not a transport deadline. InvokeFunctionSync
ties the run’s lifetime to the request context, so aborting the HTTP request cancels
the run server-side.
EmitResult
Section titled “EmitResult”Return type of emit(). Returned immediately after the event is accepted — the runs may still be in progress.
interface EmitResult { runIds: string[]; // IDs of created runs eventId: string; // ID of the stored event}RunEvent and createRunVersionFilter
Section titled “RunEvent and createRunVersionFilter”A subscription to system.run.> delivers frames for run transitions; one write can publish two (.failed and .updated). The payload of a system.run.{runId}.{event} frame is a RunEvent. Step frames (system.run.{runId}.step.{stepId}.{event}) have a different shape.
interface RunEvent { id: string; functionId: string; eventId: string; executionMode: string; status: string; attempt: number; maxAttempts: number; version?: number; // Rises with every write to the run; absent on `deleted` frames, when the committed version is unknown, and on older servers createdAt: string; updatedAt: string; startedAt?: string; completedAt?: string; output?: unknown; error?: unknown; timestamp: string;}
type RunVersionFilter = (event: SubscriptionEvent) => boolean;
function createRunVersionFilter(options?: { maxRuns?: number }): RunVersionFilter;Frames for one run can arrive out of order, so a completed frame can land before the running one. createRunVersionFilter() returns a filter that drops older run and step frames and late frames for a deleted run. It passes frames outside system.run., frames with no usable version, and frames whose version ties the highest seen. It remembers up to maxRuns run and step identities (default 10000), then forgets the least recent. Use one filter per subscription.
const keep = createRunVersionFilter();await client.subscribe('system.run.>', { onEvent: (event) => { if (keep(event)) render(event.data as RunEvent); },});Schemas (Zod)
Section titled “Schemas (Zod)”Runtime validation schemas using Zod:
import { // Push mode schemas PushRequestSchema, PushRequestEventSchema, CompletedStepSchema, ResumeContextSchema,
// Response schemas RunStatusSchema, TriggerResponseSchema, TriggerSyncResponseSchema, RunResponseSchema, ListRunsResponseSchema, HealthResponseSchema, ErrorResponseSchema,
// Consumer group schemas ConsumerGroupResponseSchema, ListConsumerGroupsResponseSchema,
// Worker schemas JobAssignmentSchema, JobEventSchema,
// WebSocket schemas WSServerMessageSchema, WSEventMessageSchema,
// Validation helpers parseAndValidate, validate,} from "@ironflow/core";
// Type-safe parsing with error handlingconst result = RunStatusSchema.safeParse(rawStatus);if (result.success) { console.log(result.data);}Constants
Section titled “Constants”Default configuration values:
import { DEFAULT_SERVER_URL, // 'http://localhost:9123' DEFAULT_WS_URL, // 'ws://localhost:9123/ws' DEFAULT_PORT, // 9123 DEFAULT_HOST, // 'localhost' DEFAULT_RECONNECT, // { ENABLED, MAX_ATTEMPTS, ... } DEFAULT_WORKER, // { MAX_CONCURRENT_JOBS, ... } DEFAULT_TIMEOUTS, // { CLIENT, FUNCTION, TRIGGER_SYNC, INVOKE_FUNCTION_SYNC, SYNC_TRANSPORT_HEADROOM } DEFAULT_RETRY, // { MAX_ATTEMPTS, INITIAL_DELAY_MS, ... } RUN_STATUS, // { RUNNING, COMPLETED, FAILED, CANCELLED, PAUSED, WAITING_FOR_CAPACITY, WAITING } STEP_STATUS, // { COMPLETED, FAILED, WAITING } ENV_VARS, // { SERVER_URL, SIGNING_KEY, API_KEY, LOG_LEVEL }} from "@ironflow/core";| Constant | Value | Description |
|---|---|---|
DEFAULT_SERVER_URL | 'http://localhost:9123' | Default Ironflow server URL |
DEFAULT_WS_URL | 'ws://localhost:9123/ws' | Default WebSocket URL |
DEFAULT_RECONNECT.ENABLED | true | Auto-reconnect enabled by default |
DEFAULT_RECONNECT.MAX_ATTEMPTS | 10 | Maximum reconnection attempts |
DEFAULT_RECONNECT.INITIAL_DELAY_MS | 1000 | Initial backoff delay |
DEFAULT_RECONNECT.MAX_DELAY_MS | 30000 | Maximum backoff delay |
DEFAULT_WORKER.MAX_CONCURRENT_JOBS | 10 | Default concurrent job limit |
DEFAULT_WORKER.HEARTBEAT_INTERVAL_MS | 30000 | Worker heartbeat interval |
DEFAULT_TIMEOUTS.CLIENT | 30000 | Client request timeout |
DEFAULT_TIMEOUTS.FUNCTION | 600000 | Exported constant. Registration does not apply it: an unset timeout means no per-function budget |
Error Classes
Section titled “Error Classes”Built-in error types with error code classification:
import { IronflowError, ConnectionError, SubscriptionError, ValidationError, SchemaValidationError, SignatureError, TimeoutError, StepError, StepTimeoutError, FunctionNotFoundError, RunNotFoundError, RunWaitTimeoutError, RunFailedError, RunCancelledError, NonRetryableError, NotConfiguredError, UnauthenticatedError, UnauthorizedError, EnterpriseRequiredError, ConflictError, ContendedError, PreconditionFailedError, PreconditionRequiredError, PayloadTooLargeError, UnsupportedMediaTypeError, InjectionUnverifiedError, InvokeError, InvokeTimeoutError, AgentInvokeTimeoutError, MemoryCatchupTimeoutError, QueueFullError, isRetryable, isIronflowError, toError,} from "@ironflow/core";
try { await someOperation();} catch (error) { if (error instanceof IronflowError) { console.log(error.code); // Error code console.log(error.message); // Error message console.log(error.retryable); // Whether retryable
if (isRetryable(error)) { // Retry the operation } }}Error Types
Section titled “Error Types”| Error Class | Description |
|---|---|
IronflowError | Base error class with code, retryable, details, and status (the HTTP status, when the error came from a response) |
ConnectionError | WebSocket/HTTP connection failures (retryable) |
SubscriptionError | Subscription-related errors; has subscriptionId (retryable) |
ValidationError | Invalid input or schema validation failures; has validationErrors |
SchemaValidationError | Zod schema validation failures |
SignatureError | Webhook signature verification failures |
TimeoutError | Operation timeout with timeoutMs property |
StepError | Step execution failures with stepId and stepName |
StepTimeoutError | step.run() exceeded its timeout; has stepName and timeout (string) properties (retryable) |
FunctionNotFoundError | Function not found with functionId property |
RunNotFoundError | Run not found with runId property |
RunWaitTimeoutError | invoke() stopped waiting while the run remained active; has runId, functionId, runStatus, and timeoutMs properties (non-retryable). emitSync() reports the same condition as waitTimedOut on the result instead of throwing |
RunFailedError | Thrown by invoke() when the targeted run fails; has runId and output properties. emitSync() reports a failure as status + error per result instead |
RunCancelledError | Thrown by invoke() when the targeted run is cancelled; has runId property. emitSync() reports it as status per result instead |
NonRetryableError | Permanent failures that should not be retried |
RetryAfterError | Retryable failure that asks for a delay: new RetryAfterError(message, retryAfterMs). The engine waits retryAfterMs instead of the computed backoff, capped at the retry policy’s maxDelayMs. Has retryAfterMs (code RETRY_AFTER) |
NotConfiguredError | Client not configured (call configure() first) |
UnauthenticatedError | HTTP 401 — missing or invalid API key |
UnauthorizedError | HTTP 403 — API key lacks required permissions |
EnterpriseRequiredError | HTTP 402 — legacy; retained for wire compatibility. Ironflow ships a single build with no Enterprise/Core split (ADR 0015), so the server never returns 402. |
ConflictError | HTTP 409 that is not a lost race — e.g. a second resumeRun() of the same run within about 2 minutes, even after the first finished. It is not queued; retry after about 2 minutes |
PreconditionFailedError | HTTP 412 — an If-Match or If-None-Match precondition failed (file storage) |
PreconditionRequiredError | HTTP 428 — the write needs If-Match with the version you read (policy set) |
PayloadTooLargeError | HTTP 413 — a file is larger than the server, bucket or token cap (file storage) |
UnsupportedMediaTypeError | HTTP 415 — the content type is not allowed by the bucket or the signed URL (file storage) |
ContendedError | HTTP 409 from a lost compare-and-set race (Connect aborted); nothing was applied, so re-read and reissue |
InjectionUnverifiedError | Connect aborted with Ironflow-Error-Reason: injection_unverified; injectStepOutput() wrote the step but could not confirm the run stood still. Unlike ContendedError the write did land — read the step rather than reissuing |
InvokeError | step.invoke() failed; has functionId, childRunId, and errorCause properties |
InvokeTimeoutError | step.invoke() timed out; extends InvokeError, adds timeoutMs property |
AgentInvokeTimeoutError | Agent invoke() exceeded its wait deadline |
MemoryCatchupTimeoutError | Agent run timed out catching up its memory stream during resume |
QueueFullError | @ironflow/browser offline write queue is at maxItems or maxBytes; the write was not stored |
toError()
Section titled “toError()”Normalizes any thrown value to an Error instance. Useful in catch blocks where the caught value may be a string, number, or plain object rather than an Error.
function toError(error: unknown): Errortry { await riskyOperation();} catch (err) { const error = toError(err); // always an Error instance console.error(error.message);}Utilities
Section titled “Utilities”ID Generation
Section titled “ID Generation”import { generateId, createRunId, createStepId, createEventId, createFunctionId,} from "@ironflow/core";
// Generate a unique ID (no arguments)const id = generateId(); // 'm1abc23-x4y5z6'
// Create branded IDs for type safetyconst runId = createRunId("run_abc123");const stepId = createStepId("step_def456");Duration Parsing
Section titled “Duration Parsing”import { parseDuration } from "@ironflow/core";
parseDuration("30s"); // 30000 (ms)parseDuration("5m"); // 300000parseDuration("2h"); // 7200000parseDuration("1d"); // 86400000Logging
Section titled “Logging”import { createLogger, createNoopLogger } from "@ironflow/core";
// Create a logger with prefixconst logger = createLogger({ prefix: "[myapp]" });logger.info("Starting...");logger.debug("Debug info", { data: 123 });
// Create a no-op logger (disables logging)const noopLogger = createNoopLogger();Other Utilities
Section titled “Other Utilities”import { calculateBackoff, sleep, createDeferred, safeJsonParse, isObject, deepMerge, isRedacted, // true for the `{ $redacted: true, sha256, redactedAt }` placeholder a redaction leaves behind createRunVersionFilter, // see RunEvent and createRunVersionFilter above} from "@ironflow/core";Event Versioning
Section titled “Event Versioning”Utilities for event schema evolution:
import { createUpcasterRegistry, defineEvent, createEventDefinitionRegistry,} from "@ironflow/core";import type { UpcasterFn, EventDefinition } from "@ironflow/core";
// Define versioned eventsconst orderCreatedV1 = defineEvent({ name: "order.created", version: 1 });const orderCreatedV2 = defineEvent({ name: "order.created", version: 2, upcast: (data: any) => ({ ...data, address: null }),});
// Registry applies upcasters in chainconst registry = createEventDefinitionRegistry();registry.register(orderCreatedV1);registry.register(orderCreatedV2);
// Upcast v1 event data to latest versionconst upcasted = registry.upcastEvent("order.created", oldData, 1);
// Pass to serve() or createWorker() for automatic upcasting// See @ironflow/node docs for serve({ eventDefinitions: registry })Sub-path Exports
Section titled “Sub-path Exports”| Path | Exports |
|---|---|
@ironflow/core | Types, schemas, errors, constants, utilities and protocol helpers |
@ironflow/core/schemas | Zod schemas and validation helpers only |
@ironflow/core/protocol | Push and WebSocket protocol types, patterns and the subscribe/resume helpers only |
@ironflow/core/gen | Generated protobuf and ConnectRPC code; needs the optional @bufbuild/protobuf and @connectrpc/connect packages. @ironflow/core/gen/* imports one generated file |
Generated Protobuf/ConnectRPC Code
Section titled “Generated Protobuf/ConnectRPC Code”Access generated service definitions and message schemas:
import { // Service definitions IronflowService, PubSubService, WorkerService,
// Message schemas RunSchema as ProtoRunSchema, EventSchema as ProtoEventSchema,} from "@ironflow/core/gen";See Also
Section titled “See Also”- JavaScript SDK Overview
- Browser Package
- Node Package
- Config Management - Environment-scoped configuration
- KV Store - Key-value storage