@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,260 @@
1
+ ---
2
+ name: supabase-auth-ssr
3
+ description: Use ao bootstrap Next.js v16 + Supabase Auth — @supabase/ssr, getAll/setAll APENAS, NUNCA auth-helpers-nextjs, proxy completo com getUser e redirects.
4
+ ---
5
+
6
+ # Supabase — Auth SSR (Next.js v16+)
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill quando bootstrap ou auditar autenticação Supabase em Next.js v16+ (App Router) com SSR. Trigger phrases:
11
+
12
+ - "Next.js + Supabase auth"
13
+ - "@supabase/ssr"
14
+ - "createServerClient", "createBrowserClient"
15
+ - "middleware.ts auth", "proxy auth"
16
+ - "cookies getAll setAll"
17
+ - "Supabase auth Next.js v16"
18
+
19
+ ## Regras absolutas
20
+
21
+ **WARNING — NEVER use auth-helpers-nextjs.** O pacote `@supabase/auth-helpers-nextjs` está **DEPRECATED** e **quebra em Next.js v16+** (cookies API mudou). **SEMPRE** use `@supabase/ssr`.
22
+
23
+ **Outras regras:**
24
+
25
+ - **Padrão exclusivo `getAll`/`setAll`** para cookies — **NUNCA** `get`/`set`/`remove` individuais. Os métodos individuais não funcionam corretamente com middleware/Server Actions em Next.js v16+.
26
+ - **Browser client e Server client são distintos:**
27
+ - Browser (`createBrowserClient`) → para Client Components ("use client")
28
+ - Server (`createServerClient`) → para Server Components, Route Handlers, Server Actions
29
+ - **Middleware (`middleware.ts`) obrigatório** para refresh de sessão SSR. Deve chamar `supabase.auth.getUser()` em cada request.
30
+ - **Auth method order** — após `createServerClient` mas **ANTES** de `getUser()`, NÃO chamar nada que produza response intermediário. Os cookies precisam fluir corretamente.
31
+ - **`NEXT_PUBLIC_*` apenas para anon key** (`NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`). **NUNCA** `NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY` — service_role bypassa RLS e seria exposto ao cliente (anti-pitfall B6).
32
+ - **Single serverClient factory** — não criar múltiplos clients em layouts (race condition na refresh de token — B13).
33
+
34
+ ## Patterns canônicos
35
+
36
+ ### Browser client — Client Components
37
+
38
+ ```ts
39
+ // utils/supabase/client.ts — PT-BR: client para Client Components
40
+ import { createBrowserClient } from '@supabase/ssr'
41
+
42
+ export function createClient() {
43
+ return createBrowserClient(
44
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
45
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
46
+ )
47
+ }
48
+ ```
49
+
50
+ ```tsx
51
+ // PT-BR: uso em Client Component
52
+ 'use client'
53
+ import { createClient } from '@/utils/supabase/client'
54
+
55
+ export function LogoutButton() {
56
+ const supabase = createClient()
57
+ return <button onClick={() => supabase.auth.signOut()}>Sair</button>
58
+ }
59
+ ```
60
+
61
+ ### Server client — Server Components / Server Actions
62
+
63
+ ```ts
64
+ // utils/supabase/server.ts — PT-BR: client para Server Components/Actions/Route Handlers
65
+ import { createServerClient } from '@supabase/ssr'
66
+ import { cookies } from 'next/headers'
67
+
68
+ export async function createClient() {
69
+ const cookieStore = await cookies()
70
+
71
+ return createServerClient(
72
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
73
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
74
+ {
75
+ cookies: {
76
+ getAll() {
77
+ return cookieStore.getAll()
78
+ },
79
+ setAll(cookiesToSet) {
80
+ try {
81
+ cookiesToSet.forEach(({ name, value, options }) =>
82
+ cookieStore.set(name, value, options)
83
+ )
84
+ } catch {
85
+ // PT-BR: ok ignorar — chamado em Server Component (sem permissão de set)
86
+ // se proxy faz refresh, sessão fica saudável mesmo sem set aqui
87
+ }
88
+ },
89
+ },
90
+ }
91
+ )
92
+ }
93
+ ```
94
+
95
+ ```tsx
96
+ // PT-BR: uso em Server Component
97
+ import { createClient } from '@/utils/supabase/server'
98
+
99
+ export default async function Dashboard() {
100
+ const supabase = await createClient()
101
+ const { data: { user } } = await supabase.auth.getUser()
102
+ if (!user) return <p>Não autenticado</p>
103
+ return <p>Olá, {user.email}</p>
104
+ }
105
+ ```
106
+
107
+ ### Middleware — refresh de sessão (obrigatório)
108
+
109
+ ```ts
110
+ // middleware.ts — PT-BR: proxy obrigatório para refresh de sessão SSR
111
+ import { createServerClient } from '@supabase/ssr'
112
+ import { NextResponse, type NextRequest } from 'next/server'
113
+
114
+ export async function middleware(request: NextRequest) {
115
+ let supabaseResponse = NextResponse.next({ request })
116
+
117
+ const supabase = createServerClient(
118
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
119
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
120
+ {
121
+ cookies: {
122
+ getAll() {
123
+ return request.cookies.getAll()
124
+ },
125
+ setAll(cookiesToSet) {
126
+ cookiesToSet.forEach(({ name, value }) =>
127
+ request.cookies.set(name, value)
128
+ )
129
+ supabaseResponse = NextResponse.next({ request })
130
+ cookiesToSet.forEach(({ name, value, options }) =>
131
+ supabaseResponse.cookies.set(name, value, options)
132
+ )
133
+ },
134
+ },
135
+ }
136
+ )
137
+
138
+ // PT-BR: ATENÇÃO — não execute código entre createServerClient e getUser()
139
+ // (qualquer cookie set/get fora desse path quebra refresh silencioso)
140
+ const { data: { user } } = await supabase.auth.getUser()
141
+
142
+ // PT-BR: redirect para /login se sem user
143
+ if (!user && !request.nextUrl.pathname.startsWith('/login')) {
144
+ const url = request.nextUrl.clone()
145
+ url.pathname = '/login'
146
+ return NextResponse.redirect(url)
147
+ }
148
+
149
+ // PT-BR: IMPORTANTE — sempre retornar supabaseResponse (cookies precisam fluir)
150
+ return supabaseResponse
151
+ }
152
+
153
+ export const config = {
154
+ matcher: [
155
+ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
156
+ ],
157
+ }
158
+ ```
159
+
160
+ ### Login com email/senha (Server Action)
161
+
162
+ ```ts
163
+ // app/login/actions.ts
164
+ 'use server'
165
+ import { createClient } from '@/utils/supabase/server'
166
+ import { redirect } from 'next/navigation'
167
+
168
+ export async function loginAction(formData: FormData) {
169
+ const supabase = await createClient()
170
+ const { error } = await supabase.auth.signInWithPassword({
171
+ email: formData.get('email') as string,
172
+ password: formData.get('password') as string,
173
+ })
174
+ if (error) return { error: error.message }
175
+ redirect('/dashboard')
176
+ }
177
+ ```
178
+
179
+ ## Anti-patterns
180
+
181
+ ### Anti-pattern 1: Importar de `@supabase/auth-helpers-nextjs`
182
+
183
+ **Errado:**
184
+ ```ts
185
+ import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
186
+ import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'
187
+ ```
188
+
189
+ **Por quê:** `@supabase/auth-helpers-nextjs` está **DEPRECATED**. Quebra em Next.js v16+ (cookies API mudou). Não recebe mais updates de segurança.
190
+
191
+ **Certo:**
192
+ ```ts
193
+ import { createServerClient, createBrowserClient } from '@supabase/ssr'
194
+ ```
195
+
196
+ ### Anti-pattern 2: `cookies: { get, set, remove }` (individual)
197
+
198
+ **Errado:**
199
+ ```ts
200
+ {
201
+ cookies: {
202
+ get(name: string) { return cookieStore.get(name) },
203
+ set(name: string, value: string) { cookieStore.set(name, value) },
204
+ remove(name: string) { cookieStore.remove(name) },
205
+ }
206
+ }
207
+ ```
208
+
209
+ **Por quê:** cookie methods individuais quebram em middleware quando há múltiplos cookies sendo set/get em uma única request. `getAll`/`setAll` são chamados em batch e preservam ordem.
210
+
211
+ **Certo:** ver pattern "Server client" e "Middleware" acima.
212
+
213
+ ### Anti-pattern 3: `NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY`
214
+
215
+ **Errado:**
216
+ ```bash
217
+ # .env.local
218
+ NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=eyJhbG...
219
+ ```
220
+
221
+ **Por quê:** `NEXT_PUBLIC_*` é **público** no client bundle. `service_role` bypassa RLS — vazamento = banco totalmente exposto.
222
+
223
+ **Certo:**
224
+ ```bash
225
+ # .env.local
226
+ NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
227
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbG... # ok público
228
+ SUPABASE_SERVICE_ROLE_KEY=eyJhbG... # privado — sem NEXT_PUBLIC_
229
+ ```
230
+
231
+ Use `service_role` apenas em código server-side que NUNCA é embarcado no bundle client (Route Handlers, Server Actions com `'use server'`, Edge Functions).
232
+
233
+ ### Anti-pattern 4: Múltiplos serverClient em layouts (race condition)
234
+
235
+ **Errado:**
236
+ ```tsx
237
+ // app/layout.tsx
238
+ const supabase1 = await createClient()
239
+ const { user } = await supabase1.auth.getUser()
240
+ // ...
241
+ // app/(dashboard)/layout.tsx
242
+ const supabase2 = await createClient() // ⚠ outro client na mesma request
243
+ const { user } = await supabase2.auth.getUser()
244
+ ```
245
+
246
+ **Por quê:** múltiplos `createServerClient` na mesma request podem corromper cookies de refresh de token. Issue [supabase/ssr#68](https://github.com/supabase/ssr/issues/68) — race condition documentada.
247
+
248
+ **Certo:** middleware faz o refresh **uma vez por request**. Layouts apenas leem o user via `getUser()` que retorna cached:
249
+ ```tsx
250
+ // app/layout.tsx — middleware já fez o refresh
251
+ const supabase = await createClient()
252
+ const { data: { user } } = await supabase.auth.getUser()
253
+ ```
254
+
255
+ ## Ver também
256
+
257
+ - [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — RLS aplicado quando user autenticado consulta tabelas
258
+ - [supabase-edge-functions](../supabase-edge-functions/SKILL.md) — Edge Functions usando service_role server-side
259
+ - [supabase-realtime](../supabase-realtime/SKILL.md) — Realtime exige usuário autenticado para canais privados
260
+ - [glossário](../_shared-supabase/glossary.md) — termos PT-BR↔EN + comandos CLI
@@ -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