@luanpdd/kit-mcp 1.35.0 → 1.36.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 (117) hide show
  1. package/bin/cli.js +2 -2
  2. package/bin/mcp.js +6 -6
  3. package/bin/ui.js +74 -74
  4. package/gates/ai-prompt-stability.md +120 -120
  5. package/gates/budget-description.md +68 -68
  6. package/gates/confidence.md +29 -29
  7. package/gates/dependency-check.md +33 -33
  8. package/gates/dept-cycle-prevention.md +179 -179
  9. package/gates/golden-signals-coverage.md +133 -133
  10. package/gates/legacy-refactor-safety.md +178 -178
  11. package/gates/multi-tenant-rls-coverage.md +102 -102
  12. package/gates/no-personal-uuid.md +72 -72
  13. package/gates/obs-agents-mcp-supabase.md +86 -86
  14. package/gates/obs-skills-frontmatter.md +76 -76
  15. package/gates/observability-coverage.md +151 -151
  16. package/gates/omm-no-regression.md +83 -83
  17. package/gates/postmortem-template-required.md +127 -127
  18. package/gates/prr-checklist-coverage.md +128 -128
  19. package/gates/regression.md +32 -32
  20. package/gates/release-pipeline-policy.md +132 -132
  21. package/gates/secrets-scan.md +33 -33
  22. package/gates/service-role-not-in-user-facing.md +113 -113
  23. package/gates/skill-must-include.md +71 -71
  24. package/gates/sync-idempotent.md +62 -62
  25. package/gates/verify-phase-goal.md +34 -34
  26. package/kit/agents/designer-ui.md +216 -216
  27. package/kit/agents/workflow-generator.md +537 -167
  28. package/kit/commands/adicionar-backlog.md +1 -1
  29. package/kit/commands/adicionar-fase.md +1 -1
  30. package/kit/commands/adicionar-tarefa.md +1 -1
  31. package/kit/commands/auditar-observabilidade.md +103 -103
  32. package/kit/commands/auditar-toil.md +129 -129
  33. package/kit/commands/caracterizar-prompt.md +195 -195
  34. package/kit/commands/criar-workflow.md +158 -158
  35. package/kit/commands/definir-perfil.md +1 -1
  36. package/kit/commands/definir-slo.md +108 -108
  37. package/kit/commands/fio.md +1 -1
  38. package/kit/commands/golden-signals.md +142 -142
  39. package/kit/commands/instrumentar-fase.md +200 -200
  40. package/kit/commands/investigar-producao.md +162 -162
  41. package/kit/commands/observabilidade.md +118 -118
  42. package/kit/commands/postmortem.md +179 -179
  43. package/kit/commands/prr.md +205 -205
  44. package/kit/commands/publicar-rapido.md +207 -207
  45. package/kit/commands/risk-budget.md +220 -220
  46. package/kit/commands/sre.md +230 -230
  47. package/kit/file-manifest.json +424 -424
  48. package/kit/framework/references/output-style.md +22 -22
  49. package/kit/hooks/post-apply-migration.js +199 -199
  50. package/kit/hooks/sidecar-tool-publisher.js +210 -210
  51. package/kit/skills/_shared-dados-distribuidos/glossary.md +224 -224
  52. package/kit/skills/_shared-legacy/glossary.md +389 -389
  53. package/kit/skills/_shared-multi-tenant/glossary.md +186 -186
  54. package/kit/skills/_shared-observability/glossary.md +396 -396
  55. package/kit/skills/_shared-sre/glossary.md +712 -712
  56. package/kit/skills/_shared-supabase/glossary.md +234 -234
  57. package/kit/skills/blameless-postmortems/SKILL.md +340 -340
  58. package/kit/skills/burn-rate-alerting/SKILL.md +258 -258
  59. package/kit/skills/cascading-failures/SKILL.md +311 -311
  60. package/kit/skills/core-analysis-loop/SKILL.md +352 -352
  61. package/kit/skills/distributed-tracing/SKILL.md +362 -362
  62. package/kit/skills/dynamic-workflow-authoring/SKILL.md +327 -223
  63. package/kit/skills/eliminating-toil/SKILL.md +243 -243
  64. package/kit/skills/event-based-slos/SKILL.md +296 -296
  65. package/kit/skills/four-golden-signals/SKILL.md +314 -314
  66. package/kit/skills/hermetic-builds/SKILL.md +323 -323
  67. package/kit/skills/legacy-monster-methods/SKILL.md +444 -444
  68. package/kit/skills/llm-as-dependency/SKILL.md +436 -436
  69. package/kit/skills/load-shedding-graceful-degradation/SKILL.md +396 -396
  70. package/kit/skills/observability-driven-development/SKILL.md +315 -315
  71. package/kit/skills/observability-maturity-model/SKILL.md +222 -222
  72. package/kit/skills/opentelemetry-standard/SKILL.md +351 -351
  73. package/kit/skills/production-readiness-review/SKILL.md +305 -305
  74. package/kit/skills/release-engineering/SKILL.md +367 -367
  75. package/kit/skills/retry-strategies/SKILL.md +372 -372
  76. package/kit/skills/sre-risk-management/SKILL.md +221 -221
  77. package/kit/skills/structured-events/SKILL.md +265 -265
  78. package/kit/skills/supabase-cron-queues/SKILL.md +275 -275
  79. package/kit/skills/supabase-database-functions/SKILL.md +332 -332
  80. package/kit/skills/supabase-declarative-schema/SKILL.md +183 -183
  81. package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -253
  82. package/kit/skills/supabase-postgres-style/SKILL.md +138 -138
  83. package/kit/skills/supabase-storage/SKILL.md +234 -234
  84. package/kit/skills/telemetry-pipelines/SKILL.md +259 -259
  85. package/kit/skills/telemetry-sampling/SKILL.md +256 -256
  86. package/kit/skills/ui-anti-padroes-ia/SKILL.md +261 -261
  87. package/kit/skills/ui-contexto-produto/SKILL.md +248 -248
  88. package/kit/skills/ui-cor-estrategia/SKILL.md +213 -213
  89. package/kit/skills/ui-critica-auditoria/SKILL.md +260 -260
  90. package/kit/skills/ui-motion-funcional/SKILL.md +264 -264
  91. package/kit/skills/ui-ritmo-espacial/SKILL.md +259 -259
  92. package/kit/skills/ui-tipografia/SKILL.md +211 -211
  93. package/package.json +1 -1
  94. package/src/cli/index.js +1114 -1114
  95. package/src/cli/render.js +194 -194
  96. package/src/cli/upgrade-check.js +135 -135
  97. package/src/core/error-redaction.js +76 -76
  98. package/src/core/failures.js +153 -153
  99. package/src/core/gate-runner.js +205 -205
  100. package/src/core/gates.js +82 -82
  101. package/src/core/logger.js +170 -170
  102. package/src/core/manifest-verify.js +174 -174
  103. package/src/core/metrics.js +268 -268
  104. package/src/core/notify.js +60 -60
  105. package/src/core/path-safety.js +141 -141
  106. package/src/core/replays.js +120 -120
  107. package/src/core/ui.js +185 -185
  108. package/src/mcp-server/install.js +149 -149
  109. package/src/mcp-server/roots.js +124 -124
  110. package/src/ui/auto-spawn.js +113 -113
  111. package/src/ui/browser.js +78 -78
  112. package/src/ui/client.js +130 -130
  113. package/src/ui/events.js +65 -65
  114. package/src/ui/lockfile.js +191 -191
  115. package/src/ui/port.js +67 -67
  116. package/src/ui/server.js +547 -547
  117. package/src/ui/wrapper.js +129 -129
