
2026/04/21 19:33
CRDT を採用した、型安全でリアルタイム協業をサポートするグラフデータベースです。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
このテキストでは、タブ間を同期する Yjs CRDT を使用してメモリ内グラフデータベースを構築するためのアルファ品質のソフトウェアである
@codemix/graph および @codemix/y-graph-storage を紹介しています。インストールは pnpm add @codemix/graph を実行することで容易であり、ネイティブ依存関係は一切必要ありません。Node またはバンドラー内でも動作します。ユーザーは GraphSchema オブジェクトを使用してスキーマを定義し、プロパティタイプ(Zod、Valibot、ArkType、または独自定義をサポート)はクエリやメタテーションを通じて直接流れ、手動での型変換を必要としません。ハッシュ、B-tree、全文検索を含むスキーマインデックスはインラインで宣言され、遅やかに構築され、増分的に維持されます。
このライブラリは、顶点と辺の作成および読み取りを行うための型安全なインターフェースを提供します(例:
addVertex、addEdge、get)。引数はコンパイル時およびランタイムの両方で検証されます。Gremlin 風のカバレッジ API(GraphTraversal)が提供されており、TypeScript がすべてのラベル、プロパティキー、ハップをスキーマに対してチェックします。フィルタリング機能には完全一致または述語(hasLabel、where を通じて)が含まれ、辺の移動は OWNS や FOLLOWS などの特定の関係に従います。
コラボレーションのため、
InMemoryGraphStorage を YGraph に交換することでオフラインファースト同期が有効になり、WebsocketProvider などのプロバイダーを通じて自動的に無衝突な同期をサポートします。細かい粒度の変更購読(graph.subscribe)はローカルおよびリモートのメタテーションに対してイベントを発行し(例:顶点の追加、辺の削除)、ライブクエリはカバレッジをラップし、基盤となるデータが変更になると自動的に再発行されます。コラボレーティブなプロパティタイプには ZodYText および ZodYArray が使用され、値が自動的に変換されるため、ピアは衝突なしで更新を見ることができます。
さらに、このパッケージは
parseQueryToSteps を介して Cypher 互換の文字列クエリをサポートします。この機能は現在、リードオンリーモードおよび API または LLM が MCP サーバーとして動作する際に適したパラメータ付きクエリを有効にしますが、このモードでの完全な書き込み操作はまだサポートされていません。ツールは MIT ライセンスで公開されており、元々は Charles Pick(codemix の創設者)による研究プロジェクトでした。Y.js へのサポートが含まれるように改修され、Opus 4.5 で Cypher 風の言語機能を追加しました。宣伝コンテンツでは、チャット、図表、共同編集を通じてコーディングエージェントを導くための製品進化のプラットフォームとして codemix を取り上げています。本文
航空路線のデモンストレーション:大手航空会社の実際のネットワークを読み込み、TypeScript でクエリを実行するライブデモ。また、あなたの顔写真を壁に追加することも可能で、これは「@codemix/graph」と「@codemix/y-graph-storage」によって駆動されています。これらはリアルなグラフデータベースであり、開いているすべてのタブを通じて Yjs CRDT を介して同期されます。ご自身のプロフィールを追加し、人々の配置を再調整したり、つながりを描画したりできます。
インストール:
npm からパッケージをインストールしてください。ネイティブ依存関係は不要で、Node.js やバンドラが動作するあらゆる環境で利用可能です。
$ pnpm add @codemix/graph
※注:本ソフトウェアはアルファ品質です。Codemix 内でのプロダクション使用や既存のユースケースでは順調に機能していますが、ご自身のデータと組み合わせる際には十分ご注意ください。
スキーマの定義: 頂点(vertices)、辺(edges)、インデックスを普通オブジェクトとして記述します。プロパティの型はすべてのクエリ、走査、および変異操作を通じて適用され、キャストやランタイムでの予期せぬ挙動はありません。
import { Graph, GraphSchema, InMemoryGraphStorage } from "@codemix/graph"; import { z } from "zod"; const schema = { vertices: { User: { properties: { email: { type: z.email(), index: { type: "hash", unique: true } }, name: { type: z.string() }, }, }, Repo: { properties: { name: { type: z.string() }, stars: { type: z.number() }, }, }, }, edges: { OWNS: { properties: {} }, FOLLOWS: { properties: {} }, }, } as const satisfies GraphSchema; const graph = new Graph({ schema, storage: new InMemoryGraphStorage() });
→ 標準的なスキーマライブラリ(Zod、Valibot、ArkType、またはご自身のものであり)に対応可能です。 → すべての変異操作において型検証が実行され、
addVertex、addEdge、および updateProperty でプロパティの正当性がチェックされます。
→ インラインでインデックスを宣言可能で、遅延ビルドおよび段階的な維持が可能(ハッシュ、B ツリー、フルテキスト検索など)。
データの追加: 頂点と辺はグラフインスタンスを通じて追加されます。引数のプロパティには、コンパイル時かつランタイムの両方でスキーマに対する検証が適用されます。
// 頂点の追加 —— アーギュメントは各ラベルのプロパティスキーマに対して型付けされています const alice = graph.addVertex("User", { name: "Alice", email: "alice@example.com" }); const bob = graph.addVertex("User", { name: "Bob", email: "bob@example.com" }); const myRepo = graph.addVertex("Repo", { name: "my-repo", stars: 0 }); // 辺の追加 graph.addEdge(alice, "OWNS", myRepo, {}); graph.addEdge(bob, "FOLLOWS", alice, {}); // プロパティの読み取り —— 型はスキーマから取得されます alice.get("name"); // string myRepo.get("stars"); // number // インプレースでの更新 graph.updateProperty(myRepo, "stars", 42); // または、要素自体を通じて myRepo.set("stars", 42);
型安全なクエリの作成: Gremlin スタイルの走査 API を提供しており、馴染み深いステップ名を使用できますが、すべてのラベル、プロパティキー、およびホップについて TypeScript がスキーマに対してチェックを実行します。
走査の開始
import { GraphTraversal } from "@codemix/graph"; const g = new GraphTraversal(graph); for (const path of g.V().hasLabel("User")) { path.value.get("name"); // string ✓ path.value.get("email"); // string ✓ }
プロパティによるフィルタリング: 厳密な一致または予測器(predicate)を用いてフィルタリング可能です。
const [alice] = g.V() .hasLabel("User") .has("email", "alice@example.com"); const seniors = g.V() .hasLabel("User") .where((v) => v.get("name").startsWith("A"));
辺の走査: ユーザーからリポジトリへの「OWNS」边を追跡します。
for (const path of g.V() .hasLabel("User") .has("email", "alice@example.com") .out("OWNS").hasLabel("Repo")) { path.value.get("stars"); // number — Repo のスキーマから型付けされています }
ラベル付けと選択: 複数のホップでの頂点をキャプチャし、それらを共通してプロジェクトします。
for (const { user, repo } of g.V() .hasLabel("User").as("user") .out("FOLLOWS") .out("OWNS").hasLabel("Repo").as("repo") .select("user", "repo")) { console.log( user.value.get("name"), // string repo.value.get("stars"), // number ); }
オフラインファーストの同期とリアルタイムなコラボレーション:
InMemoryGraphStorage を YGraph に置き換えるだけで、グラフ全体は Yjs CRDT ドキュメントの中に存在します。すべての走査、Cypher クエリ、およびインデックスは変更されず、その上にコンフリクトのない同期が追加されます。
プロバイダーの接続
import * as Y from "yjs"; import { WebsocketProvider } from "y-websocket"; import { YGraph } from "@codemix/y-graph-storage"; const doc = new Y.Doc(); const graph = new YGraph({ schema, doc }); // 任意の Yjs プロバイダーをプラグイン —— 同期は自動で行われます。 // ルームに参加するすべてのピアは同じグラフを見ます。 const provider = new WebsocketProvider("wss://my-server", "graph-room", doc);
微細な変更への購読: ローカルおよびリモートの両方の変異についてイベントが発火します。
const unsubscribe = graph.subscribe({ next(change) { // change.kind は次のいずれか: // "vertex.added" | "vertex.deleted" // "edge.added" | "edge.deleted" // "vertex.property.set" | "vertex.property.changed" console.log(change.kind, change.id); }, });
ライブクエリ: 走査をラップし、結果セットが変化する可能性があるたびに再発火します。
const topRepos = graph.query((g) => g.V().hasLabel("Repo").order("stars", "desc").limit(10) ); const unsubscribe = topRepos.subscribe({ next() { for (const path of topRepos) { console.log(path.value.get("name"), path.value.get("stars")); } }, }); // どこかで(あるいはリモートピアからでも)Repo を追加または更新すると、 // サブスクライバーが自動的にトリガーされます。 graph.updateProperty(myRepo, "stars", 99);
コラボレーティブなプロパティ型
import { ZodYText, ZodYArray } from "@codemix/y-graph-storage"; import { z } from "zod"; // スキーマにおいて Y.Text / Y.Array / Y.Map プロパティを宣言します const schema = { vertices: { Document: { properties: { title: { type: ZodYText }, // コラボレーティブな文字列 tags: { type: ZodYArray(z.string()) }, // コラボレーティブな配列 }, }, }, edges: {}, } as const satisfies GraphSchema; // 単なる値は自動的に変換されるため、Y.* を手動で構築する必要はありません const doc = graph.addVertex("Document", { title: "Hello", tags: ["crdt"] }); // インプレースでの変異 —— すべてのピアが無衝突で変化を検知します doc.get("title").insert(5, ", world"); doc.get("tags").push(["graph"]);
API および LLM 用の Cypher クエリ: 同じグラフは、Cypher 互換の文字列言語を通じてクエリ可能です。これは MCP サーバーを通じてデータを LLM に公開する場合や、トリバースルライブラリをバンドルせずに外部クライアントからアドホックなクエリを受け付ける場合に特に適しています。
解析と実行
import { parseQueryToSteps, createTraverser } from "@codemix/graph"; const { steps, postprocess } = parseQueryToSteps(` MATCH (u:User)-[:OWNS]->(r:Repo) WHERE r.stars > 100 RETURN u.name, r.name ORDER BY r.stars DESC LIMIT 10 `); const traverser = createTraverser(steps); for (const row of traverser.traverse(graph, [])) { console.log(postprocess(row)); // { u: { name: "Alice" }, r: { name: "my-repo" } } }
パラメータ付きクエリ: 文字列_interpolation_ を避けるためにパラメータを渡します。
const { steps, postprocess } = parseQueryToSteps(` MATCH (u:User { email: $email })-[:OWNS]->(r:Repo) RETURN r.name, r.stars `); const traverser = createTraverser(steps); const rows = Array.from( traverser.traverse(graph, [{ email: "alice@example.com" }]) ).map(postprocess);
変異操作:
CREATE、MERGE、SET、DELETE のすべてがサポートされています。
const { steps } = parseQueryToSteps(` MATCH (r:Repo { name: $name }) SET r.stars = r.stars + 1 `); createTraverser(steps).traverse(graph, [{ name: "my-repo" }]); // 読み取り専用を強制 —— 書き込み節句がある場合に `ReadonlyGraphError` を投げる const { steps: safeSteps } = parseQueryToSteps(query, { readonly: true });
ライセンスと履歴: このパッケージは MIT ライセンスの下で利用可能です。元々は codemix の創業者であり、著名な
ts-sql デモの著者である Charles Pick による研究プロジェクトとして書かれました。その後、codemix の構築中に構造化された知識グラフが必要となり、コードを適応させ Y.js サポートを追加し、さらに Opus 4.5 では Cypher リックなクエリ言語を導入しました。
GitHub でスターを付ける 「ここにいる間」に、製品の単一の真実のソースを持ってみましょう。人間と AI のためです。codemix は、あなたが実際に意味するもの(ビジネスドメイン、ユーザーフロー、概念、制約など)を捉え、コードベースと自動的に同期を保ち続けます。チャット、図、または共同編集を通じて製品を変えていきましょう。開発およびレビューにおいてコーディングエージェントを導き、真の理解を持ってコードを検証しましょう。あなたのチーム内のすべてのエージェントが同じコンテキストを共有します。全く新しいものを創造するか、既存のコードベースをインポートして開始してください。
何か全く新しいものを作りましょう codemix を無料でお試しください。クレジットカードは不要です。