import type { Types } from "effect"; import { pipe } from "effect/Function"; import * as Option from "effect/Option"; import * as Record from "effect/Record"; import type * as FunctionSpec from "./FunctionSpec"; import type * as GroupSpec from "./GroupSpec"; import * as Ref from "./Ref"; import type * as Spec from "./Spec"; export type Refs< Spec_ extends Spec.AnyWithProps, Predicate extends Ref.Any = Ref.Any, > = Types.Simplify, Predicate>>>; export interface FromSpec { public: Refs; internal: Refs; } type GroupRefs< Group extends GroupSpec.AnyWithProps, Predicate extends Ref.Any, > = Types.Simplify< OmitEmpty, Predicate>> & FilteredFunctions, Predicate> >; type OmitEmpty = { [K in keyof T as keyof T[K] extends never ? never : K]: T[K]; }; type FunctionSpecMatchesPredicate< FunctionSpec_ extends FunctionSpec.AnyWithProps, Predicate extends Ref.Any, > = Ref.Ref< FunctionSpec.GetRuntimeAndFunctionType, FunctionSpec.GetFunctionVisibility, any, any > extends Predicate ? true : false; type FilteredFunctions< FunctionSpecs extends FunctionSpec.AnyWithProps, Predicate extends Ref.Any, > = { [Name in FunctionSpec.Name as FunctionSpec.WithName< FunctionSpecs, Name > extends infer FunctionSpec_ extends FunctionSpec.AnyWithProps ? FunctionSpecMatchesPredicate extends true ? Name : never : never]: FunctionSpec.WithName extends infer F extends FunctionSpec.AnyWithProps ? Ref.FromFunctionSpec : never; }; type Helper< Groups extends GroupSpec.AnyWithProps, Predicate extends Ref.Any, > = { [GroupName in GroupSpec.Name]: GroupSpec.WithName< Groups, GroupName > extends infer Group extends GroupSpec.AnyWithProps ? GroupRefs : never; }; type Any = | { readonly [key: string]: Any; } | Ref.Any; export const make = ( spec: Spec_, ): FromSpec => { const refs = makeHelper(spec.groups); return { public: refs as Refs, internal: refs as Refs, }; }; const makeHelper = ( groups: Record.ReadonlyRecord, functionNamespace: Option.Option = Option.none(), ): Any => pipe( groups as Record.ReadonlyRecord, Record.map((group, name) => { const currentFunctionNamespace = Option.match(functionNamespace, { onNone: () => name, onSome: (parentNamespace) => `${parentNamespace}/${name}`, }); return Record.union( makeHelper(group.groups, Option.some(currentFunctionNamespace)), Record.map(group.functions, (function_) => Ref.make(currentFunctionNamespace, function_), ), (_subGroup, _function) => { throw new Error( `Group and function at same level have same name ('${Ref.getConvexFunctionName(_function)}')`, ); }, ); }), );