import { Layer } from "effect" type RuntimeLayer = Layer.Layer type AnyNode = Node type NodeList = readonly [] | readonly [AnyNode, ...AnyNode[]] type Output = [Item] extends [never] ? never : Item extends Node ? A : never type Error = [Item] extends [never] ? never : Item extends Node ? E : never type Missing = Exclude> type CheckDependencies = [ Missing, Dependencies>, ] extends [never] ? unknown : { readonly "Missing dependencies": Missing, Dependencies> } declare const $OutputType: unique symbol declare const $ErrorType: unique symbol export type Node = { readonly kind: "layer" | "group" readonly implementation?: Layer.Any readonly dependencies: readonly AnyNode[] readonly [$OutputType]?: () => A readonly [$ErrorType]?: () => E } export function make( implementation: Implementation, dependencies: Items & CheckDependencies>, ): Node, Layer.Error | Error> { return { kind: "layer", implementation: implementation as Layer.Any, dependencies } } export function group( dependencies: Items, ): Node, Error> { return { kind: "group", dependencies } } export type Replacement = { readonly source: Node readonly replacement: Node } type CheckReplacementErrors = [Exclude] extends [never] ? unknown : { readonly "New replacement errors": Exclude } export function replaceWithNode( source: Node, replacement: Node, E2> & CheckReplacementErrors>, ): Replacement { return { source, replacement } } export function replace( source: Node, replacement: Layer.Layer, E2, never> & CheckReplacementErrors>, ): Replacement { return { source, replacement: make(replacement as Layer.Layer, []) } } export function buildLayer(node: Node, options?: { readonly replacements?: readonly Replacement[] }) { const replacements = new Map(options?.replacements?.map((item) => [item.source, item.replacement])) const cache = new Map() const visiting = new Set() const stack: AnyNode[] = [] const ids = new Map() const visit = (input: AnyNode): RuntimeLayer => { const node = replacements.get(input) ?? input const cached = cache.get(node) if (cached) return cached if (visiting.has(node)) { const start = stack.indexOf(node) const cycle = [...stack.slice(start), node].map((item) => `${item.kind}#${ids.get(item)}`).join(" -> ") throw new Error(`Cycle detected in app graph: ${cycle}`) } if (!ids.has(node)) ids.set(node, ids.size + 1) visiting.add(node) stack.push(node) try { const dependencies = node.dependencies.map(visit) const nonEmpty = dependencies as [RuntimeLayer, ...RuntimeLayer[]] const result = node.kind === "group" ? dependencies.length === 0 ? Layer.empty : Layer.mergeAll(...nonEmpty) : dependencies.length === 0 ? (node.implementation as RuntimeLayer) : Layer.provide(node.implementation as RuntimeLayer, nonEmpty) cache.set(node, result) return result } finally { stack.pop() visiting.delete(node) } } return visit(node) as unknown as Layer.Layer } export * as LayerNode from "./layer-node"