fayzfayz sdk

Incubator

O incubator é onde os plugins nascem: uma pasta dentro do seu app que segue o mesmo contrato dos plugins oficiais. É o caminho para uma capacidade que só existe no seu produto — e é 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 nasce em src/plugins/fidelidade/:

ArquivoPapel
index.tsO PluginManifest: id, navegação, rotas e a página. O mesmo contrato de um @fayz-ai/plugin-*.
data/types.tsO contrato de dados (FidelidadeDataProvider) que todo backend implementa.
data/mock.tsProvider mock — o plugin já roda sem banco.
data/supabase.tsProvider real, passando pelo boundary do Fayz (getSupabaseClientOptional), nunca importando o SDK do Supabase direto.
schema/index.tsOnde as migrations do plugin vivem.
README.mdComo fiar o plugin e a checklist de graduação.

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',
    icon: 'Puzzle',
    version: '0.1.0',
    navigation: [{ section: 'main', position: 50, label: 'Fidelidade', route: '/fidelidade', icon: 'Puzzle' }],
    routes: [{ path: '/fidelidade', component: Page }],
  }
}

Para ligá-lo, adicione createFidelidadePlugin() ao array de plugins da config do app (o README.md gerado mostra onde). Depois confira os limites:

npx @fayz-ai/cli doctor

O boundary é obrigatório

O data/supabase.ts gerado nunca importa @supabase/supabase-js direto — ele obtém o cliente por getSupabaseClientOptional(), o boundary do Fayz, e escopa a query por tenant_id:

import { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'

// ...
const { data, error } = await sb()
  .from('fidelidade')
  .select('*')
  .eq('tenant_id', getActiveTenantId())

Se você trocasse isso por um import direto do SDK do Supabase, o fayz doctor acusaria a quebra de boundary. Essa é a regra que mantém o seu plugin promovível — o detalhe está em Padrões.

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 ...>' }]

No fayz db apply, o passo incubator (o último da ordem) aplica essas migrations junto com o resto — por isso 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ê move a pasta para um pacote próprio. O README.md gerado traz a checklist completa de graduação (contrato validado, teste de capacidade, migrations fiadas, permissões declaradas, i18n completo, boundary limpo). O mesmo contrato que você seguiu aqui é o que os plugins do catálogo seguem — não há nada que um plugin oficial faça que o seu não possa.


Próximo: cada campo que um plugin pode declarar em Manifesto do plugin.