Add reference graph type Modules (CallGraph, SecretGraph)
This commit is contained in:
@@ -1,3 +1,6 @@
|
|||||||
|
export {
|
||||||
|
CallGraph,
|
||||||
|
} from "./modules/call-graph.ts";
|
||||||
export {
|
export {
|
||||||
BaseEdgeAttributes,
|
BaseEdgeAttributes,
|
||||||
BaseNodeAttributes,
|
BaseNodeAttributes,
|
||||||
@@ -6,6 +9,9 @@ export {
|
|||||||
GraphStatus,
|
GraphStatus,
|
||||||
Metagraph,
|
Metagraph,
|
||||||
} from "./modules/metagraph.ts";
|
} from "./modules/metagraph.ts";
|
||||||
|
export {
|
||||||
|
SecretGraph,
|
||||||
|
} from "./modules/secret-graph.ts";
|
||||||
export {
|
export {
|
||||||
decrypt,
|
decrypt,
|
||||||
encrypt,
|
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