## `@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`](https://ajv.js.org)
- Lightweight **CRUD helpers** for nodes, edges, types and subgraphs
- First-class **TypeScript types** for graph documents

---

### Install

```bash
npm install @graphenix/format
# or
yarn add @graphenix/format
```

---

### Usage

#### Validate a graph document

```ts
import { validateGraph, type GraphDocument } from "@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:

```ts
import {
  validateGraph,
  type GraphDocument,
  addNode,
  addEdge
} from "@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.

```ts
import {
  addNode,
  updateNode,
  removeNode,
  addEdge,
  removeEdge,
  addType
} from "@graphenix/format";

import type { GraphDocument } from "@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 }`:

```ts
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`:

```ts
import {
  type GraphDocument,
  addSubgraph,
  addNode
} from "@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

```bash
# 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`.

---

### 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):

```ts
import {
  validateGraph,
  validateExecutionAgainstContract,
  validateFinalOutputAgainstContract,
  type GraphDocument
} from "@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`](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.

