import type { Types } from "effect"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import type { Capability } from "./capability.ts"; import type { Policy } from "./policy.ts"; import type { ProviderService } from "./provider.ts"; import type { IResource, Resource, ResourceTags } from "./resource.ts"; import type { IService, Service } from "./service.ts"; export type RuntimeHandler< Inputs extends any[] = any[], Output = any, Err = any, Req = any, > = (...inputs: Inputs) => Effect.Effect; export declare namespace RuntimeHandler { export type Caps = Extract< Effect.Effect.Context>>, Capability >; } export declare namespace Runtime { export type Binding = F extends { readonly Binding: unknown; } ? (F & { readonly cap: Cap; })["Binding"] : { readonly F: F; readonly cap: Types.Contravariant; }; } export type AnyRuntime = Runtime; export interface RuntimeProps { bindings: Policy, unknown>; } export interface IRuntime< Type extends string = string, Handler = unknown, Props = unknown, > extends IResource { type: Type; props: Props; handler: Handler; binding: unknown; /** @internal phantom */ capability: unknown; } export interface Runtime< Type extends string = string, Handler = unknown, Props = unknown, > extends IRuntime, Resource { provider: ResourceTags; < const ID extends string, Inputs extends any[], Output, Err, Req, Handler extends RuntimeHandler, >( id: ID, { handle }: { handle: Handler }, ): ( props: Props, ) => Service< ID, this, Handler, // @ts-expect-error Props >; } export const Runtime = (type: Type) => (): Self => { const Tag = Context.Tag(type)(); const provider = { tag: Tag, effect: (eff: Effect.Effect, any, any>) => Layer.effect(Tag, eff), succeed: (service: ProviderService) => Layer.succeed(Tag, service), }; const self = Object.assign( ( ...args: | [cap: Capability] | [ id: string, { handle: (...args: any[]) => Effect.Effect }, ] ) => { if (args.length === 1) { const [cap] = args; const tag = `${type}(${cap})` as const; return class extends Context.Tag(tag)() { Capability = cap; }; } else { const [id, { handle }] = args; return >(props: Props) => Object.assign( class { constructor() { throw new Error("Cannot instantiate a Service directly"); } }, { kind: "Service", type, id, attr: undefined!, impl: handle, handler: Effect.succeed(handle as any), props, runtime: self, // TODO(sam): is this right? parent: self, // @ts-expect-error provider, } satisfies IService, ); } }, { kind: "Runtime", type: type, id: undefined! as string, capability: undefined! as Capability[], provider, toString() { return `${this.type}(${this.id}${ this.capability?.length ? `, ${this.capability.map((c) => `${c}`).join(", ")}` : "" })`; }, }, ) as unknown as Self; return self as any; };