recipes / add-database

リレーショナル DB と MCP を追加する

Neon DB を接続し、MCP-first 二層設計(固定コア + meta 拡張)で誰でもカラムを足せる状態にする

前提: new-event

固定コアは手書き SQL migration、拡張レイヤ(meta jsonb)は MCP で誰でも追加できる。

1. 依存と機能

apps/<app>/package.json"@event/crm-schema": ""(MCP も使うなら "@event/mcp": "")。 event.config.tsdb: true(MCP も使うなら mcp: true)。

2. Neon を接続

Vercel Marketplace か Neon で DB を作り、env に:

DATABASE_URL=postgres://...@...pooler...neon.tech/...   # runtime(pooled)
DATABASE_URL_UNPOOLED=postgres://...@...neon.tech/...    # migration 用(直接)

3. 固定コアを適用

@event/crm-schema/migrations/0001_init.sql を pg 経由で適用する (drizzle-kit push は TTY 要求 / neon-http は multi-statement 非対応のため使わない)。 apply スクリプト apps/<app>/scripts/apply-migrations.mjs:

import { readFileSync, readdirSync } from "node:fs";
import { Client } from "pg";
const url = process.env.DATABASE_URL_UNPOOLED || process.env.DATABASE_URL;
const dir = "node_modules/@event/crm-schema/migrations";
const c = new Client({ connectionString: url });
await c.connect();
for (const f of readdirSync(dir).filter((x) => x.endsWith(".sql")).sort()) {
  process.stdout.write(`→ ${f} ... `);
  await c.query(readFileSync(`${dir}/${f}`, "utf8"));
  console.log("ok");
}
await c.end();

node --env-file=apps/<app>/.env.local apps/<app>/scripts/apply-migrations.mjs で実行。 (org/member/user/entity_lp/audit_log + compliance テーブル + 追記専用 trigger が作られる。冪等。)

4. manifest を配線

apps/<app>/src/lib/db.ts:

import { db, createManifestIndex, DEFAULT_MANIFEST } from "@event/crm-schema";
export { db };
// manifest は content/db-manifest.json に持つ(MCP addField が commit になる運用)
export const manifest = createManifestIndex(DEFAULT_MANIFEST);

書き込みは必ず guardedUpdate({ db, table, manifest, entity, id, patch, writer: "admin", expectedVersion, actor }) を通す(version 楽観ロック + audit + core/meta 振り分け)。

5. MCP server(mcp: true のとき)

apps/<app>/scripts/mcp.mjs:

import { createEventMcpServer, startStdio } from "@event/mcp";
import { db } from "@event/crm-schema";
// manifest の永続化: content/db-manifest.json を読み書きする ManifestStore を渡す
const server = createEventMcpServer({
  name: "<app>",
  db,
  manifestStore: { /* read()/write() で content/db-manifest.json を JSON I/O */ },
});
await startStdio(server);

.mcp.json に登録:

{ "mcpServers": { "<app>-db": { "command": "node", "args": ["scripts/mcp.mjs"], "env": { "DATABASE_URL": "..." } } } }

6. 使い方(「カラムを足す」= 拡張レイヤ)

MCP から schema_addField(entity + name + type + writableBy)→ ALTER TABLE なしで即書き込み可能。 固定コアの構造変更が要るときは migration_propose(SQL ファイル生成のみ・人間承認 PR へ)。 全書き込みに manifest ガード + 楽観ロック + append-only audit が効く。