Skip to content

Errors

Every error Reflow throws extends ReflowError, so one instanceof ReflowError check catches them all, and carries a stable literal code. Subclasses carry structured context in typed fields — no message parsing needed. See Error Handling for usage patterns.

typescript
import { ReflowError, WorkflowNotFoundError, ValidationError, StepTimeoutError } from 'reflow-ts'

Prefer code over instanceof. It is a closed union, so a switch over it can be checked for exhaustiveness, and it keeps working across bundling, duplicate copies of the package in a dependency tree, and realm boundaries (worker threads, vm contexts) — all cases where instanceof silently fails.

typescript
if (error instanceof ReflowError) {
  switch (error.code) {
    case 'STEP_TIMEOUT':   return retryLater(error.timeoutMs)
    case 'WAIT_TIMEOUT':   return escalate(error.eventName)
    case 'VALIDATION':     return badRequest(error.issues)
    default:               return report(error)
  }
}
ErrorcodeThrown whenProperties
ReflowErrorBase class for all Reflow errorscode
ConfigErrorCONFIGInvalid engine, retry, schedule, or stream config
WorkflowNotFoundErrorWORKFLOW_NOT_FOUNDenqueue() / schedule() with an unknown nameworkflowName
DuplicateWorkflowErrorDUPLICATE_WORKFLOWSame workflow name registered twice in one engineworkflowName
DuplicateStepErrorDUPLICATE_STEP.step() / .parallel() reuses a nameworkflowName, stepName
ParallelCompleteErrorPARALLEL_COMPLETEcomplete() called inside a parallel branchstepName
ValidationErrorVALIDATIONInput fails schema validationissues
IdempotencyConflictErrorIDEMPOTENCY_CONFLICTSame idempotency key with different inputworkflowName, idempotencyKey
SerializationErrorSERIALIZATIONA step output / input contains non-persistable datapath
StepTimeoutErrorSTEP_TIMEOUTA step attempt exceeds timeoutMstimeoutMs
WaitTimeoutErrorWAIT_TIMEOUTA waitForEvent step's timeoutMs elapses before the event arriveseventName, timeoutMs
RunCancelledErrorRUN_CANCELLEDA run is cancelled via engine.cancel()runId
LeaseExpiredErrorLEASE_EXPIREDA worker loses its lease on a runrunId
StorageErrorSTORAGEA storage backend operation failed; the driver's error is on causeoperation, cause
StepFailedErrorSTEP_FAILEDA step exhausts its retries with no error of its own (run aborted first)stepName, attempts
HookErrorHOOKA lifecycle hook, stream consumer, or onFailure handler threw. Delivered to onError, never thrown into a runsource, cause
ThrownValueErrorTHROWN_VALUEUser code threw a non-Error value (throw 'boom')value, cause
TestRunIncompleteErrorTEST_RUN_INCOMPLETEtestEngine.run() left a run non-terminal (usually a suspending workflow)runId, status
InternalErrorINTERNALAn invariant was violated — a bug in reflow-ts

Serializing errors

Every error implements toJSON(), so JSON.stringify(error) yields the discriminant, the structured context, and a flattened cause chain — no custom serializer needed.

typescript
JSON.stringify(new WaitTimeoutError('approval', 50))
// {
//   "name": "WaitTimeoutError",
//   "code": "WAIT_TIMEOUT",
//   "message": "Timed out after 50ms waiting for event \"approval\"",
//   "context": { "eventName": "approval", "timeoutMs": 50 }
// }

Keep context() free of secrets in custom subclasses — the output is intended to be logged.

ValidationError.issues

An array of { message: string; path?: ... } describing each schema violation, surfaced directly from your Standard Schema library.

Control-flow errors

RunCancelledError (RUN_CANCELLED) and LeaseExpiredError (LEASE_EXPIRED) are control-flow signals, not failures — they do not reach onRunFailed or onFailure, and they leave a run reclaimable rather than marking it failed. StepTimeoutError, by contrast, is a real failure and reaches the failure paths.

Released under the MIT License.