Add reference graph type Modules (CallGraph, SecretGraph)
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
export {
|
||||
CallGraph,
|
||||
} from "./modules/call-graph.ts";
|
||||
export {
|
||||
BaseEdgeAttributes,
|
||||
BaseNodeAttributes,
|
||||
@@ -6,6 +9,9 @@ export {
|
||||
GraphStatus,
|
||||
Metagraph,
|
||||
} from "./modules/metagraph.ts";
|
||||
export {
|
||||
SecretGraph,
|
||||
} from "./modules/secret-graph.ts";
|
||||
export {
|
||||
decrypt,
|
||||
encrypt,
|
||||
|
||||
75
src/graphs/modules/call-graph.ts
Normal file
75
src/graphs/modules/call-graph.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Type } from "@alkdev/typebox";
|
||||
import { Metagraph } from "./metagraph.ts";
|
||||
|
||||
export const CallGraph = Type.Module({
|
||||
Config: Type.Object({
|
||||
type: Type.Literal("directed"),
|
||||
multi: Type.Literal(false),
|
||||
allowSelfLoops: Type.Literal(false),
|
||||
}),
|
||||
|
||||
CallNode: Type.Composite([
|
||||
Metagraph.Import("BaseNode"),
|
||||
Type.Object({
|
||||
requestId: Type.String(),
|
||||
operationId: Type.String(),
|
||||
status: Type.Ref("CallStatus"),
|
||||
parentRequestId: Type.Optional(Type.String()),
|
||||
input: Type.Unknown(),
|
||||
output: Type.Optional(Type.Unknown()),
|
||||
identity: Type.Optional(Type.Ref("Identity")),
|
||||
startedAt: Type.Optional(Type.String({ format: "date-time" })),
|
||||
completedAt: Type.Optional(Type.String({ format: "date-time" })),
|
||||
}),
|
||||
]),
|
||||
|
||||
SubcallNode: Type.Composite([
|
||||
Metagraph.Import("BaseNode"),
|
||||
Type.Object({
|
||||
requestId: Type.String(),
|
||||
parentRequestId: Type.String(),
|
||||
operationId: Type.String(),
|
||||
status: Type.Ref("CallStatus"),
|
||||
}),
|
||||
]),
|
||||
|
||||
TriggeredEdge: Type.Composite([
|
||||
Metagraph.Import("BaseEdge"),
|
||||
Type.Object({
|
||||
type: Type.Literal("triggered"),
|
||||
}),
|
||||
]),
|
||||
|
||||
DependsOnEdge: Type.Composite([
|
||||
Metagraph.Import("BaseEdge"),
|
||||
Type.Object({
|
||||
type: Type.Literal("depends_on"),
|
||||
}),
|
||||
]),
|
||||
|
||||
TriggeredEdgeConstraints: Type.Object({
|
||||
edgeType: Type.Literal("triggered"),
|
||||
allowedSourceTypes: Type.Array(Type.String(), { default: ["Call"] }),
|
||||
allowedTargetTypes: Type.Array(Type.String(), { default: ["Call", "Subcall"] }),
|
||||
}),
|
||||
|
||||
DependsOnEdgeConstraints: Type.Object({
|
||||
edgeType: Type.Literal("depends_on"),
|
||||
allowedSourceTypes: Type.Array(Type.String(), { default: ["Call", "Subcall"] }),
|
||||
allowedTargetTypes: Type.Array(Type.String(), { default: ["Call", "Subcall"] }),
|
||||
}),
|
||||
|
||||
CallStatus: Type.Union([
|
||||
Type.Literal("pending"),
|
||||
Type.Literal("running"),
|
||||
Type.Literal("completed"),
|
||||
Type.Literal("failed"),
|
||||
Type.Literal("aborted"),
|
||||
]),
|
||||
|
||||
Identity: Type.Object({
|
||||
id: Type.String(),
|
||||
scopes: Type.Array(Type.String()),
|
||||
resources: Type.Optional(Type.Record(Type.String(), Type.Array(Type.String()))),
|
||||
}),
|
||||
});
|
||||
3
src/graphs/modules/index.ts
Normal file
3
src/graphs/modules/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { CallGraph } from "./call-graph.ts";
|
||||
export { Metagraph } from "./metagraph.ts";
|
||||
export { SecretGraph } from "./secret-graph.ts";
|
||||
44
src/graphs/modules/secret-graph.ts
Normal file
44
src/graphs/modules/secret-graph.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Type } from "@alkdev/typebox";
|
||||
import { EncryptedDataSchema } from "../crypto.ts";
|
||||
import { Metagraph } from "./metagraph.ts";
|
||||
|
||||
export const SecretGraph = Type.Module({
|
||||
Config: Type.Object({
|
||||
type: Type.Literal("undirected"),
|
||||
multi: Type.Literal(false),
|
||||
allowSelfLoops: Type.Literal(false),
|
||||
}),
|
||||
|
||||
SecretNode: Type.Composite([
|
||||
Metagraph.Import("BaseNode"),
|
||||
Type.Object({
|
||||
key: Type.String({ minLength: 1, maxLength: 255 }),
|
||||
encryptedData: EncryptedDataSchema,
|
||||
expiresAt: Type.Optional(Type.String({ format: "date-time" })),
|
||||
}),
|
||||
]),
|
||||
|
||||
ClientNode: Type.Composite([
|
||||
Metagraph.Import("BaseNode"),
|
||||
Type.Object({
|
||||
name: Type.String(),
|
||||
type: Type.String(),
|
||||
config: Type.Record(Type.String(), Type.Unknown()),
|
||||
enabled: Type.Boolean({ default: true }),
|
||||
}),
|
||||
]),
|
||||
|
||||
HasSecretEdge: Type.Composite([
|
||||
Metagraph.Import("BaseEdge"),
|
||||
Type.Object({
|
||||
type: Type.Literal("has_secret"),
|
||||
secretKey: Type.String(),
|
||||
}),
|
||||
]),
|
||||
|
||||
HasSecretEdgeConstraints: Type.Object({
|
||||
edgeType: Type.Literal("has_secret"),
|
||||
allowedSourceTypes: Type.Array(Type.String(), { default: ["Client"] }),
|
||||
allowedTargetTypes: Type.Array(Type.String(), { default: ["Secret"] }),
|
||||
}),
|
||||
});
|
||||
173
test/reference-modules.test.ts
Normal file
173
test/reference-modules.test.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { assertEquals } from "@std/assert";
|
||||
import {
|
||||
CallGraph,
|
||||
moduleToDbSchema,
|
||||
SecretGraph,
|
||||
validateEdge,
|
||||
validateNode,
|
||||
} from "../mod.ts";
|
||||
import type { TModule, TProperties } from "@alkdev/typebox";
|
||||
|
||||
Deno.test("CallGraph has required entries", () => {
|
||||
const entries = Object.keys(
|
||||
(CallGraph.Import("Config" as never).$defs as Record<string, unknown>),
|
||||
);
|
||||
const required = [
|
||||
"Config",
|
||||
"CallNode",
|
||||
"SubcallNode",
|
||||
"TriggeredEdge",
|
||||
"DependsOnEdge",
|
||||
"TriggeredEdgeConstraints",
|
||||
"DependsOnEdgeConstraints",
|
||||
"CallStatus",
|
||||
"Identity",
|
||||
];
|
||||
for (const name of required) {
|
||||
assertEquals(entries.includes(name), true, `Missing entry: ${name}`);
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("CallGraph.Config uses literal values", () => {
|
||||
const configImport = CallGraph.Import("Config" as never);
|
||||
const configSchema = configImport.$defs.Config as Record<string, unknown>;
|
||||
const properties = configSchema.properties as Record<string, unknown>;
|
||||
|
||||
const typeProp = properties.type as Record<string, unknown>;
|
||||
assertEquals(typeProp.const, "directed");
|
||||
|
||||
const multiProp = properties.multi as Record<string, unknown>;
|
||||
assertEquals(multiProp.const, false);
|
||||
|
||||
const allowSelfLoopsProp = properties.allowSelfLoops as Record<string, unknown>;
|
||||
assertEquals(allowSelfLoopsProp.const, false);
|
||||
});
|
||||
|
||||
Deno.test("validateNode with CallGraph CallNode returns true for valid data", () => {
|
||||
const result = validateNode(
|
||||
CallGraph as unknown as TModule<TProperties>,
|
||||
"CallNode",
|
||||
{
|
||||
requestId: "req-001",
|
||||
operationId: "op-001",
|
||||
status: "pending",
|
||||
input: {},
|
||||
},
|
||||
);
|
||||
assertEquals(result, true);
|
||||
});
|
||||
|
||||
Deno.test("validateNode with CallGraph CallNode returns false for invalid data", () => {
|
||||
const result = validateNode(
|
||||
CallGraph as unknown as TModule<TProperties>,
|
||||
"CallNode",
|
||||
{
|
||||
requestId: "req-001",
|
||||
},
|
||||
);
|
||||
assertEquals(result, false);
|
||||
});
|
||||
|
||||
Deno.test("moduleToDbSchema with CallGraph produces valid DbSchema", () => {
|
||||
const schema = moduleToDbSchema(
|
||||
CallGraph as unknown as TModule<TProperties>,
|
||||
"call-graph",
|
||||
);
|
||||
|
||||
assertEquals(schema.graphType.name, "call-graph");
|
||||
assertEquals(schema.graphType.config.properties !== undefined, true);
|
||||
|
||||
assertEquals(schema.nodeTypes.length, 2);
|
||||
assertEquals(schema.nodeTypes[0].name, "Call");
|
||||
assertEquals(schema.nodeTypes[1].name, "Subcall");
|
||||
|
||||
assertEquals(schema.edgeTypes.length, 2);
|
||||
assertEquals(schema.edgeTypes[0].name, "Triggered");
|
||||
assertEquals(schema.edgeTypes[1].name, "DependsOn");
|
||||
});
|
||||
|
||||
Deno.test("SecretGraph has required entries", () => {
|
||||
const entries = Object.keys(
|
||||
(SecretGraph.Import("Config" as never).$defs as Record<string, unknown>),
|
||||
);
|
||||
const required = [
|
||||
"Config",
|
||||
"SecretNode",
|
||||
"ClientNode",
|
||||
"HasSecretEdge",
|
||||
"HasSecretEdgeConstraints",
|
||||
];
|
||||
for (const name of required) {
|
||||
assertEquals(entries.includes(name), true, `Missing entry: ${name}`);
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("SecretGraph.Config uses literal values", () => {
|
||||
const configImport = SecretGraph.Import("Config" as never);
|
||||
const configSchema = configImport.$defs.Config as Record<string, unknown>;
|
||||
const properties = configSchema.properties as Record<string, unknown>;
|
||||
|
||||
const typeProp = properties.type as Record<string, unknown>;
|
||||
assertEquals(typeProp.const, "undirected");
|
||||
|
||||
const multiProp = properties.multi as Record<string, unknown>;
|
||||
assertEquals(multiProp.const, false);
|
||||
|
||||
const allowSelfLoopsProp = properties.allowSelfLoops as Record<string, unknown>;
|
||||
assertEquals(allowSelfLoopsProp.const, false);
|
||||
});
|
||||
|
||||
Deno.test("moduleToDbSchema with SecretGraph produces valid DbSchema", () => {
|
||||
const schema = moduleToDbSchema(
|
||||
SecretGraph as unknown as TModule<TProperties>,
|
||||
"secret-graph",
|
||||
);
|
||||
|
||||
assertEquals(schema.graphType.name, "secret-graph");
|
||||
|
||||
assertEquals(schema.nodeTypes.length, 2);
|
||||
assertEquals(schema.nodeTypes[0].name, "Secret");
|
||||
assertEquals(schema.nodeTypes[1].name, "Client");
|
||||
|
||||
assertEquals(schema.edgeTypes.length, 1);
|
||||
assertEquals(schema.edgeTypes[0].name, "HasSecret");
|
||||
});
|
||||
|
||||
Deno.test("validateNode with SecretGraph SecretNode validates encrypted data", () => {
|
||||
const result = validateNode(
|
||||
SecretGraph as unknown as TModule<TProperties>,
|
||||
"SecretNode",
|
||||
{
|
||||
key: "api_key",
|
||||
encryptedData: {
|
||||
keyVersion: 1,
|
||||
salt: "dGVzdA==",
|
||||
iv: "dGVzdA==dGVzdA==",
|
||||
data: "dGVzdA==",
|
||||
},
|
||||
},
|
||||
);
|
||||
assertEquals(result, true);
|
||||
});
|
||||
|
||||
Deno.test("validateEdge with CallGraph TriggeredEdge returns true for valid data", () => {
|
||||
const result = validateEdge(
|
||||
CallGraph as unknown as TModule<TProperties>,
|
||||
"TriggeredEdge",
|
||||
{
|
||||
type: "triggered",
|
||||
},
|
||||
);
|
||||
assertEquals(result, true);
|
||||
});
|
||||
|
||||
Deno.test("validateEdge with CallGraph TriggeredEdge returns false for wrong type", () => {
|
||||
const result = validateEdge(
|
||||
CallGraph as unknown as TModule<TProperties>,
|
||||
"TriggeredEdge",
|
||||
{
|
||||
type: "depends_on",
|
||||
},
|
||||
);
|
||||
assertEquals(result, false);
|
||||
});
|
||||
Reference in New Issue
Block a user