はじめに
xClaw は、データベースを 2 つのシステムに分離します。構造化された構成データ用の PostgreSQL と、柔軟な AI/チャット データ用の MongoDB です。この記事では、スキーマの設計とデータベース層の実装について説明します。
1. Drizzle ORM を使用した PostgreSQL スキーマ
1.1 なぜ霧雨が降るのですか?
| 特長 | 霧雨 | プリズマ | タイプORM |
|---|---|---|---|
| タイプセーフティ | コンパイル時 | ランタイム コード生成 | デコレーター |
| バンドルのサイズ | ~50KB | ~2MB | ~1MB |
| 生の SQL | ネイティブ | 限定 | クエリビルダー |
| 移行 | SQL ファースト | 自動 | 自動 |
| パフォーマンス | オーバーヘッドゼロ | プロキシのオーバーヘッド | 反射オーバーヘッド |
1.2 スキーマ定義
// packages/db/src/schema/tenants.ts
import { pgTable, uuid, varchar, timestamp, jsonb } from 'drizzle-orm/pg-core';
export const tenants = pgTable('tenants', {
id: uuid('id').primaryKey().defaultRandom(),
name: varchar('name', { length: 255 }).notNull(),
slug: varchar('slug', { length: 255 }).notNull().unique(),
settings: jsonb('settings').default({}),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
email: varchar('email', { length: 255 }).notNull().unique(),
passwordHash: varchar('password_hash', { length: 255 }).notNull(),
name: varchar('name', { length: 255 }).notNull(),
tenantId: uuid('tenant_id').references(() => tenants.id).notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const roles = pgTable('roles', {
id: uuid('id').primaryKey().defaultRandom(),
name: varchar('name', { length: 100 }).notNull(),
tenantId: uuid('tenant_id').references(() => tenants.id).notNull(),
permissions: jsonb('permissions').default([]),
});
export const userRoles = pgTable('user_roles', {
userId: uuid('user_id').references(() => users.id).notNull(),
roleId: uuid('role_id').references(() => roles.id).notNull(),
});
1.3 移行
# Generate migration từ schema changes
npm run db:generate
# Run migrations
npm run db:migrate
# Visual database browser
npm run db:studio
2. AI データ用の MongoDB
2.1 接続のセットアップ
// packages/db/src/mongo.ts
import { MongoClient, Db } from 'mongodb';
let client: MongoClient;
let db: Db;
export async function connectMongo(url: string): Promise<Db> {
client = new MongoClient(url);
await client.connect();
db = client.db();
// Create TTL indexes
await db.collection('audit_logs').createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 90 * 24 * 60 * 60 } // 90 days
);
await db.collection('system_logs').createIndex(
{ timestamp: 1 },
{ expireAfterSeconds: 30 * 24 * 60 * 60 } // 30 days
);
return db;
}
export function getMongo(): Db {
if (!db) throw new Error('MongoDB not connected');
return db;
}
2.2 コレクションの構造
// Sessions collection
interface Session {
_id: string;
tenantId: string;
userId: string;
title: string;
model: string;
domainId?: string;
createdAt: Date;
updatedAt: Date;
}
// Messages collection
interface Message {
_id: string;
sessionId: string;
role: 'user' | 'assistant' | 'system' | 'tool';
content: string;
toolCalls?: ToolCall[];
toolCallId?: string;
images?: string[];
usage?: { promptTokens: number; completionTokens: number };
timestamp: Date;
}
// Memory entries
interface MemoryEntry {
_id: string;
sessionId: string;
type: 'conversation' | 'entity' | 'summary';
content: string;
embedding?: number[];
createdAt: Date;
}
3. シードデータ
// packages/db/src/seed.ts
export async function seedDatabase(pgDb: DrizzleDB, mongoDB: Db) {
// Create default tenant
const [tenant] = await pgDb.insert(tenants).values({
name: 'Default',
slug: 'default',
}).returning();
// Create admin user (password: password123)
const passwordHash = await hash('password123', 12);
const [admin] = await pgDb.insert(users).values({
email: '[email protected]',
passwordHash,
name: 'Admin',
tenantId: tenant.id,
}).returning();
// Create system roles with permissions
const systemRoles = [
{ name: 'owner', permissions: ALL_PERMISSIONS }, // 60 perms
{ name: 'admin', permissions: ADMIN_PERMISSIONS }, // 52 perms
{ name: 'member', permissions: MEMBER_PERMISSIONS }, // 14 perms
{ name: 'viewer', permissions: VIEWER_PERMISSIONS }, // 8 perms
];
for (const role of systemRoles) {
await pgDb.insert(roles).values({
...role,
tenantId: tenant.id,
});
}
}
4. まとめ
- PostgreSQL + Drizzle ORM による構造化された構成データ — タイプセーフ、移行
- AI/チャット データ用 MongoDB — 柔軟なスキーマ、TTL インデックス
- Redis キャッシュ用 — セッション、レート制限
- 初期セットアップ用の シード データ — 管理者ユーザー、システム ロール
次の記事: Hono を使用した API ゲートウェイの構築 — ルート、ミドルウェア、JWT 認証。