import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import type * as Stream from "effect/Stream"; import type { Artifacts } from "./Artifacts.ts"; import type { ScopedPlanStatusSession } from "./Cli/index.ts"; import type { Diff } from "./Diff.ts"; import type { Input } from "./Input.ts"; import type { InstanceId } from "./InstanceId.ts"; import type { Platform } from "./Platform.ts"; import type { ResourceBinding, ResourceClass, ResourceLike, } from "./Resource.ts"; export interface Provider { asEffect: () => Effect.Effect, never, Provider>; [Symbol.iterator]: () => Effect.EffectIterator>; of: < ReadReq = never, DiffReq = never, PrecreateReq = never, CreateReq = never, UpdateReq = never, DeleteReq = never, TailReq = never, LogsReq = never, >( service: ProviderService< R, ReadReq, DiffReq, PrecreateReq, CreateReq, UpdateReq, DeleteReq, TailReq, LogsReq >, ) => ProviderService< R, ReadReq, DiffReq, PrecreateReq, CreateReq, UpdateReq, DeleteReq, TailReq, LogsReq >; } type LifecycleServices = InstanceId | Artifacts; export const Provider = ( type: R["Type"], ): Provider => Context.Service, ProviderService>()(type) as any; type BindingData = [Res] extends [ { Binding: infer B }, ] ? ResourceBinding[] : any[]; type Props = keyof Res["Props"] extends never ? Res["Props"] | undefined : Res["Props"]; export interface LogLine { timestamp: Date; message: string; } export interface LogsInput { since?: Date; limit?: number; } export interface ProviderService< Res extends ResourceLike = ResourceLike, ReadReq = never, DiffReq = never, PrecreateReq = never, CreateReq = never, UpdateReq = never, DeleteReq = never, TailReq = never, LogsReq = never, > { /** * The version of the provider. * * @default 0 */ version?: number; /** * Returns a stream of log lines for a deployed resource. * Used by `alchemy tail` to stream real-time logs. */ tail?(input: { id: string; instanceId: string; props: Props; output: Res["Attributes"]; }): Stream.Stream; /** * Queries historical logs for a deployed resource. * Used by `alchemy logs` to fetch past log entries. */ logs?(input: { id: string; instanceId: string; props: Props; output: Res["Attributes"]; options: LogsInput; }): Effect.Effect; // watch(); // replace(): Effect.Effect; // different interface that is persistent, watching, reloads // run?() {} // branch?() {} read?(input: { id: string; instanceId: string; olds: Props; // what is the ARN? output: Res["Attributes"] | undefined; // current state -> synced state }): Effect.Effect; /** * Properties that are always stable across any update. */ stables?: Extract[]; diff?(input: { id: string; instanceId: string; olds: Props; // Note: we do not resolve (Res["Props"]) here because diff runs during plan // -> we need a way for the diff handlers to work with Outputs news: Input>; oldBindings: BindingData; newBindings: Input>; output: Res["Attributes"] | undefined; }): Effect.Effect; // dev?:() => Effect.Effect; precreate?(input: { id: string; news: Props; instanceId: string; session: ScopedPlanStatusSession; bindings: BindingData; }): Effect.Effect; create(input: { id: string; instanceId: string; news: Props; session: ScopedPlanStatusSession; bindings: BindingData; output?: Res["Attributes"]; }): Effect.Effect; update(input: { id: string; instanceId: string; news: Props; olds: Props; output: Res["Attributes"]; session: ScopedPlanStatusSession; bindings: BindingData; }): Effect.Effect; delete(input: { id: string; instanceId: string; olds: Props; output: Res["Attributes"]; session: ScopedPlanStatusSession; bindings: BindingData; }): Effect.Effect; } export const getProviderByType = Effect.fnUntraced(function* < R extends ResourceLike, >(resourceType: string) { const context = yield* Effect.context(); const provider: ProviderService = context.mapUnsafe.get(resourceType); if (!provider) { return yield* Effect.die( new Error(`Provider not found for ${resourceType}`), ); } return provider; }); export const effect = < R extends ResourceLike, Req = never, ReadReq = never, DiffReq = never, PrecreateReq = never, CreateReq = never, UpdateReq = never, DeleteReq = never, TailReq = never, LogsReq = never, >( cls: ResourceClass | Platform, eff: Effect.Effect< ProviderService< R, ReadReq, DiffReq, PrecreateReq, CreateReq, UpdateReq, DeleteReq, TailReq, LogsReq >, never, Req >, ): Layer.Layer< Provider, never, Exclude< Req | ReadReq | DiffReq | PrecreateReq | CreateReq | UpdateReq | DeleteReq, LifecycleServices > > => // @ts-expect-error Layer.effect(Provider(cls.Type), eff); export const succeed = < R extends ResourceLike, ReadReq = never, DiffReq = never, PrecreateReq = never, CreateReq = never, UpdateReq = never, DeleteReq = never, TailReq = never, LogsReq = never, >( cls: ResourceClass | Platform, service: ProviderService< R, ReadReq, DiffReq, PrecreateReq, CreateReq, UpdateReq, DeleteReq, TailReq, LogsReq >, ): Layer.Layer< Provider, never, Exclude< ReadReq | DiffReq | PrecreateReq | CreateReq | UpdateReq | DeleteReq, LifecycleServices > > => // @ts-expect-error Layer.succeed(Provider(cls.Type), service);