1/**
2 * The shared typed Step Functions program exercised by BOTH the live
3 * `StateMachine.fromProgram` test and the in-process `Sfn.simulate` unit
4 * test — one AST, two interpreters.
5 *
6 * Covers the flagship combinators: `Sfn.gen` sequencing, `Sfn.invoke`
7 * (Lambda Task + retry), `Sfn.forEach` (inline Map), `Sfn.when` (Choice),
8 * and `Sfn.fail` + `Sfn.catchTag` (typed failure, recovered).
9 */
10import { Sfn } from "@/AWS/StepFunctions";
11import * as Data from "effect/Data";
12
13export class OrderRejected extends Data.TaggedError("OrderRejected")<{
14 readonly reason: string;
15}> {}
16
17export interface OrderInput {
18 value: number;
19 items: number[];
20}
21
22export interface OrderOutput {
23 doubled: number;
24 items: number[];
25 size: "big" | "small";
26 recovered: string;
27}
28
29export const makeOrderProgram = (doubler: Sfn.InvokableFunction) =>
30 Sfn.gen(function* (input: Sfn.Expr<OrderInput>) {
31 // Lambda Task with a bounded retry (cold starts, transient faults)
32 const doubled = yield* Sfn.invoke<{ doubled: number }>(doubler, {
33 value: input.value,
34 }).pipe(Sfn.retry({ maxAttempts: 2, initial: "1 second" }));
35
36 // inline Map: double every item through the same function
37 const items = yield* Sfn.forEach(
38 input.items,
39 (item) =>
40 Sfn.gen(function* () {
41 const result = yield* Sfn.invoke<{ doubled: number }>(doubler, {
42 value: item,
43 });
44 return result.doubled;
45 }),
46 { concurrency: 1 },
47 );
48
49 // Choice on the task result
50 const size = yield* Sfn.when(
51 Sfn.gt(doubled.doubled, 10),
52 Sfn.succeed("big"),
53 Sfn.succeed("small"),
54 );
55
56 // typed failure recovered with catchTag (narrows E back to never)
57 const recovered = yield* Sfn.fail(
58 new OrderRejected({ reason: "no stock" }),
59 "no stock",
60 ).pipe(Sfn.catchTag("OrderRejected", (error) => Sfn.succeed(error.Cause)));
61
62 return {
63 doubled: doubled.doubled,
64 items,
65 size,
66 recovered,
67 };
68 });