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 {
|
export {
|
||||||
Metagraph,
|
|
||||||
GraphConfig,
|
|
||||||
BaseNodeAttributes,
|
|
||||||
BaseEdgeAttributes,
|
BaseEdgeAttributes,
|
||||||
|
BaseNodeAttributes,
|
||||||
GRAPH_STATUS,
|
GRAPH_STATUS,
|
||||||
|
GraphConfig,
|
||||||
GraphStatus,
|
GraphStatus,
|
||||||
|
Metagraph,
|
||||||
} from "./modules/metagraph.ts";
|
} from "./modules/metagraph.ts";
|
||||||
export {
|
export {
|
||||||
encrypt,
|
|
||||||
decrypt,
|
decrypt,
|
||||||
generateEncryptionKey,
|
encrypt,
|
||||||
EncryptedDataSchema,
|
|
||||||
type EncryptedData,
|
type EncryptedData,
|
||||||
} from "./crypto.ts";
|
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