pure-schemify 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/CHANGELOG.md +11 -0
- package/README.md +153 -0
- package/TODO.md +238 -0
- package/dist/healer.js +110 -0
- package/dist/index.js +458 -0
- package/package.json +14 -0
- package/src/healer.ts +159 -0
- package/src/index.ts +495 -0
- package/tsconfig.json +25 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
### What's Changed
|
|
8
|
+
|
|
9
|
+
- **Documentation**: Created `README.md` to explain the library's purpose, implementation details, and usage.
|
|
10
|
+
- **Configuration**: Added `.gitignore` to maintain a clean repository.
|
|
11
|
+
- **Initial Commit**: documented existing source code.
|
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# purecore-schemify
|
|
2
|
+
|
|
3
|
+
**purecore-schemify** is a **High Fidelity Polyfill** for Zod-like schema validation, designed specifically for the `@purecore` ecosystem. It provides a lightweight, zero-dependency, type-safe runtime validation library that follows the fluent API design pattern familiar to TypeScript developers.
|
|
4
|
+
|
|
5
|
+
This library allows you to define schemas for your data, infer TypeScript types automatically, and validate data at runtime with informative error reporting.
|
|
6
|
+
|
|
7
|
+
## 🚀 Features
|
|
8
|
+
|
|
9
|
+
- **Zero Dependencies**: Built entirely with native TypeScript, ensuring minimal footprint and maximum portability.
|
|
10
|
+
- **Fluent API**: Chainable methods (e.g., `.min()`, `.optional()`, `.refine()`) for intuitive schema definition.
|
|
11
|
+
- **Static Type Inference**: Automatic TypeScript type inference using `z.infer<typeof schema>`.
|
|
12
|
+
- **Runtime Validation**: Robust `parse` and `safeParse` methods to ensure data integrity.
|
|
13
|
+
- **Nominal Typing Support**: Built-in support for Nominal Types via `z.nominal` and atomic behaviors, enforcing strict domain modeling.
|
|
14
|
+
- **Extensible**: check mechanisms, refinements, and transformations.
|
|
15
|
+
|
|
16
|
+
## 📦 Installation
|
|
17
|
+
|
|
18
|
+
Since this is part of the `@purecore` monorepo structure, you can typically use it by importing the source directly or linking the package.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bun install @purecore/schemify
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
_(Note: Adjust installation method based on your specific workspace configuration)_
|
|
25
|
+
|
|
26
|
+
## 🛠 Usage
|
|
27
|
+
|
|
28
|
+
### Basic Primitives
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { z } from "./src/index";
|
|
32
|
+
|
|
33
|
+
// String Schema
|
|
34
|
+
const emailSchema = z.string().email().min(5);
|
|
35
|
+
|
|
36
|
+
// Number Schema
|
|
37
|
+
const ageSchema = z.number().int().min(18).max(120);
|
|
38
|
+
|
|
39
|
+
// Parse
|
|
40
|
+
emailSchema.parse("user@example.com"); // "user@example.com"
|
|
41
|
+
ageSchema.parse(25); // 25
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Object Schemas
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
const UserSchema = z.object({
|
|
48
|
+
id: z.number(),
|
|
49
|
+
username: z.string().min(3),
|
|
50
|
+
isActive: z.boolean().default(true),
|
|
51
|
+
roles: z.array(z.string()).optional(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
type User = z.infer<typeof UserSchema>;
|
|
55
|
+
// inferred as: { id: number; username: string; isActive: boolean; roles?: string[] }
|
|
56
|
+
|
|
57
|
+
const result = UserSchema.safeParse({
|
|
58
|
+
id: 1,
|
|
59
|
+
username: "alice",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (result.success) {
|
|
63
|
+
console.log(result.data);
|
|
64
|
+
} else {
|
|
65
|
+
console.error(result.error);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 🧠 Advanced Concepts: Nominal & Atomic Types
|
|
70
|
+
|
|
71
|
+
One of the unique features of `purecore-schemify` is its first-class support for **Nominal Types** and **Atomic Behaviors**. This allows you to create opaque types that are structurally strings/numbers but semantically distinct.
|
|
72
|
+
|
|
73
|
+
### Behaviors
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// Define a behavior for a UserID
|
|
77
|
+
const UserId = z.behavior("UserId", z.string().uuid());
|
|
78
|
+
|
|
79
|
+
// Forge a value (validates and brands it)
|
|
80
|
+
const id = UserId.forge("123e4567-e89b-12d3-a456-426614174000");
|
|
81
|
+
|
|
82
|
+
// 'id' is now typed as AtomicType<string, "UserId">
|
|
83
|
+
// It cannot be accidentally assigned to a generic string or another nominal type.
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Nominal Wrapper
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const Email = z.nominal("Email", z.string().email());
|
|
90
|
+
|
|
91
|
+
const myEmail = Email.validate("test@test.com");
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 📚 Technical Deep Dive: How it Works
|
|
95
|
+
|
|
96
|
+
### The Core: `SchemifyType`
|
|
97
|
+
|
|
98
|
+
At the heart of the library is the abstract base class `SchemifyType<Output, Def, Input>`.
|
|
99
|
+
|
|
100
|
+
- **`_parseSync`**: Every specific type (String, Number, Object) implements this abstract method to perform its core validation logic.
|
|
101
|
+
- **`_process`**: This method handles the common logic for all types:
|
|
102
|
+
1. **Optional/Nullable/Default**: Checks if the value is missing or null and handles it based on configuration.
|
|
103
|
+
2. **Validation**: Calls `_parseSync`.
|
|
104
|
+
3. **Refinements**: Executes custom `.refine()` checks.
|
|
105
|
+
4. **Transforms**: Applies `.transform()` functions to modify the output.
|
|
106
|
+
|
|
107
|
+
### The Facade: `z`
|
|
108
|
+
|
|
109
|
+
The `z` export acts as a singleton factory. It provides convenient accessors to instantiate specific `SchemifyType` subclasses (`SchemifyString`, `SchemifyObject`, etc.), mimicking the API surface of popular validation libraries for ease of adoption.
|
|
110
|
+
|
|
111
|
+
### Error Handling
|
|
112
|
+
|
|
113
|
+
Errors are aggregated into a `SchemifyError` class, which contains an array of `SchemifyIssue` objects. Each issue pinpoints the `path` (e.g., `user.address.street`) and the `code` of the error, allowing for precise UI feedback (like form validation errors).
|
|
114
|
+
|
|
115
|
+
## 🧪 How to Test
|
|
116
|
+
|
|
117
|
+
You can test the library using `bun test` or by running a simple script to verify behavior.
|
|
118
|
+
|
|
119
|
+
**Example Test Script (`test.ts`):**
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { z } from "./src/index";
|
|
123
|
+
|
|
124
|
+
const schema = z.object({
|
|
125
|
+
name: z.string(),
|
|
126
|
+
age: z.number().min(0),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const data = schema.parse({ name: "Bob", age: 30 });
|
|
131
|
+
console.log("✅ Validation passed:", data);
|
|
132
|
+
} catch (e) {
|
|
133
|
+
console.error("❌ Validation failed:", e);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
schema.parse({ name: "Bob", age: -5 });
|
|
138
|
+
} catch (e) {
|
|
139
|
+
console.log("✅ Expected failure caught:", e.message);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Run it with:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
wsl bun run test.ts
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
_Created by Antigravity for the @purecore open-source initiative._
|
|
152
|
+
|
|
153
|
+
[View Changelog](./CHANGELOG.md)
|
package/TODO.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
|
|
2
|
+
**Contexto**
|
|
3
|
+
Você é um agente de codificação encarregado de **inferir tipos semânticos atômicos** a partir de código TypeScript existente que hoje usa apenas **primitivos** (`boolean`, `number`, `string`, `Date`). Não assuma bibliotecas externas instaladas. Seu objetivo é:
|
|
4
|
+
|
|
5
|
+
1. detectar candidatos a **tipos semânticos**;
|
|
6
|
+
2. propor **nomes canônicos** no padrão `dominio.entidade.nome` (com ponto);
|
|
7
|
+
3. sugerir **regras e validações** mínimas;
|
|
8
|
+
4. gerar **artefatos** auto-contidos (sem dependências externas);
|
|
9
|
+
5. listar **até 5 novos tipos** que ainda **não existem** no repositório e que valem a pena padronizar.
|
|
10
|
+
5. listar **até 5 novos tipos** que ainda **não existem** no repositório e que valem a pena padronizar, que sejam os mais específicos daquele domínio e/ou entidade.
|
|
11
|
+
|
|
12
|
+
> Use linguagem e comentários em **português**.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Entradas que você tem
|
|
17
|
+
|
|
18
|
+
* Árvore de arquivos `.ts/.tsx` e testes.
|
|
19
|
+
* Nomes de variáveis, propriedades, funções, schemas, migrações, seeds.
|
|
20
|
+
* Strings literais, sufixos/prefixos, nomes de colunas/IDs e chamadas de API.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Saídas obrigatórias (em ORDEM)
|
|
25
|
+
|
|
26
|
+
1. **Relatório** em Markdown: achados, suposições e conflitos.
|
|
27
|
+
2. **Manifesto** `semantic-types.manifest.json` com as inferências.
|
|
28
|
+
3. **Stubs** de tipos canônicos (arquivos `.ts` auto-contidos).
|
|
29
|
+
4. **Patches** (diff unificado) mostrando como aplicar os tipos.
|
|
30
|
+
5. **Roadmap** com até **5** tipos novos recomendados (ainda não mapeados).
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Padrão de Nomes (canônicos)
|
|
35
|
+
|
|
36
|
+
Use `kebab` no arquivo e **ponto** no AtomicType:
|
|
37
|
+
|
|
38
|
+
* Arquivo: `domains/{Domain}/{Entity}/{name}.ts`
|
|
39
|
+
* AtomicType interno: `{domain}.{entity}.{name}`
|
|
40
|
+
Ex.: `domains/ecommerce/order/total.ts` → AtomicType `ecommerce.order.total`.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Heurísticas de Inferência
|
|
45
|
+
|
|
46
|
+
### A) Booleans
|
|
47
|
+
|
|
48
|
+
Detecção por **prefixos** e contexto de uso:
|
|
49
|
+
|
|
50
|
+
* `is*`, `has*`, `can*`, `should*`, `enable*`, `allow*`, `show*`
|
|
51
|
+
* Exemplos canônicos:
|
|
52
|
+
|
|
53
|
+
* `isDone` → `project.task.isDone`
|
|
54
|
+
* `hasAllergies` → `health.patient.hasAllergies`
|
|
55
|
+
* `showWeekViewCalendar` → `ui.calendar.showWeekView`
|
|
56
|
+
Valide: coação para boolean, proibir `null/undefined` (ou explicitar `Maybe`), e **combinadores** só via funções (`and`, `or`) — nunca `&&` direto entre AtomicTypes.
|
|
57
|
+
|
|
58
|
+
### B) Numbers
|
|
59
|
+
|
|
60
|
+
Use nomes, unidades, e padrões:
|
|
61
|
+
|
|
62
|
+
* Sufixos/palavras-chave: `total`, `amount`, `price`, `quantity`, `count`, `size`, `capacity`, `rate`, `percentage`, `score`, `weight`, `length`, `height`, `width`, `radius`, `duration`.
|
|
63
|
+
* Padrões literais: `%`, `ms`, `s`, `min`, `h`, `kg`, `g`, `m`, `cm`, `km`, `px`, `rem`, `brl`, `usd`.
|
|
64
|
+
* Exemplos:
|
|
65
|
+
|
|
66
|
+
* `totalAppointments` → `clinic.schedule.totalAppointments` (inteiro ≥ 0)
|
|
67
|
+
* `revenueTotal` → `ecommerce.order.revenueTotal.brl` (moeda BRL)
|
|
68
|
+
* `teethExtracted` → `dentistry.procedure.teethExtracted` (inteiro 0–32)
|
|
69
|
+
* `percentage` → `metrics.kpi.percentage` (0–100 ou 0–1 — explicitar escala)
|
|
70
|
+
* `durationMs` → `time.duration.ms`
|
|
71
|
+
Valide: **faixas** (min/max), **inteireza** quando nome implicar contagem, e **unidade** no nome canônico (ex.: `.brl`, `.usd`, `.kg`, `.ms`).
|
|
72
|
+
|
|
73
|
+
### C) Strings
|
|
74
|
+
|
|
75
|
+
Detecte **formatos**:
|
|
76
|
+
|
|
77
|
+
* `email`, `phone`, `url`, `slug`, `isoCode`, `cpf/cnpj`, `uuid`, `id` (se houver padrão).
|
|
78
|
+
* Regex úteis (documente no stub):
|
|
79
|
+
|
|
80
|
+
* Email (robusto suficiente): `/^[^\s@]+@[^\s@]+\.[^\s@]+$/`
|
|
81
|
+
* URL: usar `new URL()` no validador (cair no catch se inválida)
|
|
82
|
+
* UUID v4: `/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i`
|
|
83
|
+
* Exemplos:
|
|
84
|
+
|
|
85
|
+
* `customerEmail` → `crm.customer.email`
|
|
86
|
+
* `orderId` (uuid) → `ecommerce.order.id`
|
|
87
|
+
* `countryIso2` → `geo.country.iso2`
|
|
88
|
+
|
|
89
|
+
### D) Date/Tempo
|
|
90
|
+
|
|
91
|
+
Mapeie granularidade: `createdAt`, `updatedAt`, `startAt`, `endAt`, `birthday`, `scheduledFor`, `dueAt`.
|
|
92
|
+
|
|
93
|
+
* Exemplos:
|
|
94
|
+
|
|
95
|
+
* `scheduledFor` → `calendar.event.scheduledAt` (Date)
|
|
96
|
+
* `birthday` → `identity.person.birthDate` (Date sem hora — normalize)
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Sem biblioteca externa? Forneça **Shim** local (TS puro)
|
|
101
|
+
|
|
102
|
+
Crie **um** utilitário interno para os Atomic Behavior Types de Semânticos **sem runtime**:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// src/AtomicBehaviorTypes/forge.ts
|
|
106
|
+
declare const __AtomicType: unique symbol;
|
|
107
|
+
|
|
108
|
+
export type AtomicType<T, Name extends string> = T & { readonly [__AtomicType]: Name };
|
|
109
|
+
|
|
110
|
+
export function BehaviorType<Name extends string>() {
|
|
111
|
+
return {
|
|
112
|
+
of: <T>(v: T) => v as AtomicType<T, Name>,
|
|
113
|
+
un: <T>(v: AtomicType<T, Name>) => v as unknown as T,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
## Facilitando a Instanciação com Funções 'forge'
|
|
120
|
+
|
|
121
|
+
Para tornar a criação de tipos semânticos mais intuitiva e menos verbosa, recomenda-se adicionar funções auxiliares simples, conhecidas como "forgers" ou "factories", diretamente nos módulos de cada tipo. Essas funções permitem instanciações rápidas, como `forgeEmail('sussu@gmail.com')`, evitando a necessidade de chamar métodos mais complexos como `Email.of(...)` em contextos cotidianos.
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
### Por que Usar Funções 'forge'?
|
|
125
|
+
|
|
126
|
+
- **Simplicidade:** Reduz a curva de aprendizado e o boilerplate, especialmente em código de produção onde a validação já é implícita.
|
|
127
|
+
- **Legibilidade:** Torna o código mais declarativo, focando no valor em vez da construção do tipo.
|
|
128
|
+
- **Compatibilidade:** Pode coexistir com as funções existentes (como `of` e `un`), servindo como atalhos para casos comuns.
|
|
129
|
+
- **Aplicação Gradual:** Facilita a migração de primitivos para tipos semânticos sem quebrar o fluxo de desenvolvimento.
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
### Recomendações
|
|
133
|
+
|
|
134
|
+
- **Padronização:** Sempre inclua `forge` em novos tipos para promover adoção rápida.
|
|
135
|
+
- **Validação:** A função `forge` deve herdar as validações de `of`, garantindo consistência.
|
|
136
|
+
- **Documentação:** No README de cada domínio, exemplifique usos com `forge` para onboarding.
|
|
137
|
+
- **Evolução:** Se necessário, expanda `forge` com overloads para aceitar múltiplos formatos (ex.: `forge` para datas aceitando string ou timeBehaviorType).
|
|
138
|
+
|
|
139
|
+
Essa abordagem torna os tipos semânticos mais acessíveis, acelerando a migração e reduzindo erros em projetos reais.
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
### Como Implementar
|
|
143
|
+
|
|
144
|
+
Adicione uma função `forge` em cada stub de tipo, que internamente chame a função `of` existente. Isso mantém a validação e o AtomicTypeing, mas oferece uma interface mais amigável.
|
|
145
|
+
|
|
146
|
+
#### Exemplo para Boolean (ex.: `project.task.isDone`)
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// domains/project/task/is-done.ts
|
|
150
|
+
import { AtomicType, BehaviorType } from "../../src/AtomicBehaviorTypes/forge";
|
|
151
|
+
export type IsDone = AtomicType<boolean, "project.task.isDone">;
|
|
152
|
+
|
|
153
|
+
export const IsDone = (() => {
|
|
154
|
+
const f = BehaviorType<"project.task.isDone">();
|
|
155
|
+
return {
|
|
156
|
+
of: (v: unknown): IsDone => f.of(Boolean(v)),
|
|
157
|
+
un: (v: IsDone): boolean => f.un(v),
|
|
158
|
+
and: (a: IsDone, b: IsDone): IsDone => f.of(f.un(a) && f.un(b)),
|
|
159
|
+
forge: (value: boolean): IsDone => f.of(value),
|
|
160
|
+
};
|
|
161
|
+
})();
|
|
162
|
+
```
|
|
163
|
+
Uso: `const isCompleted = IsDone.forge(true);` (equivalente a `IsDone.of(true)`).
|
|
164
|
+
|
|
165
|
+
### Number com moeda (ex.: `ecommerce.order.revenueTotal.brl`)
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
// domains/ecommerce/order/revenue-total.brl.ts
|
|
169
|
+
import { AtomicType, BehaviorType } from "../../src/AtomicBehaviorTypes/forge";
|
|
170
|
+
export type RevenueTotalBRL = AtomicType<number, "ecommerce.order.revenueTotal.brl">;
|
|
171
|
+
|
|
172
|
+
export const RevenueTotalBRL = (() => {
|
|
173
|
+
const f = BehaviorType<"ecommerce.order.revenueTotal.brl">();
|
|
174
|
+
return {
|
|
175
|
+
of: (v: unknown): RevenueTotalBRL => {
|
|
176
|
+
const n = Number(v);
|
|
177
|
+
if (!Number.isFinite(n) || n < 0) throw new TypeError("revenue must be >= 0");
|
|
178
|
+
return f.of(n);
|
|
179
|
+
},
|
|
180
|
+
un: (v: RevenueTotalBRL) => f.un(v),
|
|
181
|
+
add: (a: RevenueTotalBRL, b: RevenueTotalBRL): RevenueTotalBRL => f.of(f.un(a) + f.un(b)),
|
|
182
|
+
forge: (value: number): RevenueTotalBRL => f.of(value), // Nova função forge
|
|
183
|
+
};
|
|
184
|
+
})();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### String formatada (ex.: `crm.customer.email`)
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
// domains/universal/user/email.ts
|
|
191
|
+
import { AtomicType, BehaviorType } from "../../src/AtomicBehaviorTypes/forge";
|
|
192
|
+
export type Email = AtomicType<string, "crm.customer.email">;
|
|
193
|
+
|
|
194
|
+
export const Email = (() => {
|
|
195
|
+
const f = BehaviorType<"crm.customer.email">();
|
|
196
|
+
const emailRx = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
197
|
+
return {
|
|
198
|
+
of: (v: unknown): Email => {
|
|
199
|
+
const s = String(v).trim();
|
|
200
|
+
if (!emailRx.test(s)) throw new TypeError("invalid email");
|
|
201
|
+
return f.of(s);
|
|
202
|
+
},
|
|
203
|
+
un: (v: Email) => f.un(v),
|
|
204
|
+
forge: (value: string): Email => f.of(value),
|
|
205
|
+
};
|
|
206
|
+
})();
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Date (ex.: `calendar.event.scheduledAt`)
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
// domains/calendar/event/scheduled-at.ts
|
|
213
|
+
import { AtomicType, BehaviorType } from "../../src/AtomicBehaviorTypes/forge";
|
|
214
|
+
export type ScheduledAt = AtomicType<Date, "calendar.event.scheduledAt">;
|
|
215
|
+
|
|
216
|
+
export const ScheduledAt = (() => {
|
|
217
|
+
const f = BehaviorType<"calendar.event.scheduledAt">();
|
|
218
|
+
return {
|
|
219
|
+
of: (v: unknown): ScheduledAt => {
|
|
220
|
+
const d = v instanceof Date ? v : new Date(String(v));
|
|
221
|
+
if (Number.isNaN(d.getTime())) throw new TypeError("invalid date");
|
|
222
|
+
return f.of(d);
|
|
223
|
+
},
|
|
224
|
+
un: (v: ScheduledAt) => f.un(v),
|
|
225
|
+
forge: (value: string | Date): ScheduledAt => f.of(value),
|
|
226
|
+
};
|
|
227
|
+
})();
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
## Regras de qualidade que o agente deve aplicar
|
|
233
|
+
|
|
234
|
+
* **Nunca** substituir primitivo por tipo semântico sem **stub** + **import**.
|
|
235
|
+
* Apontar ambiguidade (`rate`, `size`, `code`) e sugerir **AtomicType** com **unidade ou domínio** explícitos.
|
|
236
|
+
* Sugerir **faixas**: contagens (≥ 0), porcentagens (0–1 ou 0–100 — escolha e documente).
|
|
237
|
+
* Não criar tipos transdomínio: `ecommerce.*` não reage com `crm.*` (a menos que o projeto já defina).
|
|
238
|
+
* Se detectar `any` em locais críticos, abrir **nota** para migração gradual com marcas semânticas.
|
package/dist/healer.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Core Healer Library
|
|
4
|
+
* Uma biblioteca para validação e "cura" automática de tipos de dados.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.healAndValidate = exports.Strategy = exports.Reflector = exports.Pharmacy = void 0;
|
|
8
|
+
// --- 1. Pharmacy: Encapsula o conhecimento de conversão de tipos ---
|
|
9
|
+
// Responsável por saber como transformar um tipo "A" num tipo "B".
|
|
10
|
+
exports.Pharmacy = {
|
|
11
|
+
/** Dicionário de "vacinas" (conversões) disponíveis */
|
|
12
|
+
vaccines: {
|
|
13
|
+
string: (value) => ({
|
|
14
|
+
to: {
|
|
15
|
+
number: !isNaN(parseFloat(value)) && isFinite(value)
|
|
16
|
+
? Number(value)
|
|
17
|
+
: undefined,
|
|
18
|
+
boolean: value.toLowerCase() === "true"
|
|
19
|
+
? true
|
|
20
|
+
: value.toLowerCase() === "false"
|
|
21
|
+
? false
|
|
22
|
+
: undefined,
|
|
23
|
+
},
|
|
24
|
+
}),
|
|
25
|
+
number: (value) => ({
|
|
26
|
+
to: {
|
|
27
|
+
string: String(value),
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
boolean: (value) => ({
|
|
31
|
+
to: {
|
|
32
|
+
number: value ? 1 : 0,
|
|
33
|
+
string: String(value),
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
/**
|
|
38
|
+
* Tenta administrar uma conversão de tipo segura.
|
|
39
|
+
* @returns O valor convertido ou undefined se não houver conversão disponível.
|
|
40
|
+
*/
|
|
41
|
+
administer: (value, fromType, toType) => {
|
|
42
|
+
try {
|
|
43
|
+
return exports.Pharmacy.vaccines[fromType]?.(value).to[toType];
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
// --- 2. Reflector: Lida com a introspecção de tipos ---
|
|
51
|
+
exports.Reflector = {
|
|
52
|
+
/** Obtém a estrutura actual do payload com os seus tipos */
|
|
53
|
+
getStructure: (obj) => Object.entries(obj).map(([key, value]) => [key, typeof value]),
|
|
54
|
+
/** Obtém o tipo alvo definido no esquema para uma determinada chave */
|
|
55
|
+
getTargetType: (schema, key) => {
|
|
56
|
+
const target = schema[key];
|
|
57
|
+
// Se o esquema define um valor padrão em vez de uma string de tipo
|
|
58
|
+
return typeof target === "string" &&
|
|
59
|
+
["string", "number", "boolean"].includes(target)
|
|
60
|
+
? target
|
|
61
|
+
: typeof target;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
// --- 3. Strategy: Gere as transições de estado e recursão ---
|
|
65
|
+
exports.Strategy = {
|
|
66
|
+
/** Verifica se o limite de tentativas de cura foi atingido */
|
|
67
|
+
isLimitReached: (rounds, limit) => rounds >= limit,
|
|
68
|
+
/** Prepara o próximo contexto com o valor "curado" */
|
|
69
|
+
prepareNextAttempt: (ctx, key, healedValue) => ({
|
|
70
|
+
...ctx,
|
|
71
|
+
payload: { ...ctx.payload, [key]: healedValue },
|
|
72
|
+
rounds: (ctx.rounds || 0) + 1,
|
|
73
|
+
}),
|
|
74
|
+
};
|
|
75
|
+
// --- 4. Logic: Redutor de Validação e Processamento ---
|
|
76
|
+
const processField = (ctx) => (failures, [key, currentType]) => {
|
|
77
|
+
const targetType = exports.Reflector.getTargetType(ctx.schema, key);
|
|
78
|
+
// Caso de Sucesso: Os tipos coincidem
|
|
79
|
+
if (currentType === targetType)
|
|
80
|
+
return failures;
|
|
81
|
+
// Caso de Cura: Tenta converter o valor
|
|
82
|
+
const healedValue = exports.Pharmacy.administer(ctx.payload[key], currentType, targetType);
|
|
83
|
+
// Verifica se a cura foi bem-sucedida E se o novo payload passa na validação (recursão)
|
|
84
|
+
const isFixed = healedValue !== undefined &&
|
|
85
|
+
(0, exports.healAndValidate)({
|
|
86
|
+
...exports.Strategy.prepareNextAttempt(ctx, key, healedValue),
|
|
87
|
+
});
|
|
88
|
+
if (!isFixed) {
|
|
89
|
+
failures.push([key, currentType]);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// Aplica a cura no payload do contexto actual (mutação controlada para o resultado final)
|
|
93
|
+
ctx.payload[key] = healedValue;
|
|
94
|
+
}
|
|
95
|
+
return failures;
|
|
96
|
+
};
|
|
97
|
+
// --- 5. Ponto de Entrada Principal ---
|
|
98
|
+
/**
|
|
99
|
+
* Tenta validar e curar um payload baseando-se num esquema.
|
|
100
|
+
* @param ctx Contexto contendo o payload e o esquema alvo.
|
|
101
|
+
* @returns Verdadeiro se o payload estiver válido (ou foi curado com sucesso).
|
|
102
|
+
*/
|
|
103
|
+
const healAndValidate = (ctx) => {
|
|
104
|
+
const { rounds = 0, LIMIT = 3, payload } = ctx;
|
|
105
|
+
if (exports.Strategy.isLimitReached(rounds, LIMIT))
|
|
106
|
+
return false;
|
|
107
|
+
const failures = exports.Reflector.getStructure(payload).reduce(processField(ctx), []);
|
|
108
|
+
return failures.length === 0;
|
|
109
|
+
};
|
|
110
|
+
exports.healAndValidate = healAndValidate;
|