import { Brand, Context, Layer } from "effect" type AnyNode = Node type NodeList = readonly [] | readonly [Item, ...Item[]] export type Output = [Item] extends [never] ? never : Item extends Node ? A : never export type Error = [Item] extends [never] ? never : Item extends Node ? E : never type NodeTag = [Item] extends [never] ? undefined : Item extends Node ? T : 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 Tag = Name & Brand.Brand<"LayerNode.Tag"> const makeTag = Brand.nominal() export interface Node { readonly kind: "layer" | "unbound" | "group" readonly name: string readonly service?: Context.Service.Any readonly implementation?: Layer.Any readonly dependencies: readonly AnyNode[] readonly tag?: T readonly [$OutputType]?: () => A readonly [$ErrorType]?: () => E } type NodeIdentity = | { readonly service: Context.Service.Any; readonly name?: never } | { readonly name: string; readonly service?: never } type DistributiveOmit = A extends unknown ? Omit : never type MakeInput< Implementation extends Layer.Any, Items extends NodeList, T extends Tag | undefined = undefined, > = NodeIdentity & { readonly layer: Implementation readonly deps: Items & CheckDependencies> readonly tag?: T } export function make< const Implementation extends Layer.Any, const Items extends NodeList, const T extends Tag | undefined = undefined, >( input: MakeInput, ): Node, Layer.Error | Error, T> { return { kind: "layer", name: input.service !== undefined ? input.service.key : input.name, service: input.service, implementation: input.layer, dependencies: input.deps, tag: input.tag, } } export function unbound(service: Context.Key, tag: T): Node { return { kind: "unbound", name: service.key, service, dependencies: [], tag, } } export function group( dependencies: Items, ): Node, Error, NodeTag> { return { kind: "group", name: "group", dependencies } } export type TagConfig = Readonly> type TagNames = keyof Config & string type NodeInTags = Node | undefined> type CheckTags = [Exclude>] extends [ never, ] ? unknown : { readonly "Invalid tag dependencies": Exclude> } export interface Tags { readonly values: { readonly [Name in TagNames]: Tag } readonly make: >( name: Name, ) => ( input: DistributiveOmit>, "tag"> & CheckTags>, ) => Node, Layer.Error | Error, Tag> } export function tags( config: Config, ): Tags { const names = Object.keys(config) as TagNames[] const values = Object.fromEntries(names.map((name) => [name, makeTag(name)])) as Tags["values"] return { values, make: ((name: TagNames) => (input: DistributiveOmit, "tag">) => make({ ...input, tag: values[name] })) as Tags["make"], } } export type Replacement = { readonly source: Layer.Any readonly replacement: Layer.Any } type CheckReplacementErrors = [Exclude] extends [never] ? unknown : { readonly "New replacement errors": Exclude } export function replace( source: Layer.Layer, replacement: Layer.Layer, E2, never> & CheckReplacementErrors>, ): Replacement { return { source, replacement } } export * as LayerNode from "./layer-node"