From 14c7fa474675956e93533ba56dd032e9f7d42800 Mon Sep 17 00:00:00 2001 From: "glm-5.1" Date: Fri, 29 May 2026 12:23:46 +0000 Subject: [PATCH] Add bridge.ts with moduleToDbSchema, validateNode, validateEdge --- src/graphs/bridge.ts | 194 +++++++++++++++++++++++++++++++++++++++++++ src/graphs/mod.ts | 23 +++-- 2 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 src/graphs/bridge.ts diff --git a/src/graphs/bridge.ts b/src/graphs/bridge.ts new file mode 100644 index 0000000..6392a00 --- /dev/null +++ b/src/graphs/bridge.ts @@ -0,0 +1,194 @@ +import type { TImport, TModule, TProperties } from "@alkdev/typebox"; +import { Check, Create } from "@alkdev/typebox/value"; + +export interface DbGraphTypeRow { + name: string; + config: Record; +} + +export interface DbNodeTypeRow { + name: string; + schema: Record; +} + +export interface DbEdgeTypeRow { + name: string; + schema: Record; + allowedSourceTypes: string[]; + allowedTargetTypes: string[]; +} + +export interface DbSchema { + graphType: DbGraphTypeRow; + nodeTypes: DbNodeTypeRow[]; + edgeTypes: DbEdgeTypeRow[]; +} + +function classifyEntry( + name: string, +): "config" | "node" | "edge" | "edgeConstraints" | "shared" | "base" { + if (name === "Config") return "config"; + if (name === "BaseNode" || name === "BaseEdge") return "base"; + if (name.endsWith("EdgeConstraints")) return "edgeConstraints"; + if (name.endsWith("Node")) return "node"; + if (name.endsWith("Edge")) return "edge"; + if (name.endsWith("Enum")) return "shared"; + return "shared"; +} + +function stripSuffix(name: string, suffix: string): string { + return name.slice(0, -suffix.length); +} + +function getEntryNames(module: TModule): string[] { + const defs = (module as unknown as { $defs: Record }).$defs; + const firstKey = Object.keys(defs)[0]; + const probe = module.Import(firstKey as keyof TProperties & string); + return Object.keys((probe as TImport).$defs); +} + +function getEntrySchema( + module: TModule, + entryName: string, +): Record { + const entryImport = module.Import( + entryName as keyof TProperties & string, + ) as TImport; + const defs = entryImport.$defs as Record; + return defs[entryName] as Record; +} + +function extractEdgeConstraintValues( + constraintImport: TImport, +): { allowedSourceTypes: string[]; allowedTargetTypes: string[] } { + const instance = Create(constraintImport) as Record; + const sourceArr = Array.isArray(instance.allowedSourceTypes) + ? instance.allowedSourceTypes as string[] + : []; + const targetArr = Array.isArray(instance.allowedTargetTypes) + ? instance.allowedTargetTypes as string[] + : []; + return { allowedSourceTypes: sourceArr, allowedTargetTypes: targetArr }; +} + +export function moduleToDbSchema( + module: TModule, + name: string, +): DbSchema { + const entryNames = getEntryNames(module); + const nodeEntries: string[] = []; + const edgeEntries: string[] = []; + const constraintEntries: string[] = []; + let hasConfig = false; + + for (const entryName of entryNames) { + const role = classifyEntry(entryName); + switch (role) { + case "config": + hasConfig = true; + break; + case "node": + nodeEntries.push(entryName); + break; + case "edge": + edgeEntries.push(entryName); + break; + case "edgeConstraints": + constraintEntries.push(entryName); + break; + case "shared": + case "base": + break; + } + } + + if (!hasConfig) { + throw new Error("Module must have a Config entry."); + } + + const edgeShortNameMap = new Map(); + for (const edgeEntry of edgeEntries) { + edgeShortNameMap.set(stripSuffix(edgeEntry, "Edge"), edgeEntry); + } + const constraintsByEdgeType = new Map< + string, + { allowedSourceTypes: string[]; allowedTargetTypes: string[] } + >(); + + for (const constraintName of constraintEntries) { + const edgeShortName = stripSuffix(constraintName, "EdgeConstraints"); + const matchedEdge = edgeShortNameMap.get(edgeShortName); + if (!matchedEdge) { + throw new Error( + `EdgeConstraints entry "${constraintName}" references edge type "${edgeShortName}" not found in module.`, + ); + } + const constraintImport = module.Import( + constraintName as keyof TProperties & string, + ) as TImport; + const values = extractEdgeConstraintValues(constraintImport); + if ( + values.allowedSourceTypes.length === 0 && + values.allowedTargetTypes.length === 0 + ) { + throw new Error( + `EdgeConstraints entry "${constraintName}" has empty allowedSourceTypes and allowedTargetTypes. Omit the entry for "no restriction".`, + ); + } + constraintsByEdgeType.set(matchedEdge, values); + } + + const configSchema = getEntrySchema(module, "Config"); + + const nodeTypes: DbNodeTypeRow[] = nodeEntries.map((entryName) => { + return { + name: stripSuffix(entryName, "Node"), + schema: getEntrySchema(module, entryName), + }; + }); + + const edgeTypes: DbEdgeTypeRow[] = edgeEntries.map((entryName) => { + const shortName = stripSuffix(entryName, "Edge"); + const constraints = constraintsByEdgeType.get(entryName); + return { + name: shortName, + schema: getEntrySchema(module, entryName), + allowedSourceTypes: constraints?.allowedSourceTypes ?? [], + allowedTargetTypes: constraints?.allowedTargetTypes ?? [], + }; + }); + + return { + graphType: { name, config: configSchema }, + nodeTypes, + edgeTypes, + }; +} + +export function validateNode( + module: TModule, + entryName: string, + data: unknown, +): boolean { + if (!entryName.endsWith("Node") || entryName === "BaseNode") { + throw new Error( + `"${entryName}" is not a node type entry. Entry name must end in "Node" (excluding "BaseNode").`, + ); + } + const entryImport = module.Import(entryName as keyof TProperties & string); + return Check(entryImport, data); +} + +export function validateEdge( + module: TModule, + entryName: string, + data: unknown, +): boolean { + if (!entryName.endsWith("Edge") || entryName === "BaseEdge") { + throw new Error( + `"${entryName}" is not an edge type entry. Entry name must end in "Edge" (excluding "BaseEdge").`, + ); + } + const entryImport = module.Import(entryName as keyof TProperties & string); + return Check(entryImport, data); +} diff --git a/src/graphs/mod.ts b/src/graphs/mod.ts index 7f2d295..edb10cb 100644 --- a/src/graphs/mod.ts +++ b/src/graphs/mod.ts @@ -1,15 +1,24 @@ export { - Metagraph, - GraphConfig, - BaseNodeAttributes, BaseEdgeAttributes, + BaseNodeAttributes, GRAPH_STATUS, + GraphConfig, GraphStatus, + Metagraph, } from "./modules/metagraph.ts"; export { - encrypt, decrypt, - generateEncryptionKey, - EncryptedDataSchema, + encrypt, type EncryptedData, -} from "./crypto.ts"; \ No newline at end of file + EncryptedDataSchema, + generateEncryptionKey, +} from "./crypto.ts"; +export { + type DbEdgeTypeRow, + type DbGraphTypeRow, + type DbNodeTypeRow, + type DbSchema, + moduleToDbSchema, + validateEdge, + validateNode, +} from "./bridge.ts";