| name | adding-database-tables |
| description | Guide for creating database tables with Drizzle ORM migrations and repository pattern Use when this capability is needed. |
| metadata | {"author":"eretica"} |
新しいデータベーステーブルの追加
このガイドは、マイグレーションとリポジトリパターンを使用して新しいデータベーステーブルを追加するためのステップバイステップの手順を提供します。
前提条件
- Drizzle ORMの理解
- リポジトリパターンの理解(CLAUDE.md § 2と§ 12.3参照)
ステップバイステップガイド
1. スキーマの定義
src/main/db/schema.tsにテーブル定義を追加します:
import { sqliteTable, text, integer, index } from 'drizzle-orm/sqlite-core';
export const features = sqliteTable('features', {
id: text('id').primaryKey(),
name: text('name').notNull(),
enabled: integer('enabled').notNull().default(1),
createdAt: text('created_at').notNull(),
updatedAt: text('updated_at').notNull(),
}, (table) => ({
enabledIdx: index('features_enabled_idx').on(table.enabled),
}));
export type FeatureRecord = typeof features.$inferSelect;
export type NewFeature = typeof features.$inferInsert;
2. マイグレーションの生成
pnpm db:generate
これにより、src/main/db/migrations/に新しいマイグレーションファイルが作成されます。
生成されたSQLの検証:
cat src/main/db/migrations/00XX_*.sql
3. リポジトリクラスの作成
src/main/db/repositories/feature.tsを作成します:
import { eq } from 'drizzle-orm';
import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
import { v4 as uuidv4 } from 'uuid';
import type { Feature } from '../../../shared/types';
import * as schema from '../schema';
export class FeatureRepository {
constructor(private db: BetterSQLite3Database<typeof schema>) {}
async findAll(): Promise<Feature[]> {
const records = await this.db
.select()
.from(schema.features);
return records.map(this.toModel);
}
async findById(id: string): Promise<Feature | null> {
records = .
.()
.(schema.)
.((schema.., id));
records. > ? .(records[]) : ;
}
(: <, | | >): <> {
now = ().();
: schema. = {
: (),
: data.,
: data. ? : ,
: now,
: now,
};
..(schema.).(newRecord);
.({ ...newRecord, : newRecord. });
}
(: , : <>): <> {
now = ().();
: <schema.> = {
...data,
: data. !== ? (data. ? : ) : ,
: now,
};
.
.(schema.)
.(updateData)
.((schema.., id));
updated = .(id);
(!updated) {
();
}
updated;
}
(: ): <> {
.
.(schema.)
.((schema.., id));
}
(: schema.): {
{
: record.,
: record.,
: record. === ,
: record.,
: record.,
};
}
}
4. リポジトリのエクスポート
src/main/db/repositories/index.tsに追加します:
export { FeatureRepository } from './feature';
5. リポジトリテストの作成
src/main/db/repositories/feature.test.tsを作成します:
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import { FeatureRepository } from './feature';
import * as schema from '../schema';
describe('FeatureRepository', () => {
let db: ReturnType<typeof drizzle<typeof schema>>;
let sqlite: Database.Database;
let repo: FeatureRepository;
beforeEach(() => {
sqlite = new Database(':memory:');
db = drizzle(sqlite, { schema });
sqlite.exec(`
CREATE TABLE features (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
`);
repo = new FeatureRepository(db);
});
afterEach(() => {
sqlite.close();
});
(, () => {
created = repo.({
: ,
: ,
});
(created.).();
(created.).();
(created.).();
found = repo.(created.);
(found).(created);
});
(, () => {
created = repo.({
: ,
: ,
});
updated = repo.(created., { : });
(updated.).();
(updated.).();
});
(, () => {
created = repo.({
: ,
: ,
});
repo.(created.);
found = repo.(created.);
(found).();
});
(, () => {
repo.({ : , : });
repo.({ : , : });
all = repo.();
(all).();
});
});
6. IPCハンドラの追加
src/main/ipc.tsを更新してリポジトリを使用します:
import { FeatureRepository } from './db/repositories/feature';
export function setupIpcHandlers(): void {
const db = getDatabase();
ipcMain.handle(IPC_CHANNELS.FEATURE_LIST, async (): Promise<Feature[]> => {
const repo = new FeatureRepository(db);
return await repo.findAll();
});
ipcMain.handle(
IPC_CHANNELS.FEATURE_ADD,
async (_event, data: Partial<Feature>): Promise<Feature> => {
const repo = new FeatureRepository(db);
return await repo.create(data);
}
);
ipcMain.handle(
IPC_CHANNELS.FEATURE_UPDATE,
async (_event, id: string, data: Partial<Feature>): Promise<> => {
repo = (db);
repo.(id, data);
}
);
ipcMain.(
.,
(_event, : ): <> => {
repo = (db);
repo.(id);
}
);
}
7. 検証
pnpm test src/main/db/repositories/feature.test.ts
pnpm db:generate
pnpm dev
pnpm db:studio
一般的なパターン
外部キー
export const childTable = sqliteTable('child_table', {
id: text('id').primaryKey(),
parentId: text('parent_id').notNull().references(() => parentTable.id),
});
JSONカラム
export const tableWithJson = sqliteTable('table_with_json', {
id: text('id').primaryKey(),
metadata: text('metadata').notNull(),
});
async create(data: Entity): Promise<Entity> {
await this.db.insert(schema.tableWithJson).values({
metadata: JSON.stringify(data.metadata),
});
}
private toModel(record: Record): Entity {
return {
...record,
metadata: JSON.parse(record.metadata),
};
}
タイムスタンプ
常にcreated_atとupdated_atを含めます:
export const entities = sqliteTable('entities', {
id: text('id').primaryKey(),
createdAt: text('created_at').notNull(),
updatedAt: text('updated_at').notNull(),
});
チェックリスト
Converted and distributed by TomeVault — claim your Tome and manage your conversions.