| 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 | */ |
| 10 | import { Sfn } from "@/AWS/StepFunctions"; |
| 11 | import * as Data from "effect/Data"; |
| 12 | |
| 13 | export class OrderRejected extends Data.TaggedError("OrderRejected")<{ |
| 14 | readonly reason: string; |
| 15 | }> {} |
| 16 | |
| 17 | export interface OrderInput { |
| 18 | value: number; |
| 19 | items: number[]; |
| 20 | } |
| 21 | |
| 22 | export interface OrderOutput { |
| 23 | doubled: number; |
| 24 | items: number[]; |
| 25 | size: "big" | "small"; |
| 26 | recovered: string; |
| 27 | } |
| 28 | |
| 29 | export 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 | }); |