fayzfayz sdk

Modelo de dados

Para um dev externo, o modelo de dados do Fayz colapsa numa ideia só: você traz o seu próprio Supabase (BYOS) e o seu projeto recebe a mesma forma de schema que a plataforma usa em escala. Essa forma tem três camadas — a biblioteca core, as tabelas de plugin (plg_) e as tabelas do seu app — e uma regra única que faz tudo se encaixar: tenant_id + RLS é a fronteira de isolamento. Acerte isso e a dúvida clássica — "onde essa tabela mora?" — desaparece.

public.*      tabelas-base que todo negócio precisa (tenants, people, tenant_members, role_permissions…)
plg_*         tabelas que um plugin instala e possui (plg_shop_products, plg_courses_*…)
suas tabelas  o que o seu app/incubator cria por cima (mesmas regras: tenant_id + RLS)

Arquitetura em transição. Esta documentação descreve a forma-alvo — a biblioteca core e as tabelas de plugin com prefixo plg_ — que está sendo lançada com a wave de industry pools. Hoje as tabelas-base não vivem num schema core. separado: moram todas em public.* (a migração 001 é explícita — all core entities live directly in the public schema), e o prefixo plg_ é uma convenção já adotada por alguns módulos, não uma regra que todos cumprem ainda. O comando e o fluxo já são estes; os rótulos e a nomenclatura migram junto com a wave.

A biblioteca core — as tabelas-base

A biblioteca core (antes chamada saas_core) traz o que todo negócio precisa. Não é um schema separado com regras próprias — as tabelas moram em public.*, prontas e isoladas:

Tabela coreO que guarda
tenantsos clientes do seu SaaS (um salão, uma clínica) — a raiz do isolamento
peoplepessoas do negócio: clientes, contatos, leads
tenant_membersquem trabalha no tenant (profissionais, atendentes)
role_permissionspapéis e permissões dentro do tenant
appointmentsagendamentos, reservas, encontros
transactionsmovimentos financeiros: entradas e saídas

Toda tabela carrega tenant_id e tem RLS. É esse par — coluna + policy — que garante que a linha de um tenant seja fisicamente invisível para outro. O isolamento não vem de separar schemas; vem do RLS chaveado no tenant_id. Esse é o ponto que sustenta o resto do modelo, e o detalhe está em RLS e multi-tenant.

Tabelas de plugin — o prefixo plg_ é a posse

Um plugin instala as suas próprias tabelas com o prefixo plg_ (por exemplo plg_shop_products, plg_courses_*). O prefixo sinaliza posse: se a tabela começa com plg_, ela pertence a um plugin, não ao core nem ao seu app. É uma convenção — adotada hoje por shop e courses e recomendada para plugins novos — não uma regra que todo módulo já cumpre (alguns conectores criam tabelas sem prefixo).

Veja as tabelas plg_ que cada plugin instala — com as colunas-chave — na ficha do plugin no catálogo.

Além das tabelas que cria, um plugin reutiliza as tabelas do core que precisa, em vez de copiá-las. Se o plugin de agenda precisa saber "quem marcou", ele referencia public.people; ele nunca guarda uma cópia da pessoa. Uma fonte de verdade por entidade.

Cada plugin embarca as suas migrations, e elas seguem regras que fazem o db apply ser reproduzível:

  • ordenadas — a sequência de aplicação é determinística.
  • versionadas — cada migration tem uma versão; o runner sabe o que já rodou.
  • fix-forward — migrations são append-only: nunca edite uma migração já aplicada, escreva a próxima que corrige. Não há .down.sql.
  • idempotentes — rodar de novo não quebra nem duplica.

As tabelas do seu app

Quando o seu produto precisa de campos ou tabelas que nem o core nem um plugin trazem, o seu app (ou um plugin incubator) cria as próprias tabelas. É onde a riqueza específica do seu domínio vive. A regra não muda: precisam carregar tenant_id e ter RLS, exatamente como o core e os plugins. Suas tabelas não são exceção ao isolamento — são iguais a todas as outras.

A regra de ouro: mesma forma, três tiers

A mesma forma de schema — core + plg_ + suas tabelas, tudo isolado por tenant_id + RLS — vale onde quer que o app rode. É isso que faz o seu BYOS ser idêntico ao que a plataforma opera em escala.

Como a plataforma hospeda em escala (contexto — o seu BYOS é a mesma forma). A plataforma serve três tiers com o mesmo shape: ① um pool por indústria — um projeto Supabase compartilhado por muitos tenants da mesma indústria, isolados só por tenant_id + RLS; ② objetos customizados — tabelas extras no mesmo pool via um mecanismo genérico, sem DDL (ainda sem contrato externo estável); ③ banco dedicado — um projeto só seu, com o mesmo shape, onde promover é copiar os dados e trocar a string de conexão. Você não precisa disso para construir: o seu BYOS já nasce na mesma forma, e migrar de tier nunca reescreve o schema.

Website e admin: mesmas tabelas, RLS diferente

Um erro comum é criar pares de tabela public_* (para o site) e admin_* (para o painel). O Fayz nunca faz isso. O lado do site e o lado do admin leem as mesmas tabelas — o que muda é a policy de RLS e o papel de quem consulta: a equipe do tenant enxerga as linhas do tenant; o cliente final enxerga só as próprias. Uma tabela, duas visões, garantidas pelo banco — não duas tabelas para manter em sincronia.

Por que isso importa na prática

Essa forma única é o que faz o cliente do CRM e a reserva da agenda concordarem sobre o que é "uma pessoa": os dois apontam para a mesma public.people. Você não precisa montar nada disso à mão — o fayz db apply cria as camadas na ordem certa (core primeiro, depois as migrations versionadas de cada plugin, depois os seus incubator) no seu próprio Supabase.


Próximo: provisione tudo isso num banco real com BYOS em Supabase.