data-handlers 0.0.1
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/LICENSE +21 -0
- package/README.md +348 -0
- package/README.pt-BR.md +348 -0
- package/handlers/dateHandler.js +27 -0
- package/handlers/nameHandler.js +28 -0
- package/handlers/numberHandler.js +25 -0
- package/index.d.ts +75 -0
- package/index.js +93 -0
- package/package.json +46 -0
- package/src/main.js +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Emersom Oliveira
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
[πΊπΈ English](./README.md) | π§π· [PortuguΓͺs](./README.pt-BR.md)
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# data-handlers
|
|
6
|
+
|
|
7
|
+
> Extensible normalization and validation library with pluggable handlers for names, numbers, dates, and more.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/data-handlers)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+
[](https://nodejs.org)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
**data-handlers** provides a unified interface to **format** and **validate** common data types. It ships with three built-in handlers β `name`, `number`, and `date` β and was designed from the ground up to be extensible via plugins.
|
|
18
|
+
|
|
19
|
+
The `register()` function (or its semantic alias `createPlugin()`) lets you add any custom type to the ecosystem: CPF, CNPJ, ZIP codes, phone numbers, slugs, and whatever else you need.
|
|
20
|
+
|
|
21
|
+
All built-in formatting is powered by the native [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) API, with zero external dependencies.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Plugin Ecosystem
|
|
26
|
+
|
|
27
|
+
Hera's key advantage over other validation libraries is its support for **official plugins targeting the Brazilian market**, which libraries like Zod completely ignore.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
data-handlers β core (normalize, validate, register, createPlugin)
|
|
31
|
+
data-handlers-cpf β CPF validation and formatting
|
|
32
|
+
data-handlers-cnpj β CNPJ validation and formatting
|
|
33
|
+
data-handlers-phone β Brazilian phone number formatting
|
|
34
|
+
data-handlers-cep β ZIP code formatting and lookup
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> The plugin packages listed above are planned and not yet published.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Requirements
|
|
42
|
+
|
|
43
|
+
- Node.js `>= 18`
|
|
44
|
+
- ESM-only (`"type": "module"`)
|
|
45
|
+
- TypeScript: types included via `index.d.ts`
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install data-handlers
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## API
|
|
58
|
+
|
|
59
|
+
### `normalize({ type, value, options? })`
|
|
60
|
+
|
|
61
|
+
Normalizes and formats a value using the handler registered for the given type. **Throws** `TypeError` if the type is unknown or the value fails validation.
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
import { normalize } from 'data-handlers'
|
|
65
|
+
|
|
66
|
+
normalize({ type: 'name', value: ' john doe ' })
|
|
67
|
+
// β 'John Doe'
|
|
68
|
+
|
|
69
|
+
normalize({ type: 'number', value: 1234567.89, options: { locale: 'pt-BR', style: 'currency', currency: 'BRL' } })
|
|
70
|
+
// β 'R$ 1.234.567,89'
|
|
71
|
+
|
|
72
|
+
normalize({ type: 'date', value: '2024-01-15', options: { locale: 'pt-BR', dateStyle: 'long' } })
|
|
73
|
+
// β '15 de janeiro de 2024'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### `validate({ type, value, options? })`
|
|
79
|
+
|
|
80
|
+
Same logic as `normalize()`, but **never throws**. Returns an object with `valid`, `value`, and `error` β ideal for form validation.
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
import { validate } from 'data-handlers'
|
|
84
|
+
|
|
85
|
+
validate({ type: 'name', value: ' john doe ' })
|
|
86
|
+
// β { valid: true, value: 'John Doe', error: null }
|
|
87
|
+
|
|
88
|
+
validate({ type: 'name', value: '' })
|
|
89
|
+
// β { valid: false, value: null, error: '[normalize:name] Expected non-empty string. Received: ' }
|
|
90
|
+
|
|
91
|
+
validate({ type: 'number', value: NaN })
|
|
92
|
+
// β { valid: false, value: null, error: '[normalize:number] Expected finite number. Received: NaN' }
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### `register(type, handler)`
|
|
98
|
+
|
|
99
|
+
Registers a custom handler for any type. Overwrites the existing handler if the type is already registered.
|
|
100
|
+
|
|
101
|
+
| Parameter | Type | Description |
|
|
102
|
+
|-----------|------------|-----------------------------------------------------------|
|
|
103
|
+
| `type` | `string` | Type identifier β case-insensitive and whitespace-trimmed |
|
|
104
|
+
| `handler` | `Function` | `(value: any, options?: any) => string` |
|
|
105
|
+
|
|
106
|
+
**Throws** `TypeError` if `handler` is not a function.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### `createPlugin(type, handler)`
|
|
111
|
+
|
|
112
|
+
Semantic alias for `register()`. Use this when **publishing a plugin** package under `data-handlers-*`.
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
// data-handlers-slug/index.js
|
|
116
|
+
import { createPlugin } from 'data-handlers'
|
|
117
|
+
|
|
118
|
+
const slugHandler = (value) => {
|
|
119
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
120
|
+
throw new TypeError(`[normalize:slug] Expected non-empty string. Received: ${value}`)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return value
|
|
124
|
+
.trim()
|
|
125
|
+
.toLowerCase()
|
|
126
|
+
.normalize('NFD')
|
|
127
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
128
|
+
.replace(/\s+/g, '-')
|
|
129
|
+
.replace(/[^a-z0-9-]/g, '')
|
|
130
|
+
.replace(/-+/g, '-')
|
|
131
|
+
.replace(/^-|-$/g, '')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
createPlugin('slug', slugHandler)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
// usage
|
|
139
|
+
import { normalize } from 'data-handlers'
|
|
140
|
+
import 'data-handlers-slug'
|
|
141
|
+
|
|
142
|
+
normalize({ type: 'slug', value: 'OlΓ‘ Mundo Legal!' })
|
|
143
|
+
// β 'ola-mundo-legal'
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
> **Tip:** Import plugins before calling `normalize()` so the `createPlugin()` side-effect runs first.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Built-in Handlers
|
|
151
|
+
|
|
152
|
+
### `name`
|
|
153
|
+
|
|
154
|
+
Normalizes a full name string to **Title Case**, trimming and collapsing extra whitespace.
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
normalize({ type: 'name', value: ' joΓ£o da silva ' })
|
|
158
|
+
// β 'JoΓ£o Da Silva'
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Throws** `TypeError` if `value` is not a non-empty string.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
### `number`
|
|
166
|
+
|
|
167
|
+
Formats a finite number into a locale-aware string via [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat).
|
|
168
|
+
|
|
169
|
+
| Option | Type | Default | Description |
|
|
170
|
+
|-----------|----------|-----------|--------------------------------|
|
|
171
|
+
| `locale` | `string` | `'en-US'` | BCP 47 language tag |
|
|
172
|
+
| `...rest` | `any` | β | Any `Intl.NumberFormatOptions` |
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
normalize({ type: 'number', value: 1234567.89, options: { locale: 'pt-BR' } })
|
|
176
|
+
// β '1.234.567,89'
|
|
177
|
+
|
|
178
|
+
normalize({ type: 'number', value: 42, options: { locale: 'en-US', style: 'currency', currency: 'USD' } })
|
|
179
|
+
// β '$42.00'
|
|
180
|
+
|
|
181
|
+
normalize({ type: 'number', value: 0.753, options: { style: 'percent', maximumFractionDigits: 1 } })
|
|
182
|
+
// β '75.3%'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Throws** `TypeError` if `value` is not a finite number.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### `date`
|
|
190
|
+
|
|
191
|
+
Formats a date value into a locale-aware string via [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat). Accepts a `Date` object, an ISO string, or any value parseable by the `Date` constructor.
|
|
192
|
+
|
|
193
|
+
| Option | Type | Default | Description |
|
|
194
|
+
|-----------|----------|-----------|----------------------------------|
|
|
195
|
+
| `locale` | `string` | `'en-US'` | BCP 47 language tag |
|
|
196
|
+
| `...rest` | `any` | β | Any `Intl.DateTimeFormatOptions` |
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
normalize({ type: 'date', value: new Date(2026, 2, 1), options: { locale: 'pt-BR' } })
|
|
200
|
+
// β '01/03/2026'
|
|
201
|
+
|
|
202
|
+
normalize({ type: 'date', value: new Date(2026, 2, 1), options: { locale: 'pt-BR', year: 'numeric', month: 'long', day: 'numeric' } })
|
|
203
|
+
// β '1 de marΓ§o de 2026'
|
|
204
|
+
|
|
205
|
+
normalize({ type: 'date', value: '2024-01-15', options: { locale: 'en-US', dateStyle: 'long' } })
|
|
206
|
+
// β 'January 15, 2024'
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Throws** `TypeError` if `value` cannot be parsed into a valid date.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Building a Plugin
|
|
214
|
+
|
|
215
|
+
A plugin is any module that imports `createPlugin` and registers a handler. The handler should **validate and format** the value β and throw a `TypeError` with a descriptive prefix if the value is invalid.
|
|
216
|
+
|
|
217
|
+
```js
|
|
218
|
+
// data-handlers-cpf (example implementation)
|
|
219
|
+
import { createPlugin } from 'data-handlers'
|
|
220
|
+
|
|
221
|
+
const isValidCPF = (cpf) => {
|
|
222
|
+
const digits = cpf.replace(/\D/g, '')
|
|
223
|
+
if (digits.length !== 11 || /^(\d)\1+$/.test(digits)) return false
|
|
224
|
+
|
|
225
|
+
const calc = (factor) =>
|
|
226
|
+
digits.slice(0, factor - 1).split('').reduce((sum, d, i) => sum + Number(d) * (factor - i), 0)
|
|
227
|
+
|
|
228
|
+
const mod = (n) => ((n * 10) % 11) % 10
|
|
229
|
+
|
|
230
|
+
return mod(calc(10)) === Number(digits[9]) && mod(calc(11)) === Number(digits[10])
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const cpfHandler = (value) => {
|
|
234
|
+
const digits = String(value).replace(/\D/g, '')
|
|
235
|
+
|
|
236
|
+
if (!isValidCPF(digits)) {
|
|
237
|
+
throw new TypeError(`[normalize:cpf] Invalid CPF. Received: ${value}`)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return digits.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
createPlugin('cpf', cpfHandler)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
```js
|
|
247
|
+
import { normalize, validate } from 'data-handlers'
|
|
248
|
+
import 'data-handlers-cpf'
|
|
249
|
+
|
|
250
|
+
normalize({ type: 'cpf', value: '11144477735' })
|
|
251
|
+
// β '111.444.777-35'
|
|
252
|
+
|
|
253
|
+
validate({ type: 'cpf', value: '00000000000' })
|
|
254
|
+
// β { valid: false, value: null, error: '[normalize:cpf] Invalid CPF. Received: 00000000000' }
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Error Handling
|
|
260
|
+
|
|
261
|
+
All handlers throw `TypeError` with a prefix that identifies the source:
|
|
262
|
+
|
|
263
|
+
| Prefix | Source |
|
|
264
|
+
|----------------------|-------------------|
|
|
265
|
+
| `[normalize]` | Core (`index.js`) |
|
|
266
|
+
| `[normalize:name]` | Name handler |
|
|
267
|
+
| `[normalize:number]` | Number handler |
|
|
268
|
+
| `[normalize:date]` | Date handler |
|
|
269
|
+
| `[normalize:*]` | External plugins |
|
|
270
|
+
|
|
271
|
+
Use `validate()` when you'd rather not deal with exceptions:
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
const { valid, value, error } = validate({ type: 'cpf', value: input })
|
|
275
|
+
|
|
276
|
+
if (!valid) {
|
|
277
|
+
console.error(error)
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## TypeScript
|
|
284
|
+
|
|
285
|
+
Types are included natively β no additional installation required.
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import { normalize, validate, register, createPlugin } from 'data-handlers'
|
|
289
|
+
import type { Handler, ValidateResult } from 'data-handlers'
|
|
290
|
+
|
|
291
|
+
const slugHandler: Handler<string> = (value) => {
|
|
292
|
+
// ...
|
|
293
|
+
return slug
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
createPlugin('slug', slugHandler)
|
|
297
|
+
|
|
298
|
+
const result: ValidateResult = validate({ type: 'slug', value: 'Hello World' })
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Project Structure
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
data-handlers
|
|
307
|
+
βββ handlers/
|
|
308
|
+
β βββ dateHandler.js # Intl.DateTimeFormat wrapper
|
|
309
|
+
β βββ nameHandler.js # Title Case normalizer
|
|
310
|
+
β βββ numberHandler.js # Intl.NumberFormat wrapper
|
|
311
|
+
βββ src/
|
|
312
|
+
β βββ main.js # Handler registry + formatType
|
|
313
|
+
βββ index.js # Public API
|
|
314
|
+
βββ index.d.ts # TypeScript types
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## API Reference
|
|
320
|
+
|
|
321
|
+
### `normalize(params)` / `validate(params)`
|
|
322
|
+
|
|
323
|
+
| Parameter | Type | Required | Description |
|
|
324
|
+
|------------------|----------|----------|----------------------------------|
|
|
325
|
+
| `params.type` | `string` | β
| Registered type identifier |
|
|
326
|
+
| `params.value` | `any` | β
| Value to process |
|
|
327
|
+
| `params.options` | `object` | β | Options forwarded to the handler |
|
|
328
|
+
|
|
329
|
+
`normalize` β returns `string` or throws `TypeError`.
|
|
330
|
+
`validate` β returns `{ valid, value, error }`, never throws.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
### `register(type, handler)` / `createPlugin(type, handler)`
|
|
335
|
+
|
|
336
|
+
| Parameter | Type | Required | Description |
|
|
337
|
+
|-----------|------------|----------|-------------------------------|
|
|
338
|
+
| `type` | `string` | β
| Type identifier to register |
|
|
339
|
+
| `handler` | `Function` | β
| `(value, options?) => string` |
|
|
340
|
+
|
|
341
|
+
`register` β general use.
|
|
342
|
+
`createPlugin` β semantic alias for plugin authors.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## License
|
|
347
|
+
|
|
348
|
+
[MIT](./LICENSE) Β© Emersom Oliveira
|
package/README.pt-BR.md
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
π§π· PortuguΓͺs | [πΊπΈ English](./README.md)
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# data-handlers
|
|
6
|
+
|
|
7
|
+
> Biblioteca de normalizaΓ§Γ£o e validaΓ§Γ£o extensΓvel com handlers plugΓ‘veis para nomes, nΓΊmeros, datas e muito mais.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/data-handlers)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+
[](https://nodejs.org)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## VisΓ£o Geral
|
|
16
|
+
|
|
17
|
+
**data-handlers** fornece uma interface unificada para **formatar** e **validar** tipos de dados comuns. Vem com trΓͺs handlers nativos β `name`, `number` e `date` β e foi projetada do zero para ser extensΓvel via plugins.
|
|
18
|
+
|
|
19
|
+
A funΓ§Γ£o `register()` (ou seu alias semΓ’ntico `createPlugin()`) permite adicionar qualquer tipo customizado ao ecossistema: CPF, CNPJ, CEP, telefone, slug, e o que mais vocΓͺ precisar.
|
|
20
|
+
|
|
21
|
+
Toda a formataΓ§Γ£o nativa Γ© feita pela API [Intl](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Intl), sem nenhuma dependΓͺncia externa.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Ecossistema de Plugins
|
|
26
|
+
|
|
27
|
+
O diferencial do Hera em relaΓ§Γ£o a outras libs de validaΓ§Γ£o Γ© o suporte a **plugins oficiais focados no mercado brasileiro**, que bibliotecas como o Zod ignoram completamente.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
data-handlers β core (normalize, validate, register, createPlugin)
|
|
31
|
+
data-handlers-cpf β validaΓ§Γ£o e formataΓ§Γ£o de CPF
|
|
32
|
+
data-handlers-cnpj β validaΓ§Γ£o e formataΓ§Γ£o de CNPJ
|
|
33
|
+
data-handlers-phone β formataΓ§Γ£o de telefone BR
|
|
34
|
+
data-handlers-cep β formataΓ§Γ£o e consulta de CEP
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> Os pacotes de plugin acima sΓ£o planejados e ainda nΓ£o foram publicados.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Requisitos
|
|
42
|
+
|
|
43
|
+
- Node.js `>= 18`
|
|
44
|
+
- Apenas ESM (`"type": "module"`)
|
|
45
|
+
- TypeScript: tipos incluΓdos via `index.d.ts`
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## InstalaΓ§Γ£o
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install data-handlers
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## API
|
|
58
|
+
|
|
59
|
+
### `normalize({ type, value, options? })`
|
|
60
|
+
|
|
61
|
+
Normaliza e formata um valor usando o handler registrado para o tipo informado. **LanΓ§a** `TypeError` se o tipo for desconhecido ou o valor for invΓ‘lido.
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
import { normalize } from 'data-handlers'
|
|
65
|
+
|
|
66
|
+
normalize({ type: 'name', value: ' john doe ' })
|
|
67
|
+
// β 'John Doe'
|
|
68
|
+
|
|
69
|
+
normalize({ type: 'number', value: 1234567.89, options: { locale: 'pt-BR', style: 'currency', currency: 'BRL' } })
|
|
70
|
+
// β 'R$ 1.234.567,89'
|
|
71
|
+
|
|
72
|
+
normalize({ type: 'date', value: '2024-01-15', options: { locale: 'pt-BR', dateStyle: 'long' } })
|
|
73
|
+
// β '15 de janeiro de 2024'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### `validate({ type, value, options? })`
|
|
79
|
+
|
|
80
|
+
Mesma lΓ³gica do `normalize()`, mas **nunca lanΓ§a**. Retorna um objeto com `valid`, `value` e `error` β ideal para validaΓ§Γ£o de formulΓ‘rios.
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
import { validate } from 'data-handlers'
|
|
84
|
+
|
|
85
|
+
validate({ type: 'name', value: ' john doe ' })
|
|
86
|
+
// β { valid: true, value: 'John Doe', error: null }
|
|
87
|
+
|
|
88
|
+
validate({ type: 'name', value: '' })
|
|
89
|
+
// β { valid: false, value: null, error: '[normalize:name] Expected non-empty string. Received: ' }
|
|
90
|
+
|
|
91
|
+
validate({ type: 'number', value: NaN })
|
|
92
|
+
// β { valid: false, value: null, error: '[normalize:number] Expected finite number. Received: NaN' }
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### `register(type, handler)`
|
|
98
|
+
|
|
99
|
+
Registra um handler customizado para qualquer tipo. Se o tipo jΓ‘ estiver registrado, ele serΓ‘ substituΓdo.
|
|
100
|
+
|
|
101
|
+
| ParΓ’metro | Tipo | DescriΓ§Γ£o |
|
|
102
|
+
|-----------|------------|---------------------------------------------------------------|
|
|
103
|
+
| `type` | `string` | Identificador do tipo β case-insensitive e sem espaΓ§os extras |
|
|
104
|
+
| `handler` | `Function` | `(value: any, options?: any) => string` |
|
|
105
|
+
|
|
106
|
+
**LanΓ§a** `TypeError` se `handler` nΓ£o for uma funΓ§Γ£o.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### `createPlugin(type, handler)`
|
|
111
|
+
|
|
112
|
+
Alias semΓ’ntico de `register()`. Use este quando estiver **publicando um plugin** `data-handlers-*`.
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
// data-handlers-slug/index.js
|
|
116
|
+
import { createPlugin } from 'data-handlers'
|
|
117
|
+
|
|
118
|
+
const slugHandler = (value) => {
|
|
119
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
120
|
+
throw new TypeError(`[normalize:slug] Expected non-empty string. Received: ${value}`)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return value
|
|
124
|
+
.trim()
|
|
125
|
+
.toLowerCase()
|
|
126
|
+
.normalize('NFD')
|
|
127
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
128
|
+
.replace(/\s+/g, '-')
|
|
129
|
+
.replace(/[^a-z0-9-]/g, '')
|
|
130
|
+
.replace(/-+/g, '-')
|
|
131
|
+
.replace(/^-|-$/g, '')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
createPlugin('slug', slugHandler)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
// uso
|
|
139
|
+
import { normalize } from 'data-handlers'
|
|
140
|
+
import 'data-handlers-slug'
|
|
141
|
+
|
|
142
|
+
normalize({ type: 'slug', value: 'OlΓ‘ Mundo Legal!' })
|
|
143
|
+
// β 'ola-mundo-legal'
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
> **Dica:** Importe o plugin antes de chamar `normalize()` para que o `createPlugin()` seja executado primeiro.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Handlers Nativos
|
|
151
|
+
|
|
152
|
+
### `name`
|
|
153
|
+
|
|
154
|
+
Normaliza uma string de nome completo para **Title Case**, removendo e colapsando espaΓ§os extras.
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
normalize({ type: 'name', value: ' joΓ£o da silva ' })
|
|
158
|
+
// β 'JoΓ£o Da Silva'
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**LanΓ§a** `TypeError` se `value` nΓ£o for uma string nΓ£o vazia.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
### `number`
|
|
166
|
+
|
|
167
|
+
Formata um nΓΊmero finito em uma string sensΓvel ao locale via [`Intl.NumberFormat`](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat).
|
|
168
|
+
|
|
169
|
+
| OpΓ§Γ£o | Tipo | PadrΓ£o | DescriΓ§Γ£o |
|
|
170
|
+
|-----------|----------|-----------|-------------------------------------|
|
|
171
|
+
| `locale` | `string` | `'en-US'` | Tag de idioma BCP 47 |
|
|
172
|
+
| `...rest` | `any` | β | Qualquer `Intl.NumberFormatOptions` |
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
normalize({ type: 'number', value: 1234567.89, options: { locale: 'pt-BR' } })
|
|
176
|
+
// β '1.234.567,89'
|
|
177
|
+
|
|
178
|
+
normalize({ type: 'number', value: 42, options: { locale: 'en-US', style: 'currency', currency: 'USD' } })
|
|
179
|
+
// β '$42.00'
|
|
180
|
+
|
|
181
|
+
normalize({ type: 'number', value: 0.753, options: { style: 'percent', maximumFractionDigits: 1 } })
|
|
182
|
+
// β '75.3%'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**LanΓ§a** `TypeError` se `value` nΓ£o for um nΓΊmero finito.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### `date`
|
|
190
|
+
|
|
191
|
+
Formata um valor de data em uma string sensΓvel ao locale via [`Intl.DateTimeFormat`](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat). Aceita `Date`, string ISO ou qualquer valor que o construtor `Date` consiga interpretar.
|
|
192
|
+
|
|
193
|
+
| OpΓ§Γ£o | Tipo | PadrΓ£o | DescriΓ§Γ£o |
|
|
194
|
+
|-----------|----------|-----------|---------------------------------------|
|
|
195
|
+
| `locale` | `string` | `'en-US'` | Tag de idioma BCP 47 |
|
|
196
|
+
| `...rest` | `any` | β | Qualquer `Intl.DateTimeFormatOptions` |
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
normalize({ type: 'date', value: new Date(2026, 2, 1), options: { locale: 'pt-BR' } })
|
|
200
|
+
// β '01/03/2026'
|
|
201
|
+
|
|
202
|
+
normalize({ type: 'date', value: new Date(2026, 2, 1), options: { locale: 'pt-BR', year: 'numeric', month: 'long', day: 'numeric' } })
|
|
203
|
+
// β '1 de marΓ§o de 2026'
|
|
204
|
+
|
|
205
|
+
normalize({ type: 'date', value: '2024-01-15', options: { locale: 'en-US', dateStyle: 'long' } })
|
|
206
|
+
// β 'January 15, 2024'
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**LanΓ§a** `TypeError` se `value` nΓ£o puder ser interpretado como uma data vΓ‘lida.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Criando um Plugin
|
|
214
|
+
|
|
215
|
+
Um plugin Γ© qualquer mΓ³dulo que importa `createPlugin` e registra um handler. O handler deve **validar e formatar** o valor β e lanΓ§ar `TypeError` com um prefixo descritivo caso o valor seja invΓ‘lido.
|
|
216
|
+
|
|
217
|
+
```js
|
|
218
|
+
// data-handlers-cpf (exemplo de implementaΓ§Γ£o)
|
|
219
|
+
import { createPlugin } from 'data-handlers'
|
|
220
|
+
|
|
221
|
+
const isValidCPF = (cpf) => {
|
|
222
|
+
const digits = cpf.replace(/\D/g, '')
|
|
223
|
+
if (digits.length !== 11 || /^(\d)\1+$/.test(digits)) return false
|
|
224
|
+
|
|
225
|
+
const calc = (factor) =>
|
|
226
|
+
digits.slice(0, factor - 1).split('').reduce((sum, d, i) => sum + Number(d) * (factor - i), 0)
|
|
227
|
+
|
|
228
|
+
const mod = (n) => ((n * 10) % 11) % 10
|
|
229
|
+
|
|
230
|
+
return mod(calc(10)) === Number(digits[9]) && mod(calc(11)) === Number(digits[10])
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const cpfHandler = (value) => {
|
|
234
|
+
const digits = String(value).replace(/\D/g, '')
|
|
235
|
+
|
|
236
|
+
if (!isValidCPF(digits)) {
|
|
237
|
+
throw new TypeError(`[normalize:cpf] Invalid CPF. Received: ${value}`)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return digits.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
createPlugin('cpf', cpfHandler)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
```js
|
|
247
|
+
import { normalize, validate } from 'data-handlers'
|
|
248
|
+
import 'data-handlers-cpf'
|
|
249
|
+
|
|
250
|
+
normalize({ type: 'cpf', value: '11144477735' })
|
|
251
|
+
// β '111.444.777-35'
|
|
252
|
+
|
|
253
|
+
validate({ type: 'cpf', value: '00000000000' })
|
|
254
|
+
// β { valid: false, value: null, error: '[normalize:cpf] Invalid CPF. Received: 00000000000' }
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Tratamento de Erros
|
|
260
|
+
|
|
261
|
+
Todos os handlers lanΓ§am `TypeError` com um prefixo que identifica a origem:
|
|
262
|
+
|
|
263
|
+
| Prefixo | Origem |
|
|
264
|
+
|----------------------|-------------------|
|
|
265
|
+
| `[normalize]` | Core (`index.js`) |
|
|
266
|
+
| `[normalize:name]` | Handler de nome |
|
|
267
|
+
| `[normalize:number]` | Handler de nΓΊmero |
|
|
268
|
+
| `[normalize:date]` | Handler de data |
|
|
269
|
+
| `[normalize:*]` | Plugins externos |
|
|
270
|
+
|
|
271
|
+
Use `validate()` quando nΓ£o quiser lidar com exceΓ§Γ΅es:
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
const { valid, value, error } = validate({ type: 'cpf', value: input })
|
|
275
|
+
|
|
276
|
+
if (!valid) {
|
|
277
|
+
console.error(error)
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## TypeScript
|
|
284
|
+
|
|
285
|
+
Os tipos sΓ£o incluΓdos nativamente β nenhuma instalaΓ§Γ£o adicional necessΓ‘ria.
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import { normalize, validate, register, createPlugin } from 'data-handlers'
|
|
289
|
+
import type { Handler, ValidateResult } from 'data-handlers'
|
|
290
|
+
|
|
291
|
+
const slugHandler: Handler<string> = (value) => {
|
|
292
|
+
// ...
|
|
293
|
+
return slug
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
createPlugin('slug', slugHandler)
|
|
297
|
+
|
|
298
|
+
const result: ValidateResult = validate({ type: 'slug', value: 'OlΓ‘ Mundo' })
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Estrutura do Projeto
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
data-handlers
|
|
307
|
+
βββ handlers/
|
|
308
|
+
β βββ dateHandler.js # Wrapper do Intl.DateTimeFormat
|
|
309
|
+
β βββ nameHandler.js # Normalizador Title Case
|
|
310
|
+
β βββ numberHandler.js # Wrapper do Intl.NumberFormat
|
|
311
|
+
βββ src/
|
|
312
|
+
β βββ main.js # Registro de handlers + formatType
|
|
313
|
+
βββ index.js # API pΓΊblica
|
|
314
|
+
βββ index.d.ts # Tipos TypeScript
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## ReferΓͺncia da API
|
|
320
|
+
|
|
321
|
+
### `normalize(params)` / `validate(params)`
|
|
322
|
+
|
|
323
|
+
| ParΓ’metro | Tipo | ObrigatΓ³rio | DescriΓ§Γ£o |
|
|
324
|
+
|------------------|----------|-------------|----------------------------------|
|
|
325
|
+
| `params.type` | `string` | β
| Identificador do tipo registrado |
|
|
326
|
+
| `params.value` | `any` | β
| Valor a ser processado |
|
|
327
|
+
| `params.options` | `object` | β | OpΓ§Γ΅es repassadas ao handler |
|
|
328
|
+
|
|
329
|
+
`normalize` β retorna `string` ou lanΓ§a `TypeError`.
|
|
330
|
+
`validate` β retorna `{ valid, value, error }`, nunca lanΓ§a.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
### `register(type, handler)` / `createPlugin(type, handler)`
|
|
335
|
+
|
|
336
|
+
| ParΓ’metro | Tipo | ObrigatΓ³rio | DescriΓ§Γ£o |
|
|
337
|
+
|-----------|------------|-------------|-------------------------------|
|
|
338
|
+
| `type` | `string` | β
| Identificador do tipo |
|
|
339
|
+
| `handler` | `Function` | β
| `(value, options?) => string` |
|
|
340
|
+
|
|
341
|
+
`register` β uso geral.
|
|
342
|
+
`createPlugin` β alias semΓ’ntico para autores de plugins.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## LicenΓ§a
|
|
347
|
+
|
|
348
|
+
[MIT](./LICENSE) Β© Emersom Oliveira
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a date value into a localized string.
|
|
3
|
+
*
|
|
4
|
+
* @param {Date|string|number} value - A Date object or any value accepted by the Date constructor.
|
|
5
|
+
* @param {Object} [options={}] - Formatting options.
|
|
6
|
+
* @param {string} [options.locale='en-US'] - A BCP 47 language tag (e.g. `'pt-BR'`, `'en-US'`).
|
|
7
|
+
* @param {...Intl.DateTimeFormatOptions} [options] - Any additional options passed to `Intl.DateTimeFormat`.
|
|
8
|
+
* @returns {string} The formatted date string.
|
|
9
|
+
* @throws {TypeError} If `value` cannot be parsed into a valid date.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* dateHandler('2024-01-15', { locale: 'pt-BR', dateStyle: 'long' })
|
|
13
|
+
* // β '15 de janeiro de 2024'
|
|
14
|
+
*/
|
|
15
|
+
export const dateHandler = (value, options = {}) => {
|
|
16
|
+
const date = value instanceof Date ? value : new Date(value)
|
|
17
|
+
|
|
18
|
+
if (Number.isNaN(date.getTime())) {
|
|
19
|
+
throw new TypeError(
|
|
20
|
+
`[normalize:date] Expected date string or Date object. Received: ${value}`
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { locale = 'en-US', ...formatOptions } = options
|
|
25
|
+
|
|
26
|
+
return new Intl.DateTimeFormat(locale, formatOptions).format(date)
|
|
27
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes a full name string to Title Case, trimming extra whitespace.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} value - The raw name string to normalize.
|
|
5
|
+
* @returns {string} The normalized name in Title Case with collapsed spaces.
|
|
6
|
+
* @throws {TypeError} If `value` is not a non-empty string.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* nameHandler(' john doe ')
|
|
10
|
+
* // β 'John Doe'
|
|
11
|
+
*/
|
|
12
|
+
export const nameHandler = (value) => {
|
|
13
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
14
|
+
throw new TypeError(
|
|
15
|
+
`[normalize:name] Expected non-empty string. Received: ${value}`
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return value
|
|
20
|
+
.trim()
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/\s+/g, ' ')
|
|
23
|
+
.split(' ')
|
|
24
|
+
.map(
|
|
25
|
+
(word) => word.charAt(0).toUpperCase() + word.slice(1)
|
|
26
|
+
)
|
|
27
|
+
.join(' ')
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a finite number into a localized string.
|
|
3
|
+
*
|
|
4
|
+
* @param {number} value - The number to format. Must be a finite number.
|
|
5
|
+
* @param {Object} [options={}] - Formatting options.
|
|
6
|
+
* @param {string} [options.locale='en-US'] - A BCP 47 language tag (e.g. `'pt-BR'`, `'de-DE'`).
|
|
7
|
+
* @param {...Intl.NumberFormatOptions} [options] - Any additional options passed to `Intl.NumberFormat`.
|
|
8
|
+
* @returns {string} The formatted number string.
|
|
9
|
+
* @throws {TypeError} If `value` is not a finite number.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* numberHandler(1234567.89, { locale: 'pt-BR', style: 'currency', currency: 'BRL' })
|
|
13
|
+
* // β 'R$ 1.234.567,89'
|
|
14
|
+
*/
|
|
15
|
+
export const numberHandler = (value, options = {}) => {
|
|
16
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
17
|
+
throw new TypeError(
|
|
18
|
+
`[normalize:number] Expected finite number. Received: ${value}`
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { locale = 'en-US', ...formatOptions } = options
|
|
23
|
+
|
|
24
|
+
return new Intl.NumberFormat(locale, formatOptions).format(value)
|
|
25
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A handler function responsible for validating and/or formatting a value.
|
|
3
|
+
* Should throw a `TypeError` if the value is invalid.
|
|
4
|
+
*
|
|
5
|
+
* @template TValue - The expected input type.
|
|
6
|
+
* @template TOptions - The options shape accepted by the handler.
|
|
7
|
+
*/
|
|
8
|
+
export type Handler<TValue = unknown, TOptions = Record<string, unknown>> = (
|
|
9
|
+
value: TValue,
|
|
10
|
+
options?: TOptions
|
|
11
|
+
) => string
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parameters accepted by `normalize()` and `validate()`.
|
|
15
|
+
*/
|
|
16
|
+
export interface NormalizeParams<
|
|
17
|
+
TValue = unknown,
|
|
18
|
+
TOptions = Record<string, unknown>
|
|
19
|
+
> {
|
|
20
|
+
/** Registered type identifier (case-insensitive). */
|
|
21
|
+
type: string
|
|
22
|
+
/** The value to normalize or validate. */
|
|
23
|
+
value: TValue
|
|
24
|
+
/** Optional options forwarded to the handler. */
|
|
25
|
+
options?: TOptions
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Result returned by `validate()`.
|
|
30
|
+
*/
|
|
31
|
+
export interface ValidateResult {
|
|
32
|
+
/** Whether the value passed handler validation. */
|
|
33
|
+
valid: boolean
|
|
34
|
+
/** The formatted/normalized value, or `null` if invalid. */
|
|
35
|
+
value: string | null
|
|
36
|
+
/** The error message, or `null` if valid. */
|
|
37
|
+
error: string | null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Normalizes a value using the handler registered for the given type.
|
|
42
|
+
* Throws if the type is unknown or the value fails validation.
|
|
43
|
+
*/
|
|
44
|
+
export function normalize<
|
|
45
|
+
TValue = unknown,
|
|
46
|
+
TOptions = Record<string, unknown>
|
|
47
|
+
>(params: NormalizeParams<TValue, TOptions>): string
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validates a value against its registered handler without throwing.
|
|
51
|
+
* Returns a result object with `valid`, `value`, and `error` fields.
|
|
52
|
+
*/
|
|
53
|
+
export function validate<
|
|
54
|
+
TValue = unknown,
|
|
55
|
+
TOptions = Record<string, unknown>
|
|
56
|
+
>(params: NormalizeParams<TValue, TOptions>): ValidateResult
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Registers a custom handler for a given type.
|
|
60
|
+
* Overwrites the existing handler if the type is already registered.
|
|
61
|
+
*/
|
|
62
|
+
export function register<
|
|
63
|
+
TValue = unknown,
|
|
64
|
+
TOptions = Record<string, unknown>
|
|
65
|
+
>(type: string, handler: Handler<TValue, TOptions>): void
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Semantic alias for `register()`. Intended for plugin authors.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* // inside @axis/hera-cpf
|
|
72
|
+
* import { createPlugin } from '@axis/hera'
|
|
73
|
+
* createPlugin('cpf', cpfHandler)
|
|
74
|
+
*/
|
|
75
|
+
export declare const createPlugin: typeof register
|
package/index.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { registry, formatType } from './src/main.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Registers a custom handler for a given type.
|
|
5
|
+
* Overwrites the existing handler if the type is already registered.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} type - The type identifier to register (case-insensitive, trimmed).
|
|
8
|
+
* @param {Function} handler - The handler function to associate with the type.
|
|
9
|
+
* @throws {TypeError} If `handler` is not a function.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* register('phone', (value) => value.replace(/\D/g, ''))
|
|
13
|
+
*/
|
|
14
|
+
export function register(type, handler) {
|
|
15
|
+
if (typeof handler !== 'function') {
|
|
16
|
+
throw new TypeError('[normalize] Handler must be a function')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const key = formatType(type)
|
|
20
|
+
|
|
21
|
+
registry.set(key, handler)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Semantic alias for {@link register}. Intended for plugin authors.
|
|
26
|
+
* Prefer this when publishing a standalone `@axis/hera-*` plugin.
|
|
27
|
+
*
|
|
28
|
+
* @type {typeof register}
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // inside @axis/hera-cpf
|
|
32
|
+
* import { createPlugin } from '@axis/hera'
|
|
33
|
+
* createPlugin('cpf', cpfHandler)
|
|
34
|
+
*/
|
|
35
|
+
export const createPlugin = register
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Normalizes a value using the handler registered for the given type.
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} params - The normalization parameters.
|
|
41
|
+
* @param {string} params.type - The type identifier (e.g. `'name'`, `'number'`, `'date'`).
|
|
42
|
+
* @param {*} params.value - The value to normalize.
|
|
43
|
+
* @param {Object} [params.options] - Optional options forwarded to the handler.
|
|
44
|
+
* @returns {string} The normalized/formatted value.
|
|
45
|
+
* @throws {TypeError} If the type is unknown or the value fails handler validation.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* normalize({ type: 'name', value: ' john doe ' })
|
|
49
|
+
* // β 'John Doe'
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* normalize({ type: 'date', value: '2024-01-15', options: { locale: 'pt-BR', dateStyle: 'short' } })
|
|
53
|
+
* // β '15/01/2024'
|
|
54
|
+
*/
|
|
55
|
+
export function normalize({ type, value, options }) {
|
|
56
|
+
const key = formatType(type)
|
|
57
|
+
const handler = registry.get(key)
|
|
58
|
+
|
|
59
|
+
if (!handler) {
|
|
60
|
+
throw new TypeError(
|
|
61
|
+
`[normalize] Unknown type: ${key}. Available: ${[...registry.keys()].join(', ')}.`
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return handler(value, options)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Validates a value against the handler registered for the given type,
|
|
70
|
+
* without throwing. Returns a result object instead.
|
|
71
|
+
*
|
|
72
|
+
* @param {Object} params - The validation parameters.
|
|
73
|
+
* @param {string} params.type - The type identifier.
|
|
74
|
+
* @param {*} params.value - The value to validate.
|
|
75
|
+
* @param {Object} [params.options] - Optional options forwarded to the handler.
|
|
76
|
+
* @returns {{ valid: boolean, value: string|null, error: string|null }}
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* validate({ type: 'cpf', value: '111.444.777-35' })
|
|
80
|
+
* // β { valid: true, value: '111.444.777-35', error: null }
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* validate({ type: 'cpf', value: '000.000.000-00' })
|
|
84
|
+
* // β { valid: false, value: null, error: '[normalize:cpf] Invalid CPF.' }
|
|
85
|
+
*/
|
|
86
|
+
export function validate({ type, value, options }) {
|
|
87
|
+
try {
|
|
88
|
+
const result = normalize({ type, value, options })
|
|
89
|
+
return { valid: true, value: result, error: null }
|
|
90
|
+
} catch (err) {
|
|
91
|
+
return { valid: false, value: null, error: err.message }
|
|
92
|
+
}
|
|
93
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "data-handlers",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Extensible normalization and validation library with pluggable handlers (name, number, date).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"types": "./index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./index.js",
|
|
11
|
+
"types": "./index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"index.js",
|
|
16
|
+
"index.d.ts",
|
|
17
|
+
"src",
|
|
18
|
+
"handlers"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"normalize",
|
|
26
|
+
"validate",
|
|
27
|
+
"formatter",
|
|
28
|
+
"format",
|
|
29
|
+
"intl",
|
|
30
|
+
"plugin",
|
|
31
|
+
"string",
|
|
32
|
+
"number",
|
|
33
|
+
"date",
|
|
34
|
+
"cpf",
|
|
35
|
+
"cnpj",
|
|
36
|
+
"br"
|
|
37
|
+
],
|
|
38
|
+
"author": "Emersom Oliveira",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"vitest": "^4.0.18"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/main.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { nameHandler } from '../handlers/nameHandler.js'
|
|
2
|
+
import { numberHandler } from '../handlers/numberHandler.js'
|
|
3
|
+
import { dateHandler } from '../handlers/dateHandler.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normalizes a type identifier to a lowercase trimmed string.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} type - The type identifier to format.
|
|
9
|
+
* @returns {string} The trimmed, lowercased type string.
|
|
10
|
+
* @throws {TypeError} If `type` is not a string.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* formatType(' Name ') // β 'name'
|
|
14
|
+
*/
|
|
15
|
+
export const formatType = (type) => {
|
|
16
|
+
if (typeof type !== 'string') {
|
|
17
|
+
throw new TypeError('[normalize] Type must be a string')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return type.trim().toLowerCase()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Registry mapping type keys to their corresponding handler functions.
|
|
25
|
+
* Built-in types: `'name'`, `'number'`, `'date'`.
|
|
26
|
+
* Can be extended at runtime via {@link register} or {@link createPlugin}.
|
|
27
|
+
*
|
|
28
|
+
* @type {Map<string, Function>}
|
|
29
|
+
*/
|
|
30
|
+
export const registry = new Map([
|
|
31
|
+
['name', nameHandler],
|
|
32
|
+
['number', numberHandler],
|
|
33
|
+
['date', dateHandler],
|
|
34
|
+
])
|