1/**
2 * The Step Functions program AST.
3 *
4 * Every {@link SfnEffect} wraps exactly one of these nodes. The AST is plain
5 * data with two interpreters: `compile.ts` (→ ASL definition +
6 * policy statements) and `simulate.ts` (→ an in-process `Effect`).
7 */
8import type * as Duration from "effect/Duration";
9import type * as Output from "../../../Output.ts";
10import type { PolicyStatement } from "../../IAM/Policy.ts";
11import type { Expr } from "./Jsonata.ts";
12import type { SfnEffect } from "./Program.ts";
13
14/**
15 * The structural surface `Sfn.invoke` needs from a Lambda Function resource:
16 * a stable logical id (simulation handler key + policy dedupe key) and the
17 * function ARN (an `Output` inside a stack, a plain string in simulations).
18 */
19export interface InvokableFunction {
20 readonly LogicalId: string;
21 readonly functionArn: Output.Output<string, any> | string;
22}
23
24/** Options for an `Sfn.integrate` / `Sfn.waitForTaskToken` task state. */
25export interface IntegrationOptions {
26 /**
27 * The optimized service-integration resource ARN, e.g.
28 * `arn:aws:states:::sqs:sendMessage`. `Sfn.waitForTaskToken` appends the
29 * `.waitForTaskToken` suffix automatically.
30 */
31 resource: string;
32 /**
33 * The task `Arguments`. `Expr` references compile to `{% ... %}` JSONata
34 * strings; embedded `Output`s (queue URLs, table names) resolve at deploy.
35 */
36 arguments?: unknown;
37 /**
38 * IAM policy statements the execution role needs for this integration
39 * (exact-ARN scoped by the caller).
40 */
41 policyStatements?: PolicyStatement[];
42 /**
43 * Task-level timeout (ASL `TimeoutSeconds`). Accepts any
44 * `Duration.Input` (`"30 seconds"`, `Duration.minutes(5)`, millis);
45 * rounded up to whole seconds (ASL's granularity).
46 */
47 timeout?: Duration.Input;
48}
49
50/** Options for `Sfn.retry` — mirrors the `Schedule.exponential` vocabulary. */
51export interface RetryOptions {
52 /**
53 * Error names (tags) that are retried, mirroring `Effect.retry`'s `while`.
54 * @default ["States.ALL"]
55 */
56 while?: string | string[];
57 /** Initial retry interval (`IntervalSeconds`). @default "1 second" */
58 initial?: Duration.Input;
59 /** Backoff multiplier (`BackoffRate`). @default 2 */
60 backoff?: number;
61 /** Maximum retry attempts (`MaxAttempts`). @default 3 */
62 maxAttempts?: number;
63 /** Cap on the interval between retries (`MaxDelaySeconds`). */
64 maxDelay?: Duration.Input;
65 /** Apply full jitter to retry intervals (`JitterStrategy: "FULL"`). */
66 jitter?: boolean;
67}
68
69/** Options for `Sfn.forEach`. */
70export interface ForEachOptions {
71 /** Maximum concurrent iterations (`MaxConcurrency`). */
72 concurrency?: number;
73}
74
75export interface GenNode {
76 readonly kind: "gen";
77 /**
78 * The user's generator. Re-invoked on every compile/simulate pass — each
79 * `yield*` is fed a typed variable reference by the tracer.
80 */
81 readonly body: (input: Expr<any>) => Generator<SfnEffect<any, any>, any, any>;
82}
83
84export interface InvokeNode {
85 readonly kind: "invoke";
86 readonly fn: InvokableFunction;
87 readonly payload: unknown;
88}
89
90export interface IntegrateNode {
91 readonly kind: "integrate";
92 readonly options: IntegrationOptions;
93 readonly waitForTaskToken: boolean;
94}
95
96export interface AllNode {
97 readonly kind: "all";
98 readonly branches: readonly SfnEffect<any, any>[];
99}
100
101export interface ForEachNode {
102 readonly kind: "forEach";
103 readonly items: Expr<any>;
104 readonly body: (item: Expr<any>) => SfnEffect<any, any>;
105 readonly options: ForEachOptions;
106}
107
108export interface SleepNode {
109 readonly kind: "sleep";
110 readonly seconds: number;
111}
112
113export interface WhenNode {
114 readonly kind: "when";
115 readonly condition: Expr<boolean>;
116 readonly onTrue: SfnEffect<any, any>;
117 readonly onFalse: SfnEffect<any, any> | undefined;
118}
119
120export interface MatchNode {
121 readonly kind: "match";
122 readonly value: Expr<any>;
123 readonly cases: Readonly<Record<string, SfnEffect<any, any>>>;
124 readonly otherwise: SfnEffect<any, any> | undefined;
125}
126
127export interface SucceedNode {
128 readonly kind: "succeed";
129 readonly value: unknown;
130}
131
132export interface FailNode {
133 readonly kind: "fail";
134 /** The ASL `Error` name (the failure's `_tag`). */
135 readonly error: string;
136 /** The ASL `Cause` string. */
137 readonly cause: string;
138 /** The typed failure `simulate` fails with (when constructed from one). */
139 readonly failure: unknown;
140}
141
142export interface RetryNode {
143 readonly kind: "retry";
144 readonly inner: SfnEffect<any, any>;
145 readonly options: RetryOptions;
146}
147
148export interface CatchNode {
149 readonly kind: "catch";
150 readonly inner: SfnEffect<any, any>;
151 /** Error names to catch — `["States.ALL"]` for `catchAll`. */
152 readonly tags: readonly string[];
153 /** Handler over the ASL error output (`{ Error, Cause }`). */
154 readonly handler: (error: Expr<ErrorOutput>) => SfnEffect<any, any>;
155}
156
157/**
158 * The shape ASL hands a Catch transition (`$states.errorOutput`): the error
159 * name and its cause. For Lambda task failures `Cause` carries the
160 * serialized exception.
161 */
162export interface ErrorOutput {
163 readonly Error: string;
164 readonly Cause: string;
165}
166
167export type AslNode =
168 | GenNode
169 | InvokeNode
170 | IntegrateNode
171 | AllNode
172 | ForEachNode
173 | SleepNode
174 | WhenNode
175 | MatchNode
176 | SucceedNode
177 | FailNode
178 | RetryNode
179 | CatchNode;