@luanpdd/kit-mcp 1.7.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/gates/agent-no-recursive-dispatch.md +48 -0
  3. package/gates/budget-description.md +68 -0
  4. package/gates/no-personal-uuid.md +72 -0
  5. package/gates/skill-must-include.md +69 -0
  6. package/gates/sync-idempotent.md +62 -0
  7. package/kit/agents/codebase-mapper.md +1 -1
  8. package/kit/agents/executor.md +17 -0
  9. package/kit/agents/planner.md +35 -0
  10. package/kit/agents/project-researcher.md +1 -1
  11. package/kit/agents/schema-checker.md +4 -4
  12. package/kit/agents/supabase-architect.md +153 -0
  13. package/kit/agents/supabase-auth-bootstrapper.md +298 -0
  14. package/kit/agents/supabase-edge-fn-writer.md +185 -0
  15. package/kit/agents/supabase-migration-writer.md +156 -0
  16. package/kit/agents/supabase-realtime-implementer.md +252 -0
  17. package/kit/agents/supabase-rls-writer.md +218 -0
  18. package/kit/agents/supabase-storage-implementer.md +240 -0
  19. package/kit/agents/user-profiler.md +1 -1
  20. package/kit/agents/verifier.md +1 -1
  21. package/kit/commands/depurar.md +17 -0
  22. package/kit/commands/fazer.md +15 -0
  23. package/kit/commands/supabase.md +148 -0
  24. package/kit/framework/workflows/discuss-phase.md +19 -0
  25. package/kit/framework/workflows/plan-phase.md +25 -0
  26. package/kit/skills/_shared-supabase/glossary.md +180 -0
  27. package/kit/skills/supabase-auth-ssr/SKILL.md +260 -0
  28. package/kit/skills/supabase-cron-queues/SKILL.md +266 -0
  29. package/kit/skills/supabase-database-functions/SKILL.md +247 -0
  30. package/kit/skills/supabase-declarative-schema/SKILL.md +183 -0
  31. package/kit/skills/supabase-edge-functions/SKILL.md +242 -0
  32. package/kit/skills/supabase-migrations/SKILL.md +175 -0
  33. package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -0
  34. package/kit/skills/supabase-postgres-style/SKILL.md +138 -0
  35. package/kit/skills/supabase-realtime/SKILL.md +236 -0
  36. package/kit/skills/supabase-rls-policies/SKILL.md +185 -0
  37. package/kit/skills/supabase-storage/SKILL.md +234 -0
  38. package/package.json +1 -1
