1. Workflows as Directed Acyclic Graphs (AST)
InSiglo, a workflow is not a simple promise chain. It is an executable Abstract Syntax Tree (AST) composed of typed nodes:
StepNode: An isolated unit of work with input schemas, lifecycles, retries, and policies.SequenceNode: Sequential execution pipeline created withflow(...).ParallelNode: Concurrent fan-out/fan-in with merge strategies (parallel,race).BranchNode: Dynamic conditional routing and predicate dispatch (branch,when).WaitNode: Durable event suspension or timer sleep (waitForEvent,wait).MapNode: Concurrently bounded array transformations (map).
2. Context & Accumulated Inference
When steps execute sequentially, return values are merged into an Accumulated Context. Each subsequent step receives the combined outputs of all prior steps. TypeScript automatically computes the union type of this accumulated state at compile time:param.$steps.<stepName> (or param.$history.<stepName>).
3. Step Lifecycle Pipeline
Each step goes through a strict 5-phase lifecycle:- Validation: Evaluates field rules (
step.spec(...)) or Standard Schemas (step.schema(...)). Rejections throwValidationException. - Before Hook (
onBefore): Runs predicates to decide whether to execute the action or skip it. If skipped,onSkipprovides fallback data. - Action: Executes the primary asynchronous business logic. If idempotency (
step.idempotent(...)) or caching (step.cache(...)) is configured, cached results short-circuit execution. - After Hook (
onAfter): Transforms or augments the action output before it is merged into accumulated context. - OnFail Hook (
onFail): Evaluates retry predicates, calculates backoff delay (fixed, linear, exponential with jitter), and coordinates compensation triggers if retries are exhausted.
4. Reserved Keys and Namespaces
Siglo reserves specific context keys prefixed with $ to expose execution metadata without polluting business properties:
Minimal Example
Failure Example: Phase Diagnostic Tagging
When an action or lifecycle hook throws, the runtime wraps the failure into aSigloError subclass and tags it with diagnostic metadata:
Testing Example
Operational Caveats
- Avoid Overwriting Context Keys: While later steps can overwrite earlier keys if intended, naming step outputs distinctly (e.g.
user,order,payment) maintains clean history and avoids confusion. If needed, access raw outputs via$steps.<stepName>. - Never Mutate Param Directly: Treat input parameters as read-only. Always return a new object with changes.
- Namespace Isolation: In multi-tenant environments, pass
{ namespace: tenantId }in execution options to isolate caches and checkpoints. - Native Abort Signals: When passing signals to external libraries (such as
fetch(url, { signal: param.signal })), preferparam.signalover legacy accessors.
