スキル一覧に戻る
ninyawee

supabase-migration

by ninyawee

containing skills and subagents use in my workflow

0🍴 0📅 2025年12月23日
GitHubで見るManusで実行

SKILL.md


name: supabase-migration description: "Database migration toolkit for Supabase projects. Use when: (1) Creating new migration files, (2) Writing schema changes (CREATE TABLE, ALTER, etc.), (3) Adding indexes, triggers, or RLS policies, (4) Fixing RLS performance issues (auth function wrapping, policy consolidation), (5) Validating migration conventions, (6) Running migrations locally, (7) Naming database objects" license: Proprietary. LICENSE.txt has complete terms

Supabase Database Migrations

Toolkit for creating and managing Supabase database migrations.

Helper Scripts Available (uv scripts - no install needed):

  • scripts/new_migration.py - Create migration file with proper naming
  • scripts/lint_migration.py - Validate migration against conventions
uv run scripts/new_migration.py --help
uv run scripts/lint_migration.py --help

Naming Conventions

Object Prefixes (REQUIRED)

Object TypePrefixExample
Tablestb_tb_users
Viewsv_v_active_users
Materialized Viewsmv_mv_daily_stats
Functionsfn_fn_get_balance_v1
Triggerstgr_tgr_update_ts
Indexesidx_idx_email
Foreign Keysfk_fk_order_user
Primary Keyspk_pk_users
Unique Constraintsuq_uq_email
Enum Typesen_en_status
RLS Policiespc_pc_users_select

Functions MUST be versioned: fn_calculate_total_v1, fn_calculate_total_v2

Field Suffixes

SuffixTypeExample
_dtdatebirth_dt
_tstimestamplogin_ts
_numnumberitems_num
_amtdecimaltotal_amt
_pctdecimaldiscount_pct
_uiduuiduser_uid
_cdtextstatus_cd
_boolbooleanactive_bool
_pntextcontact_pn
_emtextcontact_em
_txttextdescription_txt
_kgdecimalweight_kg
_pathtextavatar_path

No suffix: id, name, email, created_at, updated_at, deleted_at

Rules

  • MUST use lowercase_snake_case
  • Tables MUST use plural forms (tb_users not tb_user)

Decision Tree

Task → What type of change?
    ├─ New table → uv run scripts/new_migration.py "add_users_table"
    ├─ Alter table → uv run scripts/new_migration.py "add_avatar_to_users" --type alter
    ├─ New function → uv run scripts/new_migration.py "add_calc_fn" --type function
    └─ Before commit → uv run scripts/lint_migration.py migrations/*.sql

File Naming

supabase/migrations/YYYYMMDDHHMMSS_description.sql

Migration Template

-- Migration: [Description]
BEGIN;

-- 1. Types/Enums
DO $$ BEGIN CREATE TYPE en_status AS ENUM ('active', 'inactive');
EXCEPTION WHEN duplicate_object THEN NULL; END $$;

-- 2. Tables
CREATE TABLE IF NOT EXISTS tb_examples (
    id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    status_cd en_status NOT NULL DEFAULT 'active',
    created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- 3. Comments
COMMENT ON TABLE tb_examples IS 'Module: Description';

-- 4. Indexes
CREATE INDEX IF NOT EXISTS idx_examples_status ON tb_examples(status_cd);

-- 5. Triggers
DROP TRIGGER IF EXISTS tgr_update_examples_timestamp ON tb_examples;
CREATE TRIGGER tgr_update_examples_timestamp
    BEFORE UPDATE ON tb_examples FOR EACH ROW
    EXECUTE FUNCTION update_updated_at_column();

-- 6. RLS (use SELECT wrapper for auth functions - see below)
ALTER TABLE tb_examples ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS pc_examples_select ON tb_examples;
CREATE POLICY pc_examples_select ON tb_examples FOR SELECT
    USING (user_uid = (SELECT auth.uid()));

COMMIT;

Supabase Security

Views - MUST use security_invoker

CREATE VIEW v_active_users WITH (security_invoker) AS
SELECT * FROM tb_users WHERE deleted_at IS NULL;

SECURITY DEFINER views must be converted to functions:

Views with SECURITY DEFINER bypass RLS of the querying user. Convert to a function with explicit tenant/auth checks.

-- BAD: View with SECURITY DEFINER bypasses caller's RLS
CREATE VIEW v_line_responders WITH (security_definer) AS
SELECT tenant_id, ... FROM tb_line_events GROUP BY ...;

-- GOOD: Function with explicit auth check
CREATE OR REPLACE FUNCTION fn_get_line_responders_v1()
RETURNS TABLE (
    tenant_id uuid,
    responder_user_id text,
    message_count bigint
) LANGUAGE sql STABLE SECURITY DEFINER
SET search_path = extensions, public, pg_temp
AS $$
    SELECT tenant_id, event_payload->>'user_id', COUNT(*)
    FROM public.tb_line_events
    WHERE tenant_id = ((SELECT auth.jwt()) ->> 'tenant_id')::uuid
    GROUP BY tenant_id, event_payload->>'user_id';
$$;

REVOKE ALL ON FUNCTION fn_get_line_responders_v1() FROM PUBLIC;
GRANT EXECUTE ON FUNCTION fn_get_line_responders_v1() TO authenticated;

Functions - MUST set search_path

Mutable search_path allows object hijacking. Always set explicitly.

-- New function
CREATE OR REPLACE FUNCTION fn_example_v1()
RETURNS void AS $$ BEGIN END; $$
LANGUAGE plpgsql SECURITY DEFINER
SET search_path = extensions, public, pg_temp;

-- Fix existing function
ALTER FUNCTION fn_find_nearby_facilities(/* arg types */)
SET search_path = extensions, public, pg_temp;
Scenariosearch_path
Uses extensions (PostGIS, etc.)extensions, public, pg_temp
No extensionspublic, pg_temp
SECURITY DEFINER + authAdd REVOKE/GRANT (see Views section)

