@luanpdd/kit-mcp 1.30.2 → 1.31.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 (347) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +168 -168
  3. package/gates/agent-no-recursive-dispatch.md +84 -82
  4. package/kit/COMANDOS.md +138 -138
  5. package/kit/README.md +76 -76
  6. package/kit/agents/advisor-researcher.md +107 -106
  7. package/kit/agents/ai-mutation-tester.md +1 -0
  8. package/kit/agents/assumptions-analyzer.md +108 -107
  9. package/kit/agents/audit-log-implementer.md +314 -313
  10. package/kit/agents/auditor-consistencia-isolamento.md +414 -413
  11. package/kit/agents/b2b-saas-architect.md +157 -156
  12. package/kit/agents/burn-rate-forecaster.md +1 -0
  13. package/kit/agents/cascading-failures-auditor.md +299 -298
  14. package/kit/agents/codebase-mapper.md +769 -768
  15. package/kit/agents/crm-pipeline-implementer.md +257 -256
  16. package/kit/agents/debugger.md +814 -813
  17. package/kit/agents/detector-tenant-quente.md +338 -337
  18. package/kit/agents/evolution-go-integrator.md +201 -200
  19. package/kit/agents/example-reviewer.md +22 -21
  20. package/kit/agents/executor.md +565 -564
  21. package/kit/agents/golden-signals-instrumenter.md +1 -0
  22. package/kit/agents/incident-investigator.md +1 -0
  23. package/kit/agents/integration-checker.md +201 -200
  24. package/kit/agents/invite-flow-implementer.md +190 -189
  25. package/kit/agents/legacy-characterizer.md +369 -368
  26. package/kit/agents/lgpd-compliance-auditor.md +296 -295
  27. package/kit/agents/load-shedding-instrumenter.md +1 -0
  28. package/kit/agents/multi-tenant-isolation-auditor.md +254 -253
  29. package/kit/agents/multi-tenant-rls-writer.md +341 -340
  30. package/kit/agents/nyquist-auditor.md +179 -178
  31. package/kit/agents/observability-coverage-auditor.md +316 -315
  32. package/kit/agents/observability-instrumenter.md +1 -0
  33. package/kit/agents/omm-auditor.md +1 -0
  34. package/kit/agents/org-onboarding-implementer.md +224 -223
  35. package/kit/agents/payload-capture-instrumenter.md +274 -273
  36. package/kit/agents/phase-researcher.md +697 -696
  37. package/kit/agents/plan-checker.md +273 -272
  38. package/kit/agents/planner.md +923 -922
  39. package/kit/agents/postmortem-writer.md +1 -0
  40. package/kit/agents/project-researcher.md +653 -652
  41. package/kit/agents/prr-conductor.md +1 -0
  42. package/kit/agents/refactor-safety-auditor.md +405 -404
  43. package/kit/agents/release-pipeline-auditor.md +1 -0
  44. package/kit/agents/research-synthesizer.md +246 -245
  45. package/kit/agents/roadmapper.md +678 -677
  46. package/kit/agents/schema-checker.md +1 -0
  47. package/kit/agents/seam-finder.md +360 -359
  48. package/kit/agents/shotgun-surgery-detector.md +350 -349
  49. package/kit/agents/slo-engineer.md +1 -0
  50. package/kit/agents/storytelling-analyst.md +1 -0
  51. package/kit/agents/supabase-architect.md +1 -0
  52. package/kit/agents/supabase-auth-bootstrapper.md +1 -0
  53. package/kit/agents/supabase-branching-architect.md +563 -562
  54. package/kit/agents/supabase-cicd-pipeline-implementer.md +778 -777
  55. package/kit/agents/supabase-column-privileges-writer.md +400 -399
  56. package/kit/agents/supabase-edge-fn-tester.md +2 -1
  57. package/kit/agents/supabase-edge-fn-writer.md +2 -1
  58. package/kit/agents/supabase-migration-writer.md +386 -385
  59. package/kit/agents/supabase-rbac-implementer.md +393 -392
  60. package/kit/agents/supabase-realtime-implementer.md +364 -363
  61. package/kit/agents/supabase-rls-hardener.md +522 -521
  62. package/kit/agents/supabase-rls-writer.md +324 -323
  63. package/kit/agents/supabase-roles-implementer.md +356 -355
  64. package/kit/agents/supabase-storage-implementer.md +1 -0
  65. package/kit/agents/super-admin-implementer.md +282 -281
  66. package/kit/agents/toil-auditor.md +1 -0
  67. package/kit/agents/ui-auditor.md +438 -437
  68. package/kit/agents/ui-checker.md +303 -302
  69. package/kit/agents/ui-researcher.md +356 -355
  70. package/kit/agents/user-profiler.md +176 -175
  71. package/kit/agents/validador-evolucao-schema.md +336 -335
  72. package/kit/agents/verifier.md +729 -728
  73. package/kit/commands/adicionar-backlog.md +75 -75
  74. package/kit/commands/adicionar-fase.md +42 -42
  75. package/kit/commands/adicionar-tarefa.md +45 -45
  76. package/kit/commands/adicionar-testes.md +41 -41
  77. package/kit/commands/ajuda.md +21 -21
  78. package/kit/commands/atualizar.md +37 -37
  79. package/kit/commands/auditar-cascading.md +111 -111
  80. package/kit/commands/auditar-marco.md +179 -179
  81. package/kit/commands/auditar-observabilidade-cobertura.md +183 -183
  82. package/kit/commands/auditar-refactor.md +219 -219
  83. package/kit/commands/auditar-release.md +109 -109
  84. package/kit/commands/auditar-uat.md +23 -23
  85. package/kit/commands/autonomo.md +40 -40
  86. package/kit/commands/branch-pr.md +24 -24
  87. package/kit/commands/burn-rate-status.md +408 -408
  88. package/kit/commands/capturar-payloads.md +193 -193
  89. package/kit/commands/caracterizar.md +212 -212
  90. package/kit/commands/concluir-marco.md +247 -247
  91. package/kit/commands/configuracoes.md +36 -36
  92. package/kit/commands/dados-distribuidos.md +188 -188
  93. package/kit/commands/definir-perfil.md +10 -10
  94. package/kit/commands/depurar.md +190 -190
  95. package/kit/commands/detectar-duplicacao.md +197 -197
  96. package/kit/commands/discutir-fase.md +131 -131
  97. package/kit/commands/encontrar-seams.md +136 -136
  98. package/kit/commands/entrar-discord.md +17 -17
  99. package/kit/commands/estatisticas.md +18 -18
  100. package/kit/commands/example-greeting.md +33 -33
  101. package/kit/commands/executar-fase.md +58 -58
  102. package/kit/commands/expresso.md +56 -56
  103. package/kit/commands/fase-ui.md +34 -34
  104. package/kit/commands/fazer.md +57 -57
  105. package/kit/commands/fio.md +125 -125
  106. package/kit/commands/fluxos-trabalho.md +64 -64
  107. package/kit/commands/forense.md +176 -176
  108. package/kit/commands/gerenciador.md +38 -38
  109. package/kit/commands/inserir-fase.md +31 -31
  110. package/kit/commands/legacy.md +263 -263
  111. package/kit/commands/limpeza.md +17 -17
  112. package/kit/commands/listar-hipoteses-fase.md +45 -45
  113. package/kit/commands/listar-workspaces.md +18 -18
  114. package/kit/commands/load-shedding.md +117 -117
  115. package/kit/commands/mapear-codebase.md +70 -70
  116. package/kit/commands/multi-tenant.md +163 -163
  117. package/kit/commands/nota.md +33 -33
  118. package/kit/commands/novo-marco.md +43 -43
  119. package/kit/commands/novo-projeto.md +41 -41
  120. package/kit/commands/novo-workspace.md +43 -43
  121. package/kit/commands/pausar-trabalho.md +37 -37
  122. package/kit/commands/perfil-usuario.md +45 -45
  123. package/kit/commands/pesquisar-fase.md +195 -195
  124. package/kit/commands/planejar-fase.md +67 -67
  125. package/kit/commands/planejar-lacunas.md +33 -33
  126. package/kit/commands/plantar-ideia.md +25 -25
  127. package/kit/commands/progresso.md +24 -24
  128. package/kit/commands/proximo.md +30 -30
  129. package/kit/commands/publicar.md +490 -490
  130. package/kit/commands/rapido.md +35 -35
  131. package/kit/commands/reaplicar-patches.md +124 -124
  132. package/kit/commands/refactor-seguro.md +321 -321
  133. package/kit/commands/relatorio-sessao.md +19 -19
  134. package/kit/commands/remover-fase.md +31 -31
  135. package/kit/commands/remover-workspace.md +26 -26
  136. package/kit/commands/resumo-marco.md +50 -50
  137. package/kit/commands/retomar-trabalho.md +40 -40
  138. package/kit/commands/revisar-backlog.md +60 -60
  139. package/kit/commands/revisar-ui.md +32 -32
  140. package/kit/commands/revisar.md +37 -37
  141. package/kit/commands/saude.md +21 -21
  142. package/kit/commands/setup-notion.md +93 -93
  143. package/kit/commands/storytelling.md +179 -179
  144. package/kit/commands/sync-main.md +68 -68
  145. package/kit/commands/validar-fase.md +35 -35
  146. package/kit/commands/verificar-tarefas.md +44 -44
  147. package/kit/commands/verificar-trabalho.md +64 -64
  148. package/kit/file-manifest.json +82 -81
  149. package/kit/framework/bin/lib/commands.cjs +959 -959
  150. package/kit/framework/bin/lib/config.cjs +442 -442
  151. package/kit/framework/bin/lib/core.cjs +1230 -1230
  152. package/kit/framework/bin/lib/frontmatter.cjs +336 -336
  153. package/kit/framework/bin/lib/init.cjs +1442 -1442
  154. package/kit/framework/bin/lib/milestone.cjs +252 -252
  155. package/kit/framework/bin/lib/model-profiles.cjs +68 -68
  156. package/kit/framework/bin/lib/phase.cjs +888 -888
  157. package/kit/framework/bin/lib/profile-output.cjs +952 -952
  158. package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
  159. package/kit/framework/bin/lib/roadmap.cjs +329 -329
  160. package/kit/framework/bin/lib/security.cjs +382 -382
  161. package/kit/framework/bin/lib/state.cjs +1031 -1031
  162. package/kit/framework/bin/lib/template.cjs +222 -222
  163. package/kit/framework/bin/lib/uat.cjs +282 -282
  164. package/kit/framework/bin/lib/verify.cjs +888 -888
  165. package/kit/framework/bin/lib/workstream.cjs +491 -491
  166. package/kit/framework/bin/tools.cjs +918 -918
  167. package/kit/framework/commands/workstreams.md +63 -63
  168. package/kit/framework/references/checkpoints.md +778 -778
  169. package/kit/framework/references/continuation-format.md +249 -249
  170. package/kit/framework/references/decimal-phase-calculation.md +64 -64
  171. package/kit/framework/references/git-integration.md +295 -295
  172. package/kit/framework/references/git-planning-commit.md +38 -38
  173. package/kit/framework/references/model-profile-resolution.md +36 -36
  174. package/kit/framework/references/model-profiles.md +139 -139
  175. package/kit/framework/references/phase-argument-parsing.md +61 -61
  176. package/kit/framework/references/planning-config.md +202 -202
  177. package/kit/framework/references/questioning.md +162 -162
  178. package/kit/framework/references/tdd.md +263 -263
  179. package/kit/framework/references/ui-brand.md +160 -160
  180. package/kit/framework/references/user-profiling.md +657 -657
  181. package/kit/framework/references/verification-patterns.md +612 -612
  182. package/kit/framework/references/workstream-flag.md +58 -58
  183. package/kit/framework/templates/DEBUG.md +164 -164
  184. package/kit/framework/templates/UAT.md +265 -265
  185. package/kit/framework/templates/UI-SPEC.md +100 -100
  186. package/kit/framework/templates/VALIDATION.md +76 -76
  187. package/kit/framework/templates/claude-md.md +122 -122
  188. package/kit/framework/templates/codebase/architecture.md +185 -185
  189. package/kit/framework/templates/codebase/concerns.md +205 -205
  190. package/kit/framework/templates/codebase/conventions.md +204 -204
  191. package/kit/framework/templates/codebase/integrations.md +192 -192
  192. package/kit/framework/templates/codebase/stack.md +158 -158
  193. package/kit/framework/templates/codebase/structure.md +199 -199
  194. package/kit/framework/templates/codebase/testing.md +301 -301
  195. package/kit/framework/templates/config.json +44 -44
  196. package/kit/framework/templates/context.md +352 -352
  197. package/kit/framework/templates/continue-here.md +78 -78
  198. package/kit/framework/templates/copilot-instructions.md +7 -7
  199. package/kit/framework/templates/debug-subagent-prompt.md +91 -91
  200. package/kit/framework/templates/dev-preferences.md +20 -20
  201. package/kit/framework/templates/discovery.md +146 -146
  202. package/kit/framework/templates/discussion-log.md +63 -63
  203. package/kit/framework/templates/milestone-archive.md +123 -123
  204. package/kit/framework/templates/milestone.md +115 -115
  205. package/kit/framework/templates/phase-prompt.md +610 -610
  206. package/kit/framework/templates/planner-subagent-prompt.md +117 -117
  207. package/kit/framework/templates/project.md +186 -186
  208. package/kit/framework/templates/requirements.md +231 -231
  209. package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
  210. package/kit/framework/templates/research-project/FEATURES.md +147 -147
  211. package/kit/framework/templates/research-project/PITFALLS.md +200 -200
  212. package/kit/framework/templates/research-project/STACK.md +120 -120
  213. package/kit/framework/templates/research-project/SUMMARY.md +170 -170
  214. package/kit/framework/templates/research.md +419 -419
  215. package/kit/framework/templates/retrospective.md +54 -54
  216. package/kit/framework/templates/roadmap.md +202 -202
  217. package/kit/framework/templates/state.md +176 -176
  218. package/kit/framework/templates/summary-complex.md +59 -59
  219. package/kit/framework/templates/summary-minimal.md +41 -41
  220. package/kit/framework/templates/summary-standard.md +48 -48
  221. package/kit/framework/templates/summary.md +209 -209
  222. package/kit/framework/templates/user-profile.md +146 -146
  223. package/kit/framework/templates/user-setup.md +256 -256
  224. package/kit/framework/templates/verification-report.md +258 -258
  225. package/kit/framework/workflows/add-phase.md +112 -112
  226. package/kit/framework/workflows/add-tests.md +351 -351
  227. package/kit/framework/workflows/add-todo.md +158 -158
  228. package/kit/framework/workflows/audit-milestone.md +340 -340
  229. package/kit/framework/workflows/audit-uat.md +109 -109
  230. package/kit/framework/workflows/autonomous.md +891 -891
  231. package/kit/framework/workflows/check-todos.md +177 -177
  232. package/kit/framework/workflows/cleanup.md +152 -152
  233. package/kit/framework/workflows/complete-milestone.md +696 -696
  234. package/kit/framework/workflows/diagnose-issues.md +231 -231
  235. package/kit/framework/workflows/discovery-phase.md +289 -289
  236. package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
  237. package/kit/framework/workflows/discuss-phase.md +784 -784
  238. package/kit/framework/workflows/do.md +104 -104
  239. package/kit/framework/workflows/execute-phase.md +838 -838
  240. package/kit/framework/workflows/execute-plan.md +510 -510
  241. package/kit/framework/workflows/fast.md +102 -102
  242. package/kit/framework/workflows/forensics.md +265 -265
  243. package/kit/framework/workflows/health.md +181 -181
  244. package/kit/framework/workflows/help.md +619 -619
  245. package/kit/framework/workflows/insert-phase.md +130 -130
  246. package/kit/framework/workflows/list-phase-assumptions.md +178 -178
  247. package/kit/framework/workflows/list-workspaces.md +56 -56
  248. package/kit/framework/workflows/manager.md +362 -362
  249. package/kit/framework/workflows/map-codebase.md +377 -377
  250. package/kit/framework/workflows/milestone-summary.md +223 -223
  251. package/kit/framework/workflows/new-milestone.md +486 -486
  252. package/kit/framework/workflows/new-project.md +1159 -1159
  253. package/kit/framework/workflows/new-workspace.md +237 -237
  254. package/kit/framework/workflows/next.md +97 -97
  255. package/kit/framework/workflows/node-repair.md +92 -92
  256. package/kit/framework/workflows/note.md +156 -156
  257. package/kit/framework/workflows/pause-work.md +176 -176
  258. package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
  259. package/kit/framework/workflows/plan-phase.md +765 -765
  260. package/kit/framework/workflows/plant-seed.md +169 -169
  261. package/kit/framework/workflows/pr-branch.md +129 -129
  262. package/kit/framework/workflows/profile-user.md +450 -450
  263. package/kit/framework/workflows/progress.md +507 -507
  264. package/kit/framework/workflows/quick.md +757 -757
  265. package/kit/framework/workflows/remove-phase.md +155 -155
  266. package/kit/framework/workflows/remove-workspace.md +90 -90
  267. package/kit/framework/workflows/research-phase.md +82 -82
  268. package/kit/framework/workflows/resume-project.md +326 -326
  269. package/kit/framework/workflows/review.md +228 -228
  270. package/kit/framework/workflows/session-report.md +146 -146
  271. package/kit/framework/workflows/settings.md +283 -283
  272. package/kit/framework/workflows/ship.md +228 -228
  273. package/kit/framework/workflows/stats.md +60 -60
  274. package/kit/framework/workflows/transition.md +671 -671
  275. package/kit/framework/workflows/ui-phase.md +302 -302
  276. package/kit/framework/workflows/ui-review.md +165 -165
  277. package/kit/framework/workflows/update.md +323 -323
  278. package/kit/framework/workflows/validate-phase.md +174 -174
  279. package/kit/framework/workflows/verify-phase.md +252 -252
  280. package/kit/framework/workflows/verify-work.md +637 -637
  281. package/kit/hooks/check-update.js +118 -118
  282. package/kit/hooks/context-monitor.js +163 -163
  283. package/kit/hooks/kit-attribution-reminder.cjs +29 -50
  284. package/kit/hooks/kit-router.cjs +137 -0
  285. package/kit/hooks/prompt-guard.js +103 -103
  286. package/kit/hooks/statusline.js +125 -125
  287. package/kit/hooks/workflow-guard.js +101 -101
  288. package/kit/settings.json +45 -45
  289. package/kit/skills/ai-prompt-characterization/SKILL.md +335 -335
  290. package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -447
  291. package/kit/skills/audit-log-multi-tenant/SKILL.md +340 -340
  292. package/kit/skills/b2b-saas-architecture/SKILL.md +300 -300
  293. package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -385
  294. package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +343 -343
  295. package/kit/skills/escolha-modelo-consistencia/SKILL.md +494 -494
  296. package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -448
  297. package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +322 -322
  298. package/kit/skills/example-skill/SKILL.md +42 -42
  299. package/kit/skills/legacy-api-only-applications/SKILL.md +358 -358
  300. package/kit/skills/legacy-characterization-tests/SKILL.md +330 -330
  301. package/kit/skills/legacy-effect-analysis/SKILL.md +331 -331
  302. package/kit/skills/legacy-extract-class/SKILL.md +203 -203
  303. package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -252
  304. package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -460
  305. package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -286
  306. package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -434
  307. package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -270
  308. package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +340 -340
  309. package/kit/skills/member-invite-flow/SKILL.md +305 -305
  310. package/kit/skills/member-management-react-shadcn/SKILL.md +328 -328
  311. package/kit/skills/multi-tenant-performance-scaling/SKILL.md +316 -316
  312. package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +342 -342
  313. package/kit/skills/org-onboarding-flow/SKILL.md +257 -257
  314. package/kit/skills/org-switcher-react-pattern/SKILL.md +349 -349
  315. package/kit/skills/permission-gate-react-pattern/SKILL.md +271 -271
  316. package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -552
  317. package/kit/skills/pre-refactor-characterization/SKILL.md +421 -421
  318. package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +338 -338
  319. package/kit/skills/streams-eventos-cdc/SKILL.md +711 -711
  320. package/kit/skills/supabase-branching-workflow/SKILL.md +544 -544
  321. package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -880
  322. package/kit/skills/supabase-column-level-security/SKILL.md +426 -426
  323. package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -807
  324. package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -472
  325. package/kit/skills/supabase-edge-functions/SKILL.md +1 -1
  326. package/kit/skills/supabase-edge-functions-auth/SKILL.md +1 -1
  327. package/kit/skills/supabase-edge-functions-limits/SKILL.md +1 -1
  328. package/kit/skills/supabase-edge-functions-mcp-server/SKILL.md +1 -1
  329. package/kit/skills/supabase-edge-functions-testing/SKILL.md +1 -1
  330. package/kit/skills/supabase-edge-runtime-builtins/SKILL.md +1 -1
  331. package/kit/skills/supabase-migration-repair/SKILL.md +823 -823
  332. package/kit/skills/supabase-migrations/SKILL.md +297 -297
  333. package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -1053
  334. package/kit/skills/supabase-postgres-roles/SKILL.md +392 -392
  335. package/kit/skills/supabase-realtime/SKILL.md +460 -460
  336. package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -418
  337. package/kit/skills/supabase-rls-policies/SKILL.md +635 -635
  338. package/kit/skills/super-admin-platform-pattern/SKILL.md +326 -326
  339. package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -605
  340. package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +287 -287
  341. package/package.json +1 -1
  342. package/src/core/kit.js +216 -216
  343. package/src/core/reflect.js +247 -247
  344. package/src/core/reverse-sync.js +372 -372
  345. package/src/core/sync.js +437 -418
  346. package/src/core/watch.js +121 -121
  347. package/src/mcp-server/index.js +794 -746
@@ -1,460 +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
- ### `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
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