@horizon-apps/domain-schema-core
Version:
Core domain schema utilities for Horizon Platform - Schema generators, data enrichers, converters and specifications
818 lines (689 loc) • 22.6 kB
Markdown
# 🏗️ Formato de Schema Horizon - Especificação Completa
## 📋 Resumo Executivo
Este documento define o formato padrão para campos de schema no sistema Horizon. É um formato **minimalista, inteligente e pragmático** projetado para suportar 90+ campos sem redundância.
### Princípios Base
- **Minimalista**: Apenas 3-7 propriedades por campo em média
- **Inferência Inteligente**: Sistema deduz configurações automaticamente
- **Separação Clara**: Validação frontend ≠ Constraints de banco
- **Zero Redundância**: Nunca repetir informação
---
## 🚀 Quick Start
### Campo Básico (apenas 4 propriedades)
```javascript
{
"key": "valor_venda",
"type": "Number",
"label": "Valor de Venda",
"origin": "horizon-base/property"
}
```
### Campo Completo (seguindo ordem padrão)
```javascript
{
"key": "dormitorios",
"label": "Dormitórios",
"description": "Número de dormitórios do imóvel",
"type": "Number",
"format": "count",
"validation": { "min": 0, "max": 20 },
"filterable": true,
"composedLabel": "{{value}} dormitório{{p:s}}",
"icon": "bedroom",
"placeholder": "Ex: 3",
"categories": ["dependencias", "principais"],
"origin": "horizon-base/property"
}
```
---
## 🏗️ Ordem Padrão de Propriedades
**SEMPRE siga esta ordem ao definir campos:**
### 1. **IDENTIFICAÇÃO BÁSICA**
1. `key` - Identificador único
2. `label` - Nome para UI
3. `description` - Texto de ajuda/helper para formulários
4. `enum` - Valores possíveis
5. `type` - Tipo de dado
### 2. **FORMATAÇÃO BÁSICA**
6. `format` - Como formatar na exibição
7. `unit` - Unidade de medida
### 3. **VALIDAÇÃO E BANCO (backend)**
8. `validation` - Regras de validação
9. `db` - Configurações específicas do banco
### 4. **COMPORTAMENTO DE BUSCA**
10. `searchable` - Pesquisável por texto
11. `filterable` - Aparece nos filtros
12. `sortable` - Pode ordenar
### 5. **RELAÇÕES E DEPENDÊNCIAS**
13. `parent` - Campo pai hierárquico
14. `conditions` - Condições de visibilidade
### 6. **DISPLAY/UI (camada frontend)**
15. `value` - Valor atual do campo (adicionado na camada de display)
16. `valueLabel` - Valor formatado para exibição (adicionado na camada de display)
17. `composedLabel` - Template de exibição
18. `icon` - Ícone semântico
### 7. **FORMULÁRIOS (específico para inputs)**
19. `mask` - Máscara/validação específica
20. `placeholder` - Texto de exemplo no input
### 8. **CATEGORIZAÇÃO**
21. `categories` - Categorias múltiplas
### 9. **AUDITORIA**
22. `origin` - Origem do campo
23. `modifiedBy` - Histórico de modificações
---
## 📝 **LISTA COMPLETA DE METADADOS (23 total)**
**TODOS os metadados definidos na especificação:**
1. **`key`** - Identificador único
2. **`label`** - Nome para UI
3. **`description`** - Texto de ajuda/helper para formulários
4. **`enum`** - Valores possíveis
5. **`type`** - Tipo de dado
6. **`format`** - Como formatar na exibição
7. **`unit`** - Unidade de medida
8. **`validation`** - Regras de validação
9. **`db`** - Configurações específicas do banco
10. **`searchable`** - Pesquisável por texto
11. **`filterable`** - Aparece nos filtros
12. **`sortable`** - Pode ordenar
13. **`parent`** - Campo pai hierárquico
14. **`conditions`** - Condições de visibilidade
15. **`value`** - Valor atual do campo *(adicionado na camada de display)*
16. **`valueLabel`** - Valor formatado para exibição *(adicionado na camada de display)*
17. **`composedLabel`** - Template de exibição
18. **`icon`** - Ícone semântico
19. **`mask`** - Máscara/validação específica
20. **`placeholder`** - Texto de exemplo no input
21. **`categories`** - Categorias múltiplas
22. **`origin`** - Origem do campo
23. **`modifiedBy`** - Histórico de modificações
**Exemplo seguindo a ordem:**
```javascript
{
"key": "valor_venda",
"label": "Valor de Venda",
"description": "Preço de venda do imóvel em Reais",
"type": "Number",
"format": "currency",
"unit": "BRL",
"validation": { "min": 0, "max": 999999999.99, "precision": 2 },
"filterable": true,
"placeholder": "R$ 0,00",
"categories": ["valores", "principais"],
"origin": "horizon-base/property"
}
```
---
## 📚 Referência Completa
### 1. CAMPOS OBRIGATÓRIOS
| Campo | Tipo | Descrição | Exemplo |
|-------|------|-----------|---------|
| `key` | String | Identificador único (snake_case) | `"valor_venda"` |
| `label` | String | Nome para UI | `"Valor de Venda"` |
| `type` | String | Tipo Prisma | `"String"`, `"Number"`, `"String[]"` |
| `origin` | String | Origem do campo | `"horizon-base/property"` |
### 2. FORMATAÇÃO E DISPLAY
| Campo | Tipo | Descrição | Quando Usar |
|-------|------|-----------|-------------|
| `format` | String | Como formatar na exibição | Quando muda a forma de mostrar |
| `unit` | String | Unidade de medida | Com format currency/area |
| `mask` | String | Máscara/validação específica | Para CPF, phone, email |
| ~~`icon`~~ | ~~String~~ | ~~Ícone semântico~~ | **REMOVIDO** - Será adicionado na camada de display |
| ~~`composedLabel`~~ | ~~String~~ | ~~Template de exibição~~ | **REMOVIDO** - Será adicionado na camada de display |
#### Formats Disponíveis
```javascript
"currency" // 1000 → R$ 1.000,00
"date" // 2024-01-01 → 01/01/2024
"area" // 100 → 100 m²
"distance" // 1000 → 1 km
"percent" // 0.15 → 15%
"count" // Com pluralização automática
"geo-point" // Campo de geolocalização (coordenadas lat/lng)
```
#### Units Disponíveis
```javascript
// Moedas
"BRL", "USD", "EUR"
// Área
"m2", "km2", "hectare", "ft2"
// Distância
"m", "km", "mi"
```
#### Masks Disponíveis
**🤖 INFERÊNCIA AUTOMÁTICA:**
Quando `format` + `unit` estão presentes, o `mask` é **automaticamente inferido**:
```javascript
// INFERÊNCIA AUTOMÁTICA (não precisa definir mask)
format: "currency" + unit: "BRL" → mask: "currency-brl" // R$ 1.000,00
format: "currency" + unit: "USD" → mask: "currency-usd" // $ 1,000.00
format: "percent" → mask: "percent" // 15%
format: "date" → mask: "date" // DD/MM/YYYY
format: "area" + unit: "m2" → mask: "area" // 100 m²
format: "distance" + unit: "km" → mask: "distance" // 1,5 km
```
**📝 MASKS EXPLÍCITOS:**
Use apenas para casos específicos sem format/unit equivalente:
```javascript
"cpf" // 000.000.000-00
"cnpj" // 00.000.000/0000-00
"cep" // 00000-000
"phone" // (00) 00000-0000
"email" // Validação de email
"url" // Validação de URL
```
### 3. CATEGORIZAÇÃO E UI
| Campo | Tipo | Descrição | Exemplo |
|-------|------|-----------|---------|
| `categories` | String[] | Categorias múltiplas | `["valores", "principais"]` |
| `searchable` | Boolean | Pesquisável por texto | `true` |
| `filterable` | Boolean | Aparece nos filtros | `true` |
| `sortable` | Boolean | Pode ordenar | `true` |
#### Categories Comuns
```javascript
"valores" // Campos monetários
"localizacao" // Endereço e geolocalização
"dependencias" // Cômodos e estrutura
"identificacao" // Títulos e referências
"comercial" // Info comerciais
"principais" // Destaque no frontend
"caracteristicas" // Amenidades
"sistema" // Campos internos
"seo" // SEO e marketing
"corretor" // Info do corretor
"condominio" // Info do condomínio
```
### 4. ENUMERAÇÃO
| Campo | Tipo | Descrição | Formato |
|-------|------|-----------|---------|
| `enum` | Object | Valores possíveis | `{ "chave": "Label" }` |
```javascript
// Exemplo
"enum": {
"venda": "Venda",
"aluguel": "Aluguel",
"temporada": "Temporada"
}
```
### 5. RELAÇÕES E DEPENDÊNCIAS
| Campo | Tipo | Descrição | Uso |
|-------|------|-----------|-----|
| `parent` | String | Campo pai hierárquico | `"tipo"` |
| `conditions` | String[] | Condições de visibilidade | `["operacao:venda"]` |
#### Conditions - Operadores Disponíveis
| Categoria | Operador | Descrição | Exemplo |
|-----------|----------|-----------|---------|
| **Comparação** | `equals` | Igual a | `"tipo.equals:apartamento"` |
| | `not` | Diferente de | `"tipo.not:rural"` |
| | `in` | Está na lista | `"finalidade.in:residencial,comercial"` |
| | `notIn` | Não está na lista | `"status.notIn:vendido,alugado"` |
| **Strings** | `contains` | Contém (padrão) | `"operacao:venda"` |
| | `startsWith` | Começa com | `"titulo.startsWith:Casa"` |
| | `endsWith` | Termina com | `"titulo.endsWith:Centro"` |
| **Números** | `gt` | Maior que | `"valor.gt:100000"` |
| | `gte` | Maior ou igual | `"valor.gte:100000"` |
| | `lt` | Menor que | `"valor.lt:500000"` |
| | `lte` | Menor ou igual | `"valor.lte:500000"` |
| **Existência** | `exists` | Campo preenchido/vazio | `"condominio.exists:true"` |
```javascript
// Exemplos práticos
"conditions": [
"operacao:venda", // operacao contém "venda" (padrão)
"tipo.not:rural", // tipo diferente de "rural"
"valor.gte:100000", // valor >= 100000
"condominio.exists:true", // condominio preenchido
"finalidade.in:residencial,comercial" // finalidade é residencial OU comercial
]
```
### 6. VALIDAÇÃO (Frontend/Zod)
| Campo | Tipo | Descrição | Exemplo |
|-------|------|-----------|---------|
| `validation` | Object | Regras de validação | Ver abaixo |
```javascript
"validation": {
"required": true, // Obrigatório
"min": 0, // Valor mínimo (números)
"max": 999999999.99, // Valor máximo (números)
"minLength": 3, // Tamanho mínimo (strings)
"maxLength": 255, // Tamanho máximo (strings)
"precision": 2 // Casas decimais (infere decimal no DB)
}
```
**Importante**: NÃO incluir validações já cobertas por `mask` (cpf, email, etc)
### 7. BANCO DE DADOS
| Campo | Tipo | Descrição | Quando Usar |
|-------|------|-----------|-------------|
| `db` | Object | Configurações do banco | Quando diferente do padrão |
```javascript
"db": {
"type": "decimal(12,2)", // Tipo específico
"unique": true, // Constraint única
"index": true, // Criar índice
"default": "now()", // Valor padrão
"primary": true, // Chave primária
"autoincrement": true, // Auto incremento
// CONFIGURAÇÃO PARA GEOLOCALIZAÇÃO
"geoSource": ["latitude", "longitude"] // Campos fonte para coordenadas
}
```
#### Tipos de DB Especiais
```javascript
"text" // Texto longo
"decimal(12,2)" // Decimal com precisão
"timestamp" // Data/hora
"uuid" // UUID
"jsonb" // JSON binário (Postgres)
```
#### Tipos de Index
```javascript
true // Índice simples (btree)
false // Sem índice
"fulltext" // Busca de texto (para descriptions)
"hash" // Hash index
"gin" // GIN index (Postgres)
"gist" // GiST index (para dados geográficos PostGIS)
```
### 8. AUDITORIA
| Campo | Tipo | Descrição | Exemplo |
|-------|------|-----------|---------|
| `origin` | String | Onde foi criado | `"horizon-base/property"` |
| `modifiedBy` | String[] | Quem modificou | `["jetimob", "crm-sync"]` |
---
## 📋 Campos Removidos da Especificação Base
### `icon` e `composedLabel` - Camada de Display
**Decisão**: Os campos `icon` e `composedLabel` foram **removidos do schema base** e serão adicionados **manualmente na camada de display** apenas quando necessário.
**Justificativa**:
- Apenas poucos campos principais precisam de ícones (dormitórios, banheiros, vagas, área)
- `composedLabel` é específico da UI e pode variar por contexto
- Mantém o schema base focado nos dados essenciais
- Camada de display terá mais flexibilidade para customizações
**Implementação**:
```javascript
// Schema base (limpo)
{
"key": "dormitorios",
"type": "Number",
"label": "Dormitórios",
"format": "count"
}
// Camada de display (quando necessário)
const displayConfig = {
"dormitorios": {
"icon": "bedroom",
"composedLabel": "{{value}} dormitório{{p:s}}"
}
}
```
---
## 🧠 Sistema de Inferência Inteligente
O sistema deduz automaticamente para reduzir redundância:
### Inferência de Tipo de Banco
```javascript
// String + maxLength → varchar(N)
{ "validation": { "maxLength": 100 } } // Gera: db.type = "varchar(100)"
// String sem limite → varchar(255)
{ "type": "String" } // Gera: db.type = "varchar(255)"
// String longo + searchable → text + fulltext
{ "type": "String", "searchable": true } // Gera: db.type = "text", db.index = "fulltext"
// Number + precision → decimal
{ "validation": { "precision": 2, "max": 999999.99 } } // Gera: db.type = "decimal(8,2)"
// Format + padrões → decimal automático
{ "format": "currency" } // Gera: db.type = "decimal(12,2)"
{ "format": "area" } // Gera: db.type = "decimal(8,2)"
{ "format": "percent" } // Gera: db.type = "decimal(5,4)"
```
### Inferência de Índices
```javascript
// POR PADRÃO: TODOS OS CAMPOS SÃO INDEXADOS (db.index = true)
// Apenas explicitamos exceções:
{ "db": { "index": false } } // Campo SEM índice
{ "db": { "index": "gin" } } // Índice GIN (arrays/JSONB)
{ "db": { "index": "fulltext" } } // Índice de texto completo
{ "db": { "unique": true } } // Unique constraint (já inclui index)
// Casos especiais automáticos:
{ "type": "String[]", "searchable": true } // Gera: db.index = "gin"
{ "type": "Json", "searchable": true } // Gera: db.index = "gin"
```
### Inferência de Mask Automática
```javascript
// Format + Unit → Mask automático (frontend)
{ "format": "currency", "unit": "BRL" } // Gera: mask = "currency-brl"
{ "format": "percent" } // Gera: mask = "percent"
{ "format": "date" } // Gera: mask = "date"
{ "format": "area", "unit": "m2" } // Gera: mask = "area"
// Override manual quando necessário
{ "format": "currency", "unit": "BRL", "mask": false } // Desabilita mask
{ "format": "currency", "unit": "BRL", "mask": "custom" } // Override
```
### Inferência de Validação
```javascript
// Mask explícito (apenas casos específicos)
{ "mask": "cpf" } // Frontend: formatação + validação visual
{ "mask": "email" } // Frontend: validação de formato
{ "mask": "cep" } // Frontend: formatação CEP
// Precision para Zod (backend)
{ "validation": { "precision": 2 } } // Gera: z.number().multipleOf(0.01)
{ "validation": { "precision": 3 } } // Gera: z.number().multipleOf(0.001)
```
### Inferência para Geolocalização
```javascript
// Format geo-point + geoSource → configurações espaciais automáticas
{ "format": "geo-point", "db": { "geoSource": ["lat", "lng"] } }
// Sistema infere baseado no ORM/banco:
// - PostgreSQL/PostGIS: geography(Point, 4326) + índice gist
// - MySQL: POINT + índice spatial
// - MongoDB: 2dsphere index
// - Coluna gerada ou trigger conforme suporte do banco
```
### Cálculo Automático de Decimal
```javascript
// Sistema calcula precisão automaticamente:
validation.max = 999999.99 + precision = 2 → decimal(8,2)
validation.max = 999999999.99 + precision = 2 → decimal(12,2)
// Exemplos práticos:
{ "validation": { "max": 999999.99, "precision": 2 } } // decimal(8,2)
{ "validation": { "max": 999999999.99, "precision": 2 } } // decimal(12,2)
{ "validation": { "max": 180.0, "precision": 8 } } // decimal(11,8) - lat/lng
```
---
## 📖 Biblioteca de Exemplos
### Campos Simples
#### Campo Básico
```javascript
{
"key": "reference",
"type": "String",
"label": "Referência",
"origin": "horizon-base/property"
}
```
#### Campo com Validação
```javascript
{
"key": "email",
"type": "String",
"label": "E-mail",
"mask": "email",
"validation": {
"required": true,
"maxLength": 255
},
"origin": "horizon-base/property"
}
```
#### Campo com Formatação e Precision
```javascript
{
"key": "valor_venda",
"type": "Number",
"label": "Valor de Venda",
"format": "currency",
"unit": "BRL",
"categories": ["valores"],
"validation": {
"required": true,
"min": 0,
"max": 999999999.99,
"precision": 2
},
"origin": "horizon-base/property"
// Sistema infere automaticamente:
// - db.type = "decimal(12,2)", db.index = true
// - mask = "currency-brl" (frontend)
}
```
### Campos com Enum
```javascript
{
"key": "operacao",
"type": "String[]",
"label": "Operação",
"enum": {
"venda": "Venda",
"aluguel": "Aluguel",
"temporada": "Temporada"
},
"categories": ["comercial"],
"filterable": true,
"origin": "horizon-base/property"
}
```
### Campos com Hierarquia
```javascript
{
"key": "subtipo",
"type": "String",
"label": "Subtipo",
"parent": "tipo",
"categories": ["estrutura"],
"filterable": true,
"origin": "horizon-base/property"
}
```
### Campos com Condições
#### Condição Simples
```javascript
{
"key": "valor_aluguel",
"type": "Number",
"label": "Valor do Aluguel",
"format": "currency",
"unit": "BRL",
"conditions": ["operacao:aluguel"],
"categories": ["valores"],
"filterable": true,
"origin": "horizon-base/property"
}
```
#### Condições Múltiplas
```javascript
{
"key": "andar",
"type": "Number",
"label": "Andar",
"conditions": [
"tipo.equals:apartamento",
"operacao:venda",
"valor.gte:200000"
],
"categories": ["estrutura"],
"validation": {
"min": 1,
"max": 50
},
"origin": "horizon-base/property"
}
```
### Campos com Banco Customizado
#### Campo Único
```javascript
{
"key": "cpf",
"type": "String",
"label": "CPF",
"mask": "cpf",
"db": {
"unique": true
},
"validation": {
"required": true,
"maxLength": 14
},
"origin": "horizon-base/property"
}
```
#### Campo com Fulltext
```javascript
{
"key": "description",
"type": "String",
"label": "Descrição",
"db": {
"type": "text",
"index": "fulltext"
},
"categories": ["identificacao"],
"searchable": true,
"validation": {
"required": true
},
"origin": "horizon-base/property"
}
```
### Campos Complexos
#### Campo Principal Completo
```javascript
{
"key": "dormitorios",
"type": "Number",
"label": "Dormitórios",
"icon": "bedroom",
"categories": ["dependencias", "principais"],
"format": "count",
"composedLabel": "{{value}} dormitório{{p:s}}",
"filterable": true,
"sortable": true,
"validation": {
"min": 0,
"max": 20
},
"origin": "horizon-base/property"
// Sistema infere: mask = "count" (frontend)
}
```
#### Campo com Index Especial
```javascript
{
"key": "description",
"type": "String",
"label": "Descrição",
"categories": ["identificacao"],
"searchable": true,
"validation": {
"required": true
},
"origin": "horizon-base/property"
// Sistema infere: db.type = "text", db.index = "fulltext"
}
```
#### Campo Sem Index
```javascript
{
"key": "endereco_numero",
"type": "String",
"label": "Número",
"categories": ["localizacao"],
"db": {"index": false}, // Explicitamente SEM índice
"origin": "horizon-base/property"
}
```
#### Campo com Precision
```javascript
{
"key": "latitude",
"type": "Number",
"label": "Latitude",
"categories": ["localizacao"],
"validation": {
"min": -90.0,
"max": 90.0,
"precision": 8
},
"origin": "horizon-base/property"
// Sistema infere: db.type = "decimal(10,8)"
}
```
#### Array com Index GIN
```javascript
{
"key": "caracteristicas",
"type": "String[]",
"label": "Características",
"categories": ["caracteristicas"],
"searchable": true,
"origin": "horizon-base/property"
// Sistema infere: db.type = "jsonb", db.index = "gin"
}
```
#### Campo de Geolocalização
```javascript
{
"key": "location",
"type": "String",
"label": "Localização",
"format": "geo-point",
"db": {
"geoSource": ["latitude", "longitude"]
},
"filterable": true,
"origin": "horizon-base/property"
// Sistema infere todo o resto baseado no ORM/banco:
// - Tipo apropriado (geography, geometry, point, etc)
// - Índice espacial apropriado
// - Coluna gerada ou trigger conforme o banco
}
```
---
## ⚠️ Anti-Patterns
### ❌ Redundância
```javascript
// ERRADO - Repetindo informação
{
"db": { "type": "varchar(255)" },
"validation": { "maxLength": 255 }
}
// CERTO - Sistema infere
{
"validation": { "maxLength": 255 }
}
```
### ❌ Format Desnecessário
```javascript
// ERRADO - Type já diz tudo
{
"type": "String",
"format": "text"
}
// CERTO - Sem format
{
"type": "String"
}
```
### ❌ Validação Duplicada
```javascript
// ERRADO - Mask já valida
{
"mask": "cpf",
"validation": { "cpf": true }
}
// CERTO - Mask cuida da validação
{
"mask": "cpf"
}
```
---
## 📏 Guia de Decisão Rápido
### Quando usar cada campo?
| Se você quer... | Use... | Exemplo |
|------------------|---------|----------|
| Formatar exibição | `format` + `unit` | `"format": "currency", "unit": "BRL"` |
| Validar/mascarar | `mask` | `"mask": "cpf"` |
| Múltiplas categorias | `categories` | `"categories": ["valores", "principais"]` |
| Hierarquia pai-filho | `parent` | `"parent": "tipo"` |
| Condições de visibilidade | `conditions` | `"conditions": ["operacao:venda"]` |
| Constraints de banco | `db` | `"db": { "unique": true }` |
| Validação frontend | `validation` | `"validation": { "required": true }` |
| Rastrear origem | `origin` + `modifiedBy` | `"origin": "jetimob/import"` |
### Quantas propriedades usar?
- **Mínimo**: 4 (key, type, label, origin)
- **Média ideal**: 5-7 propriedades
- **Máximo aceitável**: 10-12 (casos muito complexos)
---
## 🎯 Regras de Ouro
1. **Comece simples**: key, type, label, origin
2. **Adicione só quando necessário**: Cada propriedade deve ter propósito claro
3. **Deixe o sistema inferir**: Não especifique o que pode ser automatizado
4. **Separe responsabilidades**: Frontend ≠ Banco
5. **Zero redundância**: Nunca repetir informação
6. **Documentes mudanças**: Use `modifiedBy` para auditoria
7. **🤖 Mask automático**: Quando `format` + `unit` existem, `mask` é inferido automaticamente
8. **📝 Mask explícito**: Use apenas para casos específicos (CPF, CEP, telefone, email, URL)
---
Este formato é a **base definitiva** para todos os schemas do sistema Horizon. Deve ser seguido consistentemente para garantir interoperabilidade, manutenibilidade e escalabilidade do sistema.