Extensions - MUST use extensions schema

CREATE EXTENSION IF NOT EXISTS pg_trgm SCHEMA extensions;

RLS Performance - MUST wrap auth functions in SELECT

BadGood
auth.uid()(SELECT auth.uid())
auth.jwt()(SELECT auth.jwt())
auth.role()(SELECT auth.role())
current_setting(...)(SELECT current_setting(...))
-- Wrap auth functions to avoid per-row re-evaluation
USING (user_uid = (SELECT auth.uid()))
USING (org_uid = ((SELECT auth.jwt()) ->> 'org_id')::uuid)

RLS Performance - Consolidate multiple permissive policies

Same role + same action = consolidate with OR (don't create multiple policies).

ScenarioApproach
Same role, same actionConsolidate with OR
Different rolesKeep separate policies
Different actionsKeep separate policies
-- Single policy with OR instead of multiple permissive policies
CREATE POLICY pc_records_select ON tb_records FOR SELECT
TO authenticated
USING (
    ((SELECT auth.jwt()) -> 'app_metadata' ->> 'role') = 'admin'
    OR user_uid = (SELECT auth.uid())
);

Multi-tenant pattern:

CREATE POLICY pc_registrations_select ON tb_registrations FOR SELECT
TO authenticated
USING (
    tenant_id = (((SELECT auth.jwt()) -> 'app_metadata' ->> 'tenant_id')::smallint)
    AND (
        ((SELECT auth.jwt()) -> 'app_metadata' ->> 'role') = ANY (ARRAY['staff', 'admin'])
        OR user_uid = (SELECT auth.uid())
    )
);

Running Migrations

supabase db reset    # Reset and apply all
supabase db push     # Deploy to remote

References

TopicWhen to Read
auth-function-wrapping.mdWhy (SELECT auth.*) improves performance
policy-consolidation.mdWhy multiple permissive policies hurt performance
security-definer-views.mdWhy SECURITY DEFINER views are dangerous
function-search-path.mdWhy mutable search_path is dangerous
multi-tenant.mdSetting up SaaS/multi-env isolation
private-schema.mdHiding tables from API
jsonb.mdUsing flexible JSON columns
advanced-types.mdltree, tstzrange, int8range

スコア

総合スコア

60/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です