1import * as Lambda from "@distilled.cloud/aws/lambda";
2import type { ConfigError } from "effect/Config";
3import * as Context from "effect/Context";
4import type * as Duration from "effect/Duration";
5import * as Effect from "effect/Effect";
6import * as Effectable from "effect/Effectable";
7import type * as Layer from "effect/Layer";
8import * as Option from "effect/Option";
9import type { Scope } from "effect/Scope";
10import type { PackageInstall } from "../../Bundle/InstalledPackages.ts";
11import type { InputProps } from "../../Input.ts";
12import * as Output from "../../Output.ts";
13import type { PlatformServices } from "../../Platform.ts";
14import { toSeconds, toWireDays } from "../../Util/Duration.ts";
15import type { DistributiveOmit } from "../../Util/types.ts";
16import { effectClass, taggedFunction } from "../../Util/effect.ts";
17import type { DurableExecutionContext, DurableStep } from "./Durable.ts";
18import {
19 DURABLE_SDK_MODULE,
20 encodeDurableEnvelope,
21 makeDurableListener,
22} from "./DurableBridge.ts";
23import {
24 Function,
25 type FunctionProps,
26 type FunctionServices,
27 type HandlerContext,
28} from "./Function.ts";
29
30type TypeId = "AWS.Lambda.DurableFunction";
31const TypeId = "AWS.Lambda.DurableFunction" as const;
32
33/**
34 * The services available inside a durable function's run body.
35 *
36 * The bridge provides all of them per durable invocation: `DurableStep`
37 * powers `Durable.step`/`Durable.sleep`/`Durable.waitForCallback`,
38 * `DurableExecutionContext` carries the execution ARN, `HandlerContext` is
39 * the raw `lambda.Context`, and a fresh `Scope` scopes per-invocation
40 * resources.
41 *
42 * Deliberately narrow: cloud clients (`Credentials`/`Region`-requiring
43 * effects) are NOT provided to the body directly — resolve typed binding
44 * clients in the init phase and call them inside `Durable.step`, which is
45 * exactly the determinism law the replay model requires.
46 */
47export type DurableRunServices =
48 | DurableStep
49 | DurableExecutionContext
50 | HandlerContext
51 | Scope;
52
53/**
54 * A durable function implementation: a function from a typed `Input` payload
55 * to an Effect producing the execution's `Result`. Code outside
56 * `Durable.step` re-runs on every replay and must be deterministic.
57 */
58export type DurableFunctionImpl<Input = unknown, Result = unknown> = (
59 input: Input,
60) => Effect.Effect<Result, never, DurableRunServices>;
61
62/**
63 * Services satisfied by the DurableFunction's own machinery (or the engine)
64 * and therefore excluded from the caller-facing requirements of the returned
65 * Effect/Layer.
66 */
67export type DurableFunctionInitServices =
68 | FunctionServices
69 | PlatformServices
70 | Function
71 | DurableRunServices;
72
73/**
74 * Properties of an {@link DurableFunction | AWS.Lambda.DurableFunction}.
75 *
76 * A DurableFunction accepts every {@link FunctionProps | Function prop}
77 * except `functionUrl` (every invocation of a durable function arrives as the
78 * durable-execution envelope — there is no HTTP surface), plus the
79 * `DurableConfig` tuning knobs below.
80 */
81/** The DurableConfig tuning knobs a DurableFunction adds to Function props. */
82export interface DurableFunctionTuning {
83 /**
84 * Maximum total duration of a durable execution, from start to terminal
85 * state (minimum 60 seconds, maximum 1 year). Rounded up to whole seconds.
86 * @default 24 hours (AWS default)
87 */
88 executionTimeout?: Duration.Input;
89 /**
90 * How long completed execution history is retained (e.g. `"7 days"`;
91 * 1–90 days). Rounded to whole days on the wire.
92 * @default "14 days" (AWS default)
93 */
94 retentionPeriod?: Duration.Input;
95}
96
97/**
98 * Props of a {@link DurableFunction}: any Function props that carry a
99 * `main` — the orchestrator body is an Effect that has to be bundled into
100 * an entrypoint — minus `functionUrl` (every invocation arrives as the
101 * durable envelope, so there is no HTTP surface), plus the DurableConfig
102 * knobs.
103 *
104 * Selected by SHAPE (`Extract<…, { main: string }>`), not by packaging, so
105 * any future main-bearing variant is durable-capable automatically; a
106 * prebuilt `image` function is excluded because it has no `main` for the
107 * `impl` Effect to live in.
108 *
109 * The Omit DISTRIBUTES over the union: a bare `Omit` computes the COMMON
110 * keys and merges the members, which would make `main` optional and let
111 * `image` through alongside it.
112 */
113export type DurableFunctionProps = DistributiveOmit<
114 Extract<FunctionProps, { main: string }>,
115 "functionUrl" | "durableConfig"
116> &
117 DurableFunctionTuning;
118
119/**
120 * Options for starting a durable execution.
121 */
122export interface DurableStartOptions<Input = unknown> {
123 /**
124 * Idempotent execution name (`DurableExecutionName`): starting again with
125 * the same name and payload reattaches to the existing execution; the same
126 * name with a different payload fails with
127 * `DurableExecutionAlreadyStartedException`.
128 */
129 name?: string;
130 /** The typed input payload delivered to the durable function body. */
131 params?: Input;
132 /**
133 * Function version or alias to pin the execution to. Durable executions
134 * replay against the version they started on. `$LATEST` is suitable for
135 * disposable development; production starts should target an immutable
136 * numbered {@link Version} or a stable {@link Alias}.
137 */
138 qualifier?: string;
139}
140
141/**
142 * A started durable execution reference.
143 */
144export interface DurableExecutionRef {
145 /** ARN of the durable execution (when returned by the Invoke response). */
146 executionArn: string | undefined;
147 statusCode: number | undefined;
148}
149
150/**
151 * The typed durable-execution handle: start, inspect, stop, and complete
152 * callbacks of durable executions of this function. Returned by
153 * `yield* MyDurableFunction` (as part of {@link DurableFunction}) and, inside
154 * the function's own init phase, by {@link DurableFunctionScope}.
155 */
156export interface DurableFunctionHandle<Input = unknown, Result = unknown> {
157 Type: TypeId;
158 name: string;
159 /** @internal phantom */
160 Result?: Result;
161 /**
162 * Start a durable execution (async `Invoke` with the alchemy payload
163 * envelope). Returns immediately; the execution progresses through
164 * checkpointed re-invocations.
165 */
166 start(
167 options?: DurableStartOptions<Input>,
168 ): Effect.Effect<DurableExecutionRef, Lambda.InvokeError>;
169 /** Fetch the execution's status/result. */
170 get(
171 executionArn: string,
172 ): Effect.Effect<
173 Lambda.GetDurableExecutionResponse,
174 Lambda.GetDurableExecutionError
175 >;
176 /** List executions of this function, optionally filtered by name/status. */
177 list(options?: {
178 name?: string;
179 statuses?: Lambda.ExecutionStatus[];
180 }): Effect.Effect<
181 Lambda.ListDurableExecutionsByFunctionResponse,
182 Lambda.ListDurableExecutionsByFunctionError
183 >;
184 /** Stop a running execution. */
185 stop(
186 executionArn: string,
187 error?: Lambda.ErrorObject,
188 ): Effect.Effect<
189 Lambda.StopDurableExecutionResponse,
190 Lambda.StopDurableExecutionError
191 >;
192 /** Complete a `Durable.waitForCallback` from the outside. */
193 sendCallbackSuccess(
194 callbackId: string,
195 result?: unknown,
196 ): Effect.Effect<void, Lambda.SendDurableExecutionCallbackSuccessError>;
197 sendCallbackFailure(
198 callbackId: string,
199 error?: Lambda.ErrorObject,
200 ): Effect.Effect<void, Lambda.SendDurableExecutionCallbackFailureError>;
201 sendCallbackHeartbeat(
202 callbackId: string,
203 ): Effect.Effect<void, Lambda.SendDurableExecutionCallbackHeartbeatError>;
204}
205
206/**
207 * The value produced by `yield* MyDurableFunction`: the typed
208 * {@link DurableFunctionHandle} plus references to the underlying
209 * {@link Function} resource and its key attributes.
210 */
211export interface DurableFunction<
212 Input = unknown,
213 Result = unknown,
214> extends DurableFunctionHandle<Input, Result> {
215 /** The underlying Lambda {@link Function} resource owned by this wrapper. */
216 function: Function;
217 /** Physical name of the underlying Lambda function. */
218 functionName: Function["functionName"];
219 /** ARN of the underlying Lambda function. */
220 functionArn: Function["functionArn"];
221}
222
223/**
224 * Inside a DurableFunction's init phase, resolves the function's own
225 * {@link DurableFunctionHandle} (e.g. for chained self-starts). Also what
226 * `yield* AWS.Lambda.DurableFunction` (the bare namespace value) resolves.
227 */
228export class DurableFunctionScope extends Context.Service<
229 DurableFunctionScope,
230 DurableFunctionHandle
231>()("AWS.Lambda.DurableFunctionScope") {}
232
233export interface DurableFunctionClass {
234 <_Self>(): {
235 <Input = unknown, Result = unknown, PropsReq = never, InitReq = never>(
236 id: string,
237 props:
238 | InputProps<DurableFunctionProps>
239 | Effect.Effect<
240 InputProps<DurableFunctionProps>,
241 ConfigError,
242 PropsReq
243 >,
244 impl: Effect.Effect<
245 DurableFunctionImpl<Input, Result>,
246 ConfigError,
247 InitReq
248 >,
249 ): Effect.Effect<
250 DurableFunction<Input, Result>,
251 never,
252 | Function["Providers"]
253 | Exclude<PropsReq | InitReq, DurableFunctionInitServices>
254 > & {
255 new (_: never): DurableFunctionImpl<Input, Result>;
256 };
257 <const Id extends string>(
258 id: Id,
259 ): Effect.Effect<DurableFunction, never, Function["Providers"]> & {
260 make<
261 Input = unknown,
262 Result = unknown,
263 PropsReq = never,
264 InitReq = never,
265 >(
266 props:
267 | InputProps<DurableFunctionProps>
268 | Effect.Effect<
269 InputProps<DurableFunctionProps>,
270 ConfigError,
271 PropsReq
272 >,
273 impl: Effect.Effect<
274 DurableFunctionImpl<Input, Result>,
275 ConfigError,
276 InitReq
277 >,
278 ): Layer.Layer<
279 _Self,
280 never,
281 | Function["Providers"]
282 | Exclude<PropsReq | InitReq, DurableFunctionInitServices>
283 >;
284 new (_: never): {};
285 };
286 };
287 <Input = unknown, Result = unknown, PropsReq = never, InitReq = never>(
288 id: string,
289 props:
290 | InputProps<DurableFunctionProps>
291 | Effect.Effect<InputProps<DurableFunctionProps>, ConfigError, PropsReq>,
292 impl: Effect.Effect<
293 DurableFunctionImpl<Input, Result>,
294 ConfigError,
295 InitReq
296 >,
297 ): Effect.Effect<
298 DurableFunction<Input, Result>,
299 never,
300 | Function["Providers"]
301 | Exclude<PropsReq | InitReq, DurableFunctionInitServices>
302 >;
303}
304
305/**
306 * Where the composed init stashes the {@link DurableFunction} value on the
307 * owned Function instance so `yield*` of any authoring form can produce it.
308 * A symbol key passes through the Resource proxy untouched (string props
309 * fabricate `Output.PropExpr` accessors).
310 */
311const DurableHandleKey = Symbol.for("alchemy/AWS.Lambda.DurableFunction");
312
313/**
314 * Vendor the Durable Execution SDK into the artifact. `build.install` roots
315 * are excluded from the bundle and npm-installed into the zip targeting the
316 * function's architecture, so this single entry both externalizes the SDK
317 * (the bridge dynamic-imports it at runtime) and ships it. Respects an
318 * explicit user entry (e.g. a pinned version).
319 */
320const withDurableSdkInstall = (
321 install: PackageInstall | undefined,
322): PackageInstall => {
323 if (install === undefined) {
324 return [DURABLE_SDK_MODULE];
325 }
326 if (Array.isArray(install)) {
327 return install.includes(DURABLE_SDK_MODULE)
328 ? install
329 : [...install, DURABLE_SDK_MODULE];
330 }
331 const record = install as Readonly<Record<string, string>>;
332 return DURABLE_SDK_MODULE in record
333 ? record
334 : { ...record, [DURABLE_SDK_MODULE]: "*" };
335};
336
337/**
338 * Lower DurableFunction props onto the base Function's props: split off the
339 * DurableConfig knobs into the internal wire-level `durableConfig` channel,
340 * disable the Function URL (durable invocations are the only surface), and
341 * vendor the Durable Execution SDK.
342 */
343const mapDurableProps = (props: DurableFunctionProps): FunctionProps => {
344 const { executionTimeout, retentionPeriod, build, ...rest } =
345 props ?? ({} as DurableFunctionProps);
346 const executionTimeoutSeconds = toSeconds(executionTimeout);
347 const retentionPeriodDays = toWireDays(retentionPeriod);
348 return {
349 ...rest,
350 // Every invocation of a DurableConfig'd function arrives as the durable
351 // envelope — a Function URL could never be served.
352 functionUrl: false,
353 build: {
354 ...build,
355 install: withDurableSdkInstall(build?.install),
356 },
357 durableConfig: {
358 ...(executionTimeoutSeconds !== undefined
359 ? { ExecutionTimeout: executionTimeoutSeconds }
360 : {}),
361 ...(retentionPeriodDays !== undefined
362 ? { RetentionPeriodInDays: retentionPeriodDays }
363 : {}),
364 },
365 };
366};
367
368const mapDurablePropsInput = (props: unknown) =>
369 Effect.isEffect(props)
370 ? Effect.map(props as Effect.Effect<DurableFunctionProps>, mapDurableProps)
371 : mapDurableProps(props as DurableFunctionProps);
372
373const resolveDurableHandle = (id: string) => (instance: unknown) => {
374 const handle = (instance as Record<symbol, unknown> | undefined)?.[
375 DurableHandleKey
376 ];
377 return handle !== undefined
378 ? Effect.succeed(handle as DurableFunction<any, any>)
379 : Effect.die(
380 new Error(
381 `AWS.Lambda.DurableFunction<${id}> has no durable handle — provide ` +
382 `its implementation (\`${id}.make(props, impl)\` or an inline ` +
383 `form) before yielding it.`,
384 ),
385 );
386};
387
388/**
389 * Compose the user's orchestrator init effect into the owned Function's init
390 * effect: resolve the durable management-plane clients, self-bind the
391 * checkpoint-protocol IAM onto the function's own execution role, register
392 * the durable listener on the owned entrypoint, and stash the typed handle
393 * for `yield* MyDurableFunction`.
394 */
395const composeDurableImpl = (
396 name: string,
397 impl: Effect.Effect<DurableFunctionImpl<any, any>, any, any>,
398): Effect.Effect<void, any, any> =>
399 Effect.gen(function* () {
400 // Self: the Function resource this wrapper owns (the Platform machinery
401 // provides `Function.Self` during its own init).
402 const host = yield* Function;
403
404 // Resolve the distilled operations once at init — they close over the
405 // ambient Credentials/Region/HttpClient so the handle's runtime
406 // callables need no cloud services of their own.
407 const invoke = yield* Lambda.invoke;
408 const getDurableExecution = yield* Lambda.getDurableExecution;
409 const listDurableExecutionsByFunction =
410 yield* Lambda.listDurableExecutionsByFunction;
411 const stopDurableExecution = yield* Lambda.stopDurableExecution;
412 const sendCallbackSuccess =
413 yield* Lambda.sendDurableExecutionCallbackSuccess;
414 const sendCallbackFailure =
415 yield* Lambda.sendDurableExecutionCallbackFailure;
416 const sendCallbackHeartbeat =
417 yield* Lambda.sendDurableExecutionCallbackHeartbeat;
418
419 // Capture the function-name Output WITHOUT resolving it. This is a
420 // self-reference — `host` is the very Function this wrapper's init is
421 // building — and `functionName`'s Output source only registers during
422 // that function's own reconcile. Yielding it here (init/plan time) would
423 // block forever (the reconcile that produces it waits on this init to
424 // finish). Resolve it lazily inside the runtime callables instead, the
425 // same posture as `InvokeFunctionHttp`.
426 const FunctionName = host.functionName;
427
428 if (!globalThis.__ALCHEMY_RUNTIME__) {
429 // Self-binding: the statements land on this function's own execution
430 // role through the standard bindings channel (precreate makes the stub,
431 // reconcile applies the collected bindings).
432 yield* host.bind`Allow(${host}, AWS.Lambda.DurableFunction(${name}))`({
433 policyStatements: [
434 // The checkpoint/replay protocol the Durable Execution SDK
435 // drives from inside the handler.
436 {
437 Effect: "Allow",
438 Action: [
439 "lambda:CheckpointDurableExecution",
440 "lambda:GetDurableExecutionState",
441 ],
442 Resource: [
443 host.functionArn,
444 Output.interpolate`${host.functionArn}:*`,
445 ],
446 },
447 // Self-start (handle.start) and chained self-invokes.
448 {
449 Effect: "Allow",
450 Action: ["lambda:InvokeFunction"],
451 Resource: [
452 host.functionArn,
453 Output.interpolate`${host.functionArn}:*`,
454 ],
455 },
456 // Management-plane handle methods. Durable execution ARNs are
457 // a distinct resource shape from the function ARN, so these
458 // stay account-wide for now.
459 {
460 Effect: "Allow",
461 Action: [
462 "lambda:GetDurableExecution",
463 "lambda:GetDurableExecutionHistory",
464 "lambda:ListDurableExecutionsByFunction",
465 "lambda:StopDurableExecution",
466 "lambda:SendDurableExecutionCallbackSuccess",
467 "lambda:SendDurableExecutionCallbackFailure",
468 "lambda:SendDurableExecutionCallbackHeartbeat",
469 ],
470 Resource: ["*"],
471 },
472 ],
473 });
474 }
475
476 const handle: DurableFunctionHandle<any, any> = {
477 Type: TypeId,
478 name,
479 start: (options) =>
480 Effect.gen(function* () {
481 // `FunctionName` is the host's own unresolved Output (captured raw at
482 // init to avoid the plan-time self-reference deadlock). Resolve both
483 // stages — Output → Accessor → string — lazily at runtime.
484 const functionName = yield* yield* FunctionName;
485 const response = yield* invoke({
486 FunctionName: functionName,
487 InvocationType: "Event",
488 DurableExecutionName: options?.name,
489 Qualifier: options?.qualifier,
490 Payload: encodeDurableEnvelope(name, options?.params),
491 });
492 return {
493 executionArn: response.DurableExecutionArn,
494 statusCode: response.StatusCode,
495 };
496 }),
497 get: (executionArn) =>
498 getDurableExecution({ DurableExecutionArn: executionArn }),
499 list: (options) =>
500 Effect.gen(function* () {
501 const functionName = yield* yield* FunctionName;
502 return yield* listDurableExecutionsByFunction({
503 FunctionName: functionName,
504 DurableExecutionName: options?.name,
505 Statuses: options?.statuses,
506 });
507 }),
508 stop: (executionArn, error) =>
509 stopDurableExecution({
510 DurableExecutionArn: executionArn,
511 Error: error,
512 }),
513 sendCallbackSuccess: (callbackId, result) =>
514 sendCallbackSuccess({
515 CallbackId: callbackId,
516 Result: result === undefined ? undefined : JSON.stringify(result),
517 }).pipe(Effect.asVoid),
518 sendCallbackFailure: (callbackId, error) =>
519 sendCallbackFailure({
520 CallbackId: callbackId,
521 Error: error,
522 }).pipe(Effect.asVoid),
523 sendCallbackHeartbeat: (callbackId) =>
524 sendCallbackHeartbeat({ CallbackId: callbackId }).pipe(Effect.asVoid),
525 };
526
527 // Resolve the body function. Bindings resolved in the impl's init close
528 // over their services; the returned closure's only leftover requirements
529 // are DurableRunServices, provided per invocation by the bridge.
530 const fn = yield* (
531 impl as Effect.Effect<DurableFunctionImpl<any, any>>
532 ).pipe(Effect.provideService(DurableFunctionScope, handle));
533
534 yield* host.listen(
535 makeDurableListener({
536 name,
537 run: (input) => fn(input) as Effect.Effect<unknown>,
538 }),
539 );
540
541 // Expose the full DurableFunction value (handle + resource refs) to
542 // `yield* MyDurableFunction` for every authoring form.
543 (host as unknown as Record<symbol, unknown>)[DurableHandleKey] = {
544 ...handle,
545 function: host,
546 functionName: host.functionName,
547 functionArn: host.functionArn,
548 } satisfies DurableFunction<any, any>;
549 });
550
551/**
552 * An AWS Lambda Durable Function — a code-first, replay-based orchestrator
553 * that IS a durable Lambda Function. `AWS.Lambda.DurableFunction` is a
554 * wrapper of {@link Function}: it owns the underlying Lambda function,
555 * configures its `DurableConfig` at `CreateFunction` (durability is a
556 * create-time property — a DurableFunction is always durable), registers the
557 * durable-execution listener on the owned entrypoint, self-binds the
558 * checkpoint-protocol IAM (`lambda:CheckpointDurableExecution`,
559 * `lambda:GetDurableExecutionState`) onto the execution role, and vendors the
560 * open-source `@aws/durable-execution-sdk-js` into the artifact (install it
561 * in your project: `npm i @aws/durable-execution-sdk-js`).
562 *
563 * Executions progress by checkpoint + replay: a `Durable.sleep` or
564 * `Durable.waitForCallback` suspends the execution with zero compute billed
565 * until Lambda re-invokes the same function version to resume, and completed
566 * `Durable.step`s replay from the checkpoint log without re-executing.
567 *
568 * Every invocation of a durable function arrives as the durable-execution
569 * envelope, so a DurableFunction has no HTTP surface (`functionUrl` is disabled) —
570 * it does one thing: run durable orchestrations. Reusing a logical id
571 * between a plain `Function` and a `DurableFunction` replaces the physical
572 * function (DurableConfig cannot be flipped in place).
573 *
574 * ### Defining a Durable Function
575 * **Example:** Class form with steps and a durable sleep
576 * ```typescript
577 * export class OrderFlow extends AWS.Lambda.DurableFunction<OrderFlow>()(
578 * "OrderFlow",
579 * {
580 * main: import.meta.url,
581 * executionTimeout: "1 hour",
582 * retentionPeriod: "7 days",
583 * },
584 * Effect.gen(function* () {
585 * // init: resolve typed binding clients (IAM lands on this function's role)
586 * const putItem = yield* AWS.DynamoDB.PutItem(table);
587 *
588 * return Effect.fn(function* (input: { orderId: string }) {
589 * const reserved = yield* AWS.Lambda.Durable.step(
590 * "reserve",
591 * putItem({ Item: { pk: { S: input.orderId } } }).pipe(Effect.orDie),
592 * { retry: { limit: 3, delay: "5 seconds" } },
593 * );
594 * yield* AWS.Lambda.Durable.sleep("cooldown", "10 minutes");
595 * return { orderId: input.orderId, reserved };
596 * });
597 * }),
598 * ) {}
599 * ```
600 *
601 * **Example:** Tag + default export (entrypoint form)
602 * ```typescript
603 * // order-flow.ts — `main` points at this module
604 * export class OrderFlow extends AWS.Lambda.DurableFunction<OrderFlow>()(
605 * "OrderFlow",
606 * ) {}
607 *
608 * export default OrderFlow.make(
609 * { main: import.meta.url, executionTimeout: "1 hour" },
610 * Effect.gen(function* () {
611 * return Effect.fn(function* (input: { orderId: string }) {
612 * return yield* AWS.Lambda.Durable.step("work", doWork(input));
613 * });
614 * }),
615 * );
616 * ```
617 *
618 * **Example:** Inline effect form
619 * ```typescript
620 * const flow = yield* AWS.Lambda.DurableFunction(
621 * "OrderFlow",
622 * { main: "./src/order-flow.ts" },
623 * Effect.gen(function* () {
624 * return Effect.fn(function* (input: { orderId: string }) {
625 * return yield* AWS.Lambda.Durable.step("work", doWork(input));
626 * });
627 * }),
628 * );
629 * ```
630 *
631 * ### Starting and Monitoring Executions
632 * **Example:** Starting an execution
633 * ```typescript
634 * const orders = yield* OrderFlow;
635 * const ref = yield* orders.start({
636 * name: "order-123", // idempotent start
637 * params: { orderId: "123" },
638 * qualifier: "live",
639 * });
640 * ```
641 *
642 * **Example:** Publish and promote for production
643 * ```typescript
644 * const orders = yield* OrderFlow;
645 * const version = yield* AWS.Lambda.Version("OrderFlowVersion", {
646 * function: orders.function,
647 * });
648 * yield* AWS.Lambda.Alias("OrderFlowLive", {
649 * version,
650 * aliasName: "live",
651 * });
652 *
653 * const ref = yield* orders.start({
654 * name: "order-123",
655 * params: { orderId: "123" },
656 * qualifier: "live",
657 * });
658 * ```
659 *
660 * **Example:** Checking status
661 * ```typescript
662 * const execution = yield* orders.get(ref.executionArn!);
663 * // execution.Status: "RUNNING" | "SUCCEEDED" | "FAILED" | ...
664 * ```
665 *
666 * ### External Callbacks
667 * **Example:** Waiting for an approval
668 * ```typescript
669 * const approval = yield* AWS.Lambda.Durable.waitForCallback<{ ok: boolean }>(
670 * "approve",
671 * (callbackId) => storeCallbackId(callbackId),
672 * { timeout: "1 day" },
673 * );
674 * ```
675 *
676 * @resource
677 */
678export const DurableFunction: DurableFunctionClass = taggedFunction(
679 DurableFunctionScope,
680 ((
681 ...args:
682 | []
683 | [id: string]
684 | [id: string, props: unknown, impl: Effect.Effect<any, any, any>]
685 ) => {
686 if (args.length === 0) {
687 // `DurableFunction<Self>()` — the binder for the class/tag forms.
688 return DurableFunction;
689 }
690 const [id, props, impl] = args;
691 if (impl === undefined) {
692 // Tag form: `class OrderFlow extends DurableFunction<OrderFlow>()("OrderFlow") {}`
693 // + `export default OrderFlow.make(props, impl)`.
694 const fnTag = (Function as any)()(id);
695 return Object.assign(
696 function (props: unknown, impl: Effect.Effect<any, any, any>) {
697 return Effect.flatMap(
698 fnTag(mapDurablePropsInput(props), composeDurableImpl(id, impl)),
699 resolveDurableHandle(id),
700 );
701 },
702 fnTag,
703 {
704 make: (props: unknown, impl: Effect.Effect<any, any, any>) =>
705 fnTag.make(
706 mapDurablePropsInput(props),
707 composeDurableImpl(id, impl),
708 ),
709 },
710 Effectable.Prototype({
711 label: `${TypeId}<${id}>`,
712 evaluate: () =>
713 Effect.flatMap(
714 Effect.serviceOption(fnTag.Self),
715 Option.match({
716 onNone: () => resolveDurableHandle(id)(undefined),
717 onSome: resolveDurableHandle(id),
718 }),
719 ),
720 }),
721 );
722 }
723 // Inline forms (eager effect / inline class): delegate to the Function
724 // platform with lowered props and the composed durable init.
725 return effectClass(
726 Effect.flatMap(
727 (Function as any)(
728 id,
729 mapDurablePropsInput(props),
730 composeDurableImpl(id, impl),
731 ) as Effect.Effect<unknown>,
732 resolveDurableHandle(id),
733 ),
734 );
735 }) as any,
736);