Pular para o conteúdo principal

Schema Generator

O que é

O generator lê YAMLs de {module}/schemas/ e gera código em 3 camadas — tipos + clients compartilhados, frontend Angular e backend Fastify. Os YAMLs são a fonte única de verdade para modelos, campos, dicionários, query configs, actions, rotas e pages de UI.

Fluxo

{module}/schemas/{domain}.yaml
↓ pnpm generate
├── libs/shared/src/{module}/{domain}/_gen/ → Tipos TS, Zod schemas, dicionários, query configs, client tipado, base actions
├── {module}/web/src/app/domains/{domain}/_gen/ → Base services (frontend Angular)
└── {module}/api/src/routes/{domain}/_gen/ → Rotas e base action classes (backend Fastify)

Módulos suportados: cms, hub, platform, tenant, user.

Comandos

pnpm generate          # Regenera tudo
pnpm generate:list # Visão geral de domínios, actions e mapeamento
pnpm generate:check # Verifica sincronização

O que é gerado por camada

Shared (libs/shared/src/{module}/{domain}/_gen/)

ArquivoConteúdo
types.tsInterface TypeScript do model principal + embedded models
schemas.tsZod schema para validação
dictionaries.tsObjetos de dicionário (status, enums) com label/color/icon
query-config.tsConfig de query para Kysely (tabela, joins, colunas, filtros)
client.tsFacade tipada <Domain>Client + factory create<Domain>Client(http, baseUrl)
actions/<name>.tsUma Base<Name> por action, com execute(...) tipado

Frontend ({module}/web/src/app/domains/{domain}/_gen/)

ArquivoConteúdo
services/base-{domain}-service.tsDomainService com pages, columns, filters, form fields

Backend ({module}/api/src/routes/{domain}/_gen/)

ArquivoConteúdo
base-{domain}-routes.tsClasse Base<Domain>Routes que instancia cada action e liga no Fastify
actions/<name>.tsBase<Name> — uma classe por action com template CRUD ou throw-stub
base-{domain}-routes.test.tsContract tests — valida response com Zod derivado do tipo declarado

Actions concretas ficam em {module}/api/src/routes/{domain}/actions/<name>.ts (só criadas se não existirem; preservam overrides).

Regras

  1. Nunca editar _gen/ — são sobrescritos a cada geração
  2. Sempre editar o YAML para alterar modelo, campo, dicionário, action ou page
  3. Lógica custom (override de SQL, side effects, etc.) vai em {domain}/actions/<name>.ts extendendo a Base<Name> gerada
  4. Schemas compartilhados são expostos via @hg/shared/{module}/{domain} (barrel do pacote)

Estrutura de um YAML

name: page
description: Páginas de sites com editor visual
service: cms
table: cms_pages
parent: site # Relacionamento hierárquico
tenant_scoped: true # RLS ativado no banco tenant

models:
Page: # Model principal
fields:
id:
type: string
label: ID
required: true
title:
type: string
label: Título
required: true
status:
type: enum
dictionary: page_status
label: Status
required: true
puck_data:
type: object
label: Dados do Editor
json: true # Mapeia para JSONB
default: '{}'

PageSeo: # Embedded model (sem tabela própria)
embedded: true
fields:
seo_title: { type: string, label: Título SEO }
seo_description: { type: string, label: Descrição SEO }

dictionaries:
page_status:
draft: { label: Rascunho, color: gray-soft, icon: file-edit }
published: { label: Publicado, color: success-soft, icon: check-circle }

actions:
- name: fetch
method: GET
path: ''
response: 'Page[]'
ui:
type: search
meta: { icon: file-text, empty: Nenhuma página encontrada }
columns:
- { key: title, label: Título, searchable: true }
- { key: status, label: Status, type: badge, dictionary: page_status }
filters:
status: { label: Status, type: select, mode: client, dictionary: page_status }
paginable: true
searchable: true
- { name: findById, method: GET, path: '/:id', response: 'Page' }
- name: create
method: POST
path: ''
request: 'Partial<Page>'
response: '{ id: string }'
ui:
type: form
label: Criar Página
icon: plus
fields:
slug: { label: Slug, required: true }
title: { label: Título, required: true }
- { name: update, method: PUT, path: '/:id', request: 'Partial<Page>', response: '{ ok: boolean }' }
- { name: delete, method: DELETE, path: '/:id', response: '{ ok: boolean }' }
- { name: publish, method: POST, path: '/:id/publish', response: '{ ok: boolean }' }

Action.ui — manifestação da action na UI

Cada action pode declarar ui: com sua forma de aparecer no frontend:

ui.typeOnde apareceO que gera
searchLista/tabela do domíniocolumns, filters, joins, paginação, sort
formModal de criação/ediçãoForm com fields (TODO: depends, mask, source)
viewDetalhe/visualizaçãoSeções, campos read-only (TODO)
buttonBotão em row ou headerLabel, ícone, confirm (TODO)

O bloco pages: no topo do YAML é legacy — em transição pra ser removido. Toda UI nova vai em actions[].ui.

Ações canônicas vs declared

Os nomes fetch, findById, create, update, delete recebem template CRUD padrão no backend (SQL gerado a partir da tabela + fields). Qualquer outro nome (publish, reprocess, findBySlug, login, etc.) gera base throw-stub que obriga a classe concreta a fornecer implementação real.

A forma no YAML é sempre a mesma — objeto ActionDefinition completo, sem açúcar/shortcut.

Campos especiais no YAML

CampoEfeito
serviceMódulo dono (cms, hub, platform, tenant, user)
tableNome da tabela no banco (com prefixo de BC: cms_pages, plt_brands)
parentDomínio pai — rotas ficam aninhadas (/sites/:siteId/pages)
base_pathOverride do path padrão /<name>s (ex: /auth, /dlq, /gateway)
frontend_pathPath customizado no frontend (default = nome do domínio)
tenant_scopedAtiva RLS (withTenant) nas queries geradas. Default: true
short_idGera short IDs (12 chars) via generateId('short') no create
embedded: trueModel sem tabela — gera tipo auxiliar
json: trueCampo mapeado como JSONB
dictionaryReferência a dicionário para enum fields

Workflow

Alterar modelo existente

  1. Editar {module}/schemas/{domain}.yaml
  2. pnpm generate
  3. Se alterou esquema do banco: criar migration em db/{public,tenant}/migrations/

Criar novo domínio

  1. pnpm generate:list — verificar em qual módulo pertence
  2. Criar {module}/schemas/{novo}.yaml
  3. pnpm generate
  4. Implementar lógica custom em {module}/api/src/routes/{novo}/actions/<name>.ts (extend Base<Name>)
  5. Criar pages/actions no frontend em {module}/web/src/app/domains/{novo}/