fayzfayz sdk

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.