@luanpdd/kit-mcp 1.29.0 → 1.30.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/LICENSE +21 -21
- package/README.md +168 -168
- package/gates/agent-no-recursive-dispatch.md +82 -82
- package/kit/COMANDOS.md +138 -138
- package/kit/README.md +76 -76
- package/kit/agents/advisor-researcher.md +106 -106
- package/kit/agents/assumptions-analyzer.md +107 -107
- package/kit/agents/audit-log-implementer.md +313 -313
- package/kit/agents/auditor-consistencia-isolamento.md +413 -413
- package/kit/agents/b2b-saas-architect.md +156 -156
- package/kit/agents/cascading-failures-auditor.md +298 -298
- package/kit/agents/codebase-mapper.md +768 -768
- package/kit/agents/crm-pipeline-implementer.md +256 -256
- package/kit/agents/debugger.md +813 -813
- package/kit/agents/detector-tenant-quente.md +337 -337
- package/kit/agents/evolution-go-integrator.md +200 -200
- package/kit/agents/example-reviewer.md +21 -21
- package/kit/agents/executor.md +564 -564
- package/kit/agents/integration-checker.md +200 -200
- package/kit/agents/invite-flow-implementer.md +189 -189
- package/kit/agents/legacy-characterizer.md +368 -368
- package/kit/agents/lgpd-compliance-auditor.md +295 -295
- package/kit/agents/multi-tenant-isolation-auditor.md +253 -253
- package/kit/agents/multi-tenant-rls-writer.md +340 -340
- package/kit/agents/nyquist-auditor.md +178 -178
- package/kit/agents/observability-coverage-auditor.md +315 -315
- package/kit/agents/org-onboarding-implementer.md +223 -223
- package/kit/agents/payload-capture-instrumenter.md +273 -273
- package/kit/agents/phase-researcher.md +696 -696
- package/kit/agents/plan-checker.md +272 -272
- package/kit/agents/planner.md +922 -922
- package/kit/agents/project-researcher.md +652 -652
- package/kit/agents/refactor-safety-auditor.md +404 -404
- package/kit/agents/research-synthesizer.md +245 -245
- package/kit/agents/roadmapper.md +677 -677
- package/kit/agents/seam-finder.md +359 -359
- package/kit/agents/shotgun-surgery-detector.md +349 -349
- package/kit/agents/supabase-branching-architect.md +562 -562
- package/kit/agents/supabase-cicd-pipeline-implementer.md +777 -777
- package/kit/agents/supabase-column-privileges-writer.md +399 -399
- package/kit/agents/supabase-edge-fn-tester.md +287 -0
- package/kit/agents/supabase-edge-fn-writer.md +239 -210
- package/kit/agents/supabase-migration-writer.md +385 -385
- package/kit/agents/supabase-rbac-implementer.md +392 -392
- package/kit/agents/supabase-realtime-implementer.md +363 -267
- package/kit/agents/supabase-rls-hardener.md +521 -521
- package/kit/agents/supabase-rls-writer.md +323 -323
- package/kit/agents/supabase-roles-implementer.md +355 -355
- package/kit/agents/super-admin-implementer.md +281 -281
- package/kit/agents/ui-auditor.md +437 -437
- package/kit/agents/ui-checker.md +302 -302
- package/kit/agents/ui-researcher.md +355 -355
- package/kit/agents/user-profiler.md +175 -175
- package/kit/agents/validador-evolucao-schema.md +335 -335
- package/kit/agents/verifier.md +728 -728
- package/kit/commands/adicionar-backlog.md +75 -75
- package/kit/commands/adicionar-fase.md +42 -42
- package/kit/commands/adicionar-tarefa.md +45 -45
- package/kit/commands/adicionar-testes.md +41 -41
- package/kit/commands/ajuda.md +21 -21
- package/kit/commands/atualizar.md +37 -37
- package/kit/commands/auditar-cascading.md +111 -111
- package/kit/commands/auditar-marco.md +179 -179
- package/kit/commands/auditar-observabilidade-cobertura.md +183 -183
- package/kit/commands/auditar-refactor.md +219 -219
- package/kit/commands/auditar-release.md +109 -109
- package/kit/commands/auditar-uat.md +23 -23
- package/kit/commands/autonomo.md +40 -40
- package/kit/commands/branch-pr.md +24 -24
- package/kit/commands/burn-rate-status.md +408 -408
- package/kit/commands/capturar-payloads.md +193 -193
- package/kit/commands/caracterizar.md +212 -212
- package/kit/commands/concluir-marco.md +247 -247
- package/kit/commands/configuracoes.md +36 -36
- package/kit/commands/dados-distribuidos.md +188 -188
- package/kit/commands/definir-perfil.md +10 -10
- package/kit/commands/depurar.md +190 -190
- package/kit/commands/detectar-duplicacao.md +197 -197
- package/kit/commands/discutir-fase.md +131 -131
- package/kit/commands/encontrar-seams.md +136 -136
- package/kit/commands/entrar-discord.md +17 -17
- package/kit/commands/estatisticas.md +18 -18
- package/kit/commands/example-greeting.md +33 -33
- package/kit/commands/executar-fase.md +58 -58
- package/kit/commands/expresso.md +56 -56
- package/kit/commands/fase-ui.md +34 -34
- package/kit/commands/fazer.md +57 -57
- package/kit/commands/fio.md +125 -125
- package/kit/commands/fluxos-trabalho.md +64 -64
- package/kit/commands/forense.md +176 -176
- package/kit/commands/gerenciador.md +38 -38
- package/kit/commands/inserir-fase.md +31 -31
- package/kit/commands/legacy.md +263 -263
- package/kit/commands/limpeza.md +17 -17
- package/kit/commands/listar-hipoteses-fase.md +45 -45
- package/kit/commands/listar-workspaces.md +18 -18
- package/kit/commands/load-shedding.md +117 -117
- package/kit/commands/mapear-codebase.md +70 -70
- package/kit/commands/multi-tenant.md +163 -163
- package/kit/commands/nota.md +33 -33
- package/kit/commands/novo-marco.md +43 -43
- package/kit/commands/novo-projeto.md +41 -41
- package/kit/commands/novo-workspace.md +43 -43
- package/kit/commands/pausar-trabalho.md +37 -37
- package/kit/commands/perfil-usuario.md +45 -45
- package/kit/commands/pesquisar-fase.md +195 -195
- package/kit/commands/planejar-fase.md +67 -67
- package/kit/commands/planejar-lacunas.md +33 -33
- package/kit/commands/plantar-ideia.md +25 -25
- package/kit/commands/progresso.md +24 -24
- package/kit/commands/proximo.md +30 -30
- package/kit/commands/publicar.md +490 -490
- package/kit/commands/rapido.md +35 -35
- package/kit/commands/reaplicar-patches.md +124 -124
- package/kit/commands/refactor-seguro.md +321 -321
- package/kit/commands/relatorio-sessao.md +19 -19
- package/kit/commands/remover-fase.md +31 -31
- package/kit/commands/remover-workspace.md +26 -26
- package/kit/commands/resumo-marco.md +50 -50
- package/kit/commands/retomar-trabalho.md +40 -40
- package/kit/commands/revisar-backlog.md +60 -60
- package/kit/commands/revisar-ui.md +32 -32
- package/kit/commands/revisar.md +37 -37
- package/kit/commands/saude.md +21 -21
- package/kit/commands/setup-notion.md +93 -93
- package/kit/commands/storytelling.md +179 -179
- package/kit/commands/supabase.md +30 -7
- package/kit/commands/sync-main.md +68 -68
- package/kit/commands/validar-fase.md +35 -35
- package/kit/commands/verificar-tarefas.md +44 -44
- package/kit/commands/verificar-trabalho.md +64 -64
- package/kit/file-manifest.json +15 -8
- package/kit/framework/bin/lib/commands.cjs +959 -959
- package/kit/framework/bin/lib/config.cjs +442 -442
- package/kit/framework/bin/lib/core.cjs +1230 -1230
- package/kit/framework/bin/lib/frontmatter.cjs +336 -336
- package/kit/framework/bin/lib/init.cjs +1442 -1442
- package/kit/framework/bin/lib/milestone.cjs +252 -252
- package/kit/framework/bin/lib/model-profiles.cjs +68 -68
- package/kit/framework/bin/lib/phase.cjs +888 -888
- package/kit/framework/bin/lib/profile-output.cjs +952 -952
- package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
- package/kit/framework/bin/lib/roadmap.cjs +329 -329
- package/kit/framework/bin/lib/security.cjs +382 -382
- package/kit/framework/bin/lib/state.cjs +1031 -1031
- package/kit/framework/bin/lib/template.cjs +222 -222
- package/kit/framework/bin/lib/uat.cjs +282 -282
- package/kit/framework/bin/lib/verify.cjs +888 -888
- package/kit/framework/bin/lib/workstream.cjs +491 -491
- package/kit/framework/bin/tools.cjs +918 -918
- package/kit/framework/commands/workstreams.md +63 -63
- package/kit/framework/references/checkpoints.md +778 -778
- package/kit/framework/references/continuation-format.md +249 -249
- package/kit/framework/references/decimal-phase-calculation.md +64 -64
- package/kit/framework/references/git-integration.md +295 -295
- package/kit/framework/references/git-planning-commit.md +38 -38
- package/kit/framework/references/model-profile-resolution.md +36 -36
- package/kit/framework/references/model-profiles.md +139 -139
- package/kit/framework/references/phase-argument-parsing.md +61 -61
- package/kit/framework/references/planning-config.md +202 -202
- package/kit/framework/references/questioning.md +162 -162
- package/kit/framework/references/tdd.md +263 -263
- package/kit/framework/references/ui-brand.md +160 -160
- package/kit/framework/references/user-profiling.md +657 -657
- package/kit/framework/references/verification-patterns.md +612 -612
- package/kit/framework/references/workstream-flag.md +58 -58
- package/kit/framework/templates/DEBUG.md +164 -164
- package/kit/framework/templates/UAT.md +265 -265
- package/kit/framework/templates/UI-SPEC.md +100 -100
- package/kit/framework/templates/VALIDATION.md +76 -76
- package/kit/framework/templates/claude-md.md +122 -122
- package/kit/framework/templates/codebase/architecture.md +185 -185
- package/kit/framework/templates/codebase/concerns.md +205 -205
- package/kit/framework/templates/codebase/conventions.md +204 -204
- package/kit/framework/templates/codebase/integrations.md +192 -192
- package/kit/framework/templates/codebase/stack.md +158 -158
- package/kit/framework/templates/codebase/structure.md +199 -199
- package/kit/framework/templates/codebase/testing.md +301 -301
- package/kit/framework/templates/config.json +44 -44
- package/kit/framework/templates/context.md +352 -352
- package/kit/framework/templates/continue-here.md +78 -78
- package/kit/framework/templates/copilot-instructions.md +7 -7
- package/kit/framework/templates/debug-subagent-prompt.md +91 -91
- package/kit/framework/templates/dev-preferences.md +20 -20
- package/kit/framework/templates/discovery.md +146 -146
- package/kit/framework/templates/discussion-log.md +63 -63
- package/kit/framework/templates/milestone-archive.md +123 -123
- package/kit/framework/templates/milestone.md +115 -115
- package/kit/framework/templates/phase-prompt.md +610 -610
- package/kit/framework/templates/planner-subagent-prompt.md +117 -117
- package/kit/framework/templates/project.md +186 -186
- package/kit/framework/templates/requirements.md +231 -231
- package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
- package/kit/framework/templates/research-project/FEATURES.md +147 -147
- package/kit/framework/templates/research-project/PITFALLS.md +200 -200
- package/kit/framework/templates/research-project/STACK.md +120 -120
- package/kit/framework/templates/research-project/SUMMARY.md +170 -170
- package/kit/framework/templates/research.md +419 -419
- package/kit/framework/templates/retrospective.md +54 -54
- package/kit/framework/templates/roadmap.md +202 -202
- package/kit/framework/templates/state.md +176 -176
- package/kit/framework/templates/summary-complex.md +59 -59
- package/kit/framework/templates/summary-minimal.md +41 -41
- package/kit/framework/templates/summary-standard.md +48 -48
- package/kit/framework/templates/summary.md +209 -209
- package/kit/framework/templates/user-profile.md +146 -146
- package/kit/framework/templates/user-setup.md +256 -256
- package/kit/framework/templates/verification-report.md +258 -258
- package/kit/framework/workflows/add-phase.md +112 -112
- package/kit/framework/workflows/add-tests.md +351 -351
- package/kit/framework/workflows/add-todo.md +158 -158
- package/kit/framework/workflows/audit-milestone.md +340 -340
- package/kit/framework/workflows/audit-uat.md +109 -109
- package/kit/framework/workflows/autonomous.md +891 -891
- package/kit/framework/workflows/check-todos.md +177 -177
- package/kit/framework/workflows/cleanup.md +152 -152
- package/kit/framework/workflows/complete-milestone.md +696 -696
- package/kit/framework/workflows/diagnose-issues.md +231 -231
- package/kit/framework/workflows/discovery-phase.md +289 -289
- package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
- package/kit/framework/workflows/discuss-phase.md +784 -784
- package/kit/framework/workflows/do.md +104 -104
- package/kit/framework/workflows/execute-phase.md +838 -838
- package/kit/framework/workflows/execute-plan.md +510 -510
- package/kit/framework/workflows/fast.md +102 -102
- package/kit/framework/workflows/forensics.md +265 -265
- package/kit/framework/workflows/health.md +181 -181
- package/kit/framework/workflows/help.md +619 -619
- package/kit/framework/workflows/insert-phase.md +130 -130
- package/kit/framework/workflows/list-phase-assumptions.md +178 -178
- package/kit/framework/workflows/list-workspaces.md +56 -56
- package/kit/framework/workflows/manager.md +362 -362
- package/kit/framework/workflows/map-codebase.md +377 -377
- package/kit/framework/workflows/milestone-summary.md +223 -223
- package/kit/framework/workflows/new-milestone.md +486 -486
- package/kit/framework/workflows/new-project.md +1159 -1159
- package/kit/framework/workflows/new-workspace.md +237 -237
- package/kit/framework/workflows/next.md +97 -97
- package/kit/framework/workflows/node-repair.md +92 -92
- package/kit/framework/workflows/note.md +156 -156
- package/kit/framework/workflows/pause-work.md +176 -176
- package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
- package/kit/framework/workflows/plan-phase.md +765 -765
- package/kit/framework/workflows/plant-seed.md +169 -169
- package/kit/framework/workflows/pr-branch.md +129 -129
- package/kit/framework/workflows/profile-user.md +450 -450
- package/kit/framework/workflows/progress.md +507 -507
- package/kit/framework/workflows/quick.md +757 -757
- package/kit/framework/workflows/remove-phase.md +155 -155
- package/kit/framework/workflows/remove-workspace.md +90 -90
- package/kit/framework/workflows/research-phase.md +82 -82
- package/kit/framework/workflows/resume-project.md +326 -326
- package/kit/framework/workflows/review.md +228 -228
- package/kit/framework/workflows/session-report.md +146 -146
- package/kit/framework/workflows/settings.md +283 -283
- package/kit/framework/workflows/ship.md +228 -228
- package/kit/framework/workflows/stats.md +60 -60
- package/kit/framework/workflows/transition.md +671 -671
- package/kit/framework/workflows/ui-phase.md +302 -302
- package/kit/framework/workflows/ui-review.md +165 -165
- package/kit/framework/workflows/update.md +323 -323
- package/kit/framework/workflows/validate-phase.md +174 -174
- package/kit/framework/workflows/verify-phase.md +252 -252
- package/kit/framework/workflows/verify-work.md +637 -637
- package/kit/hooks/check-update.js +118 -118
- package/kit/hooks/context-monitor.js +163 -163
- package/kit/hooks/kit-attribution-reminder.cjs +98 -0
- package/kit/hooks/prompt-guard.js +103 -103
- package/kit/hooks/statusline.js +125 -125
- package/kit/hooks/workflow-guard.js +101 -101
- package/kit/settings.json +45 -45
- package/kit/skills/_shared-supabase/glossary.md +17 -0
- package/kit/skills/ai-prompt-characterization/SKILL.md +335 -335
- package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -447
- package/kit/skills/audit-log-multi-tenant/SKILL.md +340 -340
- package/kit/skills/b2b-saas-architecture/SKILL.md +300 -300
- package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -385
- package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +343 -343
- package/kit/skills/escolha-modelo-consistencia/SKILL.md +494 -494
- package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -448
- package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +322 -322
- package/kit/skills/example-skill/SKILL.md +42 -42
- package/kit/skills/legacy-api-only-applications/SKILL.md +358 -358
- package/kit/skills/legacy-characterization-tests/SKILL.md +330 -330
- package/kit/skills/legacy-effect-analysis/SKILL.md +331 -331
- package/kit/skills/legacy-extract-class/SKILL.md +203 -203
- package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -252
- package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -460
- package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -286
- package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -434
- package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -270
- package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +340 -340
- package/kit/skills/member-invite-flow/SKILL.md +305 -305
- package/kit/skills/member-management-react-shadcn/SKILL.md +328 -328
- package/kit/skills/multi-tenant-performance-scaling/SKILL.md +316 -316
- package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +342 -342
- package/kit/skills/org-onboarding-flow/SKILL.md +257 -257
- package/kit/skills/org-switcher-react-pattern/SKILL.md +349 -349
- package/kit/skills/permission-gate-react-pattern/SKILL.md +271 -271
- package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -552
- package/kit/skills/pre-refactor-characterization/SKILL.md +421 -421
- package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +338 -338
- package/kit/skills/streams-eventos-cdc/SKILL.md +711 -711
- package/kit/skills/supabase-branching-workflow/SKILL.md +544 -544
- package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -880
- package/kit/skills/supabase-column-level-security/SKILL.md +426 -426
- package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -807
- package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -472
- package/kit/skills/supabase-edge-functions/SKILL.md +229 -141
- package/kit/skills/supabase-edge-functions-auth/SKILL.md +309 -0
- package/kit/skills/supabase-edge-functions-limits/SKILL.md +302 -0
- package/kit/skills/supabase-edge-functions-mcp-server/SKILL.md +279 -0
- package/kit/skills/supabase-edge-functions-testing/SKILL.md +277 -0
- package/kit/skills/supabase-edge-runtime-builtins/SKILL.md +357 -0
- package/kit/skills/supabase-migration-repair/SKILL.md +823 -823
- package/kit/skills/supabase-migrations/SKILL.md +297 -297
- package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -1053
- package/kit/skills/supabase-postgres-roles/SKILL.md +392 -392
- package/kit/skills/supabase-realtime/SKILL.md +460 -236
- package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -418
- package/kit/skills/supabase-rls-policies/SKILL.md +635 -635
- package/kit/skills/super-admin-platform-pattern/SKILL.md +326 -326
- package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -605
- package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +287 -287
- package/package.json +1 -1
- package/src/core/kit.js +216 -216
- package/src/core/reflect.js +247 -247
- package/src/core/reverse-sync.js +372 -372
- package/src/core/sync.js +418 -418
- package/src/core/watch.js +121 -121
- package/src/mcp-server/index.js +715 -693
|
@@ -1,236 +1,460 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: supabase-realtime
|
|
3
|
-
description: Use ao implementar Realtime — broadcast com private:true, naming scope:entity:id, RLS sobre realtime.messages, removeChannel cleanup, migrar de postgres_changes.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Supabase — Realtime
|
|
7
|
-
|
|
8
|
-
## Quando usar
|
|
9
|
-
|
|
10
|
-
LLM carrega esta skill quando implementar features Realtime em Supabase (chat, presence, notifications, live dashboards). Trigger phrases:
|
|
11
|
-
|
|
12
|
-
- "Supabase Realtime", "broadcast", "presence"
|
|
13
|
-
- "subscrever a mudanças no banco em tempo real"
|
|
14
|
-
- "WebSocket Supabase"
|
|
15
|
-
- "migrar postgres_changes para broadcast"
|
|
16
|
-
- "RLS realtime.messages"
|
|
17
|
-
- "channel state", "removeChannel"
|
|
18
|
-
|
|
19
|
-
## Regras absolutas
|
|
20
|
-
|
|
21
|
-
- **Use `broadcast` por default** — `postgres_changes` é pattern legado (single-threaded, não escala). **Migrar para broadcast** em features novas.
|
|
22
|
-
- **`private: true`** em todos os canais novos — exige autenticação + RLS sobre `realtime.messages`. Default em produção 2026.
|
|
23
|
-
- **Naming canônico `scope:entity:id`** — ex: `room:messages:abc123`, `user:notifications:xyz789`, `org:announcements:org_42`.
|
|
24
|
-
- **Eventos em `entity_action`** — ex: `message_inserted`, `task_updated`, `presence_joined`.
|
|
25
|
-
- **`removeChannel` no cleanup obrigatório** — chamar `supabase.removeChannel(channel)` em `useEffect return` ou equivalente. Sem cleanup, memory leak + stale state (anti-pitfall B1).
|
|
26
|
-
- **State checking antes de subscribe** — `if (channel.state === 'joined') return;` evita double-subscribe.
|
|
27
|
-
- **RLS sobre `realtime.messages`** — SELECT (read) e INSERT (write) policies separadas, com index nas colunas usadas.
|
|
28
|
-
- **Use Presence com moderação** — apenas para online status / cursors colaborativos, não para listas de objects (use queries normais).
|
|
29
|
-
- Realtime tem **retry built-in** — log `status` no callback do `subscribe` mas não implementar retry manual.
|
|
30
|
-
|
|
31
|
-
## Patterns canônicos
|
|
32
|
-
|
|
33
|
-
### Subscribe via broadcast — client com cleanup
|
|
34
|
-
|
|
35
|
-
```ts
|
|
36
|
-
// PT-BR: subscrição típica em Client Component
|
|
37
|
-
'use client'
|
|
38
|
-
import { useEffect, useState } from 'react'
|
|
39
|
-
import { createClient } from '@/utils/supabase/client'
|
|
40
|
-
|
|
41
|
-
export function ChatRoom({ roomId }: { roomId: string }) {
|
|
42
|
-
const supabase = createClient()
|
|
43
|
-
const [messages, setMessages] = useState<Message[]>([])
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
const channel = supabase
|
|
47
|
-
.channel(`room:messages:${roomId}`, { config: { private: true } })
|
|
48
|
-
.on('broadcast', { event: 'message_inserted' }, ({ payload }) => {
|
|
49
|
-
setMessages((prev) => [...prev, payload as Message])
|
|
50
|
-
})
|
|
51
|
-
.subscribe((status) => {
|
|
52
|
-
if (status === 'SUBSCRIBED') console.log('joined channel')
|
|
53
|
-
if (status === 'CHANNEL_ERROR') console.error('channel error')
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
// PT-BR: cleanup obrigatório — sem isso, memory leak
|
|
57
|
-
return () => {
|
|
58
|
-
supabase.removeChannel(channel)
|
|
59
|
-
}
|
|
60
|
-
}, [roomId, supabase])
|
|
61
|
-
|
|
62
|
-
return <ul>{messages.map((m) => <li key={m.id}>{m.text}</li>)}</ul>
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### RLS sobre `realtime.messages`
|
|
67
|
-
|
|
68
|
-
```sql
|
|
69
|
-
-- PT-BR: SELECT policy permite ouvir broadcast em canal autenticado
|
|
70
|
-
-- Granular: SELECT = read, INSERT = write — duas policies separadas
|
|
71
|
-
create policy "auth_select_realtime_messages"
|
|
72
|
-
on realtime.messages
|
|
73
|
-
for select
|
|
74
|
-
to authenticated
|
|
75
|
-
using ((select auth.uid()) is not null);
|
|
76
|
-
|
|
77
|
-
-- PT-BR: INSERT policy permite enviar broadcast
|
|
78
|
-
create policy "auth_insert_realtime_messages"
|
|
79
|
-
on realtime.messages
|
|
80
|
-
for insert
|
|
81
|
-
to authenticated
|
|
82
|
-
with check ((select auth.uid()) is not null);
|
|
83
|
-
|
|
84
|
-
-- PT-BR: index obrigatório (extension é a coluna usada por broadcast)
|
|
85
|
-
create index if not exists realtime_messages_extension_idx
|
|
86
|
-
on realtime.messages (extension);
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### DB trigger via `realtime.broadcast_changes`
|
|
90
|
-
|
|
91
|
-
Para emitir broadcast quando linha de tabela muda (substitui `postgres_changes`):
|
|
92
|
-
|
|
93
|
-
```sql
|
|
94
|
-
-- PT-BR: trigger function emite broadcast no canal scope:entity:id
|
|
95
|
-
create or replace function public.notify_message_insert()
|
|
96
|
-
returns trigger
|
|
97
|
-
language plpgsql
|
|
98
|
-
security invoker
|
|
99
|
-
set search_path = ''
|
|
100
|
-
as $$
|
|
101
|
-
begin
|
|
102
|
-
perform realtime.broadcast_changes(
|
|
103
|
-
'room:messages:' || new.room_id::text, -- canal
|
|
104
|
-
'message_inserted', -- event name
|
|
105
|
-
'INSERT', -- operation
|
|
106
|
-
'messages', -- table
|
|
107
|
-
'public', -- schema
|
|
108
|
-
new, -- new row
|
|
109
|
-
null -- old row
|
|
110
|
-
);
|
|
111
|
-
return new;
|
|
112
|
-
end;
|
|
113
|
-
$$;
|
|
114
|
-
|
|
115
|
-
create trigger messages_broadcast_on_insert
|
|
116
|
-
after insert on public.messages
|
|
117
|
-
for each row
|
|
118
|
-
execute function public.notify_message_insert();
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### Presence — apenas para online status
|
|
122
|
-
|
|
123
|
-
```ts
|
|
124
|
-
// PT-BR: presence é sparingly — só para "quem está online"
|
|
125
|
-
const channel = supabase
|
|
126
|
-
.channel(`room:${roomId}`, { config: { private: true } })
|
|
127
|
-
.on('presence', { event: 'sync' }, () => {
|
|
128
|
-
const state = channel.presenceState()
|
|
129
|
-
setOnlineUsers(Object.keys(state))
|
|
130
|
-
})
|
|
131
|
-
.subscribe(async (status) => {
|
|
132
|
-
if (status !== 'SUBSCRIBED') return
|
|
133
|
-
await channel.track({ user_id: userId, online_at: new Date().toISOString() })
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
return () => {
|
|
137
|
-
supabase.removeChannel(channel)
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Migrar de `postgres_changes` para `broadcast`
|
|
142
|
-
|
|
143
|
-
```ts
|
|
144
|
-
// ❌ PADRÃO LEGADO — postgres_changes
|
|
145
|
-
const channel = supabase
|
|
146
|
-
.channel('messages_changes')
|
|
147
|
-
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' }, callback)
|
|
148
|
-
.subscribe()
|
|
149
|
-
|
|
150
|
-
// ✅ PADRÃO ATUAL — broadcast com trigger DB
|
|
151
|
-
// 1. Criar trigger SQL `realtime.broadcast_changes` (ver pattern acima)
|
|
152
|
-
// 2. Subscribe via broadcast no client:
|
|
153
|
-
const channel = supabase
|
|
154
|
-
.channel(`room:messages:${roomId}`, { config: { private: true } })
|
|
155
|
-
.on('broadcast', { event: 'message_inserted' }, callback)
|
|
156
|
-
.subscribe()
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
1
|
+
---
|
|
2
|
+
name: supabase-realtime
|
|
3
|
+
description: Use ao implementar Realtime — broadcast com private:true, naming scope:entity:id, RLS sobre realtime.messages, removeChannel cleanup, migrar de postgres_changes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Supabase — Realtime
|
|
7
|
+
|
|
8
|
+
## Quando usar
|
|
9
|
+
|
|
10
|
+
LLM carrega esta skill quando implementar features Realtime em Supabase (chat, presence, notifications, live dashboards). Trigger phrases:
|
|
11
|
+
|
|
12
|
+
- "Supabase Realtime", "broadcast", "presence"
|
|
13
|
+
- "subscrever a mudanças no banco em tempo real"
|
|
14
|
+
- "WebSocket Supabase"
|
|
15
|
+
- "migrar postgres_changes para broadcast"
|
|
16
|
+
- "RLS realtime.messages"
|
|
17
|
+
- "channel state", "removeChannel"
|
|
18
|
+
|
|
19
|
+
## Regras absolutas
|
|
20
|
+
|
|
21
|
+
- **Use `broadcast` por default** — `postgres_changes` é pattern legado (single-threaded, não escala). **Migrar para broadcast** em features novas.
|
|
22
|
+
- **`private: true`** em todos os canais novos — exige autenticação + RLS sobre `realtime.messages`. Default em produção 2026.
|
|
23
|
+
- **Naming canônico `scope:entity:id`** — ex: `room:messages:abc123`, `user:notifications:xyz789`, `org:announcements:org_42`.
|
|
24
|
+
- **Eventos em `entity_action`** — ex: `message_inserted`, `task_updated`, `presence_joined`.
|
|
25
|
+
- **`removeChannel` no cleanup obrigatório** — chamar `supabase.removeChannel(channel)` em `useEffect return` ou equivalente. Sem cleanup, memory leak + stale state (anti-pitfall B1).
|
|
26
|
+
- **State checking antes de subscribe** — `if (channel.state === 'joined') return;` evita double-subscribe.
|
|
27
|
+
- **RLS sobre `realtime.messages`** — SELECT (read) e INSERT (write) policies separadas, com index nas colunas usadas.
|
|
28
|
+
- **Use Presence com moderação** — apenas para online status / cursors colaborativos, não para listas de objects (use queries normais).
|
|
29
|
+
- Realtime tem **retry built-in** — log `status` no callback do `subscribe` mas não implementar retry manual.
|
|
30
|
+
|
|
31
|
+
## Patterns canônicos
|
|
32
|
+
|
|
33
|
+
### Subscribe via broadcast — client com cleanup
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// PT-BR: subscrição típica em Client Component
|
|
37
|
+
'use client'
|
|
38
|
+
import { useEffect, useState } from 'react'
|
|
39
|
+
import { createClient } from '@/utils/supabase/client'
|
|
40
|
+
|
|
41
|
+
export function ChatRoom({ roomId }: { roomId: string }) {
|
|
42
|
+
const supabase = createClient()
|
|
43
|
+
const [messages, setMessages] = useState<Message[]>([])
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const channel = supabase
|
|
47
|
+
.channel(`room:messages:${roomId}`, { config: { private: true } })
|
|
48
|
+
.on('broadcast', { event: 'message_inserted' }, ({ payload }) => {
|
|
49
|
+
setMessages((prev) => [...prev, payload as Message])
|
|
50
|
+
})
|
|
51
|
+
.subscribe((status) => {
|
|
52
|
+
if (status === 'SUBSCRIBED') console.log('joined channel')
|
|
53
|
+
if (status === 'CHANNEL_ERROR') console.error('channel error')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// PT-BR: cleanup obrigatório — sem isso, memory leak
|
|
57
|
+
return () => {
|
|
58
|
+
supabase.removeChannel(channel)
|
|
59
|
+
}
|
|
60
|
+
}, [roomId, supabase])
|
|
61
|
+
|
|
62
|
+
return <ul>{messages.map((m) => <li key={m.id}>{m.text}</li>)}</ul>
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### RLS sobre `realtime.messages`
|
|
67
|
+
|
|
68
|
+
```sql
|
|
69
|
+
-- PT-BR: SELECT policy permite ouvir broadcast em canal autenticado
|
|
70
|
+
-- Granular: SELECT = read, INSERT = write — duas policies separadas
|
|
71
|
+
create policy "auth_select_realtime_messages"
|
|
72
|
+
on realtime.messages
|
|
73
|
+
for select
|
|
74
|
+
to authenticated
|
|
75
|
+
using ((select auth.uid()) is not null);
|
|
76
|
+
|
|
77
|
+
-- PT-BR: INSERT policy permite enviar broadcast
|
|
78
|
+
create policy "auth_insert_realtime_messages"
|
|
79
|
+
on realtime.messages
|
|
80
|
+
for insert
|
|
81
|
+
to authenticated
|
|
82
|
+
with check ((select auth.uid()) is not null);
|
|
83
|
+
|
|
84
|
+
-- PT-BR: index obrigatório (extension é a coluna usada por broadcast)
|
|
85
|
+
create index if not exists realtime_messages_extension_idx
|
|
86
|
+
on realtime.messages (extension);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### DB trigger via `realtime.broadcast_changes`
|
|
90
|
+
|
|
91
|
+
Para emitir broadcast quando linha de tabela muda (substitui `postgres_changes`):
|
|
92
|
+
|
|
93
|
+
```sql
|
|
94
|
+
-- PT-BR: trigger function emite broadcast no canal scope:entity:id
|
|
95
|
+
create or replace function public.notify_message_insert()
|
|
96
|
+
returns trigger
|
|
97
|
+
language plpgsql
|
|
98
|
+
security invoker
|
|
99
|
+
set search_path = ''
|
|
100
|
+
as $$
|
|
101
|
+
begin
|
|
102
|
+
perform realtime.broadcast_changes(
|
|
103
|
+
'room:messages:' || new.room_id::text, -- canal
|
|
104
|
+
'message_inserted', -- event name
|
|
105
|
+
'INSERT', -- operation
|
|
106
|
+
'messages', -- table
|
|
107
|
+
'public', -- schema
|
|
108
|
+
new, -- new row
|
|
109
|
+
null -- old row
|
|
110
|
+
);
|
|
111
|
+
return new;
|
|
112
|
+
end;
|
|
113
|
+
$$;
|
|
114
|
+
|
|
115
|
+
create trigger messages_broadcast_on_insert
|
|
116
|
+
after insert on public.messages
|
|
117
|
+
for each row
|
|
118
|
+
execute function public.notify_message_insert();
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Presence — apenas para online status
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// PT-BR: presence é sparingly — só para "quem está online"
|
|
125
|
+
const channel = supabase
|
|
126
|
+
.channel(`room:${roomId}`, { config: { private: true } })
|
|
127
|
+
.on('presence', { event: 'sync' }, () => {
|
|
128
|
+
const state = channel.presenceState()
|
|
129
|
+
setOnlineUsers(Object.keys(state))
|
|
130
|
+
})
|
|
131
|
+
.subscribe(async (status) => {
|
|
132
|
+
if (status !== 'SUBSCRIBED') return
|
|
133
|
+
await channel.track({ user_id: userId, online_at: new Date().toISOString() })
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
return () => {
|
|
137
|
+
supabase.removeChannel(channel)
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Migrar de `postgres_changes` para `broadcast`
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
// ❌ PADRÃO LEGADO — postgres_changes
|
|
145
|
+
const channel = supabase
|
|
146
|
+
.channel('messages_changes')
|
|
147
|
+
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' }, callback)
|
|
148
|
+
.subscribe()
|
|
149
|
+
|
|
150
|
+
// ✅ PADRÃO ATUAL — broadcast com trigger DB
|
|
151
|
+
// 1. Criar trigger SQL `realtime.broadcast_changes` (ver pattern acima)
|
|
152
|
+
// 2. Subscribe via broadcast no client:
|
|
153
|
+
const channel = supabase
|
|
154
|
+
.channel(`room:messages:${roomId}`, { config: { private: true } })
|
|
155
|
+
.on('broadcast', { event: 'message_inserted' }, callback)
|
|
156
|
+
.subscribe()
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `realtime.send` vs `realtime.broadcast_changes` — qual usar
|
|
160
|
+
|
|
161
|
+
A doc oficial expõe **duas funções SQL** para emitir broadcast a partir do banco. Escolha por intent:
|
|
162
|
+
|
|
163
|
+
| Função | Quando usar | Payload |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| `realtime.broadcast_changes(topic, event, op, table, schema, new, old)` | **Espelhar mudança de tabela** — INSERT/UPDATE/DELETE. Payload já formatado com schema/table/op/record. | Auto a partir de `NEW`/`OLD` |
|
|
166
|
+
| `realtime.send(payload jsonb, event text, topic text, is_private bool)` | **Notificação custom / payload filtrado** — eventos sem mapeamento 1:1 a row change. Você define exatamente o que vai. | Manual |
|
|
167
|
+
|
|
168
|
+
```sql
|
|
169
|
+
-- PT-BR: notificação custom — só campos públicos, sem PII
|
|
170
|
+
create or replace function public.notify_message_activity()
|
|
171
|
+
returns trigger
|
|
172
|
+
language plpgsql
|
|
173
|
+
security definer
|
|
174
|
+
set search_path = ''
|
|
175
|
+
as $$
|
|
176
|
+
begin
|
|
177
|
+
if tg_op = 'INSERT' then
|
|
178
|
+
perform realtime.send(
|
|
179
|
+
jsonb_build_object(
|
|
180
|
+
'message_id', new.id,
|
|
181
|
+
'room_id', new.room_id,
|
|
182
|
+
'created_at', new.created_at
|
|
183
|
+
-- author_id intencionalmente omitido (PII)
|
|
184
|
+
),
|
|
185
|
+
'message_created', -- event
|
|
186
|
+
'room:' || new.room_id::text || ':activity', -- topic
|
|
187
|
+
true -- is_private (default em prod)
|
|
188
|
+
);
|
|
189
|
+
end if;
|
|
190
|
+
return null;
|
|
191
|
+
end;
|
|
192
|
+
$$;
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
> O flag `is_private` no `realtime.send` **deve casar** com o `private` config no client. Public message para channel private = não entrega.
|
|
196
|
+
|
|
197
|
+
### REST API — broadcast server-side (sem WebSocket)
|
|
198
|
+
|
|
199
|
+
Para enviar broadcast a partir de um servidor (Edge Function, backend Next.js API route, worker, cron), use o endpoint HTTP em vez de manter um WebSocket aberto:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
// PT-BR: enviar broadcast via REST de um server — não precisa subscribe
|
|
203
|
+
const response = await fetch(
|
|
204
|
+
`https://${PROJECT_REF}.supabase.co/realtime/v1/api/broadcast`,
|
|
205
|
+
{
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers: {
|
|
208
|
+
'Content-Type': 'application/json',
|
|
209
|
+
apikey: process.env.SUPABASE_SERVICE_ROLE_KEY!, // server-only key
|
|
210
|
+
},
|
|
211
|
+
body: JSON.stringify({
|
|
212
|
+
messages: [
|
|
213
|
+
{
|
|
214
|
+
topic: `room:messages:${roomId}`,
|
|
215
|
+
event: 'message_inserted',
|
|
216
|
+
payload: { id, text, user_id },
|
|
217
|
+
private: true, // deve casar com o channel client-side
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
}),
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Tradeoff: HTTP add ~1 RTT por mensagem vs WebSocket persistente; mas elimina necessidade de manter conexão WS em serverless/edge functions.
|
|
226
|
+
|
|
227
|
+
### Broadcast Replay (v2.74.0+) — recuperar mensagens passadas
|
|
228
|
+
|
|
229
|
+
Disponível em `@supabase/supabase-js@2.74.0+`. Permite que clients **private** acessem broadcasts emitidos pelo DB (via `realtime.send`/`realtime.broadcast_changes`) **antes** de subscribar.
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
const channel = supabase.channel(`room:${roomId}`, {
|
|
233
|
+
config: {
|
|
234
|
+
private: true,
|
|
235
|
+
broadcast: {
|
|
236
|
+
replay: {
|
|
237
|
+
since: Date.now() - 60_000, // últimos 60s (epoch ms)
|
|
238
|
+
limit: 25, // máximo permitido pela spec
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
channel.on('broadcast', { event: 'message_inserted' }, ({ payload, meta }) => {
|
|
245
|
+
if (meta?.replayed) {
|
|
246
|
+
// PT-BR: mensagem do passado — não tocar som de notificação
|
|
247
|
+
appendMessage(payload, { historical: true })
|
|
248
|
+
} else {
|
|
249
|
+
appendMessage(payload, { historical: false })
|
|
250
|
+
}
|
|
251
|
+
}).subscribe()
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Quando usar:** chat com histórico recente, dashboards com últimos N eventos, reconexão após network drop, page reload sem perder estado. **Limitação:** só mensagens emitidas pelo DB (`realtime.send`/`broadcast_changes`); mensagens enviadas via client `channel.send()` NÃO são replayed.
|
|
255
|
+
|
|
256
|
+
### Authorization — usar `realtime.topic()` e JWT claims em RLS
|
|
257
|
+
|
|
258
|
+
Em policies sobre `realtime.messages`, helpers:
|
|
259
|
+
|
|
260
|
+
- **`realtime.topic()`** — retorna o nome do canal que o cliente está tentando entrar. Use para matchear policy contra app data (ex: room_id no topic).
|
|
261
|
+
- **`current_setting('request.jwt.claims')::json`** — acessa claims do JWT do client. Permite checks tipo "só usuário com role X", "só email @empresa.com".
|
|
262
|
+
|
|
263
|
+
```sql
|
|
264
|
+
-- PT-BR: só membros da room podem ouvir broadcast daquela room
|
|
265
|
+
-- Topic pattern: room:messages:<room_id> — split_part extrai room_id (3º segmento)
|
|
266
|
+
create policy "room_members_can_listen"
|
|
267
|
+
on realtime.messages
|
|
268
|
+
for select
|
|
269
|
+
to authenticated
|
|
270
|
+
using (
|
|
271
|
+
exists (
|
|
272
|
+
select 1
|
|
273
|
+
from public.room_members rm
|
|
274
|
+
where rm.user_id = (select auth.uid())
|
|
275
|
+
and rm.room_id::text = split_part(realtime.topic(), ':', 3)
|
|
276
|
+
)
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
-- PT-BR: claim-based — só usuários com role 'admin' podem broadcast em :admin topics
|
|
280
|
+
create policy "admin_role_can_broadcast_admin"
|
|
281
|
+
on realtime.messages
|
|
282
|
+
for insert
|
|
283
|
+
to authenticated
|
|
284
|
+
with check (
|
|
285
|
+
realtime.topic() like '%:admin:%'
|
|
286
|
+
and (current_setting('request.jwt.claims')::json->>'user_role') = 'admin'
|
|
287
|
+
);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
> RLS sobre `realtime.messages` é **avaliada na hora do JOIN do canal** (cache por sessão). Token refresh dispara reavaliação — ver "Custom JWT" abaixo.
|
|
291
|
+
|
|
292
|
+
### Custom JWT e refresh — `supabase.realtime.setAuth()`
|
|
293
|
+
|
|
294
|
+
Realtime mantém o token ativo do client em memória. Para tokens custom (assinados com seu JWT secret) ou refresh após expiração:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
// PT-BR: setar token customizado ANTES de subscribar canais
|
|
298
|
+
supabase.realtime.setAuth(customSignedJwt)
|
|
299
|
+
|
|
300
|
+
const channel = supabase
|
|
301
|
+
.channel('private-thing', { config: { private: true } })
|
|
302
|
+
.on('broadcast', { event: 'x' }, callback)
|
|
303
|
+
.subscribe()
|
|
304
|
+
|
|
305
|
+
// PT-BR: refresh quando token está perto de expirar
|
|
306
|
+
function onTokenRefreshed(newJwt: string) {
|
|
307
|
+
supabase.realtime.setAuth(newJwt)
|
|
308
|
+
// canais privados existentes mantêm conexão, mas RLS é reavaliada
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**NUNCA** exponha `service_role` no client — ele bypassa RLS. Token refresh deve ser feito proativamente; sem refresh, conexão fecha quando JWT expira (TTL default 1h no Supabase Auth).
|
|
313
|
+
|
|
314
|
+
### Self-send e Ack — opções para teste e confirmação
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
const channel = supabase.channel('test-channel', {
|
|
318
|
+
config: {
|
|
319
|
+
private: true,
|
|
320
|
+
broadcast: {
|
|
321
|
+
self: true, // PT-BR: receber as próprias broadcasts (default false) — útil em testes
|
|
322
|
+
ack: true, // PT-BR: confirmação do server (Promise resolve quando entregue)
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
channel.subscribe(async (status) => {
|
|
328
|
+
if (status !== 'SUBSCRIBED') return
|
|
329
|
+
const response = await channel.send({
|
|
330
|
+
type: 'broadcast',
|
|
331
|
+
event: 'test',
|
|
332
|
+
payload: {},
|
|
333
|
+
})
|
|
334
|
+
// PT-BR: com ack:true, response indica 'ok' | 'error' | 'timed_out'
|
|
335
|
+
console.log('delivery:', response)
|
|
336
|
+
})
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Use `self:true` em **dev/test only** (loops em prod). Use `ack:true` quando latência matters e você precisa retry (ex: chat com "enviada/falhou").
|
|
340
|
+
|
|
341
|
+
### `replica identity full` — old record em UPDATE/DELETE (postgres_changes legacy)
|
|
342
|
+
|
|
343
|
+
Aplica-se apenas a **`postgres_changes`** legacy (broadcast com trigger não precisa disso — você passa `old` explicit). Por default Postgres só envia o **primary key** do row antigo em UPDATE/DELETE pelo replication slot. Para receber **todas** as colunas anteriores:
|
|
344
|
+
|
|
345
|
+
```sql
|
|
346
|
+
-- PT-BR: replica identity full faz logical replication carregar row inteiro
|
|
347
|
+
alter table public.messages replica identity full;
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Custo: aumenta volume de WAL (toda coluna replicada). **DELETE com RLS não dispara para clients** — Postgres não consegue avaliar policies em rows que não existem mais; só PK aparece e mesmo assim sem auth check.
|
|
351
|
+
|
|
352
|
+
## Limits & quotas por plano
|
|
353
|
+
|
|
354
|
+
Limites canônicos da doc oficial — saber estes evita surpresa em prod:
|
|
355
|
+
|
|
356
|
+
| Métrica | Free | Pro | Pro no spend cap / Team | Enterprise |
|
|
357
|
+
|---|---|---|---|---|
|
|
358
|
+
| Concurrent connections | 200 | 500 | 10.000 | 10.000+ |
|
|
359
|
+
| Messages/sec | 100 | 500 | 2.500 | 2.500+ |
|
|
360
|
+
| Channel joins/sec | 100 | 500 | 2.500 | 2.500+ |
|
|
361
|
+
| Channels por connection | 100 | 100 | 100 | 100+ |
|
|
362
|
+
| Presence keys por objeto | 10 | 10 | 10 | 10+ |
|
|
363
|
+
| Presence msgs/sec | 20 | 50 | 1.000 | 1.000+ |
|
|
364
|
+
| Broadcast payload | 256 KB | 3 MB | 3 MB | 3+ MB |
|
|
365
|
+
| Postgres changes payload | 1 MB | 1 MB | 1 MB | 1+ MB |
|
|
366
|
+
|
|
367
|
+
**Pricing usage (pós-quota):** ~$2.50 / milhão de messages, ~$10 / mil peak connections (Pro). Quotas mensais: 2M msgs + 200 connections (Free), 5M msgs + 500 connections (Pro/Team).
|
|
368
|
+
|
|
369
|
+
## Error codes canônicos
|
|
370
|
+
|
|
371
|
+
Mensagens que aparecem em `subscribe(status, err)` callback ou em [Realtime Logs](https://supabase.com/dashboard/project/_/database/realtime-logs):
|
|
372
|
+
|
|
373
|
+
| Código | Significado | Fix |
|
|
374
|
+
|---|---|---|
|
|
375
|
+
| `too_many_connections` | Project bateu Concurrent Connections limit | Upgrade plan ou audit connection leak (cleanup faltando) |
|
|
376
|
+
| `too_many_joins` | Channel join rate excedido (joins/sec) | Throttle subscribes; usa 1 canal com vários filters em vez de N canais |
|
|
377
|
+
| `too_many_channels` | Channels por connection > 100 | Use 1 connection para N channels; nunca 1 connection por channel |
|
|
378
|
+
| `tenant_events` | Project excedeu Messages/sec | Reduza throughput ou upgrade plan; reconnect é automático quando baixa |
|
|
379
|
+
| `unauthorized` | RLS sobre `realtime.messages` negou; token inválido | Verifique policy + JWT válido + `private: true` casando com SQL `is_private` |
|
|
380
|
+
| `CHANNEL_ERROR` | Erro genérico — server-side issue | Veja Realtime Logs para detalhe; nunca silenciar este status |
|
|
381
|
+
| `TIMED_OUT` | WebSocket heartbeat não recebido (~30s) | Verifique network; em Node v18+ heartbeat custom pode ser necessário |
|
|
382
|
+
|
|
383
|
+
## Anti-patterns
|
|
384
|
+
|
|
385
|
+
### Anti-pattern 1: Canal sem `private: true`
|
|
386
|
+
|
|
387
|
+
**Errado:**
|
|
388
|
+
```ts
|
|
389
|
+
const channel = supabase.channel('messages') // canal público
|
|
390
|
+
.on('broadcast', { event: 'msg' }, callback)
|
|
391
|
+
.subscribe()
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
**Por quê:** canal público — qualquer cliente recebe payload sem RLS. Em produção isso vaza dados (broadcast pode incluir info sensível).
|
|
395
|
+
|
|
396
|
+
**Certo:**
|
|
397
|
+
```ts
|
|
398
|
+
const channel = supabase
|
|
399
|
+
.channel(`room:messages:${roomId}`, { config: { private: true } })
|
|
400
|
+
.on('broadcast', { event: 'message_inserted' }, callback)
|
|
401
|
+
.subscribe()
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Anti-pattern 2: Subscribe sem `removeChannel` no cleanup
|
|
405
|
+
|
|
406
|
+
**Errado:**
|
|
407
|
+
```tsx
|
|
408
|
+
useEffect(() => {
|
|
409
|
+
const channel = supabase.channel('...').subscribe()
|
|
410
|
+
// ⚠ sem return — canal nunca limpo
|
|
411
|
+
}, [])
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**Por quê:** memory leak. Em SPA com navegação, canais antigos continuam recebendo eventos — UI fica em estado inconsistente. WebSocket connections crescem indefinidamente.
|
|
415
|
+
|
|
416
|
+
**Certo:**
|
|
417
|
+
```tsx
|
|
418
|
+
useEffect(() => {
|
|
419
|
+
const channel = supabase.channel('...').subscribe()
|
|
420
|
+
return () => {
|
|
421
|
+
supabase.removeChannel(channel)
|
|
422
|
+
}
|
|
423
|
+
}, [])
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Anti-pattern 3: `postgres_changes` em features novas
|
|
427
|
+
|
|
428
|
+
**Errado:**
|
|
429
|
+
```ts
|
|
430
|
+
supabase.channel('changes')
|
|
431
|
+
.on('postgres_changes', { event: '*', schema: 'public', table: 'messages' }, callback)
|
|
432
|
+
.subscribe()
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Por quê:** `postgres_changes` é single-threaded em Realtime backend. Em escala (>100 connections, >1k events/sec), throughput cai drasticamente. Documentado em [Realtime Limits](https://supabase.com/docs/guides/realtime/limits).
|
|
436
|
+
|
|
437
|
+
**Certo:** trigger DB com `realtime.broadcast_changes` + subscribe via `broadcast` (ver pattern "Migrar" acima).
|
|
438
|
+
|
|
439
|
+
### Anti-pattern 4: Presence para listar objetos
|
|
440
|
+
|
|
441
|
+
**Errado:**
|
|
442
|
+
```ts
|
|
443
|
+
// ⚠ usar presence para listar tasks ativas
|
|
444
|
+
channel.on('presence', { event: 'sync' }, () => {
|
|
445
|
+
const tasks = Object.values(channel.presenceState())
|
|
446
|
+
setTasks(tasks)
|
|
447
|
+
})
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
**Por quê:** Presence é projetado para "quem está online" — state efêmero ligado a connection. Para listas de objetos, use query normal + broadcast quando muda. Presence inflado degrada toda a infraestrutura Realtime do projeto.
|
|
451
|
+
|
|
452
|
+
**Certo:** query SQL para `tasks` + broadcast em mudanças via trigger DB.
|
|
453
|
+
|
|
454
|
+
## Ver também
|
|
455
|
+
|
|
456
|
+
- [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — RLS sobre `realtime.messages` (SELECT + INSERT separados)
|
|
457
|
+
- [supabase-database-functions](../supabase-database-functions/SKILL.md) — trigger functions com `set search_path = ''`
|
|
458
|
+
- [supabase-auth-ssr](../supabase-auth-ssr/SKILL.md) — autenticação que habilita canais `private: true`
|
|
459
|
+
- [supabase-edge-functions](../supabase-edge-functions/SKILL.md) — Edge Functions disparando broadcast via `realtime.send`
|
|
460
|
+
- [glossário](../_shared-supabase/glossary.md) — termos PT-BR↔EN
|