DA
Database Rules
Supabase、RLS、マイグレーション、およびデータ操作に関する絶対ルール
Install
mkdir -p .claude/skills/database-rules && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/15798" && unzip -o skill.zip -d .claude/skills/database-rules && rm skill.zipInstalls to .claude/skills/database-rules
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Supabase、RLS、マイグレーション、およびデータ操作に関する絶対ルール39 charsno explicit “when” trigger
About this skill
Database Operations & Rules
1. SSOT (Single Source of Truth) の原則
- スキーマと型の参照: データベースの構造、テーブル名、カラム名、および
scopeやnote_typeなどの Enum値(列挙値)について、このドキュメントや他のMarkdownファイルに具体的な値をハードコードしてはならない。 - 行動規範: データの構造やラップされた厳密な型を確認する必要がある場合は、必ず共通パッケージの
packages/shared/src/types/配下のファイル群を絶対的な正(SSOT)として直接読みに行くこと。推測による型の指定は厳禁とする。 - DAL (Data Access Layer) の強制: DBアクセスロジックを確認・実装する際は、必ず
packages/shared/src/dalを唯一の正解とし、アプリ層(apps/配下)でのsupabase.from()の直接記述を厳禁とする。新しいテーブル操作を追加する場合は、必ずshared/dalに関数を定義してから呼び出すこと。 - Inboxデータの整合性:
sitecue_notesにおいてscope = 'inbox'の場合、url_patternは常に'inbox'であることがDBトリガーによって保証される。 - 日付SSOTの厳守: クライアント・サーバー・データベースを跨ぐ処理において、時間情報を介在させてはならない。時差による日付のズレを防ぐため、日付(
date)は常にブラウザのローカルタイムゾーンに基づくYYYY-MM-DD形式の純粋な「プレーン文字列」として一貫してDALへ渡し、文字列のまま完全一致で解釈・保存すること。 - 日記(Diaries)のアトミック追記&タイムスタンプ自動付与仕様:
- 日記の新規入力・追記を行う際、クライアントのローカル時刻に基づく
[HH:MM]\n形式のタイムスタンプ文字列を自動算出する仕様を絶対維持すること。 - 結合仕様(DAL層の掟): 初回入力時は
[HH:MM]\n本文として保存し、同一日における2回目以降の追記時は、既存のcontent末尾に 必ず改行2つ(\n\n) を挟んだ上で、今回のタイムスタンプ+本文をアトミックに結合してupsertすること(単なる上書きや改行不足による視覚的衝突を防ぐため)。
- 日記の新規入力・追記を行う際、クライアントのローカル時刻に基づく
- 型の使い分け (Slim Fetching):
ノート一覧などの大量データ取得時は、パフォーマンス最適化のため本文を含まない
NoteMetadata型を使用すること。詳細表示や編集、またはバックグラウンドでの遅延フェッチ完了後には、本文を含む完全なNote型を使用し、型レベルでデータの有無をガードすること。
2. データ操作の実装方針
- リストの並び替え (Fractional Indexing):
- ユーザーが手動で並び替えるリスト(
sitecue_notes.sort_order等)において、整数の再割り当てによる配列の一括更新は行わないこと。 - 少数(
double precision)を用いた Fractional Indexing を標準とし、単一レコードのupdateのみで並び替えを完結させる。
- ユーザーが手動で並び替えるリスト(
- 一括更新における
upsertの回避:- PostgreSQLの制約上、
upsertは部分的なデータのみを渡すとNOT NULL制約でエラーになる。 - そのため、部分的なカラムの更新には必ず個別の
updateを使用すること。
- PostgreSQLの制約上、
- N+1クエリの厳禁とRPCの活用:
- ダッシュボードなど、複数の親コンテキストに紐づく子データをグリッド展開する領域では、ループ内での個別クエリ発行(N+1)を厳禁とする。
- PostgreSQLの
JSON_AGGなどを活用した単一RPCによる一括フェッチを標準アーキテクチャとすること。
3. 開発・マイグレーション・ワークフロー
- マイグレーション絶対主義: 本番のダッシュボードで直接テーブルを変更することは厳禁。変更は必ずローカルでテストし、
supabase/migrations内にSQLファイルを作成すること。 - ローカルDBのリセット (
db reset):- マイグレーションのテストとして
bunx supabase db resetを実行すると、auth.users(ログインユーザー情報)も初期化される。 - そのため、リセット後の動作確認で「外部キー制約エラー」が発生した場合は、フロントエンド(拡張機能等)側で一度ログアウトし、再ログインしてユーザーを再作成すること。
- マイグレーションのテストとして
- 推測によるマイグレーションの禁止: テーブルの追加やカラムの拡張が必要だと判断した場合でも、「おそらくこうなっているだろう」という推測でマイグレーションSQLを作成してはならない。
- 既存の
supabase/schema.sqlや Supabaseのダッシュボード情報、あるいは既存のコードから正確なスキーマ状態を必ずユーザーに確認・要求すること。(すでに必要なカラムが存在している車輪の再発明や、既存の構造を破壊するリスクを排除するため)。 - どうしてもスキーマが不明な状態でSQLを書く必要がある場合は、必ず
IF NOT EXISTSなどを多用した防御的SQL(Defensive SQL)を記述すること。
- 既存の
4. セキュリティ (RLS)
- すべてのテーブルにおいて RLS (Row Level Security) の有効化を必須とする。
- 原則として
user_idカラムを設け、アクセスしてきたユーザー自身のデータのみを操作可能にするポリシーを記述すること。
5. 型定義 (packages/shared/src/types/supabase.ts) の自動生成と保護
-
AIがTypeScriptのエラーを解消するために
packages/shared/src/types/supabase.tsを直接手作業で書き換えることは絶対に禁止します。 -
カラムやテーブルを追加した場合は、必ずマイグレーションファイルを作成し、ローカルDBに適用 (
bunx supabase db reset等) した上で、bunx supabase gen types typescript --local > packages/shared/src/types/supabase.tsを実行して自動生成してください。 -
DBのマイグレーションを行った後は、プロジェクトのコンテキストを最新に保つため、必ず
bunx supabase db dump --local > supabase/schema.sqlを実行し、ファイルを上書きしてください。
7. PostgRESTのクエリ脆弱性対策
- URLやユーザー入力を条件とする場合、Supabaseの
.or('column.eq."${value}"')のような文字列連結によるクエリ構築は、カンマ(,)やクォーテーション(")によるパースエラー(406 Not Acceptable)やSQLインジェクションのリスクがあるため使用禁止です。 - 複雑なOR条件や安全なエスケープが必要な場合は、必ずDB側に RPC(PostgreSQL関数)を作成し、
.rpc('function_name', { args })を用いてパラメータとして渡してください。
8. Supabaseエラーのハンドリング (PostgrestErrorの罠)
- 事象: SupabaseのDB操作から返される例外(
PostgrestError)は、JavaScriptの標準的なErrorオブジェクトを継承していない場合がある。 - ルール:
catch (err: unknown)ブロック内でエラーメッセージを抽出する際、if (err instanceof Error)の判定だけでは漏れが発生する。必ずtypeof err === 'object' && err !== null && 'message' in errのように、オブジェクトのプロパティを安全にチェックしてerr.messageを取得すること。anyを用いた型キャストは禁止とする。