@luanpdd/kit-mcp 1.7.0 → 1.9.0

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 (70) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +39 -1
  3. package/gates/agent-no-recursive-dispatch.md +48 -0
  4. package/gates/budget-description.md +68 -0
  5. package/gates/no-personal-uuid.md +72 -0
  6. package/gates/obs-agents-mcp-supabase.md +86 -0
  7. package/gates/obs-skills-frontmatter.md +76 -0
  8. package/gates/omm-no-regression.md +83 -0
  9. package/gates/skill-must-include.md +71 -0
  10. package/gates/sync-idempotent.md +62 -0
  11. package/kit/agents/burn-rate-forecaster.md +160 -0
  12. package/kit/agents/codebase-mapper.md +1 -1
  13. package/kit/agents/executor.md +17 -0
  14. package/kit/agents/incident-investigator.md +245 -0
  15. package/kit/agents/observability-instrumenter.md +200 -0
  16. package/kit/agents/omm-auditor.md +199 -0
  17. package/kit/agents/planner.md +35 -0
  18. package/kit/agents/project-researcher.md +1 -1
  19. package/kit/agents/schema-checker.md +4 -4
  20. package/kit/agents/slo-engineer.md +224 -0
  21. package/kit/agents/supabase-architect.md +166 -0
  22. package/kit/agents/supabase-auth-bootstrapper.md +315 -0
  23. package/kit/agents/supabase-edge-fn-writer.md +207 -0
  24. package/kit/agents/supabase-migration-writer.md +174 -0
  25. package/kit/agents/supabase-realtime-implementer.md +275 -0
  26. package/kit/agents/supabase-rls-writer.md +235 -0
  27. package/kit/agents/supabase-storage-implementer.md +258 -0
  28. package/kit/agents/user-profiler.md +1 -1
  29. package/kit/agents/verifier.md +1 -1
  30. package/kit/commands/auditar-marco.md +22 -1
  31. package/kit/commands/auditar-observabilidade.md +103 -0
  32. package/kit/commands/burn-rate-status.md +140 -0
  33. package/kit/commands/concluir-marco.md +19 -1
  34. package/kit/commands/definir-slo.md +108 -0
  35. package/kit/commands/depurar.md +17 -0
  36. package/kit/commands/discutir-fase.md +26 -0
  37. package/kit/commands/fazer.md +15 -0
  38. package/kit/commands/forense.md +20 -1
  39. package/kit/commands/instrumentar-fase.md +200 -0
  40. package/kit/commands/investigar-producao.md +162 -0
  41. package/kit/commands/observabilidade.md +116 -0
  42. package/kit/commands/planejar-fase.md +20 -0
  43. package/kit/commands/supabase.md +148 -0
  44. package/kit/commands/verificar-trabalho.md +26 -0
  45. package/kit/framework/workflows/discuss-phase.md +19 -0
  46. package/kit/framework/workflows/plan-phase.md +25 -0
  47. package/kit/skills/_shared-observability/glossary.md +396 -0
  48. package/kit/skills/_shared-supabase/glossary.md +180 -0
  49. package/kit/skills/burn-rate-alerting/SKILL.md +258 -0
  50. package/kit/skills/core-analysis-loop/SKILL.md +352 -0
  51. package/kit/skills/distributed-tracing/SKILL.md +362 -0
  52. package/kit/skills/event-based-slos/SKILL.md +274 -0
  53. package/kit/skills/observability-driven-development/SKILL.md +315 -0
  54. package/kit/skills/observability-maturity-model/SKILL.md +222 -0
  55. package/kit/skills/opentelemetry-standard/SKILL.md +351 -0
  56. package/kit/skills/structured-events/SKILL.md +265 -0
  57. package/kit/skills/supabase-auth-ssr/SKILL.md +260 -0
  58. package/kit/skills/supabase-cron-queues/SKILL.md +266 -0
  59. package/kit/skills/supabase-database-functions/SKILL.md +247 -0
  60. package/kit/skills/supabase-declarative-schema/SKILL.md +183 -0
  61. package/kit/skills/supabase-edge-functions/SKILL.md +242 -0
  62. package/kit/skills/supabase-migrations/SKILL.md +175 -0
  63. package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -0
  64. package/kit/skills/supabase-postgres-style/SKILL.md +138 -0
  65. package/kit/skills/supabase-realtime/SKILL.md +236 -0
  66. package/kit/skills/supabase-rls-policies/SKILL.md +185 -0
  67. package/kit/skills/supabase-storage/SKILL.md +234 -0
  68. package/kit/skills/telemetry-pipelines/SKILL.md +259 -0
  69. package/kit/skills/telemetry-sampling/SKILL.md +256 -0
  70. package/package.json +1 -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`)