import * as Context from "effect/Context"; import type { Effect } from "effect/Effect"; import * as Layer from "effect/Layer"; import type { Capability, ICapability } from "./capability.ts"; import type { Diff } from "./diff.ts"; import type { IResource, Resource } from "./resource.ts"; import type { IRuntime } from "./runtime.ts"; export interface BindingProps { [key: string]: any; } export const isBinding = (b: any): b is AnyBinding => "runtime" in b && "capability" in b && "tag" in b && "output" in b; export type AnyBinding = Binding< F, any, any, any, string, boolean >; export interface Binding< Run extends IRuntime, Cap extends Capability = Capability, Props = any, Attr extends Run["binding"] = any, Tag extends string = Cap["type"], IsCustom extends boolean = Cap["type"] extends Tag ? false : true, > { runtime: Run; capability: Cap; tag: Tag; props: Props; attr: Attr; isCustom: IsCustom; } /** Tag for a Service that can bind a Capability to a Runtime */ export interface Bind< F extends IRuntime, Cap extends Capability, Tag extends string, > extends Context.Tag< `${F["type"]}(${Cap["type"]}, ${Tag})`, BindingService< F, Extract["base"], Resource>, F["props"] > > { /** @internal phantom */ name: Tag; } export const Binding: { AnyBinding & { isCustom: true }>( runtime: ReturnType["runtime"], // resource: new () => ReturnType["capability"]["resource"], type: ReturnType["capability"]["type"], tag: ReturnType["tag"], ): F & BindingDeclaration["runtime"], F>; AnyBinding & { isCustom: false }>( runtime: ReturnType["runtime"], // resource: new () => ReturnType["capability"]["resource"], type: ReturnType["capability"]["type"], ): F & BindingDeclaration["runtime"], F>; } = (runtime: any, cap: string, tag?: string) => { const Tag = Context.Tag(`${runtime.type}(${cap}, ${tag ?? cap})`)(); return Object.assign( (resource: any, props?: any) => ({ runtime, capability: { type: cap, resource, constraint: undefined!, sid: `${cap}${resource.id}`.replace(/[^a-zA-Z0-9]/g, ""), label: `${cap}(${resource.id})`, } satisfies ICapability, props, isCustom: false, tag: tag ?? cap, // @ts-expect-error - we smuggle this property because it interacts poorly with inference Tag, }) satisfies Binding, { provider: { effect: (eff) => Layer.effect(Tag, eff), succeed: (service) => Layer.succeed(Tag, service), }, } satisfies BindingDeclaration, ); }; export interface BindingDeclaration< Run extends IRuntime, F extends (target: any, props?: any) => AnyBinding, Tag extends string = ReturnType["tag"], Cap extends Capability = ReturnType["capability"], > { provider: { effect( eff: Effect< BindingService< Run, Parameters[0], Parameters[1], ReturnType["attr"] >, Err, Req >, ): Layer.Layer, Err, Req>; succeed( service: BindingService< Run, Parameters[0], Parameters[1], ReturnType["attr"] >, ): Layer.Layer>; }; } export interface BindingDiffProps< Source extends IResource = IResource, Target extends IResource = IResource, Props = any, Attr = any, > { source: { id: string; props: Source["props"]; oldProps?: Source["props"]; oldAttr?: Source["attr"]; }; props: Props; attr: Attr | undefined; target: { id: string; props: Target["props"]; oldProps?: Target["props"]; oldAttr?: Target["attr"]; }; } export interface BindingAttachProps< Source extends IResource, Target extends IResource, Props, Attr, > { source: { id: string; attr: Source["attr"]; props: Source["props"]; }; props: Props; attr: Attr | undefined; target: { id: string; props: Target["props"]; attr: Target["attr"]; }; } export interface BindingReattachProps< Source extends IResource, Target extends IResource, Props, Attr, > { source: { id: string; attr: Source["attr"]; props: Source["props"]; }; props: Props; attr: Attr; target: { id: string; props: Target["props"]; attr: Target["attr"]; }; } export interface BindingDetachProps< Source extends IResource, Target extends IResource, Props, Attr, > { source: { id: string; attr: Source["attr"]; props: Source["props"]; }; props: Props; attr: Attr | undefined; target: { id: string; props: Target["props"]; attr: Target["attr"]; }; } export type BindingService< Target extends IRuntime = any, Source extends IResource = IResource, Props = any, Attr extends Target["binding"] = any, DiffReq = never, PreReattachReq = never, AttachReq = never, ReattachReq = never, DetachReq = never, PostAttachReq = never, > = { diff?: ( props: BindingDiffProps, ) => Effect; preattach?: ( props: BindingAttachProps, ) => Effect, never, PreReattachReq>; attach: ( props: BindingAttachProps, ) => Effect | Attr; postattach?: ( props: BindingAttachProps, ) => Effect, never, PostAttachReq>; reattach?: ( props: BindingReattachProps, ) => Effect | Attr; detach?: ( props: BindingDetachProps, ) => Effect | void; };