import { DestroyedSignal } from "./destroy.js"; import type { Resource, ResourceFQN, ResourceID, ResourceKind, ResourceProps, } from "./resource.js"; import type { Scope } from "./scope.js"; import type { State } from "./state.js"; export type Context< Out extends Resource, Props extends ResourceProps = ResourceProps, > = CreateContext | UpdateContext | DeleteContext; export interface CreateContext extends BaseContext { phase: "create"; output?: undefined; props?: undefined; } export interface UpdateContext< Out extends Resource, Props extends ResourceProps = ResourceProps, > extends BaseContext { phase: "update"; output: Out; props: Props; } export interface DeleteContext< Out extends Resource, Props extends ResourceProps = ResourceProps, > extends BaseContext { phase: "delete"; output: Out; props: Props; } export interface BaseContext { quiet: boolean; stage: string; id: ResourceID; fqn: ResourceFQN; scope: Scope; get(key: string): Promise; set(key: string, value: T): Promise; delete(key: string): Promise; /** * Indicate that this resource is being replaced. * This will cause the resource to be deleted at the end of the stack's CREATE phase. */ replace(): void; /** * Terminate the resource lifecycle handler and destroy the resource. * * This is the final operation performed during a delete operation. * * It is so that the resource lifecycle handler can "return never" instead of * "return undefined" so that `await MyResource()` always returns a value. */ destroy(): never; /** * Create the Resource envelope (with Alchemy + User properties) */ create(props: Omit): Out; /** * Create the Resource envelope (with Alchemy + User properties) */ (id: string, props: Omit): Out; (props: Omit): Out; } export function context< Kind extends string, Props extends ResourceProps | undefined, Out extends Resource, >({ scope, phase, kind, id, fqn, seq, state, replace, }: { scope: Scope; phase: "create" | "update" | "delete"; kind: ResourceKind; id: ResourceID; fqn: ResourceFQN; seq: number; props: Props; state: State; replace: () => void; }): Context { function create(props: Omit): Out; function create(id: string, props: Omit): Out; function create( ...args: | [props: Omit] | [id: string, props: Omit] ): Out { const [ID, props] = typeof args[0] === "string" ? (args as [string, any]) : [id, args[0]]; return { ...props, Kind: kind, ID, FQN: fqn, Scope: scope, Seq: seq, } as Out; } return Object.assign(create, { stage: scope.stage, scope, id: id, fqn: fqn, phase, output: state.output, props: state.props, replace, get: (key: string) => state.data[key], set: async (key: string, value: any) => { state.data[key] = value; }, delete: async (key: string) => { const value = state.data[key]; delete state.data[key]; return value; }, quiet: scope.quiet, destroy: () => { throw new DestroyedSignal(); }, create, }) as Context; }