Add bridge.ts with moduleToDbSchema, validateNode, validateEdge
This commit is contained in:
194
src/graphs/bridge.ts
Normal file
194
src/graphs/bridge.ts
Normal file
@@ -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<string, unknown>;
|
||||
}
|
||||
|
||||
export interface DbNodeTypeRow {
|
||||
name: string;
|
||||
schema: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface DbEdgeTypeRow {
|
||||
name: string;
|
||||
schema: Record<string, unknown>;
|
||||
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<TProperties>): string[] {
|
||||
const defs = (module as unknown as { $defs: Record<string, unknown> }).$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<TProperties>,
|
||||
entryName: string,
|
||||
): Record<string, unknown> {
|
||||
const entryImport = module.Import(
|
||||
entryName as keyof TProperties & string,
|
||||
) as TImport;
|
||||
const defs = entryImport.$defs as Record<string, unknown>;
|
||||
return defs[entryName] as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function extractEdgeConstraintValues(
|
||||
constraintImport: TImport,
|
||||
): { allowedSourceTypes: string[]; allowedTargetTypes: string[] } {
|
||||
const instance = Create(constraintImport) as Record<string, unknown>;
|
||||
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<TProperties>,
|
||||
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<string, string>();
|
||||
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<TProperties>,
|
||||
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<TProperties>,
|
||||
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);
|
||||
}
|
||||
@@ -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,
|
||||
EncryptedDataSchema,
|
||||
generateEncryptionKey,
|
||||
} from "./crypto.ts";
|
||||
export {
|
||||
type DbEdgeTypeRow,
|
||||
type DbGraphTypeRow,
|
||||
type DbNodeTypeRow,
|
||||
type DbSchema,
|
||||
moduleToDbSchema,
|
||||
validateEdge,
|
||||
validateNode,
|
||||
} from "./bridge.ts";
|
||||
|
||||
Reference in New Issue
Block a user