{"id":40390897,"url":"https://github.com/maskdotdev/ray","last_synced_at":"2026-01-31T04:09:06.322Z","repository":{"id":330213425,"uuid":"1116440022","full_name":"maskdotdev/ray","owner":"maskdotdev","description":null,"archived":false,"fork":false,"pushed_at":"2026-01-28T14:13:20.000Z","size":2788,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-28T20:14:24.507Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://ray-lime-xi.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maskdotdev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-14T21:24:34.000Z","updated_at":"2026-01-28T14:16:17.000Z","dependencies_parsed_at":"2026-01-28T06:04:34.893Z","dependency_job_id":null,"html_url":"https://github.com/maskdotdev/ray","commit_stats":null,"previous_names":["maskdotdev/ray"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/maskdotdev/ray","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maskdotdev%2Fray","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maskdotdev%2Fray/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maskdotdev%2Fray/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maskdotdev%2Fray/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maskdotdev","download_url":"https://codeload.github.com/maskdotdev/ray/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maskdotdev%2Fray/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28928922,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-31T04:05:25.756Z","status":"ssl_error","status_checked_at":"2026-01-31T04:02:35.005Z","response_time":128,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-20T12:36:27.105Z","updated_at":"2026-01-31T04:09:06.313Z","avatar_url":"https://github.com/maskdotdev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ray - Embedded Graph Database\n\nA high-performance embedded graph database for Bun/TypeScript with:\n\n- **Fast reads** via mmap CSR (Compressed Sparse Row) snapshots\n- **Reliable writes** via WAL (Write-Ahead Log) + in-memory delta overlay\n- **Stable node IDs** that never change or get reused\n- **Periodic compaction** to merge snapshots with deltas\n- **MVCC** for concurrent transaction isolation\n- **Pathfinding** with Dijkstra and A* algorithms\n- **Caching** for frequently accessed nodes, edges, and properties\n\n## Features\n\n- Zero-copy mmap reading of snapshot files\n- ACID transactions with commit/rollback\n- **MVCC (Multi-Version Concurrency Control)** for snapshot isolation\n- Efficient CSR format for graph traversal\n- Binary search for edge existence checks\n- Key-based node lookup with hash index\n- Node and edge properties\n- In/out edge traversal\n- **Graph pathfinding** (shortest path, weighted paths)\n- **Query result caching** with automatic invalidation\n- Snapshot integrity checking\n\n## Installation\n\n```bash\nbun add @ray-db/ray\n```\n\nOr for development:\n\n```bash\ngit clone \u003crepo\u003e\ncd ray\nbun install\n```\n\n## Browser (WASM) prototype\n\nRayDB can run in the browser via the WASI build of the core (`@ray-db/core`).\nThis uses an in-memory filesystem by default (ephemeral per page load).\n\nBuild the WASM bundle locally:\n\n```bash\ncd ray-rs\nnpm run build:wasm\n```\n\nThen import `@ray-db/core` in your browser bundler (it uses the `browser` entry),\nor import `@ray-db/core-wasm32-wasi` directly. Persistence in the browser\nrequires wiring WASI to a persistent FS (e.g. OPFS/IndexedDB).\nSee `ray-rs/examples/browser` for a minimal demo (OPFS first, IndexedDB fallback).\n\n## Quick Start\n\n```typescript\nimport {\n  openGraphDB,\n  closeGraphDB,\n  beginTx,\n  commit,\n  createNode,\n  addEdge,\n  getNeighborsOut,\n  defineEtype,\n  getNodeByKey,\n  optimize,\n  listNodes,\n  listEdges,\n  countNodes,\n  countEdges,\n} from './src/index.ts';\n\n// Open or create a database\nconst db = await openGraphDB('./my-graph');\n\n// Start a transaction\nconst tx = beginTx(db);\n\n// Define an edge type\nconst knows = defineEtype(tx, 'knows');\n\n// Create nodes\nconst alice = createNode(tx, { key: 'user:alice' });\nconst bob = createNode(tx, { key: 'user:bob' });\n\n// Add an edge\naddEdge(tx, alice, knows, bob);\n\n// Commit the transaction\nawait commit(tx);\n\n// Query the graph\nconst friends = [...getNeighborsOut(db, alice, knows)];\nconsole.log('Alice knows:', friends);\n\n// Look up by key\nconst aliceNode = getNodeByKey(db, 'user:alice');\n\n// Compact when needed (merges delta into snapshot)\nawait optimize(db);\n\n// Close the database\nawait closeGraphDB(db);\n```\n\n## API Reference\n\n### Database Lifecycle\n\n```typescript\n// Open a database\nconst db = await openGraphDB(path, {\n  readOnly?: boolean,        // Open in read-only mode\n  createIfMissing?: boolean, // Create if doesn't exist (default: true)\n  lockFile?: boolean,        // Use file locking (default: true)\n  legacyMultiFile?: boolean, // Allow legacy directory format (default: false)\n  mvcc?: boolean,            // Enable MVCC for concurrent transactions\n  cache?: boolean,           // Enable caching (default: false)\n});\n\n// Close the database\nawait closeGraphDB(db);\n```\n\n### Transactions\n\n```typescript\n// Begin a transaction\nconst tx = beginTx(db);\n\n// Commit changes\nawait commit(tx);\n\n// Or rollback\nrollback(tx);\n\n// With MVCC enabled, concurrent transactions are supported:\nconst tx1 = beginTx(db);  // Transaction 1\nconst tx2 = beginTx(db);  // Transaction 2 (concurrent)\n```\n\n### Node Operations\n\n```typescript\n// Create a node\nconst nodeId = createNode(tx, {\n  key?: string,           // Optional unique key\n  labels?: LabelID[],     // Optional labels\n  props?: Map\u003cPropKeyID, PropValue\u003e, // Optional properties\n});\n\n// Delete a node\ndeleteNode(tx, nodeId);\n\n// Check if node exists\nnodeExists(db, nodeId);\n\n// Look up by key\ngetNodeByKey(db, 'user:alice');\n\n// List all nodes (lazy generator)\nfor (const nodeId of listNodes(db)) {\n  console.log(nodeId);\n}\n\n// Count total nodes (O(1) optimized)\nconst totalNodes = countNodes(db);\n```\n\n### Edge Operations\n\n```typescript\n// Add an edge\naddEdge(tx, srcId, etypeId, dstId);\n\n// Delete an edge\ndeleteEdge(tx, srcId, etypeId, dstId);\n\n// Check if edge exists\nedgeExists(db, srcId, etypeId, dstId);\n\n// Traverse out-neighbors\nfor (const edge of getNeighborsOut(db, nodeId)) {\n  console.log(edge.src, edge.etype, edge.dst);\n}\n\n// Traverse in-neighbors\nfor (const edge of getNeighborsIn(db, nodeId)) {\n  console.log(edge.src, edge.etype, edge.dst);\n}\n\n// Filter by edge type\nfor (const edge of getNeighborsOut(db, nodeId, knowsEtype)) {\n  // Only edges of type 'knows'\n}\n\n// List all edges (lazy generator)\nfor (const edge of listEdges(db)) {\n  console.log(`${edge.src} -\u003e ${edge.dst}`);\n}\n\n// List edges of specific type\nfor (const edge of listEdges(db, { etype: knowsEtype })) {\n  console.log(`${edge.src} knows ${edge.dst}`);\n}\n\n// Count total edges (O(1) optimized when unfiltered)\nconst totalEdges = countEdges(db);\nconst knowsCount = countEdges(db, { etype: knowsEtype });\n```\n\n### Schema Definitions\n\n```typescript\n// Define edge types\nconst knows = defineEtype(tx, 'knows');\nconst follows = defineEtype(tx, 'follows');\n\n// Define labels\nconst person = defineLabel(tx, 'Person');\n\n// Define property keys\nconst name = definePropkey(tx, 'name');\n```\n\n### Properties\n\n```typescript\nimport { PropValueTag } from './src/index.ts';\n\n// Set node property\nsetNodeProp(tx, nodeId, nameProp, {\n  tag: PropValueTag.STRING,\n  value: 'Alice'\n});\n\n// Delete node property\ndelNodeProp(tx, nodeId, nameProp);\n\n// Set edge property\nsetEdgeProp(tx, src, etype, dst, weightProp, {\n  tag: PropValueTag.F64,\n  value: 0.5\n});\n```\n\n### Maintenance\n\n```typescript\n// Get database stats\nconst s = stats(db);\nconsole.log('Nodes:', s.snapshotNodes);\nconsole.log('Edges:', s.snapshotEdges);\nconsole.log('Recommend compact:', s.recommendCompact);\n\n// Check database integrity\nconst result = check(db);\nif (!result.valid) {\n  console.error('Errors:', result.errors);\n}\n\n// Compact (merge delta into new snapshot)\nawait optimize(db);\n```\n\n### MVCC (Multi-Version Concurrency Control)\n\nMVCC enables concurrent read and write transactions with snapshot isolation:\n\n```typescript\n// Open database with MVCC enabled\nconst db = await openGraphDB('./my-graph', { mvcc: true });\n\n// Start concurrent transactions\nconst reader = beginTx(db);\nconst writer = beginTx(db);\n\n// Reader sees consistent snapshot from its start time\nconst node = getNodeByKey(db, 'user:alice');\n\n// Writer can modify data\nsetNodeProp(writer, nodeId, nameProp, { tag: PropValueTag.STRING, value: 'Updated' });\nawait commit(writer);\n\n// Reader still sees old data (snapshot isolation)\n// ...\n\n// Conflict detection prevents lost updates\nconst tx1 = beginTx(db);\nconst tx2 = beginTx(db);\nsetNodeProp(tx1, nodeId, prop, value1);\nsetNodeProp(tx2, nodeId, prop, value2);\nawait commit(tx1);  // Succeeds\nawait commit(tx2);  // Throws ConflictError\n```\n\n### Pathfinding\n\nFind shortest paths between nodes:\n\n```typescript\nimport { ray, defineNode, defineEdge, prop } from './src/api';\n\nconst db = await ray('./my-graph', { nodes: [...], edges: [...] });\n\n// Shortest path (unweighted)\nconst path = await db\n  .from(startNode)\n  .shortestPath(endNode)\n  .via(edgeType)\n  .maxDepth(10)\n  .execute();\n\n// Weighted shortest path (Dijkstra)\nconst weightedPath = await db\n  .from(startNode)\n  .shortestPath(endNode)\n  .via(edgeType)\n  .weight({ prop: distanceProp })\n  .execute();\n\n// A* pathfinding with heuristic\nconst astarPath = await db\n  .from(startNode)\n  .shortestPath(endNode)\n  .via(edgeType)\n  .weight({ prop: distanceProp })\n  .heuristic((node) =\u003e estimateDistance(node, endNode))\n  .execute();\n```\n\n### Caching\n\nEnable caching for read-heavy workloads:\n\n```typescript\nimport { \n  invalidateNodeCache, \n  invalidateEdgeCache, \n  clearCache, \n  getCacheStats \n} from './src/index.ts';\n\n// Open with caching enabled\nconst db = await openGraphDB('./my-graph', { cache: true });\n\n// Cache is automatically populated on reads and invalidated on writes\n\n// Manual cache management\ninvalidateNodeCache(db, nodeId);\ninvalidateEdgeCache(db, srcId, etypeId, dstId);\nclearCache(db);\n\n// Get cache statistics\nconst cacheStats = getCacheStats(db);\nconsole.log('Cache hits:', cacheStats.hits);\nconsole.log('Cache misses:', cacheStats.misses);\n```\n\n## Fluent API\n\nThe fluent API provides a type-safe, ergonomic interface for graph operations with schema definitions:\n\n```typescript\nimport { ray, defineNode, defineEdge, prop, optional } from '@ray-db/ray';\n\n// Define schema\nconst user = defineNode('user', {\n  key: (id: string) =\u003e `user:${id}`,\n  props: {\n    name: prop.string('name'),\n    email: prop.string('email'),\n    age: optional(prop.int('age')),\n  },\n});\n\nconst knows = defineEdge('knows', {\n  since: prop.int('since'),\n});\n\n// Initialize database with schema\nconst db = await ray('./my-graph', {\n  nodes: [user],\n  edges: [knows],\n});\n\n// Insert nodes\nconst alice = await db\n  .insert(user)\n  .values({ key: 'alice', name: 'Alice', email: 'alice@example.com', age: 30n })\n  .returning();\n\nconst bob = await db\n  .insert(user)\n  .values({ key: 'bob', name: 'Bob', email: 'bob@example.com' })\n  .returning();\n\n// Create edges\nawait db.link(alice).to(bob).via(knows).props({ since: 2024n }).execute();\n\n// Query nodes\nconst foundUser = await db.get(user, 'alice');\n\n// Lightweight reference lookup (no property loading)\nconst userRef = await db.getRef(user, 'alice');\n\n// Traverse the graph\nconst friends = await db.from(alice).out(knows).toArray();\nconst friendCount = await db.from(alice).out(knows).count();\n\n// Multi-hop traversal\nconst friendsOfFriends = await db\n  .from(alice)\n  .out(knows)\n  .out(knows)\n  .toArray();\n\n// Traverse with depth range\nconst network = await db\n  .from(alice)\n  .traverse(knows, { direction: 'out', minDepth: 1, maxDepth: 3 })\n  .toArray();\n\n// Selective property loading\nconst namesOnly = await db\n  .from(alice)\n  .out(knows)\n  .select(['name'])\n  .toArray();\n\n// Raw edge iteration (zero-copy)\nfor (const edge of db.from(alice).out(knows).rawEdges()) {\n  console.log(edge.src, '-\u003e', edge.dst);\n}\n\n// Close database\nawait db.close();\n```\n\n### Listing and Counting\n\n```typescript\n// List all nodes of a type (lazy async generator)\nfor await (const u of db.all(user)) {\n  console.log(u.name, u.email);\n}\n\n// Collect to array\nconst allUsers: typeof user[] = [];\nfor await (const u of db.all(user)) {\n  allUsers.push(u);\n}\n\n// Count all nodes in database (O(1) - fast)\nconst totalNodes = await db.count();\n\n// Count nodes of specific type (requires iteration)\nconst userCount = await db.count(user);\n\n// List all edges (lazy async generator)\nfor await (const e of db.allEdges()) {\n  console.log(`${e.src.$key} -\u003e ${e.dst.$key}`);\n}\n\n// List edges of specific type with properties\nfor await (const e of db.allEdges(knows)) {\n  console.log(`${e.src.$key} knows ${e.dst.$key} since ${e.props.since}`);\n}\n\n// Count all edges (O(1) - fast)\nconst totalEdges = await db.countEdges();\n\n// Count edges of specific type\nconst knowsCount = await db.countEdges(knows);\n```\n\n### Performance Characteristics\n\nThe fluent API is optimized for minimal overhead compared to raw graph operations:\n\n| Operation | Raw | Fluent | Overhead |\n|-----------|-----|--------|----------|\n| Insert (single) | 62µs | 65µs | **1.05x** |\n| Key lookup (`getRef`) | 125ns | 250ns | **2.00x** |\n| Key lookup (`get`) | 125ns | 1.5µs | 12x |\n| 1-hop traversal `.count()` | 1.1µs | 2.0µs | **1.85x** |\n\n**Performance tips:**\n\n- Use `getRef()` instead of `get()` when you only need the node reference (not properties)\n- Use `.count()` instead of `.toArray().length` for counting (optimized fast path)\n- Use `.select(['prop1', 'prop2'])` to load only needed properties\n- Use `.rawEdges()` for zero-copy edge iteration when you don't need node properties\n\n### Running Benchmarks\n\n```bash\n# Full benchmark suite\nbun run bench/benchmark-api-vs-raw.ts\n\n# Custom parameters\nbun run bench/benchmark-api-vs-raw.ts --nodes 10000 --edges 50000 --iterations 10000\n```\n\n## File Formats\n\nRay supports two storage formats. The directory-based format is legacy and will be\ndeprecated in favor of the single-file format.\n\n### Single-File Format (`.raydb`) - Recommended\n\nA SQLite-style single-file database for simpler deployment and backup:\n\n```typescript\nimport {\n  openSingleFileDB,\n  closeSingleFileDB,\n  optimizeSingleFile,\n  vacuumSingleFile,\n} from '@ray-db/ray';\n\n// Open or create a single-file database\nconst db = await openSingleFileDB('./my-graph.raydb', {\n  readOnly?: boolean,        // Open in read-only mode\n  createIfMissing?: boolean, // Create if doesn't exist (default: true)\n  lockFile?: boolean,        // Use file locking (default: true)\n  pageSize?: number,         // Page size (default: 4096, must be power of 2)\n  walSize?: number,          // WAL buffer size in bytes (default: 64MB)\n  mvcc?: boolean,            // Enable MVCC\n  cache?: CacheOptions,      // Enable caching\n});\n\n// All the same operations work (beginTx, createNode, etc.)\n\n// Compact to merge delta into snapshot\nawait optimizeSingleFile(db);\n\n// Vacuum to reclaim space\nawait vacuumSingleFile(db);\n\n// Close the database\nawait closeSingleFileDB(db);\n```\n\nThe single-file format contains:\n- **Header (page 0)**: Magic, version, page size, snapshot/WAL locations\n- **WAL Area**: Circular buffer for write-ahead log records\n- **Snapshot Area**: CSR snapshot data (mmap-friendly)\n\n### Multi-File Format (directory) - Deprecated (Legacy)\n\nThe original directory-based format (legacy). New deployments should use\nthe single-file `.raydb` format. A separate WAL file may be retained for\nsingle-file performance in the future, but the directory format is no longer\nrecommended.\n\nTo open an existing legacy directory, pass `{ legacyMultiFile: true }` to\n`openGraphDB`.\n\n```\ndb/\n  manifest.gdm           # Current snapshot and WAL info\n  lock.gdl               # Optional file lock\n  snapshots/\n    snap_0000000000000001.gds\n    snap_0000000000000002.gds\n  wal/\n    wal_0000000000000001.gdw\n    wal_0000000000000002.gdw\n```\n\n### Snapshot Format (`.gds`)\n\n- Magic: `GDS1`\n- CSR (Compressed Sparse Row) format for edges\n- In-edges and out-edges stored separately\n- String table for interned strings\n- Key index for fast lookups\n- CRC32C integrity checking\n\n### WAL Format (`.gdw`)\n\n- Magic: `GDW1`\n- 8-byte aligned records\n- CRC32C per record\n- Transaction boundaries (BEGIN/COMMIT/ROLLBACK)\n\n## Development\n\n```bash\n# Run tests\nbun test\n\n# Run specific test file\nbun test tests/snapshot.test.ts\n\n# Run MVCC tests\nbun test tests/mvcc.test.ts\n\n# Run benchmarks\nbun run bench/benchmark.ts\nbun run bench/benchmark-mvcc-v2.ts\n\n# Type check\nbun run tsc --noEmit\n```\n\n### Benchmark memory usage\n\nThe MVCC v2 benchmark (`bench/benchmark-mvcc-v2.ts`) includes a `scale` scenario\nthat deliberately builds a ~1M node / 5M edge graph fully in memory to stress\nsnapshot layout and MVCC. On typical machines this will use several GB of RAM.\nFor local runs, you can reduce memory pressure by lowering `--scale-nodes`\n(e.g. `--scale-nodes 200000`) or disabling the `scale` scenario via\n`--scenarios warm,contested,version-chain`.\n\nThis benchmark measures the in-memory engine; it does **not** mean your\napplication must keep all data resident. You can `closeGraphDB(db)` to flush and\nunmap the snapshot/WAL files, and later `openGraphDB` on the same path to read\nfrom the embedded on-disk snapshot again.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaskdotdev%2Fray","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaskdotdev%2Fray","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaskdotdev%2Fray/lists"}