@brasil-conjunto/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +246 -0
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/service.schema.d.ts +283 -0
- package/dist/schemas/service.schema.js +63 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/service.d.ts +53 -0
- package/dist/types/service.js +4 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/slugify.d.ts +4 -0
- package/dist/utils/slugify.js +14 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# portal-core
|
|
2
|
+
|
|
3
|
+
**A constituicao do sistema.** Biblioteca compartilhada que define o dominio do negocio — tipos, schemas, migracoes, validacao e constantes. Tudo que os outros workspaces precisam concordar passa por aqui.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Responsabilidade
|
|
8
|
+
|
|
9
|
+
Portal-core e o **contrato unico** entre todos os workspaces do monorepo. Ele define:
|
|
10
|
+
|
|
11
|
+
- **O que e um servico publico** (tipos TypeScript)
|
|
12
|
+
- **Como um servico deve ser validado** (schemas Zod)
|
|
13
|
+
- **Como o banco de dados e estruturado** (migracoes SQL)
|
|
14
|
+
- **Quais documentos existem** (constantes padronizadas)
|
|
15
|
+
- **Como normalizar texto** (funcoes utilitarias)
|
|
16
|
+
|
|
17
|
+
Se dois workspaces precisam concordar sobre a forma de um dado, a definicao vive aqui.
|
|
18
|
+
|
|
19
|
+
### O que portal-core FAZ
|
|
20
|
+
|
|
21
|
+
- Define interfaces TypeScript (`PublicService`, `DocumentRequired`, `CostItem`, `ServiceVariant`, etc.)
|
|
22
|
+
- Exporta schemas Zod para validacao em runtime
|
|
23
|
+
- Contem migracoes SQL para o Supabase/PostgreSQL
|
|
24
|
+
- Padroniza codigos de documentos (`CPF`, `RG`, `CRLV_VEICULO`, `LAUDO_MEDICO_DETRAN`, etc.)
|
|
25
|
+
- Fornece funcoes puras de normalizacao (slugify, sanitize, etc.)
|
|
26
|
+
|
|
27
|
+
### O que portal-core NAO FAZ
|
|
28
|
+
|
|
29
|
+
- Nao acessa banco de dados — define schemas, nao executa queries
|
|
30
|
+
- Nao tem dependencia de framework (sem Next.js, sem Playwright, sem AI SDK)
|
|
31
|
+
- Nao tem UI, componentes ou paginas
|
|
32
|
+
- Nao faz IO (sem HTTP, sem filesystem, sem crawling)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Arquitetura Interna
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
packages/portal-core/
|
|
40
|
+
├── src/
|
|
41
|
+
│ ├── types/ # Interfaces e tipos TypeScript
|
|
42
|
+
│ │ ├── service.ts # PublicService, ServiceVariant, ServiceSummary
|
|
43
|
+
│ │ ├── document.ts # DocumentRequired, DocumentType, DocumentWithLink
|
|
44
|
+
│ │ ├── cost.ts # CostItem, CostType
|
|
45
|
+
│ │ ├── link.ts # LinkItem
|
|
46
|
+
│ │ ├── draft.ts # ServiceDraft, ServiceDraftStatus, ServiceDraftSource
|
|
47
|
+
│ │ ├── location.ts # Location, LocationLevel
|
|
48
|
+
│ │ ├── agency.ts # Agency
|
|
49
|
+
│ │ ├── report.ts # Report, ReportStatus
|
|
50
|
+
│ │ ├── crawl.ts # CrawlTarget, CrawlRun, CrawlJob
|
|
51
|
+
│ │ └── index.ts # Re-exporta tudo
|
|
52
|
+
│ │
|
|
53
|
+
│ ├── schemas/ # Schemas Zod (validacao em runtime)
|
|
54
|
+
│ │ ├── service.schema.ts # Valida PublicService
|
|
55
|
+
│ │ ├── document.schema.ts # Valida DocumentRequired
|
|
56
|
+
│ │ ├── cost.schema.ts # Valida CostItem
|
|
57
|
+
│ │ ├── draft.schema.ts # Valida ServiceDraft
|
|
58
|
+
│ │ └── index.ts
|
|
59
|
+
│ │
|
|
60
|
+
│ ├── constants/ # Valores fixos e enums
|
|
61
|
+
│ │ ├── document-codes.ts # CPF, RG, CRLV_VEICULO, LAUDO_MEDICO_DETRAN...
|
|
62
|
+
│ │ ├── levels.ts # federal, estadual, municipal
|
|
63
|
+
│ │ ├── service-types.ts # online, presencial, misto
|
|
64
|
+
│ │ ├── categories.ts # Identificacao, Veiculo, Medico, Residencia...
|
|
65
|
+
│ │ └── index.ts
|
|
66
|
+
│ │
|
|
67
|
+
│ └── utils/ # Funcoes puras utilitarias
|
|
68
|
+
│ ├── slugify.ts # Gera slug a partir de nome
|
|
69
|
+
│ ├── sanitize.ts # Limpa HTML, normaliza whitespace
|
|
70
|
+
│ ├── text.ts # Remocao de acentos, lowercase, trim
|
|
71
|
+
│ ├── validation.ts # Helpers de validacao (isValidUrl, isGovBr, etc.)
|
|
72
|
+
│ └── index.ts
|
|
73
|
+
│
|
|
74
|
+
├── migrations/ # Migracoes SQL Supabase
|
|
75
|
+
│ ├── 001_create_locations.sql
|
|
76
|
+
│ ├── 002_create_agencies.sql
|
|
77
|
+
│ ├── 003_create_services.sql
|
|
78
|
+
│ ├── 004_create_documents.sql
|
|
79
|
+
│ ├── 005_create_reports.sql
|
|
80
|
+
│ ├── 006_create_crawl.sql
|
|
81
|
+
│ └── ...
|
|
82
|
+
│
|
|
83
|
+
├── package.json
|
|
84
|
+
├── tsconfig.json
|
|
85
|
+
└── README.md
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Principios de Organizacao
|
|
89
|
+
|
|
90
|
+
- **Um arquivo por tipo de entidade** — `service.ts` nao mistura com `document.ts`
|
|
91
|
+
- **Schemas espelham tipos** — para cada `types/service.ts` existe um `schemas/service.schema.ts`
|
|
92
|
+
- **Constantes sao exaustivas** — todos os codigos padrao documentados em um unico lugar
|
|
93
|
+
- **Utils sao funcoes puras** — sem side effects, sem IO, sem estado
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Integracoes
|
|
98
|
+
|
|
99
|
+
### Quem importa portal-core
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
portal-core
|
|
103
|
+
▲ ▲
|
|
104
|
+
│ │
|
|
105
|
+
portal-collector portal-da-pendencia
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
| Workspace | O que importa | Para que |
|
|
109
|
+
|-----------|--------------|---------|
|
|
110
|
+
| `portal-collector` | Types, Schemas, Constants, Utils | Validar dados extraidos contra o contrato. Usar codigos padrao de documentos. Normalizar texto antes de salvar. |
|
|
111
|
+
| `portal-da-pendencia` | Types, Constants | Tipar queries, tipar props de componentes, exibir nomes padrao de documentos. |
|
|
112
|
+
|
|
113
|
+
### Quem portal-core importa
|
|
114
|
+
|
|
115
|
+
**Ninguem.** Portal-core e a raiz da arvore de dependencias. Nao tem dependencias internas. Qualquer dependencia circular aqui quebra a arquitetura.
|
|
116
|
+
|
|
117
|
+
### Banco de dados
|
|
118
|
+
|
|
119
|
+
Portal-core **define** o schema (via migracoes SQL), mas **nao acessa** o banco. As migracoes sao executadas pelo Supabase CLI ou por scripts em `infra/`.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Como Usar
|
|
124
|
+
|
|
125
|
+
### Instalacao (workspace)
|
|
126
|
+
|
|
127
|
+
No `package.json` de qualquer workspace:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"dependencies": {
|
|
132
|
+
"@portal/core": "workspace:*"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Importar tipos
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import type { PublicService, DocumentRequired, CostItem } from "@portal/core/types";
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Validar dados
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { serviceSchema } from "@portal/core/schemas";
|
|
147
|
+
|
|
148
|
+
const result = serviceSchema.safeParse(rawData);
|
|
149
|
+
if (!result.success) {
|
|
150
|
+
console.error("Dados invalidos:", result.error);
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Usar constantes
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { DOCUMENT_CODES, SERVICE_LEVELS } from "@portal/core/constants";
|
|
158
|
+
|
|
159
|
+
// DOCUMENT_CODES.CPF === "CPF"
|
|
160
|
+
// DOCUMENT_CODES.CRLV_VEICULO === "CRLV_VEICULO"
|
|
161
|
+
// SERVICE_LEVELS === ["federal", "estadual", "municipal"]
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Normalizar texto
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { slugify, sanitizeHtml } from "@portal/core/utils";
|
|
168
|
+
|
|
169
|
+
slugify("Renovação de CNH — DETRAN-SC");
|
|
170
|
+
// → "renovacao-de-cnh-detran-sc"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Rodar Migracoes
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
# Via Supabase CLI (local)
|
|
179
|
+
supabase db push --db-url $DATABASE_URL
|
|
180
|
+
|
|
181
|
+
# Via script de setup
|
|
182
|
+
pnpm --filter @portal/core db:migrate
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Testes
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
pnpm --filter @portal/core test
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Testes cobrem:
|
|
194
|
+
- Schemas Zod aceitam dados validos e rejeitam invalidos
|
|
195
|
+
- Funcoes de normalizacao produzem output esperado
|
|
196
|
+
- Constantes sao exaustivas e sem duplicatas
|
|
197
|
+
- Slugify lida com acentos, caracteres especiais e edge cases
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Plano de Expansao
|
|
202
|
+
|
|
203
|
+
### Fase 1 — MVP (Agora)
|
|
204
|
+
- Tipos que espelham o schema atual do banco (`public_services`, `document_types`, etc.)
|
|
205
|
+
- Schemas Zod basicos para validacao
|
|
206
|
+
- Constantes de codigos de documentos
|
|
207
|
+
- Migracoes existentes migradas de `portal-da-pendencia/supabase/migrations/`
|
|
208
|
+
|
|
209
|
+
### Fase 2 — Modelo Hierarquico
|
|
210
|
+
- Adicionar tipos para `service_master` → `service_variants` (servico base + variacoes por localidade)
|
|
211
|
+
- Migracoes para reestruturar tabelas
|
|
212
|
+
- Schemas para validacao de variantes
|
|
213
|
+
- Tipo `Location` com hierarquia (pais → estado → municipio)
|
|
214
|
+
|
|
215
|
+
### Fase 3 — Versionamento
|
|
216
|
+
- Tipos para `service_versions` (snapshot historico)
|
|
217
|
+
- Tipos para `service_checks` (monitoramento)
|
|
218
|
+
- Schema de diff entre versoes
|
|
219
|
+
|
|
220
|
+
### Fase 4 — Pacote NPM Privado
|
|
221
|
+
- Publicar como `@portal/core` no registry privado
|
|
222
|
+
- Versionamento semantico (semver)
|
|
223
|
+
- Changelog automatico
|
|
224
|
+
- Usado por repos independentes apos separacao do monorepo
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Riscos e Mitigacoes
|
|
229
|
+
|
|
230
|
+
| Risco | Impacto | Mitigacao |
|
|
231
|
+
|-------|---------|----------|
|
|
232
|
+
| Mudanca no Core quebra Collector e App simultaneamente | Alto | Testes automaticos em CI para ambos workspaces quando Core muda. Nao mergear sem green build. |
|
|
233
|
+
| Schema diverge do banco real | Medio | Migracoes como unica fonte de verdade. Nunca alterar banco manualmente. |
|
|
234
|
+
| Tipos ficam desatualizados em relacao ao schema | Medio | Gerar tipos a partir do schema (ou manter sincronia via testes). |
|
|
235
|
+
| Over-engineering: Core vira monolito de utilidades | Baixo | Regra: so entra no Core se dois ou mais workspaces precisam. Se so um usa, fica local. |
|
|
236
|
+
| Constantes incompletas (documento novo nao cadastrado) | Baixo | Zod rejeita codigos desconhecidos. Falha ruidosa forca cadastro. |
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Regras de Ouro
|
|
241
|
+
|
|
242
|
+
1. **Se so um workspace precisa, nao vai pro Core.** Core e compartilhado, nao lixeira.
|
|
243
|
+
2. **Toda mudanca no Core exige testes nos consumidores.** CI deve rodar testes de Collector e App quando Core muda.
|
|
244
|
+
3. **Migracoes sao append-only.** Nunca editar uma migracao existente. Criar nova para corrigir.
|
|
245
|
+
4. **Tipos e schemas andam juntos.** Nao existe tipo sem schema correspondente.
|
|
246
|
+
5. **Zero dependencias de framework.** Core importa Zod e nada mais. Sem Next, sem Playwright, sem Supabase client.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { linkItemSchema, costItemSchema, stepLinkSchema, stepItemSchema, documentRequiredSchema, publicServiceSchema, } from "./service.schema.js";
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const linkItemSchema: z.ZodObject<{
|
|
3
|
+
url: z.ZodString;
|
|
4
|
+
label: z.ZodString;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
url: string;
|
|
7
|
+
label: string;
|
|
8
|
+
}, {
|
|
9
|
+
url: string;
|
|
10
|
+
label: string;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const costItemSchema: z.ZodObject<{
|
|
13
|
+
description: z.ZodString;
|
|
14
|
+
type: z.ZodEnum<["fixo", "faixa", "gratuito", "variavel"]>;
|
|
15
|
+
value: z.ZodNullable<z.ZodNumber>;
|
|
16
|
+
valueMin: z.ZodNullable<z.ZodNumber>;
|
|
17
|
+
valueMax: z.ZodNullable<z.ZodNumber>;
|
|
18
|
+
required: z.ZodBoolean;
|
|
19
|
+
note: z.ZodNullable<z.ZodString>;
|
|
20
|
+
link: z.ZodNullable<z.ZodString>;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
value: number | null;
|
|
23
|
+
type: "fixo" | "faixa" | "gratuito" | "variavel";
|
|
24
|
+
description: string;
|
|
25
|
+
valueMin: number | null;
|
|
26
|
+
valueMax: number | null;
|
|
27
|
+
required: boolean;
|
|
28
|
+
note: string | null;
|
|
29
|
+
link: string | null;
|
|
30
|
+
}, {
|
|
31
|
+
value: number | null;
|
|
32
|
+
type: "fixo" | "faixa" | "gratuito" | "variavel";
|
|
33
|
+
description: string;
|
|
34
|
+
valueMin: number | null;
|
|
35
|
+
valueMax: number | null;
|
|
36
|
+
required: boolean;
|
|
37
|
+
note: string | null;
|
|
38
|
+
link: string | null;
|
|
39
|
+
}>;
|
|
40
|
+
export declare const stepLinkSchema: z.ZodObject<{
|
|
41
|
+
anchor: z.ZodString;
|
|
42
|
+
url: z.ZodString;
|
|
43
|
+
}, "strip", z.ZodTypeAny, {
|
|
44
|
+
url: string;
|
|
45
|
+
anchor: string;
|
|
46
|
+
}, {
|
|
47
|
+
url: string;
|
|
48
|
+
anchor: string;
|
|
49
|
+
}>;
|
|
50
|
+
export declare const stepItemSchema: z.ZodObject<{
|
|
51
|
+
text: z.ZodString;
|
|
52
|
+
links: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
53
|
+
anchor: z.ZodString;
|
|
54
|
+
url: z.ZodString;
|
|
55
|
+
}, "strip", z.ZodTypeAny, {
|
|
56
|
+
url: string;
|
|
57
|
+
anchor: string;
|
|
58
|
+
}, {
|
|
59
|
+
url: string;
|
|
60
|
+
anchor: string;
|
|
61
|
+
}>, "many">>;
|
|
62
|
+
}, "strip", z.ZodTypeAny, {
|
|
63
|
+
text: string;
|
|
64
|
+
links?: {
|
|
65
|
+
url: string;
|
|
66
|
+
anchor: string;
|
|
67
|
+
}[] | undefined;
|
|
68
|
+
}, {
|
|
69
|
+
text: string;
|
|
70
|
+
links?: {
|
|
71
|
+
url: string;
|
|
72
|
+
anchor: string;
|
|
73
|
+
}[] | undefined;
|
|
74
|
+
}>;
|
|
75
|
+
export declare const documentRequiredSchema: z.ZodObject<{
|
|
76
|
+
standard_code: z.ZodString;
|
|
77
|
+
name: z.ZodString;
|
|
78
|
+
category: z.ZodString;
|
|
79
|
+
required: z.ZodBoolean;
|
|
80
|
+
condition: z.ZodNullable<z.ZodString>;
|
|
81
|
+
link: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
82
|
+
}, "strip", z.ZodTypeAny, {
|
|
83
|
+
required: boolean;
|
|
84
|
+
standard_code: string;
|
|
85
|
+
name: string;
|
|
86
|
+
category: string;
|
|
87
|
+
condition: string | null;
|
|
88
|
+
link?: string | null | undefined;
|
|
89
|
+
}, {
|
|
90
|
+
required: boolean;
|
|
91
|
+
standard_code: string;
|
|
92
|
+
name: string;
|
|
93
|
+
category: string;
|
|
94
|
+
condition: string | null;
|
|
95
|
+
link?: string | null | undefined;
|
|
96
|
+
}>;
|
|
97
|
+
export declare const publicServiceSchema: z.ZodObject<{
|
|
98
|
+
slug: z.ZodString;
|
|
99
|
+
name: z.ZodString;
|
|
100
|
+
agency: z.ZodString;
|
|
101
|
+
level: z.ZodEnum<["federal", "estadual", "municipal"]>;
|
|
102
|
+
state: z.ZodString;
|
|
103
|
+
municipality: z.ZodString;
|
|
104
|
+
url: z.ZodString;
|
|
105
|
+
type: z.ZodEnum<["online", "presencial", "misto"]>;
|
|
106
|
+
steps: z.ZodEffects<z.ZodArray<z.ZodObject<{
|
|
107
|
+
text: z.ZodString;
|
|
108
|
+
links: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
109
|
+
anchor: z.ZodString;
|
|
110
|
+
url: z.ZodString;
|
|
111
|
+
}, "strip", z.ZodTypeAny, {
|
|
112
|
+
url: string;
|
|
113
|
+
anchor: string;
|
|
114
|
+
}, {
|
|
115
|
+
url: string;
|
|
116
|
+
anchor: string;
|
|
117
|
+
}>, "many">>;
|
|
118
|
+
}, "strip", z.ZodTypeAny, {
|
|
119
|
+
text: string;
|
|
120
|
+
links?: {
|
|
121
|
+
url: string;
|
|
122
|
+
anchor: string;
|
|
123
|
+
}[] | undefined;
|
|
124
|
+
}, {
|
|
125
|
+
text: string;
|
|
126
|
+
links?: {
|
|
127
|
+
url: string;
|
|
128
|
+
anchor: string;
|
|
129
|
+
}[] | undefined;
|
|
130
|
+
}>, "many">, {
|
|
131
|
+
text: string;
|
|
132
|
+
links?: {
|
|
133
|
+
url: string;
|
|
134
|
+
anchor: string;
|
|
135
|
+
}[] | undefined;
|
|
136
|
+
}[], unknown>;
|
|
137
|
+
links: z.ZodArray<z.ZodObject<{
|
|
138
|
+
url: z.ZodString;
|
|
139
|
+
label: z.ZodString;
|
|
140
|
+
}, "strip", z.ZodTypeAny, {
|
|
141
|
+
url: string;
|
|
142
|
+
label: string;
|
|
143
|
+
}, {
|
|
144
|
+
url: string;
|
|
145
|
+
label: string;
|
|
146
|
+
}>, "many">;
|
|
147
|
+
documents: z.ZodArray<z.ZodObject<{
|
|
148
|
+
standard_code: z.ZodString;
|
|
149
|
+
name: z.ZodString;
|
|
150
|
+
category: z.ZodString;
|
|
151
|
+
required: z.ZodBoolean;
|
|
152
|
+
condition: z.ZodNullable<z.ZodString>;
|
|
153
|
+
link: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
154
|
+
}, "strip", z.ZodTypeAny, {
|
|
155
|
+
required: boolean;
|
|
156
|
+
standard_code: string;
|
|
157
|
+
name: string;
|
|
158
|
+
category: string;
|
|
159
|
+
condition: string | null;
|
|
160
|
+
link?: string | null | undefined;
|
|
161
|
+
}, {
|
|
162
|
+
required: boolean;
|
|
163
|
+
standard_code: string;
|
|
164
|
+
name: string;
|
|
165
|
+
category: string;
|
|
166
|
+
condition: string | null;
|
|
167
|
+
link?: string | null | undefined;
|
|
168
|
+
}>, "many">;
|
|
169
|
+
costs: z.ZodArray<z.ZodObject<{
|
|
170
|
+
description: z.ZodString;
|
|
171
|
+
type: z.ZodEnum<["fixo", "faixa", "gratuito", "variavel"]>;
|
|
172
|
+
value: z.ZodNullable<z.ZodNumber>;
|
|
173
|
+
valueMin: z.ZodNullable<z.ZodNumber>;
|
|
174
|
+
valueMax: z.ZodNullable<z.ZodNumber>;
|
|
175
|
+
required: z.ZodBoolean;
|
|
176
|
+
note: z.ZodNullable<z.ZodString>;
|
|
177
|
+
link: z.ZodNullable<z.ZodString>;
|
|
178
|
+
}, "strip", z.ZodTypeAny, {
|
|
179
|
+
value: number | null;
|
|
180
|
+
type: "fixo" | "faixa" | "gratuito" | "variavel";
|
|
181
|
+
description: string;
|
|
182
|
+
valueMin: number | null;
|
|
183
|
+
valueMax: number | null;
|
|
184
|
+
required: boolean;
|
|
185
|
+
note: string | null;
|
|
186
|
+
link: string | null;
|
|
187
|
+
}, {
|
|
188
|
+
value: number | null;
|
|
189
|
+
type: "fixo" | "faixa" | "gratuito" | "variavel";
|
|
190
|
+
description: string;
|
|
191
|
+
valueMin: number | null;
|
|
192
|
+
valueMax: number | null;
|
|
193
|
+
required: boolean;
|
|
194
|
+
note: string | null;
|
|
195
|
+
link: string | null;
|
|
196
|
+
}>, "many">;
|
|
197
|
+
keywords: z.ZodArray<z.ZodString, "many">;
|
|
198
|
+
finalidade: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
199
|
+
tempoEletronico: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
200
|
+
tempoFisico: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
201
|
+
contato: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
202
|
+
}, "strip", z.ZodTypeAny, {
|
|
203
|
+
url: string;
|
|
204
|
+
type: "online" | "presencial" | "misto";
|
|
205
|
+
links: {
|
|
206
|
+
url: string;
|
|
207
|
+
label: string;
|
|
208
|
+
}[];
|
|
209
|
+
name: string;
|
|
210
|
+
slug: string;
|
|
211
|
+
agency: string;
|
|
212
|
+
level: "federal" | "estadual" | "municipal";
|
|
213
|
+
state: string;
|
|
214
|
+
municipality: string;
|
|
215
|
+
steps: {
|
|
216
|
+
text: string;
|
|
217
|
+
links?: {
|
|
218
|
+
url: string;
|
|
219
|
+
anchor: string;
|
|
220
|
+
}[] | undefined;
|
|
221
|
+
}[];
|
|
222
|
+
documents: {
|
|
223
|
+
required: boolean;
|
|
224
|
+
standard_code: string;
|
|
225
|
+
name: string;
|
|
226
|
+
category: string;
|
|
227
|
+
condition: string | null;
|
|
228
|
+
link?: string | null | undefined;
|
|
229
|
+
}[];
|
|
230
|
+
costs: {
|
|
231
|
+
value: number | null;
|
|
232
|
+
type: "fixo" | "faixa" | "gratuito" | "variavel";
|
|
233
|
+
description: string;
|
|
234
|
+
valueMin: number | null;
|
|
235
|
+
valueMax: number | null;
|
|
236
|
+
required: boolean;
|
|
237
|
+
note: string | null;
|
|
238
|
+
link: string | null;
|
|
239
|
+
}[];
|
|
240
|
+
keywords: string[];
|
|
241
|
+
finalidade?: string | null | undefined;
|
|
242
|
+
tempoEletronico?: string | null | undefined;
|
|
243
|
+
tempoFisico?: string | null | undefined;
|
|
244
|
+
contato?: string[] | undefined;
|
|
245
|
+
}, {
|
|
246
|
+
url: string;
|
|
247
|
+
type: "online" | "presencial" | "misto";
|
|
248
|
+
links: {
|
|
249
|
+
url: string;
|
|
250
|
+
label: string;
|
|
251
|
+
}[];
|
|
252
|
+
name: string;
|
|
253
|
+
slug: string;
|
|
254
|
+
agency: string;
|
|
255
|
+
level: "federal" | "estadual" | "municipal";
|
|
256
|
+
state: string;
|
|
257
|
+
municipality: string;
|
|
258
|
+
documents: {
|
|
259
|
+
required: boolean;
|
|
260
|
+
standard_code: string;
|
|
261
|
+
name: string;
|
|
262
|
+
category: string;
|
|
263
|
+
condition: string | null;
|
|
264
|
+
link?: string | null | undefined;
|
|
265
|
+
}[];
|
|
266
|
+
costs: {
|
|
267
|
+
value: number | null;
|
|
268
|
+
type: "fixo" | "faixa" | "gratuito" | "variavel";
|
|
269
|
+
description: string;
|
|
270
|
+
valueMin: number | null;
|
|
271
|
+
valueMax: number | null;
|
|
272
|
+
required: boolean;
|
|
273
|
+
note: string | null;
|
|
274
|
+
link: string | null;
|
|
275
|
+
}[];
|
|
276
|
+
keywords: string[];
|
|
277
|
+
steps?: unknown;
|
|
278
|
+
finalidade?: string | null | undefined;
|
|
279
|
+
tempoEletronico?: string | null | undefined;
|
|
280
|
+
tempoFisico?: string | null | undefined;
|
|
281
|
+
contato?: string[] | undefined;
|
|
282
|
+
}>;
|
|
283
|
+
export type PublicServiceInput = z.infer<typeof publicServiceSchema>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const linkItemSchema = z.object({
|
|
3
|
+
url: z.string().url(),
|
|
4
|
+
label: z.string(),
|
|
5
|
+
});
|
|
6
|
+
export const costItemSchema = z.object({
|
|
7
|
+
description: z.string(),
|
|
8
|
+
type: z.enum(["fixo", "faixa", "gratuito", "variavel"]),
|
|
9
|
+
value: z.number().nullable(),
|
|
10
|
+
valueMin: z.number().nullable(),
|
|
11
|
+
valueMax: z.number().nullable(),
|
|
12
|
+
required: z.boolean(),
|
|
13
|
+
note: z.string().nullable(),
|
|
14
|
+
link: z.string().nullable(),
|
|
15
|
+
});
|
|
16
|
+
export const stepLinkSchema = z.object({
|
|
17
|
+
anchor: z.string(),
|
|
18
|
+
url: z.string().url(),
|
|
19
|
+
});
|
|
20
|
+
export const stepItemSchema = z.object({
|
|
21
|
+
text: z.string(),
|
|
22
|
+
links: z.array(stepLinkSchema).optional(),
|
|
23
|
+
});
|
|
24
|
+
export const documentRequiredSchema = z.object({
|
|
25
|
+
standard_code: z.string(),
|
|
26
|
+
name: z.string(),
|
|
27
|
+
category: z.string(),
|
|
28
|
+
required: z.boolean(),
|
|
29
|
+
condition: z.string().nullable(),
|
|
30
|
+
link: z.string().url().nullable().optional(),
|
|
31
|
+
});
|
|
32
|
+
/** Coerce legacy steps (string[] or old { text, link? }[]) to StepItem[] for backward compatibility. */
|
|
33
|
+
const stepsSchema = z.preprocess((val) => {
|
|
34
|
+
if (!Array.isArray(val) || val.length === 0)
|
|
35
|
+
return val;
|
|
36
|
+
const first = val[0];
|
|
37
|
+
if (typeof first === "string") {
|
|
38
|
+
return val.map((s) => ({ text: s }));
|
|
39
|
+
}
|
|
40
|
+
if (first && typeof first === "object" && "link" in first && "text" in first) {
|
|
41
|
+
return val.map((s) => s.link ? { text: s.text, links: [{ anchor: "clique aqui", url: s.link }] } : { text: s.text });
|
|
42
|
+
}
|
|
43
|
+
return val;
|
|
44
|
+
}, z.array(stepItemSchema));
|
|
45
|
+
export const publicServiceSchema = z.object({
|
|
46
|
+
slug: z.string().min(1),
|
|
47
|
+
name: z.string().min(1),
|
|
48
|
+
agency: z.string().min(1),
|
|
49
|
+
level: z.enum(["federal", "estadual", "municipal"]),
|
|
50
|
+
state: z.string().min(1),
|
|
51
|
+
municipality: z.string().min(1),
|
|
52
|
+
url: z.string().url(),
|
|
53
|
+
type: z.enum(["online", "presencial", "misto"]),
|
|
54
|
+
steps: stepsSchema,
|
|
55
|
+
links: z.array(linkItemSchema),
|
|
56
|
+
documents: z.array(documentRequiredSchema),
|
|
57
|
+
costs: z.array(costItemSchema),
|
|
58
|
+
keywords: z.array(z.string()),
|
|
59
|
+
finalidade: z.string().nullable().optional(),
|
|
60
|
+
tempoEletronico: z.string().nullable().optional(),
|
|
61
|
+
tempoFisico: z.string().nullable().optional(),
|
|
62
|
+
contato: z.array(z.string()).optional(),
|
|
63
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { LinkItem, CostItem, StepLink, StepItem, DocumentRequired, PublicService, } from "./service.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service types — shared contract for public services across workspaces.
|
|
3
|
+
*/
|
|
4
|
+
export interface LinkItem {
|
|
5
|
+
url: string;
|
|
6
|
+
label: string;
|
|
7
|
+
}
|
|
8
|
+
export interface CostItem {
|
|
9
|
+
description: string;
|
|
10
|
+
type: "fixo" | "faixa" | "gratuito" | "variavel";
|
|
11
|
+
value: number | null;
|
|
12
|
+
valueMin: number | null;
|
|
13
|
+
valueMax: number | null;
|
|
14
|
+
required: boolean;
|
|
15
|
+
note: string | null;
|
|
16
|
+
link: string | null;
|
|
17
|
+
}
|
|
18
|
+
/** Anchor is the substring in the step text to turn into a link (e.g. "clique aqui"); url is the redirect. */
|
|
19
|
+
export interface StepLink {
|
|
20
|
+
anchor: string;
|
|
21
|
+
url: string;
|
|
22
|
+
}
|
|
23
|
+
export interface StepItem {
|
|
24
|
+
text: string;
|
|
25
|
+
links?: StepLink[];
|
|
26
|
+
}
|
|
27
|
+
export interface DocumentRequired {
|
|
28
|
+
standard_code: string;
|
|
29
|
+
name: string;
|
|
30
|
+
category: string;
|
|
31
|
+
required: boolean;
|
|
32
|
+
condition: string | null;
|
|
33
|
+
link?: string | null;
|
|
34
|
+
}
|
|
35
|
+
export interface PublicService {
|
|
36
|
+
slug: string;
|
|
37
|
+
name: string;
|
|
38
|
+
agency: string;
|
|
39
|
+
level: "federal" | "estadual" | "municipal";
|
|
40
|
+
state: string;
|
|
41
|
+
municipality: string;
|
|
42
|
+
url: string;
|
|
43
|
+
type: "online" | "presencial" | "misto";
|
|
44
|
+
steps: StepItem[];
|
|
45
|
+
links: LinkItem[];
|
|
46
|
+
documents: DocumentRequired[];
|
|
47
|
+
costs: CostItem[];
|
|
48
|
+
keywords: string[];
|
|
49
|
+
finalidade?: string | null;
|
|
50
|
+
tempoEletronico?: string | null;
|
|
51
|
+
tempoFisico?: string | null;
|
|
52
|
+
contato?: string[];
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { slugify } from "./slugify.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { slugify } from "./slugify.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slugify for service slugs (normalize NFD, strip diacritics, lowercase, dashes).
|
|
3
|
+
*/
|
|
4
|
+
export function slugify(text) {
|
|
5
|
+
return text
|
|
6
|
+
.normalize("NFD")
|
|
7
|
+
.replace(/\p{Diacritic}/gu, "")
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.trim()
|
|
10
|
+
.replace(/[^\p{L}\p{N}\s-]/gu, "")
|
|
11
|
+
.replace(/\s+/g, "-")
|
|
12
|
+
.replace(/-+/g, "-")
|
|
13
|
+
.replace(/^-|-$/g, "");
|
|
14
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@brasil-conjunto/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": ["dist"],
|
|
6
|
+
"exports": {
|
|
7
|
+
"./types": {
|
|
8
|
+
"types": "./dist/types/index.d.ts",
|
|
9
|
+
"import": "./dist/types/index.js",
|
|
10
|
+
"default": "./dist/types/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./schemas": {
|
|
13
|
+
"types": "./dist/schemas/index.d.ts",
|
|
14
|
+
"import": "./dist/schemas/index.js",
|
|
15
|
+
"default": "./dist/schemas/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./utils": {
|
|
18
|
+
"types": "./dist/utils/index.d.ts",
|
|
19
|
+
"import": "./dist/utils/index.js",
|
|
20
|
+
"default": "./dist/utils/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -p tsconfig.build.json",
|
|
25
|
+
"prepublishOnly": "npm run build",
|
|
26
|
+
"test": "vitest run"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"zod": "^3.23.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"typescript": "^5.6.0",
|
|
33
|
+
"vitest": "^2.1.0"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
}
|
|
38
|
+
}
|