{
  "kind": "documentation",
  "format": 1,
  "note": "Reference text, not instructions. One verified relate sample from https://relatedb.ai/docs/guides/integrations/vercel-ai-sdk/: the program exactly as the page shows it, the standard output a run of it recorded (npm run docs:samples compares every replay with it byte for byte), the relate client build, toolchain and platform of the last replay, and how the docs' sample runner builds and runs it. Before launch every client is a source build, not a published package; verified.client names the upstream commit it was built from, and verified.docs_engine the build the rest of the docs were recorded from.",
  "id": "graph-tool",
  "url": "https://relatedb.ai/docs/guides/integrations/vercel-ai-sdk/samples/graph-tool.json",
  "page": {
    "id": "guides/integrations/vercel-ai-sdk",
    "title": "Use relate with the Vercel AI SDK",
    "url": "https://relatedb.ai/docs/guides/integrations/vercel-ai-sdk/",
    "md_url": "https://relatedb.ai/docs/guides/integrations/vercel-ai-sdk.md"
  },
  "section": {
    "anchor": "write-the-tool",
    "heading": "Write the tool",
    "url": "https://relatedb.ai/docs/guides/integrations/vercel-ai-sdk/#write-the-tool"
  },
  "language": "js",
  "client": "Node.js",
  "file": "graph-tool.mjs",
  "env": null,
  "code": "import { Database, RelateError } from \"@relate/relate\";\nimport { tool } from \"ai\";\nimport { z } from \"zod\";\n\n// Setup: the app that owns ./team writes the team graph once, on a writable handle.\nconst owner = Database.open(\"./team\").connect();\nif (owner.query(\"MATCH (p:Person) RETURN count(p) AS n\").rows[0].n === 0) {\n  owner.query(`\n    CREATE (ada:Person {name: 'Ada', role: 'engineer'}),\n           (grace:Person {name: 'Grace', role: 'engineer'}),\n           (noor:Person {name: 'Noor', role: 'maintainer'}),\n           (relate:Project {name: 'relate', language: 'Rust'}),\n           (tck:Project {name: 'opencypher-tck', language: 'Gherkin'}),\n           (ada)-[:WORKS_ON {since: 2024}]->(relate),\n           (grace)-[:WORKS_ON {since: 2023}]->(relate),\n           (grace)-[:WORKS_ON {since: 2022}]->(tck),\n           (noor)-[:MAINTAINS]->(relate),\n           (ada)-[:KNOWS]->(grace)`);\n}\n\n// The tool's own handle: read-only, so the engine refuses every write on it.\nconst graph = Database.open(\"./team\", { readOnly: true }).connect();\nconst MAX_ROWS = 50;\n\nexport const readGraph = tool({\n  description:\n    \"Run one read-only Cypher statement against the team graph (Person and Project nodes; WORKS_ON, MAINTAINS and KNOWS relationships) and return its rows.\",\n  inputSchema: z.object({\n    statement: z.string().describe(\"One Cypher statement that only reads\"),\n    params: z.record(z.string(), z.unknown()).default({}).describe(\"Values for the statement's $parameters\"),\n  }),\n  execute: async ({ statement, params }) => {\n    try {\n      // EXPLAIN plans the statement without running it; query type \"r\" is a pure read.\n      const plan = graph.query(`EXPLAIN ${statement}`, params);\n      if (plan.queryType !== \"r\") {\n        return { error: `refused: the statement's query type is \"${plan.queryType}\", and this tool runs only reads (\"r\")` };\n      }\n      const result = graph.query(statement, params);\n      // Cypher integers outside ±(2^53 − 1) arrive as bigint, which JSON cannot carry.\n      const rows = JSON.parse(JSON.stringify(result.rows.slice(0, MAX_ROWS), (_, v) => (typeof v === \"bigint\" ? v.toString() : v)));\n      return { columns: result.columns, rows, truncated: result.rows.length > MAX_ROWS };\n    } catch (e) {\n      if (e instanceof RelateError) return { error: e.message, code: e.code };\n      throw e;\n    }\n  },\n});\n\n// Check: call the tool as the AI SDK does, without a model.\nconst call = { toolCallId: \"check\", messages: [] };\nconsole.log(JSON.stringify(await readGraph.execute({\n  statement: \"MATCH (p:Person)-[w:WORKS_ON]->(:Project {name: $project}) RETURN p.name AS name, w.since AS since ORDER BY since\",\n  params: { project: \"relate\" },\n}, call)));\nconsole.log(JSON.stringify(await readGraph.execute({\n  statement: \"MATCH (p:Person {name: $name}) DETACH DELETE p\",\n  params: { name: \"Ada\" },\n}, call)));\ntry {\n  graph.query(\"CREATE (:Person {name: 'Mallory'})\");\n} catch (e) {\n  console.log(e.gqlStatus, e.message);\n}\nconst check = Database.open(\"./team\", { readOnly: true }).connect();\nconsole.log(\"people:\", check.query(\"MATCH (p:Person) RETURN count(p) AS people\").rows[0].people);",
  "code_sha256": "865538a888db242b54492abcd489e1ee3490e9922f32f080d90ca8027b1a594a",
  "source_sha256": "6423a5774738c6463c9acf11a52336a06062e49e517939a838bbf8545a45c3d8",
  "output": {
    "text": "{\"columns\":[\"name\",\"since\"],\"rows\":[{\"name\":\"Grace\",\"since\":2023},{\"name\":\"Ada\",\"since\":2024}],\"truncated\":false}\n{\"error\":\"refused: the statement's query type is \\\"w\\\", and this tool runs only reads (\\\"r\\\")\"}\n42N18 semantic error: WriteOnReadOnlyAccessDatabase: No write operations are allowed on this database. The database is in read-only mode on this Neo4j instance.\npeople: 3",
    "sha256": "678ecaed5f76424224046baa7ebbc05304a43d878cda7b675783d790db16810a"
  },
  "verified": {
    "version": "0.1.2",
    "client": {
      "label": "Node.js",
      "package": "@relate/relate",
      "engine_commit": "f0c09d2",
      "client_commit": "f0c09d2",
      "build": "A copy of clients/javascript at upstream v0.1.2 (f0c09d2) with koffi, loading librelate_ffi built from the same commit (cargo build -p relate-ffi --release), with the pinned frameworks beside it.",
      "toolchain": {
        "node": "26.13.0"
      }
    },
    "packages": [
      "ai@7.0.165",
      "zod@4.6.7",
      "@mastra/core@1.82.0"
    ],
    "replayed": "2026-11-09",
    "platform": "macOS on arm64",
    "docs_engine": {
      "version": "0.1.2",
      "commit": "f0c09d2",
      "source_build": false
    }
  },
  "run": {
    "runner": "npm run docs:samples",
    "setup": "bash scripts/samples/setup.sh --upstream <path to the relate checkout>",
    "variables": [
      "RELATE_NODE",
      "NODE_PATH",
      "RELATE_LIB"
    ],
    "program": "<run dir>/graph-tool.mjs",
    "files": [
      {
        "path": "<run dir>/node_modules",
        "link": "$NODE_PATH",
        "note": "a link, so require and import both resolve @relate/relate"
      }
    ],
    "build": null,
    "run": {
      "command": [
        "$RELATE_NODE",
        "graph-tool.mjs"
      ],
      "environment": {
        "DO_NOT_TRACK": "1"
      },
      "unset": [
        "NODE_OPTIONS"
      ],
      "directory": "<run dir>",
      "timeout_s": 60
    },
    "passes": "It exits 0 within 60 s and prints at least one line on standard output.",
    "output": "Standard output, with line endings as \\n, trailing spaces and blank lines removed, and the temporary directories' paths written as `.`; standard error is not recorded.",
    "placeholders": {
      "<run dir>": "a fresh, empty temporary directory: the program's working directory, deleted afterwards",
      "<build dir>": "a second fresh temporary directory, where a compiled sample is built",
      "$NAME": "a variable from the env.sh that scripts/samples/setup.sh writes (described in its header)"
    }
  }
}
