import { Brand, Context, Layer } from "effect" type AnyNode = Node type NodeList = readonly [] | readonly [Item, ...Item[]] 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 Tier = Name & Brand.Brand<"LayerNode.Tier"> const makeTier = 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 tier?: 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 Tier | undefined = undefined, > = NodeIdentity & { readonly layer: Implementation readonly deps: Items & CheckDependencies> readonly tier?: T } export function make< const Implementation extends Layer.Any, const Items extends NodeList, const T extends Tier | undefined = undefined, >( input: MakeInput, ): Node< Layer.Success, 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, tier: input.tier, } } export function unbound( service: Context.Key, tier: T, ): Node { return { kind: "unbound", name: service.key, service, dependencies: [], tier, } } export function group( dependencies: Items, ): Node, Error> { return { kind: "group", name: "group", dependencies } } type AllowedTierNames = Names extends readonly [ infer Head extends string, ...infer Tail extends readonly string[], ] ? Head extends Name ? Head | Tail[number] : AllowedTierNames : never type NodeInTiers = Node | undefined> type CheckTiers = [Exclude>] extends [ never, ] ? unknown : { readonly "Invalid tier dependencies": Exclude> } export interface Tiers { readonly names: Names readonly values: { readonly [K in Names[number]]: Tier } readonly make: ( name: Name, ) => ( input: DistributiveOmit>, "tier"> & CheckTiers>, ) => Node< Layer.Success, Layer.Error | Error, Tier > } export function tiers(names: Names): Tiers { const values = Object.fromEntries(names.map((name) => [name, makeTier(name)])) as Tiers["values"] return { names, values, make: ((name: Names[number]) => (input: DistributiveOmit, "tier">) => make({ ...input, tier: values[name] })) as Tiers["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"