Why We Built a Zero-Daemon Saga Orchestrator in Pure TypeScript
Every backend service starts with a clean, linear async function:- The payment gateway throws a transient
503—so you wrapstripe.chargein aforloop with exponential backoff. shipping.dispatchfails after the customer’s card was already charged—so you add mutablelet chargeIdandlet reservationIdflags outside a gianttry/catchblock and manually callstripe.refund()andinventory.release()in reverse order.- A mobile client double-taps “Pay” on a flaky connection—so you bolt on Redis or PostgreSQL idempotency keys.
- A webhook-driven fraud check needs to pause the workflow for 10 minutes—so you split the function across two queue handlers and a database state machine.
- Keep writing ad-hoc imperative plumbing in every endpoint and hope nobody forgets a rollback branch.
- Adopt a distributed workflow cluster (like Temporal or Inngest), introducing external server daemons, separate worker fleets, deterministic replay constraints, and millisecond-to-second RPC hops for every step.
@siglo/core) to occupy the missing middle: zero-daemon, in-process workflow orchestration in pure TypeScript that scales up to durable PostgreSQL Optimistic Concurrency Control (OCC) checkpoints when you need durability—using the database you already run.
Architecture: In-Process Execution vs. External Orchestrator Daemons
For 95% of backend workflows—checkout pipelines, multi-provider AI agents, onboarding flows, Kafka consumers, and multi-service sagas—you don’t need an external orchestration cluster. You need structured control flow, LIFO compensations, retries, idempotency, and atomic state checkpoints inside your existing application.
1. Declarative Sagas Without Nested try/catch
In Siglo, a workflow is a composition of typed Step primitives. Each step can declare its own retry policy (.onFail()) and its own rollback logic (.compensate()):
shipOrder throws an unrecoverable error:
- Siglo halts forward execution immediately.
- It unwinds the completed steps in strict LIFO (Last-In, First-Out) order:
chargeCardis refunded first, and thenreserveInventoryis released. - Even if a process crashes mid-flight, durable compensation records stored in the checkpoint allow a background reconciler (
Step.reconcileAbandonedStacks) or explicitcheckoutFlow.compensate(stackId)call to finish unwinding the Saga cleanly.
2. Durable Checkpoints with PostgreSQL Optimistic Concurrency Control (OCC)
How do you get crash recovery and distributed suspension (waitForEvent) without a central coordinator daemon? By turning your existing PostgreSQL database into an OCC state store via @siglo/postgres.
When you register PostgresStorageAdapter, Siglo persists SerializedExecutionStack snapshots after each step with an atomic monotonically increasing version column:
- Zero Split-Brain (
WHERE stack_id = $3 AND version = $4): If two worker replicas attempt to advance or resume the same workflowstackIdconcurrently, exactly oneUPDATEmatchesversion = $4. The losing replica receivesrowCount === 0and immediately throws anOptimisticLockExceptionbefore executing duplicate downstream side effects. - Monotonic Cancellation Preservation: Even if an in-flight step finishes writing a checkpoint milliseconds after another node cancels the workflow, the
CASE WHEN state->>'status' = 'cancelled'guard prevents overwriting a terminal cancellation state.
3. Suspending for Webhooks Without Holding a Thread (waitForEvent)
Because state is checkpointed to PostgreSQL or Redis, a workflow can pause for minutes or days waiting for an external webhook (like a Stripe 3DS callback or human approval) with zero active memory or open connections:
waitForEvent:
- Siglo writes a
WAITINGrecord tosiglo_wait_recordskeyed byorder:${ctx.orderId}along with thecontinuation_step_index. - The
run()promise throwsWorkflowSuspendedException(or HTTP202 Acceptedwhen mounted via@siglo/express,@siglo/fastify, or@siglo/hono), freeing the Node.js event loop. - When the webhook arrives hours later, calling
await orderApprovalFlow.resume('order:ord_123', { approvedBy: 'alice' })atomically claims the wait record (WHERE correlation_key = $1 AND status = 'WAITING'), hydrates the checkpoint, merges the webhook payload into the accumulated context, and resumes atchargeCard.
4. Static AST Extraction: Your Code Is the Architecture Diagram
Because Siglo workflows are composed from declarative primitives (Step.create, flow, parallel, branch, when, map, waitForEvent), the workflow graph is inspectable before a single line of business logic runs.
- Call
checkoutFlow.toMermaid()ornpx @siglo/cli export src/checkout.ts --format=mermaidin CI to auto-comment architecture diagrams on every Pull Request. - Install the VS Code or JetBrains IntelliJ extension to view live interactive workflow DAGs side-by-side with your editor.
- Attach
@siglo/monitorin development or staging to watch live execution waterfalls, inspect step input/output diffs, and replay failed runs fromhttp://localhost:4000.
Try It in 60 Seconds
You don’t need Docker, a local daemon, or a cloud account to try Siglo:- GitHub Repository: github.com/jsoftgem/siglo
- Incremental Adoption Guide: Migrate 1 Endpoint in 5 Minutes
- Documentation: siglo.dev
