feat(graph): implement fromSpecs, addOperation, addTypedEdge, buildTypeEdges

This commit is contained in:
2026-05-21 21:28:27 +00:00
parent d63ef886d8
commit 5514c5aacc
5 changed files with 368 additions and 13 deletions

View File

@@ -4,4 +4,5 @@ export {
defaultEdgeType,
resolveDefaultNodeAttrs,
} from "./defaults.js";
export { typeCompat, type TypeCompatResult, type TypeMismatch } from "./type-compat.js";
export { typeCompat, type TypeCompatResult, type TypeMismatch } from "./type-compat.js";
export { buildTypeEdges } from "../graph/construction.js";

View File

@@ -7,6 +7,12 @@ import {
NodeNotFoundError,
CycleError,
} from "../error/index.js";
import {
OperationNodeAttrs as OperationNodeAttrsSchema,
OperationEdgeAttrs as OperationEdgeAttrsSchema,
} from "../schema/index.js";
import type { OperationNodeAttrs } from "../schema/index.js";
import { typeCompat, type TypeCompatResult } from "../analysis/type-compat.js";
export interface FlowGraphOptions {
type?: "directed";
@@ -14,6 +20,26 @@ export interface FlowGraphOptions {
allowSelfLoops?: false;
}
export interface OperationSpec {
name: string;
namespace: string;
version: string;
type: "query" | "mutation" | "subscription";
inputSchema: TSchema;
outputSchema: TSchema;
description?: string;
tags?: string[];
}
type OperationGraph = FlowGraph<typeof OperationNodeAttrsSchema, typeof OperationEdgeAttrsSchema>;
type TypedEdgeAttrs = {
edgeType: "typed";
compatible: boolean;
detail?: string;
mismatches?: TypeCompatResult["mismatches"];
};
type Attrs = Record<string, unknown>;
export class FlowGraph<
@@ -154,10 +180,47 @@ export class FlowGraph<
return this._graph.outNeighbors(nodeId) ?? [];
}
static fromSpecs(
_specs: unknown[],
): FlowGraph<TSchema, TSchema> {
throw new Error("not implemented");
addOperation(spec: OperationSpec): void {
const key = `${spec.namespace}.${spec.name}`;
this.addNode(key, {
name: spec.name,
namespace: spec.namespace,
version: spec.version,
type: spec.type,
inputSchema: spec.inputSchema,
outputSchema: spec.outputSchema,
...(spec.description !== undefined ? { description: spec.description } : {}),
...(spec.tags !== undefined ? { tags: spec.tags } : {}),
} as Static<NodeAttrs>);
}
addTypedEdge(
source: string,
target: string,
attrs: { compatible: boolean; detail?: string; mismatches?: TypeCompatResult["mismatches"] },
): void {
if (!this._graph.hasNode(source)) {
throw new NodeNotFoundError(source);
}
if (!this._graph.hasNode(target)) {
throw new NodeNotFoundError(target);
}
const edgeAttrs: TypedEdgeAttrs = {
edgeType: "typed",
compatible: attrs.compatible,
...(attrs.detail !== undefined ? { detail: attrs.detail } : {}),
...(attrs.mismatches !== undefined ? { mismatches: attrs.mismatches } : {}),
};
this.addEdge(source, target, edgeAttrs as Static<EdgeAttrs>);
}
static fromSpecs(specs: OperationSpec[]): OperationGraph {
const graph = new FlowGraph<typeof OperationNodeAttrsSchema, typeof OperationEdgeAttrsSchema>();
for (const spec of specs) {
graph.addOperation(spec);
}
buildTypeEdges(graph);
return graph;
}
static fromCallEvents(
@@ -196,4 +259,25 @@ export class FlowGraph<
}
return [];
}
}
export function buildTypeEdges(graph: OperationGraph): void {
const nodeKeys = graph.nodes();
for (const source of nodeKeys) {
for (const target of nodeKeys) {
if (source === target) continue;
const sourceAttrs = graph.getNodeAttributes(source as never) as unknown as OperationNodeAttrs;
const targetAttrs = graph.getNodeAttributes(target as never) as unknown as OperationNodeAttrs;
const result = typeCompat(sourceAttrs.outputSchema as TSchema, targetAttrs.inputSchema as TSchema);
if (result === undefined) continue;
if (graph.hasEdge(source, target)) continue;
if (willCreateCycle(graph.graph, source, target)) continue;
const detail = result.detail ?? `${sourceAttrs.namespace}.${sourceAttrs.name}.output → ${targetAttrs.namespace}.${targetAttrs.name}.input`;
graph.addTypedEdge(source, target, {
compatible: result.compatible,
detail,
...(result.mismatches !== undefined ? { mismatches: result.mismatches } : {}),
});
}
}
}

View File

@@ -1 +1 @@
export { FlowGraph, type FlowGraphOptions } from "./construction.js";
export { FlowGraph, buildTypeEdges, type FlowGraphOptions, type OperationSpec } from "./construction.js";

View File

@@ -1,3 +1,3 @@
export * from "./error/index.js";
export { FlowGraph, type FlowGraphOptions } from "./graph/index.js";
export { FlowGraph, buildTypeEdges, type FlowGraphOptions, type OperationSpec } from "./graph/index.js";