Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

TypeScript client

mushroomdb-client is a typed wrapper over the HTTP API and its WebSocket stream. It talks to a running mushroomdb serve; it does not embed the engine.

npm install mushroomdb-client

It needs Node 18 or later, and runs in a browser. In a Node without a global WebSocket, install ws as well if you use subscriptions.

Quick start

import { MushroomClient } from 'mushroomdb-client';

const client = new MushroomClient('http://127.0.0.1:8080', { token: 'your-token' });

// A read.
const result = await client.query(
  'MATCH (p:Person) RETURN p.id AS id, p.name AS name LIMIT 10',
);
console.log(result.columns); // ['id', 'name']
console.log(result.rows);    // [['person-01', 'Alice'], ...]

// A read with a parameter.
const one = await client.query(
  'MATCH (p:Person {id: $pid}) RETURN p.name AS name',
  { params: { pid: 'person-01' } },
);

// Why are these two linked?
const why = await client.explain('person-01', 'proj-01');

The token is sent as an Authorization: Bearer header, and on the WebSocket URL as ?token= for subscriptions. Leave the second argument out for a server started without one.

Methods

Each method calls one route. The route’s page has the request and the response in full.

CallWhat it doesRoute
client.query(Run a Cypher read or writePOST /query
client.ingest(Insert many nodes of one labelPOST /ingest
client.stats(Node, edge and rule countsGET /stats
client.suggest(Rules the store proposes from its own valuesGET /suggest
client.explain(Why two nodes are linkedGET /explain
client.createRule(Declare a rulePOST /rules
client.node(One node’s label and properties; null when it does not existGET /node/{key}
client.neighborhood(What is within a few hops of a nodeGET /node/{key}/neighborhood
client.algo(PageRank, connected components or degree, by namePOST /algo/pagerank, /wcc, /degree
client.renameNode(Change a node’s keyPOST /nodes/{key}/rename
client.upsertEdge(Create an edge, and its endpoints if they are missingPOST /edges/upsert
client.subscribe(Stream rule and write eventsGET /subscribe
client.health(Server liveness and node/edge counts (when authenticated)GET /health
client.metrics(Detailed runtime metrics including slow-query logGET /metrics
client.findSimilar(Approximate nearest-neighbour vector searchPOST /find_similar
client.createNode(Insert a node by label and keyPOST /nodes
client.deleteNode(Remove a node; throws when it does not existDELETE /node/{key}
client.nodeEdges(All edges attached to a nodeGET /node/{key}/edges
client.nodeHistory(Per-commit change log for a nodeGET /node/{key}/history
client.edgeHistory(Edge presence log between two nodesGET /history/edge
client.wasLinked(Whether two nodes were linked at a given commitGET /history/was_linked
client.createEdge(Insert an explicit (non-derived) edgePOST /edges
client.deleteEdge(Remove an explicit edgeDELETE /edges/{etype}/{src}/{dst}
client.setNodeProp(Set or overwrite one property on a nodePUT /node/{key}/prop/{field}
client.removeNodeProp(Delete one property from a nodeDELETE /node/{key}/prop/{field}
client.backup(Copy the store to a directory and verify itPOST /backup
client.watch(Stream the raw mutation log over WebSocketGET /watch

What it does not wrap

All HTTP routes and WebSocket endpoints have a method on the client.

Creating a node, setting a property and deleting can also be done through client.query, with a Cypher CREATE, SET or DELETE.

Query options

client.query(cypher, opts?) accepts three optional fields beyond params:

OptionTypeWhat it does
as_ofnumberTime-travel: run the query on the graph as it was at this WAL commit index (0 to commit_seq − 1, read-only). A commit index from a nodeHistory/edgeHistory entry is the usual source.
maskstring[]Node-key allow-list: the query sees only nodes whose key is in this set. Makes the query read-only.
namespacestringRestrict results to this namespace, intersecting with mask and any role. Makes the query read-only.

Errors

A response that is not a success throws MushroomError. Its detail, which is also its message, is what the server said went wrong. Empty or plain-text error bodies (e.g. 404 with no JSON, or 413) surface as MushroomError("HTTP <status>") rather than a raw parse error.

import { MushroomError } from 'mushroomdb-client';

try {
  await client.query('BAD CYPHER');
} catch (err) {
  if (err instanceof MushroomError) {
    console.error('Query failed:', err.detail);
  }
}

Subscriptions

const handle = await client.subscribe(
  { rules: ['skill_fit'], writes: true },
  (ev) => {
    switch (ev.type) {
      case 'edge_fired':
        console.log(`${ev.rule}: ${ev.src_key} -> ${ev.dst_key} (commit ${ev.commit_seq})`);
        break;
      case 'lagged':
        console.warn(`missed ${ev.missed} events; re-read what you depend on`);
        break;
    }
  },
);

// Later:
await handle.close();
  • A subscription sees only writes made through the server it is connected to.
  • It does not reconnect. When the connection drops, no more events arrive; subscribe again.
  • A lagged event means the server’s queue for this subscriber overflowed and some events were not sent. The stream goes on.
  • In a Node without a global WebSocket, pass the ws constructor as wsConstructor in the first argument.

Every option of every method is in the package’s own README.