06 · Seu próprio plugin
Ligar plugins do catálogo é ótimo, mas em algum momento você quer uma capacidade que só existe no seu produto. O incubator é o caminho: um plugin app-local que segue o mesmo contrato dos plugins oficiais. É o passo que transforma você de usuário do SDK em autor.
Gere o plugin
npx @fayz-ai/cli create plugin fidelidade
✓ Created app-local plugin "src/plugins/fidelidade" Add createFidelidadePlugin() to your app's plugins array (see src/plugins/fidelidade/README.md). Run "fayz doctor" to check boundaries.
O que foi criado em src/plugins/fidelidade/:
| Arquivo | Papel |
|---|---|
index.ts | O PluginManifest: id, navegação, rotas e a página. O mesmo contrato de um @fayz-ai/plugin-*. |
data/types.ts | O contrato de dados (FidelidadeDataProvider) que todo backend implementa. |
data/mock.ts | Provider mock — o plugin já roda sem banco. |
data/supabase.ts | Provider real, passando pelo boundary do Fayz (getSupabaseClientOptional), nunca importando o SDK do Supabase direto. |
schema/index.ts | Onde as migrations do plugin vivem. |
README.md | Como fiar o plugin e a checklist de graduação para oficial. |
O contrato é o mesmo dos oficiais
O index.ts exporta uma factory que devolve um PluginManifest — id, navigation, routes e uma página. Ele escolhe o provider automaticamente (Supabase quando há backend, mock caso contrário) via createSafeDataProvider:
export function createFidelidadePlugin(options?: FidelidadePluginOptions): PluginManifest {
const provider =
options?.dataProvider ??
createSafeDataProvider(
() => createSupabaseFidelidadeProvider(),
() => createMockFidelidadeProvider(),
)
// ...
return {
id: 'fidelidade',
name: 'Fidelidade',
navigation: [{ section: 'main', label: 'Fidelidade', route: '/fidelidade', icon: 'Puzzle' }],
routes: [{ path: '/fidelidade', component: Page }],
}
}
Para ligá-lo, adicione createFidelidadePlugin() ao array de plugins da config do app (veja o README.md do plugin). E confira os limites com o doctor:
npx @fayz-ai/cli doctor
✓ Você deve ver: a pasta src/plugins/fidelidade/ com os seis arquivos, e o doctor com 0 error(s). O data/supabase.ts já usa getSupabaseClientOptional — se você importasse @supabase/supabase-js direto, o doctor acusaria a quebra de boundary.
Onde vão as migrations
As tabelas do seu plugin vivem em schema/index.ts e são referenciadas no manifesto via migrations: [...]:
migrations: [{ id: 'fidelidade-0001', version: '0.1.0', sql: '<create table ...>' }]
Quando você rodar fayz db apply, o passo incubator (o último da ordem) aplica essas migrations junto com o resto — foi por isso que a ordem spine → drizzle → seed → plugins → incubator importa.
De incubator a oficial
Promover um plugin app-local para um pacote @fayz-ai/plugin-* oficial é empacotamento, não reescrita: o manifesto não muda, você só move a pasta. O README.md gerado traz a checklist completa de graduação (contrato validado, teste de capacidade, permissões, i18n, boundary limpo). O mesmo contrato que você seguiu aqui é o que os plugins do catálogo seguem.
← Anterior: 05 · Dados reais com Supabase · Próximo: 07 · Publicar →