@x12i/graphenix-format
v2.0.0
Published
Graphenix graph description format: JSON schema, validation, and CRUD helpers.
Maintainers
Readme
@x12i/graphenix-format
TypeScript/JavaScript utilities for working with the Graphenix Format 1.0.0:
- JSON Schema for validation (
schema/graphenix-format-1.0.0.schema.json) - Runtime validator built on
ajv - Lightweight CRUD helpers for nodes, edges, types and subgraphs
- First-class TypeScript types for graph documents
Install
npm install @x12i/graphenix-format
# or
yarn add @x12i/graphenix-formatUsage
Validate a graph document
import { validateGraph, type GraphDocument } from "@x12i/graphenix-format";
const doc: GraphDocument = {
formatVersion: "1.0.0",
id: "graph:auth/user-signup",
graph: {
nodes: [],
edges: [],
inputs: [],
outputs: []
}
};
const result = validateGraph(doc);
if (!result.valid) {
console.error(result.errors);
}Each error has:
message: human readable messagepath: JSON Pointer to the failing instancekeywordandparamsfromajvfor tooling.
Quick start: minimal Graphenix document
This is a minimal but complete Graphenix Format 1.0.0 document that you can load, validate, and then modify:
import {
validateGraph,
type GraphDocument,
addNode,
addEdge
} from "@x12i/graphenix-format";
const doc: GraphDocument = {
formatVersion: "1.0.0",
id: "graph:auth/user-signup",
name: "User Signup",
graph: {
nodes: [
{
id: "node:validate-input",
kind: "builtin:validate",
inputs: [
{
id: "in:payload",
direction: "input",
type: "builtin:object",
required: true
}
],
outputs: [
{
id: "out:validated",
direction: "output",
type: "builtin:object"
}
],
parameters: {
schemaType: "type:User"
}
}
],
edges: [],
inputs: [
{
id: "graph-input:request",
name: "Request Body",
type: "builtin:object",
target: {
nodeId: "node:validate-input",
portId: "in:payload"
}
}
],
outputs: [],
metadata: {}
},
types: [
{
id: "type:User",
kind: "object",
fields: [
{ name: "id", type: "builtin:string", required: true },
{ name: "email", type: "builtin:string", required: true }
]
}
]
};
// Validate against the Graphenix JSON Schema
const res = validateGraph(doc);
if (!res.valid) {
throw new Error(JSON.stringify(res.errors, null, 2));
}
// Programmatically add a new node + edge
const withCreateNode = addNode(doc, {
id: "node:create-user",
kind: "task:http-request",
inputs: [
{
id: "in:validated",
direction: "input",
type: "type:User",
required: true
}
],
outputs: [
{
id: "out:user",
direction: "output",
type: "type:User"
}
],
parameters: {
method: "POST",
url: "https://example.com/users"
}
});
const finalDoc = addEdge(withCreateNode, {
id: "edge:validate-to-create",
from: { nodeId: "node:validate-input", portId: "out:validated" },
to: { nodeId: "node:create-user", portId: "in:validated" }
});This matches the structure described in GRAPHENIX-FORMAT.md and can be fed directly to your executor.
CRUD helpers
CRUD helpers operate on an in-memory GraphDocument and return a new document by default (immutable), or mutate in-place when mutate: true is passed.
import {
addNode,
updateNode,
removeNode,
addEdge,
removeEdge,
addType
} from "@x12i/graphenix-format";
import type { GraphDocument } from "@x12i/graphenix-format";
let doc: GraphDocument = /* ... */;
// Add a node (immutable)
doc = addNode(doc, {
id: "node:validate-input",
kind: "builtin:validate",
inputs: [],
outputs: []
});
// Update a node
doc = updateNode(doc, "node:validate-input", {
name: "Validate Input"
});
// Remove a node (also removes attached edges and graph IO)
doc = removeNode(doc, "node:validate-input");All CRUD helpers accept an optional { mutate?: boolean }:
addNode(doc, newNode, { mutate: true }); // modifies `doc` in placeAvailable helpers:
- Nodes:
getNode,addNode,updateNode,removeNode - Edges:
getEdge,addEdge,updateEdge,removeEdge - Types:
getType,addType,updateType,removeType - Subgraphs:
getSubgraph,addSubgraph,updateSubgraph,removeSubgraph
Working with subgraphs and types
Subgraphs are just nested graph structures that can be referenced via node kind:
import {
type GraphDocument,
addSubgraph,
addNode
} from "@x12i/graphenix-format";
let doc: GraphDocument = /* existing document */;
// Add a reusable subgraph
doc = addSubgraph(doc, {
id: "graph:user-validation",
name: "User Validation",
graph: {
nodes: [],
edges: [],
inputs: [],
outputs: []
}
});
// Use it as a node in the main graph
doc = addNode(doc, {
id: "node:user-validation",
kind: "subgraph:graph:user-validation",
inputs: [],
outputs: []
});For a full description of all fields and constraints, see GRAPHENIX-FORMAT.md in this repo.
Build & test locally
# install dev dependencies
npm install
# build ESM + CJS
npm run build
# run a tiny smoke test that validates the minimal example graph
npm testThe minimal example graph lives in src/examples/minimal-graph.ts and mirrors the example from GRAPHENIX-FORMAT.md.
Design-level graph input contracts (graph.inputs[].contract)
graph.inputs describes external input ports (id, type, target). The optional contract block on each input adds the design-level semantic role of the input value and optional resolver hints — without changing execution semantics.
The contract is execution-neutral: validators accept it, but engines are not required to perform any lookup based on it.
Suggested semanticKind values:
record— a structured record fed in as raw input.query— a search/query specification.user-input— direct end-user input.content— opaque content payload (text, blob, document).metadata— descriptive metadata about another value.execution— a prior graph execution as input.
For the execution semantic, the resolver block can declare how to look up the prior execution (source graphId and an optional metadataFilter).
Example: a normal record input
{
"id": "rawRecord",
"type": "object",
"target": {
"nodeId": "q1",
"portId": "record"
},
"contract": {
"semanticKind": "record",
"required": true,
"schema": {
"type": "object"
}
}
}Example: a prior graph execution as input
{
"id": "upstreamGroupTriage",
"name": "Upstream group triage execution",
"type": "execution",
"target": {
"nodeId": "q1",
"portId": "upstreamExecution"
},
"contract": {
"semanticKind": "execution",
"required": true,
"resolver": {
"kind": "graph-execution",
"graphId": "network-vuln-group-triage.v1",
"metadataFilter": {
"status": "completed"
}
}
}
}TypeScript:
import type { GraphInput, GraphInputContract } from "@x12i/graphenix-format";
const input: GraphInput = {
id: "rawRecord",
type: "object",
target: { nodeId: "q1", portId: "record" },
contract: {
semanticKind: "record",
required: true,
schema: { type: "object" }
}
};Non-goals:
- Engines are not required to perform runtime lookup for
resolver. - Worox-specific execution behavior is not defined in Graphenix core.
- The format is not split into design and runtime sections.
metadataandextensionsare unchanged.
Worox-graph alignment (metadata.graphEntry / metadata.graphResponse)
Worox-graph attaches entry and response contracts to graph JSON; graphenix-format exposes the same shapes as optional fields on the graph document:
| Field | Type | Role |
|-------|------|------|
| metadata.graphEntry | GraphEntryContract | Entry / execution expectations |
| metadata.graphResponse | GraphResponseContract | Final output shape |
GraphEntryContract may include:
summary— short description of what the graph expects at entry.requiredExecutionPaths/notableExecutionPaths— path descriptors (engine-specific item shapes).executionSchema— JSON Schema for the merged execution object after entry (optional tooling validation only).
GraphResponseContract may include:
summary— description of the response.finalOutputSchema— JSON Schema for the final output value (optional tooling validation only).
I/O visibility layers (single narrative for authors):
- Layer 01 — Entry: what must be satisfied when the graph is invoked; described by
graphEntry(summary, paths,executionSchema). - Layer 08 — Response: what leaves the graph as the final result; described by
graphResponse(summary,finalOutputSchema).
Optional AJV checks (not required for graphenix planning):
import {
validateGraph,
validateExecutionAgainstContract,
validateFinalOutputAgainstContract,
type GraphDocument
} from "@x12i/graphenix-format";
const doc: GraphDocument = /* ... with metadata.graphEntry / metadata.graphResponse ... */;
validateGraph(doc);
validateExecutionAgainstContract(doc, mergedExecution);
validateFinalOutputAgainstContract(doc, finalOutput);For how worox-graph’s task/DAG JSON differs from port-based Graph, see docs/interop-worox-graph.md.
Planning-only catalog fields (worox-graph): optional metadata.catalogRequests on the document, and optional metadata.catalogRequest / metadata.catalogBinding on nodes, align with @woroces/worox-graph (refs.ts). JSON Schema $defs WoroxCatalogRequest, WoroxCatalogBinding, WoroxCatalogRequests describe the same keys for optional cross-validation; execution is unchanged.
Notes
- The package is execution-agnostic: it only knows about the static Graphenix format.
- Runtime concerns (scheduling, retries, parallelism, logging, etc.) are intentionally left to your executor/runtime.
