import * as Context from "effect/Context"; import * as S from "effect/Schema"; import type { Instance } from "..//Util/instance.ts"; import type { Pointer } from "..//Util/pointer.ts"; import { isAspect, type Aspect, type AspectLike } from "./Aspect.ts"; export class AspectGraph extends Context.Service< AspectGraph, { aspects: AspectIndex; schema: S.Schema; } >()("AspectGraphService") {} export type AspectIndex = { [type in AspectSet["type"]]: { [id in Extract, { type: type }>["id"]]: Extract< Extract, { type: type }>, { id: id } >; }; }; export type AspectCategory = { [id in keyof Aspects["id"]]: Extract; }; export type AspectSet = | A | Visit>; type Visit = Pointer.Resolve extends infer A ? A extends { type: string; id: string; references: infer References extends any[]; } ? FQN extends Seen ? never : Instance | Visit> : A extends readonly (infer I)[] ? Visit : A extends Record ? Visit : never : never; type FQN = A["id"] extends string ? `${A["type"]}:${A["id"]}` : never; const getFqn = (a: A): FQN => `${a.type}:${a.id}` as FQN; export type AspectKinds = { [type in keyof AspectIndex]: { // @ts-expect-error [id in keyof AspectIndex[type]]: InstanceType< // @ts-expect-error AspectIndex[type][id]["class"] >; // @ts-expect-error }[keyof AspectIndex[type]]; }[keyof AspectIndex]; export const deriveGraph = (agent: A): AspectIndex => { const seen = new Set(); return [agent, ...agent.references.flatMap((v) => visit(v, seen))].reduce( (acc: AspectIndex, aspect) => ({ ...acc, [aspect.type]: { ...acc[aspect.type as keyof AspectIndex], [aspect.id as keyof AspectIndex[keyof AspectIndex]]: aspect, }, }), {} as AspectIndex, ); }; const visit = (a: A, seen: Set): Aspect[] => { if (isAspect(a)) { const fqn = getFqn(a); if (!seen.has(fqn)) { seen.add(fqn); return [a, ...a.references.flatMap((v) => visit(v, seen))]; } } else if (Array.isArray(a)) { return a.flatMap((v) => visit(v, seen)); } else if (a instanceof Set) { return Array.from(a).flatMap((v) => visit(v, seen)); } else if (a instanceof Map) { return Array.from(a.values()).flatMap((v) => visit(v, seen)); } else if (typeof a === "object" && a !== null) { return Object.values(a).flatMap((v) => visit(v, seen)); } return []; };