import * as Effect from "effect/Effect" import * as Command from "effect/unstable/cli/Command" import { Spec } from "./spec" import { Daemon } from "../services/daemon" export type Input = Value extends Spec.Node ? Input : Value extends Command.Command ? Input : never type RuntimeHandler = (input: unknown) => Effect.Effect type Loader = () => Promise<{ default: (input: Input) => Effect.Effect }> type ProvidedCommand = Command.Command export type Handlers = keyof Node["commands"] extends never ? Loader : { readonly $?: Loader } & { readonly [Key in keyof Node["commands"]]: Handlers } interface LazyHandler { readonly spec: Command.Command.Any readonly load: () => Promise<{ default: RuntimeHandler }> } type RuntimeHandlers = | (() => Promise<{ default: RuntimeHandler }>) | { readonly $?: () => Promise<{ default: RuntimeHandler }> readonly [key: string]: RuntimeHandlers | (() => Promise<{ default: RuntimeHandler }>) | undefined } export function handler( _node: Node, run: (input: Input) => Effect.Effect, ) { return run } export function handlers(root: Root, handlers: Handlers) { const result: LazyHandler[] = [] function add(node: Spec.Any, value: RuntimeHandlers) { if (typeof value === "function") { result.push({ spec: node.spec, load: value as () => Promise<{ default: RuntimeHandler }> }) return } if (value.$) result.push({ spec: node.spec, load: value.$ as () => Promise<{ default: RuntimeHandler }> }) for (const [name, child] of Object.entries(node.commands)) add(child, value[name] as RuntimeHandlers) } add(root, handlers as RuntimeHandlers) return result } export function run(commands: Spec.Any, handlers: ReadonlyArray, options: { readonly version: string }) { return Command.run(provide(commands, handlers), options) as Effect.Effect } function provide(node: Spec.Any, handlers: ReadonlyArray): ProvidedCommand { const handler = handlers.find((handler) => handler.spec === node.spec) const spec = handler ? node.spec.pipe( Command.withHandler((input) => Effect.gen(function* () { yield* Effect.flatMap(Effect.promise(handler.load), (module) => module.default(input)) }), ), ) : node.spec if (!Object.keys(node.commands).length) return spec as ProvidedCommand return spec.pipe( Command.withSubcommands(Object.values(node.commands).map((child) => provide(child, handlers))), ) as ProvidedCommand } export * as Runtime from "./runtime"