Padrões
Um plugin que segue o contrato funciona. Um plugin que segue os padrões compõe: convive com os outros plugins, aceita um backend real sem reescrita e é promovível a oficial. Esta página são as convenções que fazem essa diferença.
Factory, não instância
Um plugin é exportado como uma factory — uma função que devolve o PluginManifest — não como um objeto pronto. Isso deixa quem monta o app injetar opções (um provider, uma posição de menu, um vertical) sem editar o plugin:
export function createFidelidadePlugin(options?: FidelidadePluginOptions): PluginManifest {
// ...
}
Ligar o plugin é chamar a factory no array de plugins. Configurar é passar opções. Nunca é editar o interior do plugin.
Provider-first: o dado é um contrato
A UI do plugin nunca fala com um banco direto. Ela fala com um DataProvider — uma interface que todo backend implementa. O plugin define o contrato em data/types.ts, entrega um mock e um supabase, e escolhe entre eles com createSafeDataProvider:
const provider =
options?.dataProvider ??
createSafeDataProvider(
() => createSupabaseFidelidadeProvider(), // usado quando há backend
() => createMockFidelidadeProvider(), // fallback sem banco
)
O ganho: o plugin roda no dia zero (mock), e trocar para dados reais não toca em nenhuma tela — só troca o provider. É o mesmo padrão que faz um plugin ser testável sem infra (veja Testar e debugar).
O boundary de provider: fale com o Fayz, não com o provedor
A regra que o fayz doctor fiscaliza: um app ou plugin não importa SDKs de provedor direto (@supabase/supabase-js, mercadopago, stripe, googleapis, …). Ele obtém acesso pelo boundary do Fayz — getSupabaseClientOptional() / a interface DataProvider — ou pelo spine de conectores.
import { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'
// ✔ passa pelo boundary do Fayz, escopa por tenant
// import { createClient } from '@supabase/supabase-js'
// -> o doctor acusaria isto como quebra de boundary
Existe uma exceção deliberada: um plugin app-local pode ser dono de um conector + Edge Function para um provedor que o Fayz ainda não suporta — porque a credencial fica no servidor e o app ainda chama o próprio boundary, não o provedor do navegador. Essa exceção é declarada com um marcador na linha do import, não casual.
A superfície suportada
Um app só depende de pacotes da superfície pública suportada — @fayz-ai/sdk, core, saas, ui, auth, db, storefront, shop e os @fayz-ai/plugin-*. Importar um subpath interno ou um pacote fora dessa lista faz o doctor avisar (off-surface-import / off-surface-dependency). A regra existe para o upgrade ficar seguro: o que não está na superfície pode mudar sem aviso.
Tabelas plg_ e reúso do core
Por convenção, um plugin instala as suas próprias tabelas com o prefixo plg_ (é o que shop e courses fazem — plg_shop_products, plg_courses_*) para deixar a posse óbvia. A convenção é recomendada, não universal: nem todo módulo a cumpre — o conector de openbanking, por exemplo, cria bank_integrations sem prefixo. O que o plugin não cria, ele reutiliza: referencia as tabelas da biblioteca core que precisa em vez de copiá-las. Um plugin jamais redefine uma entidade do core; se precisa saber "quem marcou", referencia public.people. Uma fonte de verdade por entidade.
As migrations que o plugin embarca seguem regras que fazem o fayz db apply ser reproduzível:
- ordenadas — a sequência de aplicação é determinística.
- versionadas — cada migration tem versão; o runner sabe o que já rodou.
- fix-forward — migrations são append-only: nunca edite uma já aplicada, escreva a próxima que corrige. Não há
.down.sql. - idempotentes — rodar de novo não quebra nem duplica.
Isolamento sempre
Toda tabela plg_ que o plugin possui carrega tenant_id e RLS na forma canônica. Isso não é opcional nem "depois" — é a condição para a tabela existir no modelo. O detalhe está em RLS e multi-tenant.
A fiscalização é soft por design: o fayz doctor reporta essas violações como avisos, não falha o build. A ideia é visibilidade sem atrapalhar o DX de quem está experimentando. Mas um plugin que quer graduar a oficial precisa estar com o doctor limpo.
Próximo: exercite o app e pegue quebras cedo em Testar e debugar.