SetsunaはTurso(SQLite互換のエッジデータベース)を使用し、Prisma ORMでデータアクセスを行います。
| 項目 | 値 |
|---|---|
| データベース | Turso |
| エンジン | libSQL (SQLite互換) |
| ORM | Prisma |
| アダプター | @prisma/adapter-libsql |
| 環境 | 接続先 |
|---|---|
| 開発 | ローカルSQLiteファイル (file:./dev.db) |
| 本番 | Tursoクラウド (libsql://xxx.turso.io) |
// prisma/schema.prisma
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
datasource db {
provider = "sqlite"
}
model Room {
id String @id @default(cuid())
code String @unique
createdAt DateTime @default(now())
expiresAt DateTime
messages Message[]
@@index([code])
@@index([expiresAt])
}
model Message {
id String @id @default(cuid())
content String
roomId String
room Room @relation(fields: [roomId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@index([roomId])
@@index([createdAt])
}ルーム(テキスト共有セッション)を管理します。
| カラム | 型 | NULL | デフォルト | 説明 |
|---|---|---|---|---|
id |
TEXT | NO | cuid() |
主キー(CUID形式) |
code |
TEXT | NO | - | ルームコード(6文字、ユニーク) |
createdAt |
DATETIME | NO | now() |
作成日時 |
expiresAt |
DATETIME | NO | - | 有効期限(作成から24時間後) |
| インデックス名 | カラム | 用途 |
|---|---|---|
Room_code_key |
code |
ルームコードでの検索(ユニーク制約) |
Room_expiresAt_idx |
expiresAt |
期限切れルームの検索・削除 |
code: UNIQUE制約
ルーム内で共有されるテキストメッセージを管理します。
| カラム | 型 | NULL | デフォルト | 説明 |
|---|---|---|---|---|
id |
TEXT | NO | cuid() |
主キー(CUID形式) |
content |
TEXT | NO | - | メッセージ内容(最大10,000文字) |
roomId |
TEXT | NO | - | 所属ルームID(外部キー) |
createdAt |
DATETIME | NO | now() |
作成日時 |
| インデックス名 | カラム | 用途 |
|---|---|---|
Message_roomId_idx |
roomId |
ルームごとのメッセージ取得 |
Message_createdAt_idx |
createdAt |
時系列ソート |
| 参照元 | 参照先 | ON DELETE |
|---|---|---|
roomId |
Room.id |
CASCADE |
CASCADE削除: ルームが削除されると、関連するすべてのメッセージも自動的に削除されます。
┌─────────────────────────────────────┐
│ Room │
├─────────────────────────────────────┤
│ id : TEXT (PK, CUID) │
│ code : TEXT (UNIQUE) │
│ createdAt : DATETIME │
│ expiresAt : DATETIME │
└─────────────────────────────────────┘
│
│ 1:N
▼
┌─────────────────────────────────────┐
│ Message │
├─────────────────────────────────────┤
│ id : TEXT (PK, CUID) │
│ content : TEXT │
│ roomId : TEXT (FK → Room.id) │
│ createdAt : DATETIME │
└─────────────────────────────────────┘
// src/lib/db.ts
import { PrismaClient } from '@/generated/prisma/client';
import { PrismaLibSql } from '@prisma/adapter-libsql';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
function createPrismaClient(): PrismaClient {
// Production: Use Turso
if (process.env.TURSO_DATABASE_URL && process.env.TURSO_AUTH_TOKEN) {
const adapter = new PrismaLibSql({
url: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
});
return new PrismaClient({ adapter });
}
// Development: Use local SQLite via libsql
const adapter = new PrismaLibSql({
url: process.env.DATABASE_URL ?? 'file:./prisma/dev.db',
});
return new PrismaClient({
adapter,
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
});
}
export const prisma = globalForPrisma.prisma ?? createPrismaClient();
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}Prisma 7 では全接続でドライバーアダプターが必須です。PrismaLibSql が直接接続設定を受け取り、内部でlibSQLクライアントを生成します。
環境変数 TURSO_DATABASE_URL と TURSO_AUTH_TOKEN が設定されている場合はTursoに接続し、それ以外はローカルSQLiteを使用します。
Note:
prisma.config.ts(プロジェクトルート)にもdatasource URLを定義しています。これはPrisma CLIコマンド(migrate等)用であり、ランタイムの接続はアダプター経由です。
import { prisma } from '@/lib/db';
import { generateRoomCode } from '@/lib/room-code';
const ROOM_EXPIRY_HOURS = 24;
async function createRoom() {
const code = generateRoomCode();
const expiresAt = new Date();
expiresAt.setHours(expiresAt.getHours() + ROOM_EXPIRY_HOURS);
const room = await prisma.room.create({
data: {
code,
expiresAt,
},
});
return room;
}async function getRoom(code: string) {
const room = await prisma.room.findUnique({
where: {
code,
expiresAt: {
gt: new Date(), // 期限切れでないもの
},
},
include: {
_count: {
select: { messages: true },
},
},
});
return room;
}async function createMessage(roomCode: string, content: string) {
const room = await prisma.room.findUnique({
where: { code: roomCode },
});
if (!room) {
throw new Error('Room not found');
}
const message = await prisma.message.create({
data: {
content,
roomId: room.id,
},
});
return message;
}async function getMessages(
roomCode: string,
options?: {
after?: string;
limit?: number;
}
) {
const { after, limit = 50 } = options ?? {};
const room = await prisma.room.findUnique({
where: { code: roomCode },
});
if (!room) {
return [];
}
const messages = await prisma.message.findMany({
where: {
roomId: room.id,
...(after && {
createdAt: {
gt: (
await prisma.message.findUnique({
where: { id: after },
})
)?.createdAt,
},
}),
},
orderBy: {
createdAt: 'asc',
},
take: limit + 1, // hasMore判定用に+1
});
const hasMore = messages.length > limit;
if (hasMore) {
messages.pop();
}
return { messages, hasMore };
}async function cleanupExpiredRooms() {
const result = await prisma.room.deleteMany({
where: {
expiresAt: {
lt: new Date(),
},
},
});
return result.count;
}# マイグレーション作成・適用
pnpm exec prisma migrate dev --name init
# Prismaクライアント生成
pnpm exec prisma generate
# DBリセット(開発用)
pnpm exec prisma migrate reset# スキーマをTursoに適用
pnpm exec prisma db push
# または、マイグレーションSQL生成
pnpm exec prisma migrate diff \
--from-empty \
--to-schema-datamodel ./prisma/schema.prisma \
--script > migration.sql
# Turso CLIで適用
turso db shell setsuna < migration.sqlDB関連の環境変数設定は SPEC.md#環境変数 を参照。
要約:
- 開発:
DATABASE_URL=file:./dev.db - 本番:
TURSO_DATABASE_URL+TURSO_AUTH_TOKEN
Room.code: ルームアクセス時の高速検索Room.expiresAt: クリーンアップ処理の効率化Message.roomId: ルームごとのメッセージ取得Message.createdAt: 時系列ソート
findUniqueを優先(インデックスを活用)- ページネーションで大量データを防ぐ
selectで必要なフィールドのみ取得
Tursoはコネクションレスなので、接続プーリングの設定は不要です。 ただし、Prismaのグローバルインスタンス化により、開発時のHot Reload問題を回避しています。