npm.io
2.0.0 • Published 4 months ago

@x12i/graphenix-format

Licence
MIT
Version
2.0.0
Deps
1
Size
83 kB
Vulns
0
Weekly
0

@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-format

Usage
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 message
  • path: JSON Pointer to the failing instance
  • keyword and params from ajv for 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 place

Available 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 test

The 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.
  • metadata and extensions are 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 01Entry: what must be satisfied when the graph is invoked; described by graphEntry (summary, paths, executionSchema).
  • Layer 08Response: 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.

Keywords