mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-10 01:39:36 +00:00
138 lines
4.9 KiB
TypeScript
138 lines
4.9 KiB
TypeScript
import { Brand, Context, Layer } from "effect"
|
|
|
|
type AnyNode = Node<unknown, unknown, any>
|
|
type NodeList<Item extends AnyNode = AnyNode> = readonly [] | readonly [Item, ...Item[]]
|
|
export type Output<Item> = [Item] extends [never] ? never : Item extends Node<infer A, unknown, any> ? A : never
|
|
export type Error<Item> = [Item] extends [never] ? never : Item extends Node<unknown, infer E, any> ? E : never
|
|
type NodeTag<Item> = [Item] extends [never] ? undefined : Item extends Node<unknown, unknown, infer T> ? T : never
|
|
type Missing<Required, Dependencies extends NodeList> = Exclude<Required, Output<Dependencies[number]>>
|
|
type CheckDependencies<Implementation extends Layer.Any, Dependencies extends NodeList> = [
|
|
Missing<Layer.Services<Implementation>, Dependencies>,
|
|
] extends [never]
|
|
? unknown
|
|
: { readonly "Missing dependencies": Missing<Layer.Services<Implementation>, Dependencies> }
|
|
declare const $OutputType: unique symbol
|
|
declare const $ErrorType: unique symbol
|
|
|
|
export type Tag<Name extends string = string> = Name & Brand.Brand<"LayerNode.Tag">
|
|
|
|
const makeTag = Brand.nominal<Tag>()
|
|
|
|
export interface Node<A, E = never, T extends Tag | undefined = undefined> {
|
|
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, K extends PropertyKey> = A extends unknown ? Omit<A, K> : never
|
|
|
|
type MakeInput<
|
|
Implementation extends Layer.Any,
|
|
Items extends NodeList,
|
|
T extends Tag | undefined = undefined,
|
|
> = NodeIdentity & {
|
|
readonly layer: Implementation
|
|
readonly deps: Items & CheckDependencies<Implementation, NoInfer<Items>>
|
|
readonly tag?: T
|
|
}
|
|
|
|
export function make<
|
|
const Implementation extends Layer.Any,
|
|
const Items extends NodeList,
|
|
const T extends Tag | undefined = undefined,
|
|
>(
|
|
input: MakeInput<Implementation, Items, T>,
|
|
): Node<
|
|
Layer.Success<Implementation>,
|
|
Layer.Error<Implementation> | Error<Items[number]>,
|
|
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<R, Shape, const T extends Tag>(
|
|
service: Context.Key<R, Shape>,
|
|
tag: T,
|
|
): Node<R, never, T> {
|
|
return {
|
|
kind: "unbound",
|
|
name: service.key,
|
|
service,
|
|
dependencies: [],
|
|
tag,
|
|
}
|
|
}
|
|
|
|
export function group<const Items extends readonly AnyNode[]>(
|
|
dependencies: Items,
|
|
): Node<Output<Items[number]>, Error<Items[number]>, NodeTag<Items[number]>> {
|
|
return { kind: "group", name: "group", dependencies }
|
|
}
|
|
|
|
export type TagConfig = Readonly<Record<string, readonly string[]>>
|
|
type TagNames<Config extends TagConfig> = keyof Config & string
|
|
type NodeInTags<Names extends string> = Node<unknown, unknown, Tag<Names> | undefined>
|
|
type CheckTags<Items extends NodeList, Names extends string> = [Exclude<Items[number], NodeInTags<Names>>] extends [
|
|
never,
|
|
]
|
|
? unknown
|
|
: { readonly "Invalid tag dependencies": Exclude<Items[number], NodeInTags<Names>> }
|
|
|
|
export interface Tags<Config extends TagConfig> {
|
|
readonly values: { readonly [Name in TagNames<Config>]: Tag<Name> }
|
|
readonly make: <Name extends TagNames<Config>>(
|
|
name: Name,
|
|
) => <const Implementation extends Layer.Any, const Items extends NodeList>(
|
|
input: DistributiveOmit<MakeInput<Implementation, Items, Tag<Name>>, "tag"> &
|
|
CheckTags<Items, Name | Extract<Config[Name][number], string>>,
|
|
) => Node<
|
|
Layer.Success<Implementation>,
|
|
Layer.Error<Implementation> | Error<Items[number]>,
|
|
Tag<Name>
|
|
>
|
|
}
|
|
|
|
export function tags<
|
|
const Config extends { readonly [Name in keyof Config]: readonly (keyof Config & string)[] },
|
|
>(config: Config): Tags<Config> {
|
|
const names = Object.keys(config) as TagNames<Config>[]
|
|
const values = Object.fromEntries(names.map((name) => [name, makeTag(name)])) as Tags<Config>["values"]
|
|
return {
|
|
values,
|
|
make: ((name: TagNames<Config>) => (input: DistributiveOmit<MakeInput<Layer.Any, NodeList, Tag>, "tag">) =>
|
|
make({ ...input, tag: values[name] })) as Tags<Config>["make"],
|
|
}
|
|
}
|
|
|
|
export type Replacement = {
|
|
readonly source: Layer.Any
|
|
readonly replacement: Layer.Any
|
|
}
|
|
|
|
type CheckReplacementErrors<SourceError, ReplacementError> = [Exclude<ReplacementError, SourceError>] extends [never]
|
|
? unknown
|
|
: { readonly "New replacement errors": Exclude<ReplacementError, SourceError> }
|
|
|
|
export function replace<A, E, R, E2>(
|
|
source: Layer.Layer<A, E, R>,
|
|
replacement: Layer.Layer<NoInfer<A>, E2, never> & CheckReplacementErrors<E, NoInfer<E2>>,
|
|
): Replacement {
|
|
return { source, replacement }
|
|
}
|
|
|
|
export * as LayerNode from "./layer-node"
|