@@ -1,275 +1,275 @@
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
- ## Padrões Exactly-Once em pgmq (v1.22+)
262
-
263
- > Background jobs em pgmq tendem a duplicate processing em retry/timeout. Padrão canônico (DDIA Ch 11):
264
- > 1. **Dedup table** com `unique(event_id)` — INSERT antes do processamento; falha = já processado.
265
- > 2. **Idempotency key** no handler — mesmo input → mesmo output (sem efeitos colaterais).
266
- > 3. **Transactional outbox** — write DB + event em mesma transação atomic; processador async lê outbox e publica.
267
- >
268
- > Detalhes completos em [`streams-eventos-cdc`](../streams-eventos-cdc/SKILL.md) (v1.22).
269
-
270
- ## Ver também
271
-
272
- - [supabase-edge-functions](../supabase-edge-functions/SKILL.md) — Edge Functions consumindo pgmq
273
- - [supabase-database-functions](../supabase-database-functions/SKILL.md) — funções com `set search_path = ''` chamadas em cron
274
- - [supabase-migrations](../supabase-migrations/SKILL.md) — extensions criadas em migrations
275
- - [glossário](../_shared-supabase/glossary.md) — pg_cron, pgmq, pg_net
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
+ ## Padrões Exactly-Once em pgmq (v1.22+)
262
+
263
+ > Background jobs em pgmq tendem a duplicate processing em retry/timeout. Padrão canônico (DDIA Ch 11):
264
+ > 1. **Dedup table** com `unique(event_id)` — INSERT antes do processamento; falha = já processado.
265
+ > 2. **Idempotency key** no handler — mesmo input → mesmo output (sem efeitos colaterais).
266
+ > 3. **Transactional outbox** — write DB + event em mesma transação atomic; processador async lê outbox e publica.
267
+ >
268
+ > Detalhes completos em [`streams-eventos-cdc`](../streams-eventos-cdc/SKILL.md) (v1.22).
269
+
270
+ ## Ver também
271
+
272
+ - [supabase-edge-functions](../supabase-edge-functions/SKILL.md) — Edge Functions consumindo pgmq
273
+ - [supabase-database-functions](../supabase-database-functions/SKILL.md) — funções com `set search_path = ''` chamadas em cron
274
+ - [supabase-migrations](../supabase-migrations/SKILL.md) — extensions criadas em migrations
275
+ - [glossário](../_shared-supabase/glossary.md) — pg_cron, pgmq, pg_net