@attrx/role-morphic 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 +301 -0
- package/dist/index.d.mts +1190 -0
- package/dist/index.d.ts +1190 -0
- package/dist/index.js +4680 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4648 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# RoleMorphic
|
|
2
|
+
|
|
3
|
+
Motor de conversão polimórfica de valores.
|
|
4
|
+
|
|
5
|
+
Um valor pode assumir múltiplas formas (variantes) mantendo sua identidade semântica.
|
|
6
|
+
|
|
7
|
+
## Instalação
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @attrx/role-morphic
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Os 4 Pilares
|
|
14
|
+
|
|
15
|
+
Cada role implementa 4 operações fundamentais:
|
|
16
|
+
|
|
17
|
+
| Pilar | Descrição | Exemplo |
|
|
18
|
+
|-------|-----------|---------|
|
|
19
|
+
| **Convert** | Transforma entre variantes | `hectare → acre` |
|
|
20
|
+
| **Cast** | Normaliza input sujo | `"100 ha" → 100` |
|
|
21
|
+
| **Validate** | Verifica regras semânticas | `área ≥ 0` |
|
|
22
|
+
| **Format** | Apresenta como string | `2.5 → "2.5 ha"` |
|
|
23
|
+
|
|
24
|
+
## Uso Rápido
|
|
25
|
+
|
|
26
|
+
### Direto (Recomendado)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { areaRole, lengthRole, colorRole, dateRole } from '@attrx/role-morphic';
|
|
30
|
+
|
|
31
|
+
// Area
|
|
32
|
+
areaRole.convert('hectare', 'acre', 1); // 2.47105
|
|
33
|
+
areaRole.cast('hectare', '100 ha'); // 100
|
|
34
|
+
areaRole.validate('hectare', 100); // { valid: true, errors: [] }
|
|
35
|
+
areaRole.format('hectare', 2.5); // "2.5 ha"
|
|
36
|
+
|
|
37
|
+
// Length
|
|
38
|
+
lengthRole.convert('kilometer', 'mile', 100); // 62.1371
|
|
39
|
+
lengthRole.format('meter', 1500); // "1500 m"
|
|
40
|
+
|
|
41
|
+
// Color
|
|
42
|
+
colorRole.convert('hex', 'rgb_object', '#ff0000'); // { r: 255, g: 0, b: 0, a: 1 }
|
|
43
|
+
colorRole.format('hex', '#ff0000', { uppercase: true }); // "#FF0000"
|
|
44
|
+
|
|
45
|
+
// Date
|
|
46
|
+
dateRole.convert('iso', 'timestamp', '2024-12-05T00:00:00.000Z'); // 1733356800000
|
|
47
|
+
dateRole.format('iso', '2024-12-05T00:00:00.000Z', { dateOnly: true });
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Via RoleMorphic (Registry)
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { RoleMorphic, areaRole, lengthRole } from '@attrx/role-morphic';
|
|
54
|
+
|
|
55
|
+
const morph = new RoleMorphic();
|
|
56
|
+
morph.register('area', areaRole.toSpec());
|
|
57
|
+
morph.register('length', lengthRole.toSpec());
|
|
58
|
+
|
|
59
|
+
// Convert
|
|
60
|
+
morph.convert('area:hectare', 'area:acre', 1); // 2.47105
|
|
61
|
+
morph.convert('length:kilometer', 'length:mile', 100); // 62.1371
|
|
62
|
+
|
|
63
|
+
// Try (Result type)
|
|
64
|
+
const result = morph.tryConvert('area:hectare', 'area:acre', 1);
|
|
65
|
+
if (result.ok) {
|
|
66
|
+
console.log(result.value); // 2.47105
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## API
|
|
71
|
+
|
|
72
|
+
### Role Instance Methods
|
|
73
|
+
|
|
74
|
+
Cada role exporta uma instância singleton (ex: `areaRole`, `lengthRole`):
|
|
75
|
+
|
|
76
|
+
| Método | Retorno | Throws |
|
|
77
|
+
|--------|---------|--------|
|
|
78
|
+
| `convert(from, to, value)` | `T` | Sim |
|
|
79
|
+
| `tryConvert(from, to, value)` | `Result<T>` | Não |
|
|
80
|
+
| `cast(variant, input)` | `T \| null` | Não |
|
|
81
|
+
| `tryCast(variant, input)` | `Result<T>` | Não |
|
|
82
|
+
| `validate(variant, value)` | `ValidationResult` | Não |
|
|
83
|
+
| `isValid(variant, value)` | `boolean` | Não |
|
|
84
|
+
| `format(variant, value, options?)` | `string` | Não |
|
|
85
|
+
| `getVariants()` | `string[]` | Não |
|
|
86
|
+
| `hasVariant(name)` | `boolean` | Não |
|
|
87
|
+
| `toSpec()` | `RoleSpec` | Não |
|
|
88
|
+
|
|
89
|
+
### RoleMorphic Methods
|
|
90
|
+
|
|
91
|
+
| Método | Retorno | Throws |
|
|
92
|
+
|--------|---------|--------|
|
|
93
|
+
| `register(id, spec)` | `void` | Sim (duplicado) |
|
|
94
|
+
| `convert(from, to, value)` | `T` | Sim |
|
|
95
|
+
| `tryConvert(from, to, value)` | `Result<T>` | Não |
|
|
96
|
+
| `hasRole(id)` | `boolean` | Não |
|
|
97
|
+
| `hasVariant(fullId)` | `boolean` | Não |
|
|
98
|
+
| `listRoles()` | `string[]` | Não |
|
|
99
|
+
| `listVariants(roleId)` | `string[]` | Não |
|
|
100
|
+
|
|
101
|
+
## Roles Disponíveis
|
|
102
|
+
|
|
103
|
+
### SimpleRoles (Numéricas)
|
|
104
|
+
|
|
105
|
+
| Role | Base | Variantes | Exemplo |
|
|
106
|
+
|------|------|-----------|---------|
|
|
107
|
+
| **Area** | square_meter | 12 | hectare, acre, km² |
|
|
108
|
+
| **Length** | meter | 17 | km, mile, foot, inch |
|
|
109
|
+
| **Mass** | kilogram | 16 | gram, pound, ounce |
|
|
110
|
+
| **Temperature** | celsius | 4 | fahrenheit, kelvin |
|
|
111
|
+
| **Volume** | liter | 18 | ml, gallon, cup |
|
|
112
|
+
| **Speed** | meter_per_second | 7 | km/h, mph, knot |
|
|
113
|
+
| **Time** | second | 15 | minute, hour, day |
|
|
114
|
+
| **Energy** | joule | 12 | calorie, kwh, btu |
|
|
115
|
+
| **Power** | watt | 11 | kw, hp, btu/h |
|
|
116
|
+
| **Pressure** | pascal | 12 | bar, psi, atm |
|
|
117
|
+
| **Frequency** | hertz | 9 | khz, mhz, rpm |
|
|
118
|
+
| **Angle** | radian | 7 | degree, turn, grad |
|
|
119
|
+
| **Digital** | byte | 12 | kb, mb, gb, gib |
|
|
120
|
+
|
|
121
|
+
### ComplexRoles (Heterogêneas)
|
|
122
|
+
|
|
123
|
+
| Role | Base | Variantes |
|
|
124
|
+
|------|------|-----------|
|
|
125
|
+
| **Color** | rgb_object | hex, rgb_string, hsl_object, hsl_string |
|
|
126
|
+
| **Date** | timestamp | iso, epoch |
|
|
127
|
+
|
|
128
|
+
## Exemplos por Role
|
|
129
|
+
|
|
130
|
+
### Area
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { areaRole } from '@attrx/role-morphic';
|
|
134
|
+
|
|
135
|
+
areaRole.convert('hectare', 'acre', 1); // 2.47105
|
|
136
|
+
areaRole.convert('square_meter', 'hectare', 10000); // 1
|
|
137
|
+
areaRole.format('hectare', 2.5, { verbose: true }); // "2.5 hectares"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Length
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { lengthRole } from '@attrx/role-morphic';
|
|
144
|
+
|
|
145
|
+
lengthRole.convert('mile', 'kilometer', 1); // 1.609344
|
|
146
|
+
lengthRole.convert('inch', 'centimeter', 1); // 2.54
|
|
147
|
+
lengthRole.cast('meter', '100 m'); // 100
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Temperature
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { temperatureRole } from '@attrx/role-morphic';
|
|
154
|
+
|
|
155
|
+
temperatureRole.convert('celsius', 'fahrenheit', 0); // 32
|
|
156
|
+
temperatureRole.convert('celsius', 'kelvin', 0); // 273.15
|
|
157
|
+
temperatureRole.format('celsius', 25); // "25 °C"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Color
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { colorRole } from '@attrx/role-morphic';
|
|
164
|
+
|
|
165
|
+
// Hex → RGB
|
|
166
|
+
colorRole.convert('hex', 'rgb_object', '#ff0000');
|
|
167
|
+
// { r: 255, g: 0, b: 0, a: 1 }
|
|
168
|
+
|
|
169
|
+
// RGB → HSL
|
|
170
|
+
colorRole.convert('rgb_object', 'hsl_object', { r: 255, g: 0, b: 0, a: 1 });
|
|
171
|
+
// { h: 0, s: 100, l: 50, a: 1 }
|
|
172
|
+
|
|
173
|
+
// Format options
|
|
174
|
+
colorRole.format('hex', '#ff0000', { uppercase: true }); // "#FF0000"
|
|
175
|
+
colorRole.format('rgb_string', { r: 255, g: 0, b: 0, a: 1 }, { compact: true });
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Date
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { dateRole } from '@attrx/role-morphic';
|
|
182
|
+
|
|
183
|
+
// ISO → Timestamp
|
|
184
|
+
dateRole.convert('iso', 'timestamp', '2024-12-05T00:00:00.000Z');
|
|
185
|
+
// 1733356800000
|
|
186
|
+
|
|
187
|
+
// Timestamp → ISO
|
|
188
|
+
dateRole.convert('timestamp', 'iso', 1733356800000);
|
|
189
|
+
// "2024-12-05T00:00:00.000Z"
|
|
190
|
+
|
|
191
|
+
// Format options
|
|
192
|
+
dateRole.format('iso', '2024-12-05T10:30:00.000Z', {
|
|
193
|
+
dateStyle: 'long',
|
|
194
|
+
timeStyle: 'short',
|
|
195
|
+
locale: 'pt-BR',
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## FormatOptions
|
|
200
|
+
|
|
201
|
+
### Base (todas as roles)
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
type BaseFormatOptions = {
|
|
205
|
+
decimals?: number; // Casas decimais
|
|
206
|
+
locale?: string; // 'pt-BR', 'en-US'
|
|
207
|
+
notation?: 'standard' | 'scientific' | 'compact';
|
|
208
|
+
verbose?: boolean; // Nome completo vs símbolo
|
|
209
|
+
};
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Color
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
type ColorFormatOptions = BaseFormatOptions & {
|
|
216
|
+
uppercase?: boolean; // #FF0000 vs #ff0000
|
|
217
|
+
includeAlpha?: boolean; // Incluir alpha mesmo quando 1
|
|
218
|
+
compact?: boolean; // rgb(255,0,0) vs rgb(255, 0, 0)
|
|
219
|
+
};
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Date
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
type DateFormatOptions = BaseFormatOptions & {
|
|
226
|
+
dateStyle?: 'full' | 'long' | 'medium' | 'short';
|
|
227
|
+
timeStyle?: 'full' | 'long' | 'medium' | 'short';
|
|
228
|
+
timeZone?: string; // 'America/Sao_Paulo', 'UTC'
|
|
229
|
+
dateOnly?: boolean;
|
|
230
|
+
timeOnly?: boolean;
|
|
231
|
+
};
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Arquitetura
|
|
235
|
+
|
|
236
|
+
- **Hub-and-Spoke**: Toda conversão passa pela variante base (2N vs N² funções)
|
|
237
|
+
- **SimpleRole**: Para roles numéricas - usa `factors` para conversão linear
|
|
238
|
+
- **ComplexRole**: Para roles heterogêneas - cada variante implementa `IVariant`
|
|
239
|
+
|
|
240
|
+
Ver [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) para detalhes.
|
|
241
|
+
|
|
242
|
+
## Criando Roles Custom
|
|
243
|
+
|
|
244
|
+
### SimpleRole
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import { SimpleRole, SimpleUnitConfig } from '@attrx/role-morphic';
|
|
248
|
+
|
|
249
|
+
const CURRENCY_UNITS: Record<string, SimpleUnitConfig> = {
|
|
250
|
+
brl: { factor: 1, symbol: 'R$' },
|
|
251
|
+
usd: { factor: 0.20, symbol: '$' }, // 1 BRL = 0.20 USD
|
|
252
|
+
eur: { factor: 0.18, symbol: '€' },
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
class CurrencyRole extends SimpleRole {
|
|
256
|
+
readonly name = 'currency';
|
|
257
|
+
readonly base = 'brl';
|
|
258
|
+
readonly units = CURRENCY_UNITS;
|
|
259
|
+
readonly aliases = { real: 'brl', dollar: 'usd', euro: 'eur' };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const currencyRole = new CurrencyRole();
|
|
263
|
+
currencyRole.convert('brl', 'usd', 100); // 20
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Via RoleSpec
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { RoleMorphic, RoleSpec } from '@attrx/role-morphic';
|
|
270
|
+
|
|
271
|
+
const currencySpec: RoleSpec<number> = {
|
|
272
|
+
base: 'cents',
|
|
273
|
+
variants: {
|
|
274
|
+
cents: {
|
|
275
|
+
type: 'number',
|
|
276
|
+
toBase: (v) => v,
|
|
277
|
+
fromBase: (v) => v,
|
|
278
|
+
},
|
|
279
|
+
dollars: {
|
|
280
|
+
type: 'number',
|
|
281
|
+
toBase: (d) => Math.round(d * 100),
|
|
282
|
+
fromBase: (c) => c / 100,
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const morph = new RoleMorphic();
|
|
288
|
+
morph.register('currency', currencySpec);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Testes
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
pnpm test
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Status:** 15 roles, 1324 testes
|
|
298
|
+
|
|
299
|
+
## Licença
|
|
300
|
+
|
|
301
|
+
MIT
|