A fully type-safe, TypeScript-first in-memory property graph database with a Cypher-like query language, a type-safe TinkerPop / Gremlin-style traversal API (GraphTraversal), multiple index types, and pluggable storage adapters including a Yjs CRDT-based adapter for collaborative/realtime/offline-first use.
This is the knowledge graph database for the codemix product intelligence platform.
| Package | Description |
|---|---|
@codemix/graph |
Core graph database: Cypher queries + type-safe Gremlin-style traversals (GraphTraversal) |
@codemix/text-search |
BM25-based full-text search with English stemming |
@codemix/y-graph-storage |
Yjs CRDT storage adapter for collaborative/offline-first use |
A fully typed, in-memory graph database. Vertices and edges are strongly typed against a user-defined schema. You can query with a subset of Cypher or with a fluent, type-safe Apache TinkerPop / Gremlin-style API — see GraphTraversal in the package docs.
- Type-safe TinkerPop / Gremlin traversals —
GraphTraversalexposes familiar steps (V(),E(),out(),in(),both(),hasLabel(),as()/select(),repeat()…) with schema-derived typing on paths and properties; pairs withAsyncGraph.queryfor remote execution - Cypher query language — parsed via a PEG grammar, supporting
MATCH,WHERE,RETURN,CREATE,SET,DELETE,MERGE,UNWIND,UNION,WITH, multi-statement queries, and more - Strongly typed schema — vertex/edge labels and their properties are defined once and inferred throughout
- Standard Schema validation — property values are validated using Standard Schema compatible validators (works with Zod, Valibot, etc.)
- Multiple index types — hash (O(1) equality), B-tree (O(log n) range), and full-text (BM25-scored)
- Async/distributed graph —
AsyncGraphsupports serializable operations for use across network boundaries - Readonly mode — parse queries with
readonly: trueto prevent mutation steps
pnpm add @codemix/graphRunning pnpm install in the monorepo configures Git to use the tracked hooks in .githooks.
The pre-commit hook formats staged supported source files with oxfmt and restages them automatically. You can re-run the hook setup at any time with pnpm run setup:hooks.
import { Graph, GraphSchema } from "@codemix/graph";
import { InMemoryGraphStorage } from "@codemix/graph";
import * as z from "zod";
// 1. Define your schema
const schema = {
vertices: {
Person: {
properties: {
name: { type: z.string() },
age: { type: z.number() },
},
},
},
edges: {
knows: { properties: {} },
},
} as const satisfies GraphSchema;
// 2. Create the graph
const graph = new Graph({
schema,
storage: new InMemoryGraphStorage(),
});
// 3. Add data
const alice = graph.addVertex("Person", { name: "Alice", age: 30 });
const bob = graph.addVertex("Person", { name: "Bob", age: 25 });
graph.addEdge(alice, "knows", bob, {});
// 4. Query with Cypher
const results = graph.query("MATCH (a:Person)-[:knows]->(b:Person) RETURN a.name, b.name");
// [{ a: { name: "Alice" }, b: { name: "Bob" } }]The same graph is navigable with a fluent API modeled on Apache TinkerPop Gremlin; labels and properties stay typed end-to-end:
import { GraphTraversal } from "@codemix/graph";
const g = new GraphTraversal(graph);
for (const path of g.V().hasLabel("Person").out("knows")) {
console.log(path.value.get("name"));
}See the type-safe TinkerPop / Gremlin traversal API section in @codemix/graph for the full step reference.
import { GraphSchema } from "@codemix/graph";
import * as z from "zod";
const schema = {
vertices: {
Product: {
properties: {
sku: { type: z.string() },
price: { type: z.number().positive() },
name: {
type: z.string(),
index: { type: "fulltext" }, // full-text search index
},
},
indexes: {
sku: { type: "hash", unique: true }, // unique hash index
},
},
},
edges: {
PURCHASED: {
properties: {
quantity: { type: z.number().int().positive() },
},
},
},
} as const satisfies GraphSchema;| Type | Lookup | Use case |
|---|---|---|
hash |
O(1) | Equality (=) on high-cardinality properties |
btree |
O(log n) | Range queries (>, <, >=, <=, BETWEEN) |
fulltext |
BM25 scored | CONTAINS / free-text search via @codemix/text-search |
All index types support a unique: true constraint that throws UniqueConstraintViolationError on duplicate values.
MATCHwith node and relationship patterns, variable-length paths (*1..5), shortest pathWHEREwith boolean logic, property access,IN,STARTS WITH,ENDS WITH,CONTAINS,IS NULL,IS NOT NULL, label expressions (IS LABELED)RETURNwith aliases (AS),DISTINCT,ORDER BY,SKIP,LIMITCREATE,SET,DELETE,REMOVE,MERGEWITH(pipeline intermediate results)UNWINDUNION/UNION ALL- Multi-statement queries (semicolon-separated)
CALLprocedures (built-in and custom viaProcedureRegistry)- Aggregation functions:
COUNT,SUM,AVG,MIN,MAX,COLLECT - List comprehensions, pattern comprehensions,
REDUCE,EXISTSsubqueries - Temporal types:
date,datetime,localtime,localdatetime,duration - Arithmetic, string functions, math functions, type conversion functions
CASEexpressions (simple and searched)
For advanced use cases, you can parse a query to a step plan without running it:
import { parseQueryToSteps } from "@codemix/graph";
const { steps, postprocess } = parseQueryToSteps(
"MATCH (n:Person) RETURN n.name",
{ readonly: true }, // throws ReadonlyGraphError if query mutates
);import { functionRegistry, procedureRegistry } from "@codemix/graph";
// Register a custom scalar function
functionRegistry.register("myLib.greet", {
call: ([name]) => `Hello, ${name}!`,
});
// Register a custom procedure
procedureRegistry.register("myLib.listItems", {
call: function* (ctx, [], yields) {
yield { item: "foo" };
yield { item: "bar" };
},
});AsyncGraph wraps a regular Graph and exposes mutations as serializable operation objects. This is useful for sending graph mutations over a network or message bus.
import { AsyncGraph } from "@codemix/graph";
const asyncGraph = new AsyncGraph({
schema,
storage: new InMemoryGraphStorage(),
});
// Subscribe to operations emitted by writes
asyncGraph.on("operation", (op) => {
sendToRemote(op); // op is a plain JSON-serializable object
});
asyncGraph.addVertex("Person", { name: "Alice", age: 30 });A lightweight, dependency-free full-text search library used internally by @codemix/graph for full-text indexes.
- BM25-inspired relevance scoring
- English word stemming
- Works on plain strings — no indexing infrastructure required
import { createMatcher, rankDocuments } from "@codemix/text-search";
// Score a single document
const match = createMatcher("quick brown fox");
match("The quick brown fox jumps over the lazy dog"); // ~0.85
match("A slow gray elephant"); // ~0.0
// Rank a list of documents
const results = rankDocuments("database performance", [
"How to improve database query performance",
"Database connection pooling best practices",
"Unrelated article about cooking",
]);
// Returns documents sorted by relevance score descendingA Yjs CRDT-backed storage adapter for @codemix/graph. Enables real-time collaborative and offline-first graph databases that sync automatically between peers.
- Stores graph data inside a
Y.Doc— compatible with any Yjs provider (WebSocket, WebRTC, IndexedDB, etc.) - Observable — subscribe to vertex/edge changes reactively
- Zod integration via
ZodYTypeshelpers for schema validation
import * as Y from "yjs";
import { Graph } from "@codemix/graph";
import { YGraphStorage } from "@codemix/y-graph-storage";
const doc = new Y.Doc();
const storage = new YGraphStorage(doc, schema);
const graph = new Graph({ schema, storage });
// Changes to the graph are automatically reflected in the Y.Doc
// and will sync to connected peers via any Yjs provider
graph.addVertex("Person", { name: "Alice", age: 30 });This is a pnpm monorepo managed with pnpm workspaces.
- Node.js 20+
- pnpm 9+
pnpm install| Command | Description |
|---|---|
pnpm test |
Run all tests across packages (watch mode) |
pnpm test:coverage |
Run all tests with coverage report |
pnpm build |
Build all packages |
pnpm typecheck |
Type-check all packages |
pnpm lint |
Lint with oxlint |
pnpm lint:fix |
Lint and auto-fix |
pnpm format |
Format with oxfmt |
pnpm format:check |
Check formatting |
# Run tests for a single package
pnpm --filter @codemix/graph test
# Build a single package
pnpm --filter @codemix/graph buildMIT