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 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
+ [![npm version](https://img.shields.io/npm/v/data-handlers)](https://www.npmjs.com/package/data-handlers)
10
+ [![license](https://img.shields.io/npm/l/data-handlers)](./LICENSE)
11
+ [![node](https://img.shields.io/node/v/data-handlers)](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
@@ -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
+ [![npm version](https://img.shields.io/npm/v/data-handlers)](https://www.npmjs.com/package/data-handlers)
10
+ [![license](https://img.shields.io/npm/l/data-handlers)](./LICENSE)
11
+ [![node](https://img.shields.io/node/v/data-handlers)](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
+ ])