@@ -0,0 +1,266 @@
1
+ ---
2
+ name: supabase-cron-queues
3
+ description: Use ao orquestrar background jobs — pg_cron + pgmq + pg_net pattern cron → pgmq → Edge Function. Sem dep externa. Postgres 15.6.1.143+.
4
+ ---
5
+
6
+ # Supabase — Cron + Queues (background jobs)
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill quando implementar background jobs, scheduled tasks ou queues em Supabase **sem dependência externa** (Inngest, Trigger.dev, etc.). Trigger phrases:
11
+
12
+ - "pg_cron", "supabase cron job"
13
+ - "pgmq", "Postgres Message Queue"
14
+ - "pg_net", "HTTP from database"
15
+ - "background job Supabase"
16
+ - "scheduled task Supabase"
17
+
18
+ ## Regras absolutas
19
+
20
+ - **Extensions necessárias:**
21
+ - **`pg_cron`** — jobs scheduled (cron syntax)
22
+ - **`pgmq`** — Postgres Message Queue (requer Postgres **15.6.1.143+**)
23
+ - **`pg_net`** — HTTP requests do banco (recomendado v0.10.0+)
24
+ - **Pattern canônico:** **`cron → pgmq → Edge Function`** — `pg_cron` enfileira mensagens em `pgmq`, Edge Function consome (via cron ou polling).
25
+ - **Jobs `pg_cron` curtos** (< 10 min) — jobs longos bloqueiam scheduler. Para jobs longos, enfileire em `pgmq` e processe via Edge Function.
26
+ - **`pgmq.send`** para enfileirar; **`pgmq.read` + `pgmq.archive`** para consumir. Visibility timeout previne double processing.
27
+ - **`pg_net` é async** — `net.http_post` retorna `request_id`, response chega em `net._http_response`. Não bloqueia caller.
28
+ - **Idempotência** — Edge Function consumer deve ser idempotente (mesma mensagem pode ser entregue 2× em retry).
29
+ - **Cleanup** — sem `pgmq.archive` ou `pgmq.delete`, mensagem reaparece após visibility timeout (re-processed).
30
+
31
+ ## Patterns canônicos
32
+
33
+ ### Setup das extensions + criar fila
34
+
35
+ ```sql
36
+ -- PT-BR: habilitar extensions (uma vez por projeto)
37
+ create extension if not exists pg_cron;
38
+ create extension if not exists pgmq;
39
+ create extension if not exists pg_net;
40
+
41
+ -- PT-BR: criar fila pgmq
42
+ select pgmq.create('email_jobs');
43
+
44
+ -- PT-BR: opcional — criar fila com retention customizado
45
+ -- select pgmq.create_partitioned('large_jobs');
46
+ ```
47
+
48
+ ### Pattern canônico — `cron → pgmq → Edge Function`
49
+
50
+ ```sql
51
+ -- PT-BR: 1. cron job a cada 5 min enfileira pendências em pgmq
52
+ select cron.schedule(
53
+ 'enqueue-pending-emails',
54
+ '*/5 * * * *', -- a cada 5 min
55
+ $$
56
+ insert into pgmq.q_email_jobs (message)
57
+ select jsonb_build_object(
58
+ 'user_id', id,
59
+ 'kind', 'reminder',
60
+ 'enqueued_at', now()
61
+ )
62
+ from public.users
63
+ where pending_email = true
64
+ limit 1000; -- batch limitado
65
+ $$
66
+ );
67
+
68
+ -- PT-BR: 2. cron job a cada minuto despara processamento via Edge Function
69
+ select cron.schedule(
70
+ 'process-email-queue',
71
+ '*/1 * * * *', -- a cada minuto
72
+ $$
73
+ select net.http_post(
74
+ url := 'https://<project-ref>.supabase.co/functions/v1/process-emails',
75
+ headers := jsonb_build_object(
76
+ 'Content-Type', 'application/json',
77
+ 'Authorization', 'Bearer ' || current_setting('supabase.functions_token', true)
78
+ ),
79
+ body := '{}'::jsonb
80
+ );
81
+ $$
82
+ );
83
+ ```
84
+
85
+ ### Edge Function consumer — pgmq.read + archive
86
+
87
+ ```ts
88
+ // supabase/functions/process-emails/index.ts
89
+ // PT-BR: consume da fila pgmq, processa, archive
90
+ import { createClient } from 'npm:@supabase/supabase-js@2'
91
+
92
+ Deno.serve(async () => {
93
+ const supabase = createClient(
94
+ Deno.env.get('SUPABASE_URL')!,
95
+ Deno.env.get('SUPABASE_SECRET_KEYS')!
96
+ )
97
+
98
+ // PT-BR: pegar até 10 mensagens com visibility timeout 30s
99
+ const { data: msgs, error } = await supabase.rpc('pgmq_read', {
100
+ queue_name: 'email_jobs',
101
+ vt: 30, // visibility timeout em segundos
102
+ qty: 10, // máximo por chamada
103
+ })
104
+
105
+ if (error || !msgs?.length) {
106
+ return new Response('no jobs', { status: 200 })
107
+ }
108
+
109
+ for (const m of msgs) {
110
+ try {
111
+ // PT-BR: processar mensagem (idempotente!)
112
+ await sendEmail(m.message.user_id, m.message.kind)
113
+
114
+ // PT-BR: archive remove da fila e move para arquivo
115
+ await supabase.rpc('pgmq_archive', {
116
+ queue_name: 'email_jobs',
117
+ msg_id: m.msg_id,
118
+ })
119
+ } catch (err) {
120
+ // PT-BR: erro — não archive; visibility timeout expira e mensagem reaparece
121
+ console.error('processing error', m.msg_id, err)
122
+ }
123
+ }
124
+
125
+ return new Response(`processed ${msgs.length}`)
126
+ })
127
+ ```
128
+
129
+ ### Job cron simples — sem queue (cuidado: < 10 min)
130
+
131
+ ```sql
132
+ -- PT-BR: ok para tarefas leves e rápidas (cleanup, agregação)
133
+ select cron.schedule(
134
+ 'cleanup-old-sessions',
135
+ '0 3 * * *', -- 3am diário
136
+ $$
137
+ delete from public.sessions where expires_at < now() - interval '30 days';
138
+ $$
139
+ );
140
+ ```
141
+
142
+ ### Listar e remover jobs cron
143
+
144
+ ```sql
145
+ -- PT-BR: listar todos os jobs
146
+ select * from cron.job;
147
+
148
+ -- PT-BR: remover job
149
+ select cron.unschedule('process-email-queue');
150
+ ```
151
+
152
+ ### `pg_net.http_post` async
153
+
154
+ ```sql
155
+ -- PT-BR: dispara HTTP request, retorna request_id imediatamente
156
+ select net.http_post(
157
+ url := 'https://api.example.com/webhook',
158
+ headers := jsonb_build_object('Authorization', 'Bearer xxx'),
159
+ body := jsonb_build_object('event', 'task_completed'),
160
+ timeout_milliseconds := 5000
161
+ );
162
+
163
+ -- PT-BR: response chega em net._http_response (consultar depois)
164
+ select * from net._http_response order by created desc limit 10;
165
+ ```
166
+
167
+ ## Anti-patterns
168
+
169
+ ### Anti-pattern 1: Job cron longo (> 10 min)
170
+
171
+ **Errado:**
172
+ ```sql
173
+ select cron.schedule(
174
+ 'heavy-batch-process',
175
+ '0 * * * *',
176
+ $$ select pg_sleep(900); ... $$ -- ⚠ 15 min em pg_cron
177
+ );
178
+ ```
179
+
180
+ **Por quê:** `pg_cron` worker bloqueia outros jobs enquanto roda. Se job > 10 min ou trava, scheduler atrasa cascata. Em retry após failure, pode trancar inteiramente.
181
+
182
+ **Certo:** cron enfileira; Edge Function processa pesado:
183
+ ```sql
184
+ -- cron: leve (só enfileira)
185
+ select cron.schedule('enqueue-heavy', '0 * * * *', $$
186
+ insert into pgmq.q_heavy_jobs (message) select ...;
187
+ $$);
188
+ -- Edge Function: pesado (consome com timeout próprio)
189
+ ```
190
+
191
+ ### Anti-pattern 2: HTTP síncrono direto de pg_cron
192
+
193
+ **Errado:**
194
+ ```sql
195
+ select cron.schedule('call-api', '*/1 * * * *', $$
196
+ -- ⚠ pg_net é async, mas user pode tentar sync com loops
197
+ select net.http_get('https://api.example.com/long');
198
+ $$);
199
+ ```
200
+
201
+ **Por quê:** HTTP requests podem demorar segundos a minutos. Se response demora, próxima execução do cron empilha. Em alta latência, scheduler fica trancado.
202
+
203
+ **Certo:** enfileire em pgmq + Edge Function processa:
204
+ ```sql
205
+ -- cron: enfileira
206
+ insert into pgmq.q_api_calls (message) values ('{"endpoint": "/long"}');
207
+ -- Edge Function: chama API com timeout próprio + archive
208
+ ```
209
+
210
+ ### Anti-pattern 3: `pgmq.read` sem `archive` ou `delete`
211
+
212
+ **Errado:**
213
+ ```ts
214
+ const { data: msgs } = await supabase.rpc('pgmq_read', { queue_name: 'jobs', vt: 30, qty: 10 })
215
+ for (const m of msgs) {
216
+ await processJob(m.message)
217
+ // ⚠ esqueceu pgmq_archive
218
+ }
219
+ ```
220
+
221
+ **Por quê:** após visibility timeout (30s), mensagem reaparece — mesmo job rodado novamente. Em loop, leva a re-processing infinito.
222
+
223
+ **Certo:**
224
+ ```ts
225
+ for (const m of msgs) {
226
+ try {
227
+ await processJob(m.message)
228
+ await supabase.rpc('pgmq_archive', { queue_name: 'jobs', msg_id: m.msg_id })
229
+ } catch (err) {
230
+ // PT-BR: NÃO archive; mensagem retorna após vt para retry
231
+ }
232
+ }
233
+ ```
234
+
235
+ ### Anti-pattern 4: Edge Function não-idempotente
236
+
237
+ **Errado:**
238
+ ```ts
239
+ async function processJob(msg) {
240
+ await sendEmail(msg.user_id) // ⚠ envia email mesmo se já enviado
241
+ await chargeCard(msg.amount) // ⚠ cobra mesmo se já cobrado
242
+ }
243
+ ```
244
+
245
+ **Por quê:** retries entregam mesma mensagem 2×+. Sem idempotência, side effects duplicam — usuário recebe 2 emails ou é cobrado 2×.
246
+
247
+ **Certo:** rastreie estado:
248
+ ```ts
249
+ async function processJob(msg) {
250
+ const { data: existing } = await supabase
251
+ .from('email_log')
252
+ .select('id')
253
+ .eq('msg_id', msg.id)
254
+ .single()
255
+ if (existing) return // já processado
256
+ await sendEmail(msg.user_id)
257
+ await supabase.from('email_log').insert({ msg_id: msg.id })
258
+ }
259
+ ```
260
+
261
+ ## Ver também
262
+
263
+ - [supabase-edge-functions](../supabase-edge-functions/SKILL.md) — Edge Functions consumindo pgmq
264
+ - [supabase-database-functions](../supabase-database-functions/SKILL.md) — funções com `set search_path = ''` chamadas em cron
265
+ - [supabase-migrations](../supabase-migrations/SKILL.md) — extensions criadas em migrations
266
+ - [glossário](../_shared-supabase/glossary.md) — pg_cron, pgmq, pg_net
@@ -0,0 +1,247 @@
1
+ ---
2
+ name: supabase-database-functions
3
+ description: Use ao criar funções Postgres — SECURITY INVOKER por padrão, SET search_path = '' SEMPRE, schema-qualified names, IMMUTABLE/STABLE quando possível.
4
+ ---
5
+
6
+ # Supabase — Database Functions
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill quando criar ou auditar funções Postgres em projeto Supabase. Trigger phrases:
11
+
12
+ - "criar função Postgres", "create or replace function"
13
+ - "trigger de banco", "function trigger"
14
+ - "SECURITY INVOKER vs DEFINER"
15
+ - "search_path", "set search_path"
16
+ - "função imutável", "stable function"
17
+
18
+ ## Regras absolutas
19
+
20
+ - **Sempre `SECURITY INVOKER`** por default — função roda com permissões de quem invoca (mais seguro). `SECURITY DEFINER` apenas com justificativa explícita escrita em comentário no topo da função.
21
+ - **Sempre `set search_path = ''`** — sem isso, função vulnerável a hijack de schema. Documentado em [Database Advisors lint 0011](https://supabase.com/docs/guides/database/database-advisors).
22
+ - **Schema-qualified** (em todas as referências a tabelas, colunas, outras funções): `public.tasks`, não `tasks`. Sem qualifier, lookup falha quando `search_path = ''`.
23
+ - Marque **`IMMUTABLE`** se função não consulta DB e sempre retorna o mesmo para os mesmos inputs (ex: formatadores de string).
24
+ - Marque **`STABLE`** se função consulta DB mas não modifica e retorna o mesmo dentro de uma transação (ex: lookups). Permite Postgres cachear o resultado por query.
25
+ - Use **`VOLATILE`** apenas se função modifica dados ou tem side effects (default — não precisa explicitar).
26
+ - Error handling com `RAISE EXCEPTION 'mensagem'` — nunca silent fail.
27
+ - Para triggers: include `CREATE TRIGGER` válido junto com `CREATE FUNCTION` na mesma migration.
28
+
29
+ ## Patterns canônicos
30
+
31
+ ### Função simples — SECURITY INVOKER + search_path
32
+
33
+ ```sql
34
+ -- formatador puro: IMMUTABLE
35
+ create or replace function public.format_full_name(first_name text, last_name text)
36
+ returns text
37
+ language sql
38
+ security invoker
39
+ set search_path = ''
40
+ immutable
41
+ as $$
42
+ select first_name || ' ' || last_name;
43
+ $$;
44
+ ```
45
+
46
+ ### Função com query — STABLE + schema-qualified
47
+
48
+ ```sql
49
+ -- conta tasks de um usuário (não modifica) — STABLE permite caching
50
+ create or replace function public.get_user_task_count(p_user_id uuid)
51
+ returns integer
52
+ language plpgsql
53
+ security invoker
54
+ set search_path = ''
55
+ stable
56
+ as $$
57
+ declare
58
+ v_count integer;
59
+ begin
60
+ select count(*) into v_count
61
+ from public.tasks -- schema-qualified obrigatório
62
+ where user_id = p_user_id;
63
+ return v_count;
64
+ end;
65
+ $$;
66
+ ```
67
+
68
+ ### Trigger — atualizar `updated_at`
69
+
70
+ ```sql
71
+ -- function + trigger juntos na mesma migration
72
+ create or replace function public.set_updated_at()
73
+ returns trigger
74
+ language plpgsql
75
+ security invoker
76
+ set search_path = ''
77
+ as $$
78
+ begin
79
+ new.updated_at := now();
80
+ return new;
81
+ end;
82
+ $$;
83
+
84
+ create trigger tasks_set_updated_at
85
+ before update on public.tasks
86
+ for each row
87
+ execute function public.set_updated_at();
88
+ ```
89
+
90
+ ### Função com error handling
91
+
92
+ ```sql
93
+ create or replace function public.transfer_credits(
94
+ p_from_user uuid,
95
+ p_to_user uuid,
96
+ p_amount integer
97
+ )
98
+ returns void
99
+ language plpgsql
100
+ security invoker
101
+ set search_path = ''
102
+ as $$
103
+ declare
104
+ v_from_balance integer;
105
+ begin
106
+ if p_amount <= 0 then
107
+ raise exception 'Valor de transferência deve ser positivo: %', p_amount;
108
+ end if;
109
+
110
+ select balance into v_from_balance
111
+ from public.accounts
112
+ where user_id = p_from_user
113
+ for update; -- lock para evitar race
114
+
115
+ if v_from_balance < p_amount then
116
+ raise exception 'Saldo insuficiente';
117
+ end if;
118
+
119
+ update public.accounts
120
+ set balance = balance - p_amount
121
+ where user_id = p_from_user;
122
+
123
+ update public.accounts
124
+ set balance = balance + p_amount
125
+ where user_id = p_to_user;
126
+ end;
127
+ $$;
128
+ ```
129
+
130
+ ### `SECURITY DEFINER` — quando justificável
131
+
132
+ ```sql
133
+ -- caso raro: função precisa fazer algo que invoker não pode fazer
134
+ -- ex: contar todos os usuários (acessível só para admins via app_metadata)
135
+ -- mas exposto via RPC para qualquer authenticated com auth check interno
136
+
137
+ -- comentário JUSTIFICANDO o DEFINER (obrigatório)
138
+ create or replace function public.count_active_users()
139
+ returns integer
140
+ -- security definer porque: precisamos bypassar RLS de auth.users que bloqueia leitura
141
+ -- mitigação: validamos role admin via app_metadata logo no topo
142
+ language plpgsql
143
+ security definer
144
+ set search_path = ''
145
+ stable
146
+ as $$
147
+ declare
148
+ v_count integer;
149
+ begin
150
+ -- validar admin via app_metadata (não user_metadata!)
151
+ if (auth.jwt()->'app_metadata'->>'role') is distinct from 'admin' then
152
+ raise exception 'Acesso negado: apenas admins';
153
+ end if;
154
+
155
+ select count(*) into v_count
156
+ from public.users
157
+ where last_seen_at > now() - interval '30 days';
158
+ return v_count;
159
+ end;
160
+ $$;
161
+ ```
162
+
163
+ ## Anti-patterns
164
+
165
+ ### Anti-pattern 1: `SECURITY DEFINER` + sem `set search_path` + sem schema qualifier
166
+
167
+ **Errado:**
168
+ ```sql
169
+ create or replace function f()
170
+ returns integer
171
+ language plpgsql
172
+ security definer -- ⚠ sem justificativa
173
+ as $$ -- ⚠ sem set search_path
174
+ begin
175
+ return (select count(*) from tasks); -- ⚠ sem public. qualifier
176
+ end;
177
+ $$;
178
+ ```
179
+
180
+ **Por quê:** atacante pode criar `tasks` em schema próprio + manipular `search_path` via `set local search_path = atacante,public` antes de invocar. Função `SECURITY DEFINER` executa com permissões do owner — atacante consegue ler/escrever onde não deveria.
181
+
182
+ **Certo:**
183
+ ```sql
184
+ create or replace function public.f()
185
+ returns integer
186
+ language plpgsql
187
+ security invoker -- prefira invoker
188
+ set search_path = '' -- bloqueia hijack
189
+ stable
190
+ as $$
191
+ begin
192
+ return (select count(*) from public.tasks); -- qualified
193
+ end;
194
+ $$;
195
+ ```
196
+
197
+ ### Anti-pattern 2: Função consulta DB mas marcada `IMMUTABLE`
198
+
199
+ **Errado:**
200
+ ```sql
201
+ create or replace function public.user_count_immutable()
202
+ returns integer
203
+ language sql
204
+ immutable -- ⚠ função consulta DB — não imutável
205
+ set search_path = ''
206
+ as $$
207
+ select count(*) from public.users;
208
+ $$;
209
+ ```
210
+
211
+ **Por quê:** `IMMUTABLE` diz para Postgres "este resultado nunca muda para os mesmos inputs". Postgres pode cachear ou pré-computar. Mas a contagem de usuários muda — Postgres pode retornar valor stale indefinidamente.
212
+
213
+ **Certo:** usar `stable` (consulta DB, não modifica, mesmo em uma transação) ou `volatile` (default — recompute sempre).
214
+
215
+ ### Anti-pattern 3: Silent fail sem `raise exception`
216
+
217
+ **Errado:**
218
+ ```sql
219
+ create or replace function public.deduct_credits(p_user uuid, p_amount integer)
220
+ returns void
221
+ language plpgsql
222
+ security invoker
223
+ set search_path = ''
224
+ as $$
225
+ begin
226
+ -- ⚠ sem validação — atualiza mesmo com saldo negativo
227
+ update public.accounts
228
+ set balance = balance - p_amount
229
+ where user_id = p_user;
230
+ end;
231
+ $$;
232
+ ```
233
+
234
+ **Por quê:** silent fail oculta bugs. Saldo fica negativo sem aviso; testes downstream falham com mensagens enigmáticas.
235
+
236
+ **Certo:**
237
+ ```sql
238
+ -- valida + raise exception se inválido (ver pattern "transfer_credits" acima)
239
+ ```
240
+
241
+ ## Ver também
242
+
243
+ - [supabase-postgres-style](../supabase-postgres-style/SKILL.md) — convenção de naming + style aplicada em funções
244
+ - [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — funções e RLS interagem (SECURITY INVOKER respeita RLS do invoker)
245
+ - [supabase-migrations](../supabase-migrations/SKILL.md) — funções em migrations são versionadas
246
+ - [supabase-cron-queues](../supabase-cron-queues/SKILL.md) — funções invocadas por `pg_cron` jobs
247
+ - [glossário](../_shared-supabase/glossary.md) — termos PT-BR↔EN + comandos CLI
@@ -0,0 +1,183 @@
1
+ ---
2
+ name: supabase-declarative-schema
3
+ description: Use ao gerenciar schema via supabase/schemas/ — workflow stop → db diff -f → revisar → apply. Inclui caveats sobre views, RLS, partitions.
4
+ ---
5
+
6
+ # Supabase — Declarative Database Schema
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill quando trabalhar com `supabase/schemas/` (declarative source-of-truth) em vez de migrations imperativas. Trigger phrases:
11
+
12
+ - "supabase schemas/", "declarative schema"
13
+ - "supabase db diff", "gerar migration de schema"
14
+ - "schema source of truth"
15
+ - "como adicionar tabela em projeto declarative"
16
+
17
+ ## Regras absolutas
18
+
19
+ - **Workflow canônico:**
20
+ 1. Editar arquivos `.sql` em `supabase/schemas/` (representando estado **final** desejado de cada entidade)
21
+ 2. **`supabase stop`** — derrubar containers locais (necessário antes de diff)
22
+ 3. **`supabase db diff -f <name>`** — gera migration em `supabase/migrations/<timestamp>_<name>.sql`
23
+ 4. **Revisar manualmente** a migration gerada (diff é heurístico — pode gerar SQL incorreto em renames, drops, etc.)
24
+ 5. `supabase db reset` para aplicar local; `supabase db push` para aplicar remote
25
+ - **Nunca pule `supabase stop`** antes de `db diff` — diff sem stop produz output inconsistente.
26
+ - **Nunca pule revisão** da migration gerada — especialmente para renames (diff pode gerar `drop+create` em vez de `rename column`).
27
+ - **DML (INSERT/UPDATE/DELETE) NÃO é declarative** — fica em migrations imperativas (`supabase/migrations/`) ou `supabase/seed.sql`.
28
+ - **Files ordenados lexicograficamente** — para gerenciar dependências (FKs), nomeie de forma que a ordem de execução resolva referências (ex: `01_users.sql`, `02_tasks.sql`).
29
+ - **Adicione novas colunas no fim** da definição da tabela — evita diffs falsos em PRs.
30
+ - Seu `kit` de schemas reflete estado final, **não** o histórico — migrations carregam o histórico.
31
+
32
+ ## Patterns canônicos
33
+
34
+ ### Estrutura típica de `supabase/schemas/`
35
+
36
+ ```
37
+ supabase/
38
+ ├── schemas/
39
+ │ ├── 01_extensions.sql -- create extension if not exists ...
40
+ │ ├── 02_users.sql -- public.users (mirror de auth.users)
41
+ │ ├── 03_tasks.sql -- public.tasks
42
+ │ ├── 04_tasks_rls.sql -- policies em public.tasks
43
+ │ └── 05_functions.sql -- public.set_updated_at, etc.
44
+ ├── migrations/ -- gerado por db diff (revisado e commitado)
45
+ └── seed.sql -- DML (não declarative)
46
+ ```
47
+
48
+ ### Workflow de mudança
49
+
50
+ ```bash
51
+ # PT-BR: 1. editar schemas/
52
+ # (ex: adicionar coluna priority em supabase/schemas/03_tasks.sql)
53
+
54
+ # PT-BR: 2. parar containers (obrigatório antes de diff)
55
+ supabase stop
56
+
57
+ # PT-BR: 3. gerar migration
58
+ supabase db diff -f add_priority_to_tasks
59
+
60
+ # PT-BR: 4. revisar arquivo gerado
61
+ # supabase/migrations/<timestamp>_add_priority_to_tasks.sql
62
+ # (verificar se diff capturou só o intended change — não renames falsos, drops indevidos)
63
+
64
+ # PT-BR: 5. aplicar local
65
+ supabase db reset
66
+
67
+ # PT-BR: 6. (depois) aplicar remote
68
+ supabase db push
69
+ ```
70
+
71
+ ### Schema com FK e RLS
72
+
73
+ ```sql
74
+ -- supabase/schemas/03_tasks.sql
75
+ create table if not exists public.tasks (
76
+ id uuid primary key default gen_random_uuid(),
77
+ user_id uuid not null references auth.users (id) on delete cascade,
78
+ title text not null,
79
+ status text not null default 'todo',
80
+ priority text not null default 'low', -- novas colunas: append no fim
81
+ created_at timestamptz not null default now()
82
+ );
83
+
84
+ alter table public.tasks enable row level security;
85
+
86
+ -- policies em arquivo separado (04_tasks_rls.sql) ou aqui
87
+ -- mas sempre granulares (ver supabase-rls-policies)
88
+ ```
89
+
90
+ ## Caveats — limitações conhecidas do declarative
91
+
92
+ O `migra` diff tool (usado por `supabase db diff`) tem edge cases. **Sempre revise** a migration gerada antes de aplicar.
93
+
94
+ ### DML (INSERT/UPDATE/DELETE)
95
+ - **Não rastreável** por declarative (declarative só captura DDL — Data Definition Language).
96
+ - Use `supabase/seed.sql` para seed data ou migrations imperativas para mudanças de dados.
97
+
98
+ ### View ownership e atributos
99
+ - Diff **não captura mudanças de owner** de views.
100
+ - **Security invoker em views** não é diferenciado por diff — usar migration manual se mudar.
101
+ - **Materialized views** têm suporte limitado.
102
+ - **Mudança de column type em views** não recria a view — diff pode falhar silenciosamente.
103
+
104
+ ### RLS policies
105
+ - `alter policy` statements são suportados mas podem ter edge cases.
106
+ - **Column privileges** não são totalmente capturados.
107
+
108
+ ### Outras entidades
109
+ - **Schema privileges:** não rastreados (cada schema diffado separadamente).
110
+ - **Comments on objects:** não rastreados.
111
+ - **Partitions:** suporte limitado — partitioned tables podem precisar migration manual.
112
+ - **`alter publication ... add table`:** não detectado por diff.
113
+ - **`create domain`:** ignorado por diff (usar migration imperativa).
114
+ - **`grant` statements:** duplicados a partir de default privileges — verificar saída.
115
+
116
+ ## Anti-patterns
117
+
118
+ ### Anti-pattern 1: `db diff` com containers up
119
+
120
+ **Errado:**
121
+ ```bash
122
+ # containers ainda rodando
123
+ supabase db diff -f my_change # ⚠ output inconsistente
124
+ ```
125
+
126
+ **Por quê:** diff compara schema declarado em `schemas/` com DB local atual. Se containers up, DB tem state inconsistente (mid-transaction, locks abertos).
127
+
128
+ **Certo:**
129
+ ```bash
130
+ supabase stop
131
+ supabase db diff -f my_change
132
+ ```
133
+
134
+ ### Anti-pattern 2: Aplicar migration gerada sem revisão
135
+
136
+ **Errado:**
137
+ ```bash
138
+ supabase db diff -f rename_column
139
+ supabase db push # ⚠ aplicou sem revisar
140
+ ```
141
+
142
+ **Por quê:** diff é heurístico. Em renames, pode gerar `drop column old + create column new` em vez de `alter table ... rename column`. Resultado: dados perdidos.
143
+
144
+ **Certo:** sempre abrir `supabase/migrations/<timestamp>_*.sql` e revisar antes de aplicar.
145
+
146
+ ### Anti-pattern 3: DML em `supabase/schemas/`
147
+
148
+ **Errado:**
149
+ ```sql
150
+ -- supabase/schemas/03_tasks.sql
151
+ create table if not exists public.tasks (...);
152
+
153
+ insert into public.tasks (id, title) values (...); -- ⚠ DML não é declarative
154
+ ```
155
+
156
+ **Por quê:** declarative captura apenas DDL. Inserts em `schemas/` rodam quando schema é aplicado, mas não são rastreáveis em migrations — recriam sempre que `db reset`.
157
+
158
+ **Certo:** mover INSERTs para `supabase/seed.sql` ou migration imperativa.
159
+
160
+ ### Anti-pattern 4: Adicionar coluna no meio da definição
161
+
162
+ **Errado:**
163
+ ```sql
164
+ -- antes
165
+ create table public.tasks (id uuid, title text, created_at timestamptz);
166
+
167
+ -- depois (coluna adicionada NO MEIO)
168
+ create table public.tasks (id uuid, priority text, title text, created_at timestamptz);
169
+ ```
170
+
171
+ **Por quê:** diff pode interpretar como reorder e gerar SQL ineficiente (drop + recreate de várias colunas).
172
+
173
+ **Certo:** appendar no fim:
174
+ ```sql
175
+ create table public.tasks (id uuid, title text, created_at timestamptz, priority text);
176
+ ```
177
+
178
+ ## Ver também
179
+
180
+ - [supabase-migrations](../supabase-migrations/SKILL.md) — formato e regras dos arquivos gerados em `migrations/`
181
+ - [supabase-postgres-style](../supabase-postgres-style/SKILL.md) — estilo SQL nas declarações
182
+ - [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — como expressar RLS em schemas/
183
+ - [glossário](../_shared-supabase/glossary.md) — comandos CLI canônicos (`supabase stop`, `db diff -f`, `db reset`)