{"_id":"@codemix/graph","_rev":"4-b83fe70e518e0e064e1b245e0f081578","name":"@codemix/graph","dist-tags":{"latest":"0.3.0"},"versions":{"0.0.2":{"name":"@codemix/graph","version":"0.0.2","license":"MIT","_id":"@codemix/graph@0.0.2","maintainers":[{"name":"charlespick","email":"charles@codemix.com"}],"dist":{"shasum":"86d5faf48f653a523397e82c85f02c646148f5c0","tarball":"https://registry.npmjs.org/@codemix/graph/-/graph-0.0.2.tgz","fileCount":101,"integrity":"sha512-DN7O1J+eDlUAqvFr8gUq4MqY/Ps9k0LsdEI+MmujopBKbnPRTZ8AVOgIBHMAPdjSZ/cvm0zc9zuYwxGoN2QvfQ==","signatures":[{"sig":"MEYCIQD03aY0gzCeQ1yKJc845Jl8T62xXrV/A+lOG64SahI+iwIhAKUdBWer8YtsjqYLMPL2E2g8G7bZXyjyuD88TZDNx7UT","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2097864},"type":"module","_from":"file:codemix-graph-0.0.2.tgz","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"}}},"scripts":{"test":"vitest","build":"pnpm run build:grammar && tsc","typecheck":"tsc --noEmit","build:grammar":"peggy --format es src/grammar.peggy -o src/grammar.js --dts --return-types '{\"MultiStatement\": \"import('\\''./AST.js'\\'').Query | import('\\''./AST.js'\\'').UnionQuery | import('\\''./AST.js'\\'').MultiStatement\", \"CypherStatement\": \"import('\\''./AST.js'\\'').Query | import('\\''./AST.js'\\'').UnionQuery\"}' && mkdir -p dist && cp src/grammar.* dist","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"charlespick","email":"charles@codemix.com"},"_resolved":"/private/var/folders/r0/55br5jrd4cg5b76v64v3j7c80000gn/T/7684fcd8b818b06d649b52235a240d81/codemix-graph-0.0.2.tgz","_integrity":"sha512-DN7O1J+eDlUAqvFr8gUq4MqY/Ps9k0LsdEI+MmujopBKbnPRTZ8AVOgIBHMAPdjSZ/cvm0zc9zuYwxGoN2QvfQ==","_npmVersion":"11.6.2","description":"The codemix graph database.","directories":{},"_nodeVersion":"25.1.0","dependencies":{"@codemix/text-search":"0.0.2","@standard-schema/spec":"^1.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"peggy":"^5.1.0","vitest":"^4.1.3","typescript":"^6.0.2","@vitest/coverage-istanbul":"^4.1.3"},"_npmOperationalInternal":{"tmp":"tmp/graph_0.0.2_1775677058695_0.46903175260513863","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@codemix/graph","version":"0.1.0","license":"MIT","_id":"@codemix/graph@0.1.0","maintainers":[{"name":"charlespick","email":"charles@codemix.com"}],"dist":{"shasum":"e93a8ad083db5437afdee295c5ad4179ffa21d12","tarball":"https://registry.npmjs.org/@codemix/graph/-/graph-0.1.0.tgz","fileCount":101,"integrity":"sha512-JQx30dn/1w5iiQ0wQz6Xk13YJmmRGY2lsBEfC6fM2Y7tRgauc5y7/BwFb8bXyQFniWPMQib9HEaf/w/vfUze7w==","signatures":[{"sig":"MEUCIQDRW8sj/d1QkAeoJldCxwraHW8msqP5pJVh9DWu4ClUfQIgPECLRRL+Q+JagRPuY1dTcEX4IhcOB6J3Ld3I7HpD74s=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2105015},"type":"module","_from":"file:codemix-graph-0.1.0.tgz","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"}}},"scripts":{"test":"vitest","build":"pnpm run build:grammar && tsc","typecheck":"tsc --noEmit","build:grammar":"peggy --format es src/grammar.peggy -o src/grammar.js --dts --return-types '{\"MultiStatement\": \"import('\\''./AST.js'\\'').Query | import('\\''./AST.js'\\'').UnionQuery | import('\\''./AST.js'\\'').MultiStatement\", \"CypherStatement\": \"import('\\''./AST.js'\\'').Query | import('\\''./AST.js'\\'').UnionQuery\"}' && mkdir -p dist && cp src/grammar.* dist","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"charlespick","email":"charles@codemix.com"},"_resolved":"/tmp/ac34f5880d942ed5f323bf958730ab29/codemix-graph-0.1.0.tgz","_integrity":"sha512-JQx30dn/1w5iiQ0wQz6Xk13YJmmRGY2lsBEfC6fM2Y7tRgauc5y7/BwFb8bXyQFniWPMQib9HEaf/w/vfUze7w==","_npmVersion":"10.9.7","description":"The codemix graph database.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"@codemix/text-search":"0.0.2","@standard-schema/spec":"^1.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"peggy":"^5.1.0","vitest":"^4.1.3","typescript":"^6.0.2","@vitest/coverage-istanbul":"^4.1.3"},"_npmOperationalInternal":{"tmp":"tmp/graph_0.1.0_1775830459766_0.07681260090449205","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@codemix/graph","version":"0.2.0","license":"MIT","_id":"@codemix/graph@0.2.0","maintainers":[{"name":"charlespick","email":"charles@codemix.com"}],"dist":{"shasum":"135f03689a3dc1bc0dde2e6c5bd583ad4157197a","tarball":"https://registry.npmjs.org/@codemix/graph/-/graph-0.2.0.tgz","fileCount":101,"integrity":"sha512-psZXtNo7+3lM3KziKFMELWnTrq3PHekMi2Y9hmYV3kQQvvHFKR+CmHrZtXHkGgeqxmcUdw45UcO8/THnkOmSuw==","signatures":[{"sig":"MEQCIAEoVbOzEnv/m6EwM1s9VKl7STyNP3GE7va2I5GRD5KfAiAQDHnjS4jitZphGGBXF4yHlOfGXkSVJiq0idK4sgsmaA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2120858},"type":"module","_from":"file:codemix-graph-0.2.0.tgz","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"}}},"scripts":{"test":"vitest","build":"pnpm run build:grammar && tsc","typecheck":"tsc --noEmit","build:grammar":"peggy --format es src/grammar.peggy -o src/grammar.js --dts --return-types '{\"MultiStatement\": \"import('\\''./AST.js'\\'').Query | import('\\''./AST.js'\\'').UnionQuery | import('\\''./AST.js'\\'').MultiStatement\", \"CypherStatement\": \"import('\\''./AST.js'\\'').Query | import('\\''./AST.js'\\'').UnionQuery\"}' && mkdir -p dist && cp src/grammar.* dist","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"charlespick","email":"charles@codemix.com"},"_resolved":"/tmp/a5d1d772c612af538389af29531fe789/codemix-graph-0.2.0.tgz","_integrity":"sha512-psZXtNo7+3lM3KziKFMELWnTrq3PHekMi2Y9hmYV3kQQvvHFKR+CmHrZtXHkGgeqxmcUdw45UcO8/THnkOmSuw==","_npmVersion":"10.9.7","description":"The codemix graph database.","directories":{},"_nodeVersion":"22.22.2","dependencies":{"@codemix/text-search":"0.0.2","@standard-schema/spec":"^1.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"peggy":"^5.1.0","vitest":"^4.1.3","typescript":"^6.0.2","@vitest/coverage-istanbul":"^4.1.3"},"_npmOperationalInternal":{"tmp":"tmp/graph_0.2.0_1775832727013_0.7045580456374845","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@codemix/graph","version":"0.3.0","description":"The codemix graph database.","license":"MIT","type":"module","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"}}},"publishConfig":{"access":"public"},"dependencies":{"@standard-schema/spec":"^1.1.0","@codemix/text-search":"0.0.2"},"devDependencies":{"@vitest/coverage-istanbul":"^4.1.3","peggy":"^5.1.0","typescript":"^6.0.2","vitest":"^4.1.3"},"scripts":{"build":"pnpm run build:grammar && tsc","build:grammar":"peggy --format es src/grammar.peggy -o src/grammar.js --dts --return-types '{\"MultiStatement\": \"import('\\''./AST.js'\\'').Query | import('\\''./AST.js'\\'').UnionQuery | import('\\''./AST.js'\\'').MultiStatement\", \"CypherStatement\": \"import('\\''./AST.js'\\'').Query | import('\\''./AST.js'\\'').UnionQuery\"}' && mkdir -p dist && cp src/grammar.* dist","typecheck":"tsc --noEmit","test":"vitest","test:coverage":"vitest run --coverage"},"_id":"@codemix/graph@0.3.0","_integrity":"sha512-64QaMv4hXsFDxzEIrJ+MGeVpv4Y0AOKiFccIZvkWaneGZvCgvSUu3exkH7C7UUYSsVlRfJDAerFk95BBeuMDgQ==","_resolved":"/tmp/5d03a0f75ca72a7e2d4d011e64f6544b/codemix-graph-0.3.0.tgz","_from":"file:codemix-graph-0.3.0.tgz","_nodeVersion":"22.22.2","_npmVersion":"10.9.7","dist":{"integrity":"sha512-64QaMv4hXsFDxzEIrJ+MGeVpv4Y0AOKiFccIZvkWaneGZvCgvSUu3exkH7C7UUYSsVlRfJDAerFk95BBeuMDgQ==","shasum":"52f927d8cf32fe01f0bce272fd8d26461ff787e3","tarball":"https://registry.npmjs.org/@codemix/graph/-/graph-0.3.0.tgz","fileCount":101,"unpackedSize":2142735,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIApmd9h1OeVsIWxfG0bWWlxi2Doh716T4gyP5UaChI4XAiEA7F6Wye16e1ir99PBZOFCuArKUA0QStcxMXjIGZ0rfNk="}]},"_npmUser":{"name":"charlespick","email":"charles@codemix.com"},"directories":{},"maintainers":[{"name":"charlespick","email":"charles@codemix.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/graph_0.3.0_1775840033529_0.8890793064588862"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-08T19:37:38.563Z","modified":"2026-04-10T16:53:53.858Z","0.0.2":"2026-04-08T19:37:38.909Z","0.1.0":"2026-04-10T14:14:19.972Z","0.2.0":"2026-04-10T14:52:07.243Z","0.3.0":"2026-04-10T16:53:53.736Z"},"license":"MIT","description":"The codemix graph database.","maintainers":[{"name":"charlespick","email":"charles@codemix.com"}],"readme":"# @codemix/graph\n\nA fully type-safe, TypeScript-first in-memory property graph database with a (mostly) Cypher-compatible query language, a **type-safe [Apache TinkerPop](https://tinkerpop.apache.org/) / [Gremlin](https://tinkerpop.apache.org/docs/current/reference/#gremlin)-style traversal API** (`GraphTraversal`), lazy indexes, and async transport support.\n\nPart of the [codemix product intelligence platform](https://codemix.com/).\n\n## Table of Contents\n\n- [Features](#features)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Defining a Schema](#defining-a-schema)\n- [Creating a Graph](#creating-a-graph)\n- [Mutating the Graph](#mutating-the-graph)\n  - [Vertices](#vertices)\n  - [Edges](#edges)\n  - [Updating Properties](#updating-properties)\n  - [Deleting Elements](#deleting-elements)\n- [Querying with Cypher](#querying-with-cypher)\n  - [Parsing Queries](#parsing-queries)\n  - [Supported Clauses](#supported-clauses)\n  - [Supported Functions](#supported-functions)\n  - [Supported Procedures](#supported-procedures)\n- [Type-safe TinkerPop / Gremlin traversal API](#type-safe-tinkerpop--gremlin-traversal-api)\n  - [Starting a Traversal](#starting-a-traversal)\n  - [Navigation Steps](#navigation-steps)\n  - [Filtering](#filtering)\n  - [Labeling and Selection](#labeling-and-selection)\n  - [Ordering, Skipping, Limiting](#ordering-skipping-limiting)\n  - [Aggregation](#aggregation)\n  - [Repeat Traversals](#repeat-traversals)\n  - [Shortest Path](#shortest-path)\n  - [Union and Intersection](#union-and-intersection)\n- [Indexes](#indexes)\n  - [Hash Index](#hash-index)\n  - [B-Tree Index](#b-tree-index)\n  - [Full-Text Index](#full-text-index)\n- [Async Transport](#async-transport)\n- [Custom Storage](#custom-storage)\n- [Schema Guide Generation](#schema-guide-generation)\n- [Error Reference](#error-reference)\n\n---\n\n## Features\n\n- **TypeScript-first** — schema-derived types flow through the entire API; vertex/edge properties are fully typed.\n- **Cypher-compatible query language** — parse and execute `MATCH … WHERE … RETURN` queries, `UNION`, multi-statement queries, `CREATE`, `SET`, `DELETE`, `MERGE`, `UNWIND`, `CALL`, `FOREACH`, and more.\n- **Type-safe TinkerPop / Gremlin traversals** — `GraphTraversal` mirrors familiar Gremlin steps (`V`, `E`, `out` / `in` / `both`, `hasLabel`, `as` / `select`, `repeat`, …) with **schema-derived TypeScript types** on `TraversalPath` and property access, not untyped strings at every hop.\n- **Lazy indexes** — hash, B-tree, and full-text indexes are built on first use and maintained incrementally on every mutation.\n- **Unique constraints** — enforce uniqueness on any indexed property.\n- **Standard Schema validation** — property types are validated via the [Standard Schema](https://github.com/standard-schema/standard-schema) spec (compatible with Zod, Valibot, ArkType, etc.).\n- **Async transport** — serialize traversal steps to JSON and execute them on a remote graph via any async channel.\n- **In-memory storage** — built-in `InMemoryGraphStorage` with optional custom `GraphStorage` implementations.\n\n---\n\n## Installation\n\n```bash\nnpm install @codemix/graph\n# or\npnpm add @codemix/graph\n```\n\n---\n\n## Quick Start\n\n```ts\nimport { Graph, GraphSchema, InMemoryGraphStorage, GraphTraversal } from \"@codemix/graph\";\nimport * as v from \"valibot\"; // any Standard Schema library\n\nconst schema = {\n  vertices: {\n    Person: {\n      properties: {\n        name: { type: v.string() },\n        age: { type: v.number() },\n      },\n    },\n  },\n  edges: {\n    knows: { properties: {} },\n  },\n} as const satisfies GraphSchema;\n\nconst graph = new Graph({ schema, storage: new InMemoryGraphStorage() });\n\nconst alice = graph.addVertex(\"Person\", { name: \"Alice\", age: 30 });\nconst bob = graph.addVertex(\"Person\", { name: \"Bob\", age: 25 });\ngraph.addEdge(alice, \"knows\", bob, {});\n\nconst g = new GraphTraversal(graph);\nfor (const path of g.V().hasLabel(\"Person\").out(\"knows\")) {\n  console.log(path.value.get(\"name\")); // \"Bob\"\n}\n```\n\n---\n\n## Defining a Schema\n\nA schema is a plain object that satisfies the `GraphSchema` interface:\n\n```ts\nimport { GraphSchema } from \"@codemix/graph\";\nimport { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n// Minimal helper – use Zod/Valibot/ArkType in practice\nfunction t<T>(defaultValue: T): StandardSchemaV1<T> {\n  return {\n    \"~standard\": {\n      version: 1,\n      vendor: \"my-app\",\n      validate: (v) => ({ value: v as T }),\n    },\n  };\n}\n\nconst schema = {\n  vertices: {\n    Movie: {\n      properties: {\n        title: { type: t(\"\") },\n        released: { type: t(0) },\n      },\n    },\n    Person: {\n      properties: {\n        name: { type: t(\"\") },\n        born: { type: t(0) },\n      },\n    },\n  },\n  edges: {\n    ACTED_IN: {\n      properties: {\n        roles: { type: t([] as string[]) },\n      },\n    },\n    DIRECTED: { properties: {} },\n  },\n} as const satisfies GraphSchema;\n\nexport type MySchema = typeof schema;\n```\n\nProperties defined in the schema are validated on every `addVertex`, `addEdge`, and `updateProperty` call (disable with `validateProperties: false`).\n\n---\n\n## Creating a Graph\n\n```ts\nimport { Graph, InMemoryGraphStorage } from \"@codemix/graph\";\n\nconst graph = new Graph<MySchema>({\n  schema, // required\n  storage: new InMemoryGraphStorage(), // required\n  validateProperties: true, // default: true\n  generateId: () => crypto.randomUUID(), // optional custom ID generator\n});\n```\n\n---\n\n## Mutating the Graph\n\n### Vertices\n\n```ts\n// Two equivalent signatures:\nconst person = graph.addVertex(\"Person\", { name: \"Keanu Reeves\", born: 1964 });\nconst movie = graph.addVertex({\n  label: \"Movie\",\n  properties: { title: \"The Matrix\", released: 1999 },\n});\n\n// Read properties\nperson.get(\"name\"); // \"Keanu Reeves\"\nperson.label; // \"Person\"\nperson.id; // \"Person:<uuid>\"\n```\n\n### Edges\n\n```ts\n// Four-argument form:\nconst edge = graph.addEdge(person, \"ACTED_IN\", movie, { roles: [\"Neo\"] });\n\n// Object form:\ngraph.addEdge({\n  outV: person,\n  label: \"ACTED_IN\",\n  inV: movie,\n  properties: { roles: [\"Neo\"] },\n});\n\n// Access endpoints\nedge.outV; // source Vertex\nedge.inV; // target Vertex\n```\n\n### Updating Properties\n\n```ts\ngraph.updateProperty(person, \"born\", 1965);\n// or via the element itself:\nperson.set(\"born\", 1965);\n```\n\n### Deleting Elements\n\n```ts\ngraph.deleteVertex(person); // also accepts ElementId string\ngraph.deleteEdge(edge);\n```\n\n---\n\n## Querying with Cypher\n\n### Parsing Queries\n\nUse `parseQueryToSteps` to compile a Cypher string and get back executable steps plus a result mapper:\n\n```ts\nimport { Graph, InMemoryGraphStorage, parseQueryToSteps, GraphTraversal } from \"@codemix/graph\";\n\nconst { steps, postprocess } = parseQueryToSteps(\n  \"MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name = $name RETURN p.name, m.title\",\n);\n\n// Execute against a graph:\nimport { createTraverser } from \"@codemix/graph\";\nconst traverser = createTraverser(steps);\nfor (const row of traverser.traverse(graph, [{ name: \"Keanu Reeves\" }])) {\n  console.log(postprocess(row));\n  // { p: { name: \"Keanu Reeves\" }, m: { title: \"The Matrix\" } }\n}\n```\n\nEnforce read-only mode (throws `ReadonlyGraphError` on `CREATE` / `SET` / `DELETE` / etc.):\n\n```ts\nconst { steps } = parseQueryToSteps(query, { readonly: true });\n```\n\nYou can also access the lower-level parse → AST → steps pipeline:\n\n```ts\nimport { parse, astToSteps, anyAstToSteps } from \"@codemix/graph\";\n\nconst ast = parse(\"MATCH (n) RETURN n\");\nconst steps = astToSteps(ast);\n```\n\n### Supported Clauses\n\n| Clause                     | Description                                                                             |\n| -------------------------- | --------------------------------------------------------------------------------------- |\n| `MATCH`                    | Pattern matching with node/edge/path patterns                                           |\n| `OPTIONAL MATCH`           | Left-outer-join style optional pattern                                                  |\n| `WHERE`                    | Boolean conditions, `IS NULL`, `IN`, `STARTS WITH`, `ENDS WITH`, `CONTAINS`, `=~` regex |\n| `RETURN`                   | Property projection, aliases, `DISTINCT`                                                |\n| `ORDER BY … ASC/DESC`      | Multi-key ordering                                                                      |\n| `SKIP` / `LIMIT`           | Pagination                                                                              |\n| `CREATE`                   | Create nodes and edges                                                                  |\n| `MERGE`                    | Upsert node/edge patterns                                                               |\n| `SET`                      | Update properties or labels                                                             |\n| `DELETE` / `DETACH DELETE` | Remove elements                                                                         |\n| `REMOVE`                   | Remove properties or labels                                                             |\n| `UNWIND`                   | Expand a list into rows                                                                 |\n| `WITH`                     | Pipeline intermediate results                                                           |\n| `CALL … YIELD`             | Invoke registered procedures                                                            |\n| `FOREACH`                  | Iterate and apply mutations                                                             |\n| `UNION` / `UNION ALL`      | Combine result sets                                                                     |\n| Multi-statement (`;`)      | Execute multiple statements sequentially                                                |\n\nPattern quantifiers (`*`, `+`, `{n,m}`) and parenthesised path patterns are supported.\n\n### Supported Functions\n\nScalar: `abs`, `ceil`, `floor`, `round`, `sign`, `sqrt`, `exp`, `log`, `log10`, `toInteger`, `toFloat`, `toString`, `toBoolean`, `toLower`, `toUpper`, `trim`, `ltrim`, `rtrim`, `left`, `right`, `substring`, `replace`, `split`, `reverse`, `length`, `size`, `isEmpty`, `coalesce`, `nullIf`, `type`, `startNode`, `endNode`, `id`, `labels`, `keys`, `properties`, `nodes`, `relationships`, `range`, `randomUUID`\n\nList: `head`, `last`, `tail`, `reverse` (list), `sort`, `reduce`, `zip`, `unzip`\n\nAggregate: `count`, `sum`, `avg`, `min`, `max`, `collect`, `percentileCont`, `percentileDisc`, `stDev`, `stDevP`\n\nTemporal: `date`, `time`, `localTime`, `datetime`, `localdatetime`, `duration`, `date.truncate`, `datetime.truncate`, and arithmetic on temporal values.\n\nPath: `shortestPath`, `allShortestPaths`\n\nPredicate: `exists`, `any`, `all`, `none`, `single`\n\n### Supported Procedures\n\n| Procedure                        | Description                           |\n| -------------------------------- | ------------------------------------- |\n| `db.labels()`                    | Return all vertex labels in the graph |\n| `db.relationshipTypes()`         | Return all edge labels                |\n| `db.propertyKeys()`              | Return all property keys              |\n| `db.schema.nodeTypeProperties()` | Return node type/property metadata    |\n| `db.schema.relTypeProperties()`  | Return edge type/property metadata    |\n\nRegister custom procedures via `ProcedureRegistry`:\n\n```ts\nimport { procedureRegistry } from \"@codemix/graph\";\n\nprocedureRegistry.register({\n  name: \"my.procedure\",\n  description: \"Does something useful\",\n  params: [{ name: \"input\", required: true }],\n  yields: [{ name: \"result\" }],\n  invoke({ params }) {\n    yield[params[0]?.toString().toUpperCase()];\n  },\n});\n```\n\n---\n\n## Type-safe TinkerPop / Gremlin traversal API\n\n`GraphTraversal` ([`src/Traversals.ts`](./src/Traversals.ts)) is the programmatic counterpart to Cypher: a **fluent, Gremlin-style** API in the spirit of [Apache TinkerPop](https://tinkerpop.apache.org/) — same mental model as `g.V().out('knows')` in Gremlin — but **fully typed** against your `GraphSchema` so labels, edge directions, and property keys are checked by TypeScript.\n\nIf you already know Gremlin, the step names and composition will feel familiar; the main difference is that paths carry typed vertices/edges from your schema instead of generic maps.\n\n### Starting a Traversal\n\n```ts\nimport { GraphTraversal } from \"@codemix/graph\";\n\nconst g = new GraphTraversal(graph);\n\n// All vertices (optionally filtered by id)\ng.V();\ng.V(\"Person:abc-123\");\n\n// All edges (optionally filtered by id)\ng.E();\ng.E(\"ACTED_IN:xyz-456\");\n```\n\n### Navigation Steps\n\n```ts\ng.V()\n  .out(\"ACTED_IN\") // outgoing edges of type ACTED_IN → arrive at movies\n  .in(\"DIRECTED\") // incoming edges of type DIRECTED  → arrive at directors\n  .both() // traverse any edge in either direction\n  .outE(\"ACTED_IN\") // outgoing edges (stay on Edge)\n  .inE() // incoming edges (stay on Edge)\n  .bothE(); // both directions (stay on Edge)\n```\n\n### Filtering\n\n```ts\ng.V()\n  .hasLabel(\"Person\")\n  .has(\"born\", 1964) // exact value match\n  .has(\"name\", (name) => name.startsWith(\"K\"))\n  .where((v) => v.get(\"age\") > 30);\n```\n\n### Labeling and Selection\n\n```ts\ng.V().hasLabel(\"Person\").as(\"actor\").out(\"ACTED_IN\").as(\"movie\").select(\"actor\", \"movie\");\n// yields { actor: TraversalPath, movie: TraversalPath }\n```\n\n### Ordering, Skipping, Limiting\n\n```ts\ng.V().hasLabel(\"Person\").order(\"born\", \"asc\").skip(10).limit(5);\n```\n\n### Aggregation\n\n```ts\n// Count\ng.V().hasLabel(\"Person\").count();\n\n// Values\ng.V().hasLabel(\"Person\").values(\"name\"); // yields strings\n\n// Dedup\ng.V().hasLabel(\"Person\").values(\"name\").dedup();\n```\n\n### Repeat Traversals\n\n```ts\n// Walk up to 3 hops outward via \"knows\"\ng.V()\n  .hasLabel(\"Person\")\n  .repeat((t) => t.out(\"knows\"))\n  .times(3)\n  .emit();\n```\n\n### Shortest Path\n\n```ts\ng.V(alice.id).shortestPath().to(george.id).through(\"knows\").direction(\"out\");\n```\n\n### Union and Intersection\n\n```ts\n// Union two traversals\ng.union(g.V().hasLabel(\"Person\"), g.V().hasLabel(\"Organisation\"));\n\n// Intersection\ng.intersect(g.V().hasLabel(\"Person\"), g.V().has(\"name\", \"Alice\"));\n```\n\n---\n\n## Indexes\n\nIndexes are declared in the schema and built lazily on first use, then maintained incrementally on every mutation.\n\n### Hash Index\n\nO(1) equality lookups. Supports optional unique constraint.\n\n```ts\nconst schema = {\n  vertices: {\n    User: {\n      properties: {\n        email: {\n          type: t(\"\"),\n          index: { type: \"hash\", unique: true }, // enforce uniqueness\n        },\n        role: {\n          type: t(\"\"),\n          index: { type: \"hash\" },\n        },\n      },\n    },\n  },\n  edges: {},\n} as const satisfies GraphSchema;\n```\n\n### B-Tree Index\n\nO(log n) range queries (less-than, greater-than, between). Also supports unique constraint.\n\n```ts\nage: {\n  type: t(0),\n  index: { type: \"btree\" },\n}\n```\n\n### Full-Text Index\n\nBM25-ranked text search via `@codemix/text-search`.\n\n```ts\nbio: {\n  type: t(\"\"),\n  index: {\n    type: \"fulltext\",\n    options: { stemming: true }, // MatcherOptions from @codemix/text-search\n  },\n}\n```\n\nQuery with the `CALL db.index.fulltext.queryNodes` procedure or use the `IndexManager` directly:\n\n```ts\nconst results = graph.indexManager.query(\"User\", \"bio\", \"machine learning\");\n```\n\nDuplicate inserts into a unique-indexed property throw `UniqueConstraintViolationError`.\n\n---\n\n## Async Transport\n\n`AsyncGraph` decouples query compilation from execution. The client serialises steps to JSON; the server executes them against a real `Graph` and streams results back.\n\n**Server side:**\n\n```ts\nimport { Graph, InMemoryGraphStorage, handleAsyncCommand } from \"@codemix/graph\";\n\nconst graph = new Graph({ schema, storage: new InMemoryGraphStorage() });\n\n// In your WebSocket / worker message handler:\nasync function* onMessage(command) {\n  yield* handleAsyncCommand(graph, command);\n}\n```\n\n**Client side:**\n\n```ts\nimport { AsyncGraph, GraphTraversal } from \"@codemix/graph\";\n\nconst remote = new AsyncGraph({\n  schema,\n  transport: async function* (command) {\n    // Send command to the server and stream back results\n    const ws = getWebSocket();\n    ws.send(JSON.stringify(command));\n    for await (const message of ws) {\n      yield JSON.parse(message);\n    }\n  },\n});\n\nfor await (const path of remote.query((g) => g.V().hasLabel(\"Person\"))) {\n  console.log(path.value.get(\"name\"));\n}\n```\n\n---\n\n## Custom Storage\n\nImplement the `GraphStorage` interface to plug in any backend:\n\n```ts\nimport { GraphStorage, StoredVertex, StoredEdge, ElementId } from \"@codemix/graph\";\n\nclass MyStorage implements GraphStorage {\n  getVertexById(id: ElementId): StoredVertex | undefined {\n    /* … */\n  }\n  getVertices(labels: string[]): Iterable<StoredVertex> {\n    /* … */\n  }\n  getVerticesByIds(ids: Iterable<ElementId>): Iterable<StoredVertex> {\n    /* … */\n  }\n  getEdgeById(id: ElementId): StoredEdge | undefined {\n    /* … */\n  }\n  getEdges(labels: string[]): Iterable<StoredEdge> {\n    /* … */\n  }\n  getEdgesByIds(ids: Iterable<ElementId>): Iterable<StoredEdge> {\n    /* … */\n  }\n  getIncomingEdges(vertexId: ElementId): Iterable<StoredEdge> {\n    /* … */\n  }\n  getOutgoingEdges(vertexId: ElementId): Iterable<StoredEdge> {\n    /* … */\n  }\n  addVertex(vertex: StoredVertex): void {\n    /* … */\n  }\n  addEdge(edge: StoredEdge): void {\n    /* … */\n  }\n  deleteVertex(id: ElementId): void {\n    /* … */\n  }\n  deleteEdge(id: ElementId): void {\n    /* … */\n  }\n  updateProperty(id: ElementId, key: string, value: unknown): void {\n    /* … */\n  }\n}\n\nconst graph = new Graph({ schema, storage: new MyStorage() });\n```\n\n---\n\n## Schema Guide Generation\n\nGenerate a human (or LLM) readable description of the query language and your schema for use in prompts or documentation:\n\n```ts\nimport { generateGrammarDescription, generateSchemaGuide } from \"@codemix/graph\";\n\n// Language grammar description (schema-agnostic)\nconst grammar = generateGrammarDescription();\n\n// Schema-specific guide (lists vertex/edge labels, their properties, and indexes)\nconst guide = generateSchemaGuide(schema);\n\nconsole.log(grammar);\nconsole.log(guide);\n```\n\n---\n\n## Error Reference\n\nAll errors extend `GraphError`.\n\n| Error class                      | Thrown when                                                 |\n| -------------------------------- | ----------------------------------------------------------- |\n| `VertexNotFoundError`            | `getVertexById` with `throwIfNotFound: true` and no match   |\n| `EdgeNotFoundError`              | `getEdgeById` with `throwIfNotFound: true` and no match     |\n| `ElementNotFoundError`           | Generic element lookup failure                              |\n| `LabelNotFoundError`             | `getElementById` called with an unknown label               |\n| `GraphConsistencyError`          | An edge references a vertex that no longer exists           |\n| `PropertyValidationError`        | A property key is not defined in the schema (strict mode)   |\n| `PropertyTypeError`              | A property value fails Standard Schema validation           |\n| `AsyncValidationError`           | A property schema returns a `Promise` (only sync supported) |\n| `UniqueConstraintViolationError` | An insert/update would violate a unique index               |\n| `ReadonlyGraphError`             | A mutation step is found when `readonly: true` is set       |\n| `MaxIterationsExceededError`     | A traversal step hits the configured iteration limit        |\n| `MemoryLimitExceededError`       | A collection operation exceeds the configured size limit    |\n| `InvalidComparisonError`         | Comparing values of incompatible types                      |\n\n```ts\nimport { UniqueConstraintViolationError } from \"@codemix/graph\";\n\ntry {\n  graph.addVertex(\"User\", { email: \"alice@example.com\" });\n  graph.addVertex(\"User\", { email: \"alice@example.com\" }); // duplicate\n} catch (err) {\n  if (err instanceof UniqueConstraintViolationError) {\n    console.error(err.property, err.value, err.existingElementId);\n  }\n}\n```\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md"}