@luanpdd/kit-mcp 1.6.1 → 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.
- package/CHANGELOG.md +126 -0
- package/gates/agent-no-recursive-dispatch.md +48 -0
- package/gates/budget-description.md +68 -0
- package/gates/no-personal-uuid.md +72 -0
- package/gates/skill-must-include.md +69 -0
- package/gates/sync-idempotent.md +62 -0
- package/kit/agents/advisor-researcher.md +1 -14
- package/kit/agents/assumptions-analyzer.md +1 -14
- package/kit/agents/codebase-mapper.md +2 -15
- package/kit/agents/debugger.md +1 -19
- package/kit/agents/executor.md +18 -18
- package/kit/agents/integration-checker.md +1 -16
- package/kit/agents/nyquist-auditor.md +1 -16
- package/kit/agents/phase-researcher.md +1 -14
- package/kit/agents/plan-checker.md +1 -16
- package/kit/agents/planner.md +36 -16
- package/kit/agents/project-researcher.md +2 -15
- package/kit/agents/research-synthesizer.md +1 -9
- package/kit/agents/roadmapper.md +1 -14
- package/kit/agents/schema-checker.md +4 -4
- package/kit/agents/supabase-architect.md +153 -0
- package/kit/agents/supabase-auth-bootstrapper.md +298 -0
- package/kit/agents/supabase-edge-fn-writer.md +185 -0
- package/kit/agents/supabase-migration-writer.md +156 -0
- package/kit/agents/supabase-realtime-implementer.md +252 -0
- package/kit/agents/supabase-rls-writer.md +218 -0
- package/kit/agents/supabase-storage-implementer.md +240 -0
- package/kit/agents/ui-auditor.md +1 -16
- package/kit/agents/ui-checker.md +1 -16
- package/kit/agents/ui-researcher.md +1 -14
- package/kit/agents/user-profiler.md +2 -10
- package/kit/agents/verifier.md +2 -17
- package/kit/commands/depurar.md +17 -0
- package/kit/commands/expresso.md +9 -0
- package/kit/commands/fazer.md +32 -4
- package/kit/commands/proximo.md +7 -0
- package/kit/commands/rapido.md +6 -0
- package/kit/commands/supabase.md +148 -0
- package/kit/framework/references/output-style.md +22 -0
- package/kit/framework/workflows/discuss-phase.md +62 -327
- package/kit/framework/workflows/help.md +14 -1
- package/kit/framework/workflows/new-project.md +16 -107
- package/kit/framework/workflows/plan-phase.md +53 -147
- package/kit/skills/_shared-supabase/glossary.md +180 -0
- package/kit/skills/supabase-auth-ssr/SKILL.md +260 -0
- package/kit/skills/supabase-cron-queues/SKILL.md +266 -0
- package/kit/skills/supabase-database-functions/SKILL.md +247 -0
- package/kit/skills/supabase-declarative-schema/SKILL.md +183 -0
- package/kit/skills/supabase-edge-functions/SKILL.md +242 -0
- package/kit/skills/supabase-migrations/SKILL.md +175 -0
- package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -0
- package/kit/skills/supabase-postgres-style/SKILL.md +138 -0
- package/kit/skills/supabase-realtime/SKILL.md +236 -0
- package/kit/skills/supabase-rls-policies/SKILL.md +185 -0
- package/kit/skills/supabase-storage/SKILL.md +234 -0
- package/package.json +1 -1
- package/src/core/kit.js +55 -22
- package/src/core/sync.js +3 -1
|
@@ -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`)
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: supabase-edge-functions
|
|
3
|
+
description: Use ao escrever Edge Functions — Deno + imports npm:/jsr: (NUNCA bare), Deno.serve, env vars pre-populadas, file writes APENAS em /tmp, EdgeRuntime.waitUntil.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Supabase — Edge Functions (Deno)
|
|
7
|
+
|
|
8
|
+
## Quando usar
|
|
9
|
+
|
|
10
|
+
LLM carrega esta skill quando criar, editar ou debugar Supabase Edge Functions (Deno runtime). Trigger phrases:
|
|
11
|
+
|
|
12
|
+
- "criar Edge Function", "Supabase functions"
|
|
13
|
+
- "Deno + Supabase"
|
|
14
|
+
- "supabase functions deploy"
|
|
15
|
+
- "Edge Function background task"
|
|
16
|
+
- "import npm: jsr: em Edge Function"
|
|
17
|
+
|
|
18
|
+
## Regras absolutas
|
|
19
|
+
|
|
20
|
+
- **Runtime é Deno**, não Node.js. Use APIs Deno (`Deno.serve`, `Deno.env`, `Deno.writeTextFile`).
|
|
21
|
+
- **Imports SEMPRE com `npm:` ou `jsr:`** prefix. **NUNCA** bare specifiers (`import x from 'pkg'` falha em runtime).
|
|
22
|
+
- **Use versão pinada** nos imports — `npm:hono@4.6.7`, `npm:@supabase/supabase-js@2`. Sem version, runtime resolve para latest e quebra em deploy.
|
|
23
|
+
- **Env vars pre-populadas** (não definir manualmente):
|
|
24
|
+
- `SUPABASE_URL`
|
|
25
|
+
- `SUPABASE_PUBLISHABLE_KEYS` (anon key — para client-side context)
|
|
26
|
+
- `SUPABASE_SECRET_KEYS` (service role — server-side only)
|
|
27
|
+
- `SUPABASE_DB_URL` (conexão direta ao Postgres)
|
|
28
|
+
- Para outros secrets, set via `supabase secrets set --env-file path/to/.env`.
|
|
29
|
+
- **`Deno.serve`** é o entry point canônico. **Nunca** `addEventListener('fetch')` (deprecated) ou `serve` de `https://deno.land/std@0.168.0/http/server.ts` (não usar).
|
|
30
|
+
- **File writes APENAS em `/tmp`** — qualquer outro path é read-only.
|
|
31
|
+
- Para tarefas em background após resposta, use **`EdgeRuntime.waitUntil(promise)`**. Sem isso, função termina antes da promise.
|
|
32
|
+
- Multi-rota com Hono ou Express deve **prefixar** todas as rotas com `/<function-name>` (ex: `/my-function/users`) — sem prefix, request 404 quando deployada.
|
|
33
|
+
|
|
34
|
+
## Patterns canônicos
|
|
35
|
+
|
|
36
|
+
### Função básica — Deno.serve + npm: import
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// supabase/functions/hello/index.ts
|
|
40
|
+
// PT-BR: imports versionados sempre com npm:
|
|
41
|
+
import { createClient } from 'npm:@supabase/supabase-js@2'
|
|
42
|
+
|
|
43
|
+
Deno.serve(async (req) => {
|
|
44
|
+
const supabase = createClient(
|
|
45
|
+
Deno.env.get('SUPABASE_URL')!,
|
|
46
|
+
Deno.env.get('SUPABASE_SECRET_KEYS')! // service role server-side
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const { data, error } = await supabase
|
|
50
|
+
.from('tasks')
|
|
51
|
+
.select('id, title')
|
|
52
|
+
.limit(10)
|
|
53
|
+
|
|
54
|
+
if (error) {
|
|
55
|
+
return new Response(JSON.stringify({ error: error.message }), {
|
|
56
|
+
status: 500,
|
|
57
|
+
headers: { 'Content-Type': 'application/json' },
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return new Response(JSON.stringify(data), {
|
|
62
|
+
headers: { 'Content-Type': 'application/json' },
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Background task com `EdgeRuntime.waitUntil`
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// supabase/functions/audit-log/index.ts
|
|
71
|
+
// PT-BR: responde rápido, processa pesado em background
|
|
72
|
+
|
|
73
|
+
Deno.serve(async (req) => {
|
|
74
|
+
const body = await req.json()
|
|
75
|
+
|
|
76
|
+
// PT-BR: `waitUntil` mantém runtime alive até promise resolver
|
|
77
|
+
EdgeRuntime.waitUntil((async () => {
|
|
78
|
+
// PT-BR: file write apenas em /tmp
|
|
79
|
+
await Deno.writeTextFile(
|
|
80
|
+
`/tmp/audit-${Date.now()}.log`,
|
|
81
|
+
JSON.stringify(body)
|
|
82
|
+
)
|
|
83
|
+
// PT-BR: pode chamar APIs externas, gerar embeddings, etc.
|
|
84
|
+
await fetch('https://example.com/audit', {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
body: JSON.stringify(body),
|
|
87
|
+
})
|
|
88
|
+
})())
|
|
89
|
+
|
|
90
|
+
// PT-BR: response volta imediatamente
|
|
91
|
+
return new Response('accepted', { status: 202 })
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Multi-rota com Hono
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// supabase/functions/api/index.ts
|
|
99
|
+
// PT-BR: rotas prefixadas com /api (nome da function)
|
|
100
|
+
import { Hono } from 'npm:hono@4.6.7'
|
|
101
|
+
|
|
102
|
+
const app = new Hono().basePath('/api')
|
|
103
|
+
|
|
104
|
+
app.get('/users', (c) => c.json({ users: [] }))
|
|
105
|
+
app.get('/users/:id', (c) => c.json({ id: c.req.param('id') }))
|
|
106
|
+
app.post('/users', async (c) => {
|
|
107
|
+
const body = await c.req.json()
|
|
108
|
+
return c.json({ created: body }, 201)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
Deno.serve(app.fetch)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Função usando JSR e Node built-in
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
// supabase/functions/hash/index.ts
|
|
118
|
+
// PT-BR: imports do JSR + Node built-in (precisa node: prefix)
|
|
119
|
+
import { encodeHex } from 'jsr:@std/encoding/hex'
|
|
120
|
+
import { createHash } from 'node:crypto'
|
|
121
|
+
|
|
122
|
+
Deno.serve(async (req) => {
|
|
123
|
+
const { text } = await req.json()
|
|
124
|
+
const hash = createHash('sha256').update(text).digest('hex')
|
|
125
|
+
return new Response(JSON.stringify({ hash }), {
|
|
126
|
+
headers: { 'Content-Type': 'application/json' },
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Auth — service-role server-side
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
// supabase/functions/admin-action/index.ts
|
|
135
|
+
// PT-BR: service-role bypassa RLS — apenas server-side
|
|
136
|
+
import { createClient } from 'npm:@supabase/supabase-js@2'
|
|
137
|
+
|
|
138
|
+
Deno.serve(async (req) => {
|
|
139
|
+
// PT-BR: extrair JWT do header Authorization e validar
|
|
140
|
+
const authHeader = req.headers.get('Authorization')
|
|
141
|
+
if (!authHeader?.startsWith('Bearer ')) {
|
|
142
|
+
return new Response('unauthorized', { status: 401 })
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// PT-BR: client com service-role para operação privilegiada
|
|
146
|
+
const supabase = createClient(
|
|
147
|
+
Deno.env.get('SUPABASE_URL')!,
|
|
148
|
+
Deno.env.get('SUPABASE_SECRET_KEYS')!
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
// PT-BR: validar JWT e extrair user
|
|
152
|
+
const { data: { user }, error } = await supabase.auth.getUser(
|
|
153
|
+
authHeader.replace('Bearer ', '')
|
|
154
|
+
)
|
|
155
|
+
if (!user || error) return new Response('unauthorized', { status: 401 })
|
|
156
|
+
|
|
157
|
+
// PT-BR: agora pode operar com privilégios de service_role
|
|
158
|
+
await supabase.from('audit_log').insert({ user_id: user.id, action: 'admin_view' })
|
|
159
|
+
|
|
160
|
+
return new Response('ok')
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Anti-patterns
|
|
165
|
+
|
|
166
|
+
### Anti-pattern 1: Bare specifier sem `npm:`/`jsr:`
|
|
167
|
+
|
|
168
|
+
**Errado:**
|
|
169
|
+
```ts
|
|
170
|
+
import { createClient } from '@supabase/supabase-js' // ⚠ bare specifier
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Por quê:** Deno não resolve bare specifiers. Runtime falha em startup com erro `Module not found`.
|
|
174
|
+
|
|
175
|
+
**Certo:**
|
|
176
|
+
```ts
|
|
177
|
+
import { createClient } from 'npm:@supabase/supabase-js@2'
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Anti-pattern 2: `Deno.writeTextFile` fora de `/tmp`
|
|
181
|
+
|
|
182
|
+
**Errado:**
|
|
183
|
+
```ts
|
|
184
|
+
await Deno.writeTextFile('/data/audit.log', data) // ⚠ filesystem read-only
|
|
185
|
+
await Deno.writeTextFile('./local/x.log', data) // ⚠ idem
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Por quê:** Edge Functions runtime tem filesystem read-only exceto `/tmp`. Writes fora de `/tmp` falham com `EACCES`.
|
|
189
|
+
|
|
190
|
+
**Certo:**
|
|
191
|
+
```ts
|
|
192
|
+
await Deno.writeTextFile(`/tmp/audit-${Date.now()}.log`, data)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Anti-pattern 3: Trabalho pesado inline na response
|
|
196
|
+
|
|
197
|
+
**Errado:**
|
|
198
|
+
```ts
|
|
199
|
+
Deno.serve(async (req) => {
|
|
200
|
+
const body = await req.json()
|
|
201
|
+
await processHeavyJob(body) // ⚠ trava response 30s+
|
|
202
|
+
await sendEmail(body) // ⚠ idem
|
|
203
|
+
return new Response('done')
|
|
204
|
+
})
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Por quê:** cliente espera resposta. Edge Functions têm timeout (default 60s). Falhas pontuais quebram UX.
|
|
208
|
+
|
|
209
|
+
**Certo:** use `EdgeRuntime.waitUntil` para liberar resposta:
|
|
210
|
+
```ts
|
|
211
|
+
Deno.serve(async (req) => {
|
|
212
|
+
const body = await req.json()
|
|
213
|
+
EdgeRuntime.waitUntil((async () => {
|
|
214
|
+
await processHeavyJob(body)
|
|
215
|
+
await sendEmail(body)
|
|
216
|
+
})())
|
|
217
|
+
return new Response('accepted', { status: 202 })
|
|
218
|
+
})
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Anti-pattern 4: Multi-rota sem prefix
|
|
222
|
+
|
|
223
|
+
**Errado:**
|
|
224
|
+
```ts
|
|
225
|
+
const app = new Hono() // ⚠ sem basePath
|
|
226
|
+
app.get('/users', handler)
|
|
227
|
+
Deno.serve(app.fetch) // request a /users → 404 em produção
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Por quê:** quando deployado, URL é `https://<ref>.supabase.co/functions/v1/<name>/...`. Sem `basePath('/<name>')` no router, request a `/users` não casa.
|
|
231
|
+
|
|
232
|
+
**Certo:**
|
|
233
|
+
```ts
|
|
234
|
+
const app = new Hono().basePath('/api') // PT-BR: prefix com nome da function
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Ver também
|
|
238
|
+
|
|
239
|
+
- [supabase-auth-ssr](../supabase-auth-ssr/SKILL.md) — clients usam `npm:@supabase/supabase-js`
|
|
240
|
+
- [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — service-role server-side bypassa RLS
|
|
241
|
+
- [supabase-cron-queues](../supabase-cron-queues/SKILL.md) — Edge Functions invocadas por `pg_net.http_post`
|
|
242
|
+
- [glossário](../_shared-supabase/glossary.md) — comandos CLI (`supabase functions deploy`)
|