@zentjs/zentjs 0.0.2 → 0.0.4

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.
Files changed (2) hide show
  1. package/README.md +27 -1544
  2. package/package.json +8 -8
package/README.md CHANGED
@@ -5,1187 +5,26 @@
5
5
  [![npm downloads](https://img.shields.io/npm/dm/%40zentjs%2Fzentjs)](https://www.npmjs.com/package/@zentjs/zentjs)
6
6
  [![license](https://img.shields.io/npm/l/%40zentjs%2Fzentjs)](LICENSE)
7
7
 
8
- > Framework web minimalista e performático para Node.js, inspirado no Express e Fastify.
8
+ Framework web minimalista e performático para Node.js, inspirado no Express e Fastify.
9
9
 
10
- **Zero dependências em runtime** · **ESM-only** · **Node.js 24**
10
+ **Zero dependências em runtime** · **ESM-only** · **Node.js >= 24**
11
11
 
12
- ---
12
+ > ⚠️ **Atenção:** este projeto está em constante desenvolvimento e **ainda não deve ser usado em produção**.
13
13
 
14
- ## Sumário
14
+ ## Criado e contribuído por
15
15
 
16
- - [Visão Geral](#visão-geral)
17
- - [Plano de Documentação](#plano-de-documentação)
18
- - [Estado da Documentação](#estado-da-documentação)
19
- - [Padrão Editorial](#padrão-editorial)
20
- - [Glossário de Termos](#glossário-de-termos)
21
- - [Motivação e Princípios](#motivação-e-princípios)
22
- - [Arquitetura Geral](#arquitetura-geral)
23
- - [Estrutura de Diretórios](#estrutura-de-diretórios)
24
- - [Componentes Principais](#componentes-principais)
25
- - [Application (Zent)](#1-application-zent)
26
- - [Router (Radix Tree)](#2-router-radix-tree)
27
- - [Request](#3-request)
28
- - [Response](#4-response)
29
- - [Middleware Pipeline](#5-middleware-pipeline)
30
- - [Plugin System](#6-plugin-system)
31
- - [Lifecycle Hooks](#7-lifecycle-hooks)
32
- - [Context (ctx)](#8-context-ctx)
33
- - [Error Handling](#9-error-handling)
34
- - [Fluxo de uma Requisição](#fluxo-de-uma-requisição)
35
- - [API Pública](#api-pública)
36
- - [Route Groups](#route-groups)
37
- - [Exemplos de Uso](#exemplos-de-uso)
38
- - [Guia: Primeira API (CRUD básico)](#guia-primeira-api-crud-básico)
39
- - [Guia: Autenticação por plugin](#guia-autenticação-por-plugin)
40
- - [Guia: Métricas com requestMetrics](#guia-métricas-com-requestmetrics)
41
- - [Guia: Testes com inject + Vitest](#guia-testes-com-inject--vitest)
42
- - [Roadmap de Implementação](#roadmap-de-implementação)
43
- - [Decisões Técnicas (ADRs)](#decisões-técnicas-adrs)
44
- - [Contribuição](#contribuição)
45
- - [Referências](#referências)
16
+ - [walber-vaz](https://github.com/walber-vaz)
46
17
 
47
- ---
48
-
49
- ## Plano de Documentação
50
-
51
- A evolução da documentação agora segue etapas incrementais com checklist versionado.
52
-
53
- - Arquivo de acompanhamento: [docs/DOCUMENTATION_TODO.md](docs/DOCUMENTATION_TODO.md)
54
- - Status atual: **Etapa 6 concluída**
55
- - Próxima etapa: manutenção contínua da documentação por PR
56
-
57
- > Regra de ouro: documentar apenas comportamento já implementado no runtime.
58
-
59
- ---
60
-
61
- ## Estado da Documentação
62
-
63
- Este README é a referência principal do projeto e segue atualização incremental por etapas.
64
-
65
- - Fonte de controle: [docs/DOCUMENTATION_TODO.md](docs/DOCUMENTATION_TODO.md)
66
- - Escopo atual: consistência final entre runtime, README e exemplos
67
- - Política: exemplos e contratos devem refletir o runtime real em `src/`
68
-
69
- ---
70
-
71
- ## Padrão Editorial
72
-
73
- Para manter consistência entre seções, usar o padrão abaixo:
74
-
75
- - **API pública**: assinatura + parâmetros + retorno + exemplo mínimo executável
76
- - **Fluxos**: ordem de execução explícita (hooks, middlewares, handler)
77
- - **Erros**: classe/condição + status HTTP + formato de resposta
78
- - **Exemplos**: preferir snippets curtos, com foco em um conceito por bloco
79
-
80
- ---
81
-
82
- ## Glossário de Termos
83
-
84
- - **Handler**: função final da rota que produz o resultado da requisição
85
- - **Middleware**: função `async (ctx, next)` no pipeline (antes/depois do handler)
86
- - **Hook**: interceptador de fase específica do lifecycle (`onRequest`, `preHandler`, etc.)
87
- - **Plugin Scope**: escopo encapsulado criado por `register()`, com herança controlada
88
-
89
- ---
90
-
91
- ## Visão Geral
92
-
93
- **ZentJS** é uma framework HTTP para construção de APIs e aplicações web em Node.js.
94
- O objetivo é combinar o melhor dos dois mundos:
95
-
96
- | Inspiração | O que trazemos |
97
- | ----------- | -------------------------------------------------------------------------- |
98
- | **Express** | API simples e intuitiva, middleware `(req, res, next)` |
99
- | **Fastify** | Performance, sistema de plugins com encapsulamento, hooks de ciclo de vida |
100
-
101
- O resultado é uma framework leve, sem dependências de runtime, construída 100% sobre o módulo nativo `node:http`.
102
-
103
- ---
104
-
105
- ## Motivação e Princípios
106
-
107
- ### Por que criar outra framework?
108
-
109
- 1. **Aprendizado profundo** — Entender os internos de uma framework HTTP modular.
110
- 2. **Zero dependências** — O core não depende de nenhum pacote externo.
111
- 3. **ESM nativo** — Sem CommonJS, sem transpilação, sem build step.
112
- 4. **Performance by design** — Roteamento via Radix Tree, sem regex em hot path.
113
- 5. **Developer Experience** — API clara, erros descritivos, tipagem via JSDoc.
114
-
115
- ### Princípios Arquiteturais
116
-
117
- | Princípio | Descrição |
118
- | -------------------------- | ---------------------------------------------------------- |
119
- | **Single Responsibility** | Cada módulo tem uma única razão para mudar |
120
- | **Open/Closed** | Extensível via plugins, fechado para modificação no core |
121
- | **Composição > Herança** | Plugins e middlewares compõem funcionalidade |
122
- | **Fail Fast** | Erros são detectados e reportados o mais cedo possível |
123
- | **Convention over Config** | Defaults sensatos, mas tudo configurável |
124
- | **Immutable por padrão** | Objetos de configuração não são mutados após inicialização |
125
-
126
- ---
127
-
128
- ## Arquitetura Geral
129
-
130
- ```text
131
- ┌─────────────────────────────────────────────────────────────────────┐
132
- │ ZentJS Core │
133
- │ │
134
- │ ┌────────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
135
- │ │ Server │───▶│ Router │───▶│ Middleware│───▶│ Handler │ │
136
- │ │ (node:http)│ │(RadixTree)│ │ Pipeline │ │ (user fn) │ │
137
- │ └────────────┘ └───────────┘ └───────────┘ └───────────┘ │
138
- │ │ │ │
139
- │ ▼ ▼ │
140
- │ ┌───────────┐ ┌────────────┐ │
141
- │ │ Request │ │ Response │ │
142
- │ │ (wrapper) │◄──────── Context (ctx) ───────────▶│ (wrapper) │ │
143
- │ └───────────┘ └────────────┘ │
144
- │ │
145
- │ ┌──────────────────────────────────────────────────────────┐ │
146
- │ │ Lifecycle Hooks │ │
147
- │ │ onRequest → preParsing → preValidation → preHandler │ │
148
- │ │ → onSend → onResponse → onError │ │
149
- │ └──────────────────────────────────────────────────────────┘ │
150
- │ │
151
- │ ┌──────────────────────────────────────────────────────────┐ │
152
- │ │ Plugin System │ │
153
- │ │ register() → encapsulated scope → decorators │ │
154
- │ └──────────────────────────────────────────────────────────┘ │
155
- └─────────────────────────────────────────────────────────────────────┘
156
- ```
157
-
158
- ---
159
-
160
- ## Estrutura de Diretórios
161
-
162
- ```text
163
- zentjs/
164
- ├── src/
165
- │ ├── index.mjs # Entry point — exporta a função zent()
166
- │ │
167
- │ ├── core/
168
- │ │ ├── application.mjs # Classe Zent (instância do app)
169
- │ │ └── context.mjs # Objeto de contexto por requisição
170
- │ │
171
- │ ├── http/
172
- │ │ ├── request.mjs # Wrapper do IncomingMessage
173
- │ │ └── response.mjs # Wrapper do ServerResponse
174
- │ │
175
- │ ├── router/
176
- │ │ ├── index.mjs # Router público
177
- │ │ ├── node.mjs # Nó da árvore (estático, param, wildcard)
178
- │ │ ├── radix-tree.mjs # Implementação da Radix Tree
179
- │ │
180
- │ ├── middleware/
181
- │ │ └── pipeline.mjs # Executor da cadeia de middlewares
182
- │ │
183
- │ ├── plugins/
184
- │ │ ├── manager.mjs # Registro e carregamento de plugins
185
- │ │ ├── body-parser.mjs # Parser de body (JSON, URL-encoded, text)
186
- │ │ └── cors.mjs # Middleware CORS built-in
187
- │ │
188
- │ ├── hooks/
189
- │ │ └── lifecycle.mjs # Gerenciador dos lifecycle hooks
190
- │ │
191
- │ └── errors/
192
- │ ├── http-error.mjs # Classe base HttpError
193
- │ └── error-handler.mjs # Handler global de erros
194
-
195
- ├── test/
196
- │ ├── setupTests.mjs
197
- │ ├── unit/
198
- │ │ └── ... (13 arquivos de testes unitários)
199
- │ └── integration/
200
- │ └── plugins-http.integration.test.mjs
201
-
202
- ├── examples/
203
- │ ├── hello-world.mjs
204
- │ ├── rest-api.mjs
205
- │ └── with-plugins.mjs
206
-
207
- ├── package.json
208
- ├── vitest.config.mjs
209
- ├── eslint.config.mjs
210
- └── README.md
211
- ```
212
-
213
- ---
214
-
215
- ## Componentes Principais
216
-
217
- ### 1. Application (Zent)
218
-
219
- O ponto de entrada da framework. Cria e configura a instância do servidor.
220
-
221
- **Arquivo:** `src/core/application.mjs`
222
-
223
- **Responsabilidades:**
224
-
225
- - Inicializar o servidor HTTP
226
- - Registrar rotas (proxy para o Router)
227
- - Registrar middlewares globais
228
- - Registrar plugins
229
- - Gerenciar lifecycle hooks
230
- - Iniciar/parar o servidor (`listen` / `close`)
231
-
232
- **Interface:**
233
-
234
- ```js
235
- import { zent } from 'zentjs';
236
-
237
- const app = zent({
238
- // Opções de configuração
239
- ignoreTrailingSlash: true, // /users e /users/ são a mesma rota
240
- });
241
-
242
- // Registrar rotas
243
- app.get('/hello', (ctx) => {
244
- return ctx.res.json({ message: 'Hello, World!' });
245
- });
246
-
247
- // Iniciar servidor
248
- app.listen({ port: 3000, host: '0.0.0.0' }, (err, address) => {
249
- if (err) throw err;
250
- console.log(`Server listening on ${address}`);
251
- });
252
- ```
253
-
254
- **Diagrama de classe:**
255
-
256
- ```text
257
- ┌────────────────────────────────────┐
258
- │ Zent (Application) │
259
- ├────────────────────────────────────┤
260
- │ - _server: HttpServer │
261
- │ - _router: Router │
262
- │ - _plugins: PluginManager │
263
- │ - _hooks: LifecycleManager │
264
- │ - _middlewares: Middleware[] │
265
- │ - _options: ZentOptions │
266
- ├────────────────────────────────────┤
267
- │ + get(path, opts?, handler) │
268
- │ + post(path, opts?, handler) │
269
- │ + put(path, opts?, handler) │
270
- │ + patch(path, opts?, handler) │
271
- │ + delete(path, opts?, handler) │
272
- │ + head(path, opts?, handler) │
273
- │ + options(path, opts?, handler) │
274
- │ + all(path, opts?, handler) │
275
- │ + use(middleware) │
276
- │ + register(plugin, opts?) │
277
- │ + addHook(name, fn) │
278
- │ + decorate(name, value) │
279
- │ + listen(opts, callback?) │
280
- │ + close() │
281
- │ + inject(opts): Promise<Response> │
282
- └────────────────────────────────────┘
283
- ```
284
-
285
- > **`inject()`** permite testar rotas sem abrir uma porta de rede (inspirado no Fastify).
286
-
287
- ---
288
-
289
- ### 2. Router (Radix Tree)
290
-
291
- Roteamento de alta performance usando uma **Radix Tree** (também chamada Patricia Trie ou Compact Prefix Tree).
292
-
293
- **Arquivo:** `src/router/radix-tree.mjs`
294
-
295
- **Por que Radix Tree?**
296
-
297
- | Abordagem | Complexidade (lookup) | Usado por |
298
- | -------------- | --------------------- | ----------------------- |
299
- | Array linear | O(n) | Express |
300
- | Regex matching | O(n) | Koa |
301
- | **Radix Tree** | **O(k)** \* | **Fastify**, **ZentJS** |
302
-
303
- _\* k = comprimento do path, independente do número de rotas_
304
-
305
- **Funcionalidades:**
306
-
307
- - Rotas estáticas: `/users/list`
308
- - Parâmetros nomeados: `/users/:id`
309
- - Wildcard: `/static/*filepath`
310
- - Suporte a múltiplos métodos HTTP por path
311
-
312
- **Estrutura do nó:**
313
-
314
- ```js
315
- // Cada nó na Radix Tree
316
- {
317
- prefix: '/users', // Fragmento do path
318
- children: Map {}, // Filhos indexados pelo primeiro caractere
319
- paramChild: null, // Filho de parâmetro (:param)
320
- wildcardChild: null, // Filho wildcard (*)
321
- handlers: Map { // Handlers por método HTTP
322
- 'GET': { handler, hooks, middlewares },
323
- 'POST': { handler, hooks, middlewares }
324
- }
325
- }
326
- ```
327
-
328
- **Exemplo de árvore para as rotas:**
329
-
330
- ```text
331
- GET /users
332
- GET /users/:id
333
- POST /users
334
- GET /users/:id/posts
335
- GET /about
336
- ```
337
-
338
- ```text
339
- root ('')
340
- ├── /u
341
- │ └── sers
342
- │ ├── [GET, POST handlers]
343
- │ └── /:id
344
- │ ├── [GET handler]
345
- │ └── /posts
346
- │ └── [GET handler]
347
- └── /about
348
- └── [GET handler]
349
- ```
350
-
351
- **API do Router:**
352
-
353
- ```js
354
- class Router {
355
- add(method, path, handler, opts?) // Adiciona rota
356
- find(method, path) // Busca rota → { handler, params, hooks }
357
- all(path, handler) // Registra para todos os métodos HTTP
358
- group(prefix, opts?, callback) // Agrupa rotas sob prefixo
359
- }
360
- ```
361
-
362
- **Route Groups:**
363
-
364
- ```js
365
- // Grupo com prefixo + middlewares compartilhados
366
- router.group('/api', { middlewares: [auth] }, (group) => {
367
- group.get('/users', listUsers); // GET /api/users
368
- group.post('/users', createUser); // POST /api/users
369
-
370
- // Sub-grupo aninhado — herda middlewares do pai
371
- group.group('/admin', { middlewares: [adminOnly] }, (admin) => {
372
- admin.delete('/users/:id', deleteUser); // DELETE /api/admin/users/:id
373
- // middlewares executados: [auth, adminOnly]
374
- });
375
- });
376
- ```
377
-
378
- ---
379
-
380
- ### 3. Request
381
-
382
- Wrapper sobre `http.IncomingMessage` que fornece uma API mais ergonômica.
383
-
384
- **Arquivo:** `src/http/request.mjs`
385
-
386
- **Propriedades e métodos:**
387
-
388
- ```js
389
- class ZentRequest {
390
- // Propriedades parseadas do request original
391
- get method() // 'GET', 'POST', etc.
392
- get url() // URL completa
393
- get path() // Path sem query string
394
- get query() // Query params como objeto { key: value }
395
- get headers() // Headers como objeto
396
- get params() // Route params { id: '123' }
397
- get ip() // IP do cliente
398
- get hostname() // Hostname da requisição
399
- get protocol() // 'http' ou 'https'
400
-
401
- // Body (populado após parsing)
402
- get body() // Body parseado (JSON, form, etc.)
403
-
404
- // Helpers
405
- is(type) // Verifica Content-Type
406
- get(header) // Retorna valor de um header
407
- }
408
- ```
409
-
410
- **Decisão:** O body **não** é parseado automaticamente. O usuário deve usar o middleware `bodyParser()` ou ler manualmente. Isso garante zero overhead para rotas que não precisam de body.
411
-
412
- ---
413
-
414
- ### 4. Response
415
-
416
- Wrapper sobre `http.ServerResponse` com API fluente (chainable).
417
-
418
- **Arquivo:** `src/http/response.mjs`
419
-
420
- **API:**
421
-
422
- ```js
423
- class ZentResponse {
424
- // Status
425
- status(code) // Define status code → retorna this
426
-
427
- // Headers
428
- header(name, value) // Define header → retorna this
429
- type(contentType) // Atalho para Content-Type → retorna this
430
-
431
- // Envio de resposta
432
- json(data) // Serializa como JSON e envia
433
- send(data) // Envia string/Buffer
434
- html(data) // Envia como text/html
435
- redirect(url, code?) // Redireciona (default 302)
436
- empty(code?) // Resposta sem body (default 204)
437
-
438
- // Propriedades
439
- get sent() // Boolean: response já foi enviada?
440
- get statusCode() // Status code atual
441
- }
442
- ```
443
-
444
- **Exemplo de uso:**
445
-
446
- ```js
447
- app.get('/users/:id', (ctx) => {
448
- const user = findUser(ctx.req.params.id);
449
-
450
- if (!user) {
451
- return ctx.res.status(404).json({ error: 'User not found' });
452
- }
453
-
454
- return ctx.res.json(user);
455
- });
456
- ```
457
-
458
- ---
459
-
460
- ### 5. Middleware Pipeline
461
-
462
- Sistema de middlewares inspirado no Express, mas com execução baseada em `async/await`.
463
-
464
- **Arquivo:** `src/middleware/pipeline.mjs`
465
-
466
- **Signature do middleware:**
467
-
468
- ```js
469
- // Middleware com next()
470
- async function myMiddleware(ctx, next) {
471
- // Antes do handler
472
- console.log('Before');
473
-
474
- await next();
475
-
476
- // Depois do handler (response já preparada)
477
- console.log('After');
478
- }
479
- ```
480
-
481
- **Tipos de middleware:**
482
-
483
- ```text
484
- ┌──────────────────────────────────────────────────┐
485
- │ Middleware Pipeline │
486
- │ │
487
- │ 1. Global Middlewares app.use(fn) │
488
- │ 2. Route-level Middlewares route opts │
489
- │ 3. Plugin-scoped dentro de plugins │
490
- │ │
491
- │ Execução: Onion Model (como Koa) │
492
- │ │
493
- │ ┌───────────────────────────────────────────┐ │
494
- │ │ Middleware 1 (before) │ │
495
- │ │ ┌───────────────────────────────────┐ │ │
496
- │ │ │ Middleware 2 (before) │ │ │
497
- │ │ │ ┌──────────────────────────┐ │ │ │
498
- │ │ │ │ Route Handler │ │ │ │
499
- │ │ │ └──────────────────────────┘ │ │ │
500
- │ │ │ Middleware 2 (after) │ │ │
501
- │ │ └───────────────────────────────────┘ │ │
502
- │ │ Middleware 1 (after) │ │
503
- │ └───────────────────────────────────────────┘ │
504
- └──────────────────────────────────────────────────┘
505
- ```
506
-
507
- **Assinaturas suportadas na aplicação:**
508
-
509
- - `app.use(fn)` — middleware global
510
- - `app.use('/prefix', fn)` — middleware global com prefix match
511
- - `route options.middlewares` — middleware por rota
512
- - `group(..., { middlewares })` — middleware herdado por grupo/subgrupo
513
-
514
- **Ordem de execução efetiva:**
515
-
516
- 1. Middlewares globais registrados via `app.use()`
517
- 2. Middlewares herdados do escopo de plugin/grupo
518
- 3. Middlewares específicos da rota
519
- 4. Handler da rota (dentro do pipeline)
520
-
521
- > `preHandler` é executado imediatamente antes do handler, dentro do estágio final do pipeline.
522
-
523
- **Implementação do pipeline executor:**
524
-
525
- ```js
526
- function compose(middlewares) {
527
- return function (ctx) {
528
- let index = -1;
529
-
530
- function dispatch(i) {
531
- if (i <= index) {
532
- return Promise.reject(new Error('next() called multiple times'));
533
- }
534
- index = i;
535
-
536
- const fn = middlewares[i];
537
- if (!fn) return Promise.resolve();
538
-
539
- return Promise.resolve(fn(ctx, () => dispatch(i + 1)));
540
- }
541
-
542
- return dispatch(0);
543
- };
544
- }
545
- ```
546
-
547
- ---
548
-
549
- ### 6. Plugin System
550
-
551
- Inspirado diretamente no Fastify, com **encapsulamento de escopo**.
552
-
553
- **Arquivos:** `src/plugins/manager.mjs`, `src/core/application.mjs` (criação de escopo)
554
-
555
- **Conceito:**
556
-
557
- - Cada plugin recebe uma instância "encapsulada" do app
558
- - Decorators, hooks e middlewares de plugin são aplicados por escopo
559
- - Plugins podem ter dependências e são registrados de forma assíncrona
560
-
561
- **Garantias de encapsulamento no runtime atual:**
562
-
563
- - Pai → Filho: middlewares, hooks e decorators são herdados
564
- - Filho → Pai: alterações no escopo filho **não** voltam para o pai
565
- - Irmão A ↔ Irmão B: não compartilham decorators/hooks/middlewares locais
566
- - Prefixo: `register(plugin, { prefix })` é acumulativo em plugins aninhados
567
-
568
- **API:**
569
-
570
- ```js
571
- // Definir um plugin
572
- async function dbPlugin(app, opts) {
573
- const connection = await connectDB(opts.uri);
574
-
575
- // Decorator: adiciona propriedade ao escopo atual do plugin
576
- app.decorate('db', connection);
577
-
578
- // Hook específico do escopo
579
- app.addHook('onRequest', async (ctx) => {
580
- ctx.state.db = connection;
581
- });
582
- }
583
-
584
- // Registrar plugin
585
- app.register(dbPlugin, { uri: 'mongodb://localhost/mydb' });
586
- ```
587
-
588
- **Encapsulamento:**
589
-
590
- ```text
591
- ┌─────────────────────────────────────────────┐
592
- │ Root Scope (app) │
593
- │ ├── global middlewares │
594
- │ ├── global hooks │
595
- │ │ │
596
- │ │ ┌────────────────────────────────────┐ │
597
- │ │ │ Plugin Scope A │ │
598
- │ │ │ ├── herda middlewares do pai │ │
599
- │ │ │ ├── decorators locais (scope.db) │ │
600
- │ │ │ └── rotas locais (/api/v1/*) │ │
601
- │ │ └────────────────────────────────────┘ │
602
- │ │ │
603
- │ │ ┌────────────────────────────────────┐ │
604
- │ │ │ Plugin Scope B │ │
605
- │ │ │ ├── herda middlewares do pai │ │
606
- │ │ │ ├── NÃO acessa app.db (de A) │ │
607
- │ │ │ └── rotas locais (/api/v2/*) │ │
608
- │ │ └────────────────────────────────────┘ │
609
- │ │ │
610
- └─────────────────────────────────────────────┘
611
- ```
612
-
613
- **Herança de escopo (resumo):**
614
-
615
- | Recurso | Pai → Filho | Filho → Pai | Irmãos |
616
- | ------------ | ----------- | ----------- | ------ |
617
- | Decorators | Sim | Não | Não |
618
- | Hooks | Sim | Não | Não |
619
- | Middlewares | Sim | Não | Não |
620
- | Prefixo rota | Sim | Não | Não |
621
-
622
- **Exemplo com plugins irmãos (sem vazamento):**
623
-
624
- ```js
625
- import { NotFoundError, zent } from 'zentjs';
626
-
627
- const app = zent();
628
-
629
- async function pluginA(scope) {
630
- scope.decorate('token', 'A');
631
- scope.get('/a', (ctx) => ctx.res.json({ token: scope.token }));
632
- }
633
-
634
- async function pluginB(scope) {
635
- scope.get('/b', (ctx) => {
636
- if (!scope.hasDecorator('token')) {
637
- throw new NotFoundError('Decorator token is not available in plugin B');
638
- }
639
- return ctx.res.json({ token: scope.token });
640
- });
641
- }
642
-
643
- app.register(pluginA, { prefix: '/v1' });
644
- app.register(pluginB, { prefix: '/v1' });
645
- ```
646
-
647
- No exemplo acima, o decorator `token` criado em `pluginA` não fica visível em `pluginB`.
648
-
649
- **Propriedades do Plugin Manager:**
650
-
651
- ```text
652
- ┌──────────────────────────────────────┐
653
- │ PluginManager │
654
- ├──────────────────────────────────────┤
655
- │ - _plugins: PluginEntry[] │
656
- │ - _loaded: boolean │
657
- ├──────────────────────────────────────┤
658
- │ + register(fn, opts?) │
659
- │ + load(): Promise<void> │
660
- │ + createScope(parent): Zent │
661
- └──────────────────────────────────────┘
662
- ```
663
-
664
- ---
665
-
666
- ### 7. Lifecycle Hooks
667
-
668
- Hooks permitem interceptar diferentes fases do ciclo de vida de uma requisição.
669
-
670
- **Arquivo:** `src/hooks/lifecycle.mjs`
671
-
672
- **Hooks disponíveis (em ordem de execução):**
673
-
674
- ```text
675
- Requisição chega
676
-
677
-
678
- ┌─────────────┐
679
- │ onRequest │ → Primeira coisa executada (logging, auth check)
680
- └──────┬──────┘
681
-
682
- ┌─────────────┐
683
- │ preParsing │ → Antes de fazer parse do body
684
- └──────┬──────┘
685
-
686
- ┌───────────────┐
687
- │ preValidation │ → Antes de validar o input (schema)
688
- └──────┬────────┘
689
-
690
- ┌─────────────┐
691
- │ preHandler │ → Depois de validar, antes do handler
692
- └──────┬──────┘
693
-
694
- ┌─────────────┐
695
- │ Handler │ → Função do usuário
696
- └──────┬──────┘
697
-
698
- ┌─────────────┐
699
- │ onSend │ → Antes de enviar a resposta (pode modificar payload)
700
- └──────┬──────┘
701
-
702
- ┌─────────────┐
703
- │ onResponse │ → Depois que a resposta foi enviada (cleanup, metrics)
704
- └──────┬──────┘
705
-
706
- Fim
707
-
708
- ┌─────────────┐
709
- │ onError │ → Chamado quando qualquer erro ocorre (em qualquer fase)
710
- └─────────────┘
711
- ```
712
-
713
- **Signature dos hooks:**
714
-
715
- ```js
716
- // onRequest, preParsing, preValidation, preHandler
717
- app.addHook('onRequest', async (ctx) => {
718
- ctx.req.startTime = Date.now();
719
- });
720
-
721
- // onSend — pode modificar o payload
722
- app.addHook('onSend', async (ctx, payload) => {
723
- // Retornar payload modificado
724
- return payload;
725
- });
726
-
727
- // onResponse — chamado após envio (não pode modificar a resposta)
728
- app.addHook('onResponse', async (ctx) => {
729
- const duration = Date.now() - ctx.req.startTime;
730
- console.log(`${ctx.req.method} ${ctx.req.path} - ${duration}ms`);
731
- });
732
-
733
- // onError — handler de erro global
734
- app.addHook('onError', async (ctx, error) => {
735
- console.error(error);
736
- });
737
- ```
738
-
739
- **Ordem de execução no runtime (quando a rota existe):**
740
-
741
- 1. `onRequest` global → `onRequest` da rota
742
- 2. `preParsing` global → `preParsing` da rota
743
- 3. `preValidation` global → `preValidation` da rota
744
- 4. Pipeline de middlewares
745
- 5. `preHandler` global → `preHandler` da rota
746
- 6. Handler da rota
747
- 7. `onSend` global → `onSend` da rota (somente quando handler retorna payload e `ctx.res` ainda não foi enviado)
748
- 8. `onResponse` global → `onResponse` da rota
749
-
750
- Se ocorrer erro em qualquer etapa:
751
-
752
- 1. `onError` global
753
- 2. `onError` da rota
754
- 3. `setErrorHandler()` customizado (ou handler padrão)
755
-
756
- ---
757
-
758
- ### 8. Context (ctx)
759
-
760
- Objeto criado **por requisição** que carrega todo o estado.
761
-
762
- **Arquivo:** `src/core/context.mjs`
763
-
764
- ```js
765
- class Context {
766
- constructor(req, res, app) {
767
- this.req = req; // ZentRequest
768
- this.res = res; // ZentResponse
769
- this.app = app; // Instância da aplicação
770
- this.state = {}; // Espaço livre para o usuário armazenar dados
771
- }
772
- }
773
- ```
774
-
775
- **Uso no handler:**
776
-
777
- ```js
778
- app.get('/dashboard', async (ctx) => {
779
- // ctx.req → Request
780
- // ctx.res → Response
781
- // ctx.state → dados do middleware (ex: user autenticado)
782
- // ctx.app → instância (acesso a decorators: ctx.app.db)
783
-
784
- const userId = ctx.state.user.id;
785
- const data = await ctx.app.db.findDashboard(userId);
786
-
787
- return ctx.res.json(data);
788
- });
789
- ```
790
-
791
- ---
792
-
793
- ### 9. Error Handling
794
-
795
- Sistema de erros estruturado com classes customizadas e error handler global.
796
-
797
- **Arquivos:** `src/errors/http-error.mjs`, `src/errors/error-handler.mjs`
798
-
799
- **Hierarquia de erros:**
800
-
801
- ```text
802
- Error
803
- └── HttpError
804
- ├── BadRequestError (400)
805
- ├── UnauthorizedError (401)
806
- ├── ForbiddenError (403)
807
- ├── NotFoundError (404)
808
- ├── MethodNotAllowedError (405)
809
- ├── ConflictError (409)
810
- ├── UnprocessableEntityError (422)
811
- ├── TooManyRequestsError (429)
812
- └── InternalServerError (500)
813
- ```
814
-
815
- **Uso:**
816
-
817
- ```js
818
- import { NotFoundError, BadRequestError } from 'zentjs';
819
-
820
- app.get('/users/:id', async (ctx) => {
821
- if (!isValidId(ctx.req.params.id)) {
822
- throw new BadRequestError('Invalid user ID');
823
- }
824
-
825
- const user = await findUser(ctx.req.params.id);
826
- if (!user) {
827
- throw new NotFoundError('User not found');
828
- }
829
-
830
- return ctx.res.json(user);
831
- });
832
- ```
833
-
834
- **Formato de resposta de erro padrão:**
835
-
836
- ```json
837
- {
838
- "statusCode": 404,
839
- "error": "Not Found",
840
- "message": "User not found"
841
- }
842
- ```
843
-
844
- **Comportamento padrão do ErrorHandler:**
845
-
846
- - Se o erro já for `HttpError`, usa o `statusCode` e `toJSON()` da classe
847
- - Se for erro genérico, converte para `InternalServerError` (500)
848
- - Se `ctx.res.sent === true`, não envia resposta duplicada
849
- - Se `setErrorHandler()` falhar, faz fallback para o handler padrão
850
-
851
- **404 customizado com `setNotFoundHandler()`:**
852
-
853
- ```js
854
- app.setNotFoundHandler(async (ctx) => {
855
- return ctx.res.status(404).json({
856
- statusCode: 404,
857
- error: 'Not Found',
858
- message: `Route ${ctx.req.method} ${ctx.req.path} not found`,
859
- });
860
- });
861
- ```
862
-
863
- Se o not-found handler não enviar resposta, o framework envia automaticamente o payload padrão de `NotFoundError`.
864
-
865
- **Falha comum de parsing (JSON inválido):**
866
-
867
- ```js
868
- import { bodyParser, zent } from 'zentjs';
869
-
870
- const app = zent();
871
- app.use(bodyParser());
872
-
873
- app.post('/echo', (ctx) => ctx.res.json(ctx.req.body));
874
- // body JSON inválido -> BadRequestError(400) com message "Invalid JSON body"
875
- ```
876
-
877
- **Error handler customizado:**
878
-
879
- ```js
880
- app.setErrorHandler((error, ctx) => {
881
- // Lógica customizada
882
- return ctx.res.status(error.statusCode || 500).json({
883
- success: false,
884
- error: error.message,
885
- });
886
- });
887
- ```
888
-
889
- ---
890
-
891
- ## Fluxo de uma Requisição
892
-
893
- Diagrama completo do ciclo de vida de uma requisição HTTP no ZentJS:
894
-
895
- ```text
896
- Cliente HTTP
897
-
898
-
899
- ┌──────────────────────────────────────────────────────────────┐
900
- │ node:http Server │
901
- │ (req, res) callback │
902
- └───────────────────────┬──────────────────────────────────────┘
903
-
904
-
905
- ┌─────────────────┐
906
- │ Criar Context │ ZentRequest + ZentResponse
907
- │ (ctx) │ são instanciados
908
- └────────┬────────┘
909
-
910
-
911
- ┌──────────────────┐
912
- │ onRequest hooks │ Logging, rate limiting, etc.
913
- └────────┬─────────┘
914
-
915
-
916
- ┌──────────────────┐
917
- │ Router.find() │ Radix Tree lookup
918
- │ (method + path) │ → handler + params + route hooks
919
- └────────┬─────────┘
920
-
921
- ┌────────┴──────────┐
922
- │ Rota encontrada? │
923
- └────┬────────┬─────┘
924
- │ Não │ Sim
925
- ▼ ▼
926
- ┌───────────┐ ┌─────────────────┐
927
- │ 404 Error │ │ preParsing │ body-parser, upload
928
- └───────────┘ └───────┬─────────┘
929
-
930
-
931
- ┌─────────────────┐
932
- │ preValidation │ schema validation
933
- └────────┬────────┘
934
-
935
-
936
- ┌──────────────────────┐
937
- │ Middleware Pipeline │ Global + Route middlewares
938
- │ (onion model) │
939
- └──────────┬───────────┘
940
-
941
-
942
- ┌─────────────────┐
943
- │ preHandler │ Última chance antes do handler
944
- └────────┬────────┘
945
-
946
-
947
- ┌─────────────────┐
948
- │ Route Handler │ Função do usuário
949
- └────────┬────────┘
950
-
951
-
952
- ┌─────────────────┐
953
- │ onSend │ Serialização, compressão
954
- └────────┬────────┘
955
-
956
-
957
- ┌─────────────────┐
958
- │ res.end() │ Resposta enviada ao cliente
959
- └────────┬────────┘
960
-
961
-
962
- ┌─────────────────┐
963
- │ onResponse │ Métricas, cleanup
964
- └─────────────────┘
965
-
966
- ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
967
- Em caso de erro em qualquer fase:
968
- ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
969
- ┌─────────────────┐
970
- │ onError hook │
971
- │ Error Handler │ Formata e envia erro
972
- └─────────────────┘
973
- ```
974
-
975
- ---
976
-
977
- ## API Pública
978
-
979
- ### Criação da instância
980
-
981
- ```js
982
- import { zent } from 'zentjs';
983
-
984
- const app = zent(options?);
985
- ```
986
-
987
- **Opções:**
988
-
989
- | Opção | Tipo | Default | Descrição |
990
- | --------------------- | --------- | ------- | --------------------------------- |
991
- | `ignoreTrailingSlash` | `boolean` | `true` | `/foo` e `/foo/` são equivalentes |
992
- | `caseSensitive` | `boolean` | `false` | Paths sensíveis a maiúsculas |
993
-
994
- > As opções acima são as efetivamente consumidas pela aplicação no runtime atual.
995
-
996
- ### Métodos de roteamento
997
-
998
- ```js
999
- app.get(path, handler, options?);
1000
- app.post(path, handler, options?);
1001
- app.put(path, handler, options?);
1002
- app.patch(path, handler, options?);
1003
- app.delete(path, handler, options?);
1004
- app.head(path, handler, options?);
1005
- app.options(path, handler, options?);
1006
- app.all(path, handler, options?); // todos os métodos HTTP
1007
- app.route(definition); // definição completa da rota
1008
- ```
1009
-
1010
- **`options` por rota:**
1011
-
1012
- ```js
1013
- {
1014
- middlewares: [authMiddleware],
1015
- hooks: {
1016
- onRequest: [fn],
1017
- preParsing: [fn],
1018
- preValidation: [fn],
1019
- preHandler: [fn],
1020
- onSend: [fn],
1021
- onResponse: [fn],
1022
- onError: [fn],
1023
- },
1024
- }
1025
- ```
1026
-
1027
- **`app.route(definition)`**
1028
-
1029
- ```js
1030
- app.route({
1031
- method: 'POST',
1032
- path: '/users',
1033
- handler: createUser,
1034
- middlewares: [authMiddleware],
1035
- hooks: { preValidation: [validateBody] },
1036
- });
1037
- ```
1038
-
1039
- ### Middleware
1040
-
1041
- ```js
1042
- app.use(middleware); // Global
1043
- app.use('/api', middleware); // Com prefixo
1044
- ```
1045
-
1046
- Assinaturas válidas:
1047
-
1048
- - `use(middleware)`
1049
- - `use(prefix, middleware)`
1050
-
1051
- ### Route Groups
1052
-
1053
- Agrupa rotas sob um prefixo compartilhado, com middlewares e hooks herdados:
1054
-
1055
- ```js
1056
- // Grupo simples
1057
- app.group('/api/v1', (group) => {
1058
- group.get('/users', listUsers); // GET /api/v1/users
1059
- group.post('/users', createUser); // POST /api/v1/users
1060
- group.get('/users/:id', getUser); // GET /api/v1/users/:id
1061
- });
1062
-
1063
- // Grupo com middlewares compartilhados
1064
- app.group('/admin', { middlewares: [authMiddleware] }, (group) => {
1065
- group.get('/dashboard', dashboard); // GET /admin/dashboard (com auth)
1066
- group.delete('/users/:id', deleteUser);
1067
- });
1068
-
1069
- // Sub-grupos aninhados (middlewares acumulam: pai → filho → rota)
1070
- app.group('/api', { middlewares: [cors] }, (api) => {
1071
- api.group('/v1', { middlewares: [rateLimit] }, (v1) => {
1072
- v1.get('/products', listProducts); // middlewares: [cors, rateLimit]
1073
- });
1074
- api.group('/v2', (v2) => {
1075
- v2.get('/products', listProductsV2); // middlewares: [cors]
1076
- });
1077
- });
1078
- ```
1079
-
1080
- **Características:**
1081
-
1082
- - Prefixo aplicado automaticamente a todas as rotas do grupo
1083
- - Middlewares do grupo executam **antes** dos middlewares da rota
1084
- - Hooks são mesclados (grupo → rota)
1085
- - Sub-grupos herdam middlewares/hooks dos grupos pai
1086
- - Mesma API de conveniência (`get`, `post`, `all`, `route`, etc.)
1087
-
1088
- ### Plugins
1089
-
1090
- ```js
1091
- app.register(plugin, options?); // ex: { prefix: '/api' }
1092
- app.decorate(name, value);
1093
- app.hasDecorator(name);
1094
- ```
1095
-
1096
- Contrato de plugin:
1097
-
1098
- ```js
1099
- async function myPlugin(scope, opts) {
1100
- // scope possui API compatível com app
1101
- // e respeita encapsulamento por escopo
1102
- }
1103
- ```
1104
-
1105
- Métrica mínima por hooks (built-in):
1106
-
1107
- ```js
1108
- import { requestMetrics } from 'zentjs';
1109
-
1110
- const metrics = requestMetrics({
1111
- onRecord: (record) => {
1112
- // { method, path, statusCode, durationMs }
1113
- console.log(record);
1114
- },
1115
- });
1116
-
1117
- app.addHook('onRequest', metrics.onRequest);
1118
- app.addHook('onResponse', metrics.onResponse);
1119
- ```
1120
-
1121
- ### Lifecycle
1122
-
1123
- ```js
1124
- app.addHook(hookName, hookFunction);
1125
- app.setErrorHandler(handler);
1126
- app.setNotFoundHandler(handler);
1127
- ```
1128
-
1129
- `hookName` suportados no lifecycle global:
1130
-
1131
- - `onRequest`
1132
- - `preParsing`
1133
- - `preValidation`
1134
- - `preHandler`
1135
- - `onSend`
1136
- - `onResponse`
1137
- - `onError`
1138
-
1139
- ### Servidor
1140
-
1141
- ```js
1142
- await app.listen({ port, host }, callback?); // default: 3000 / 0.0.0.0
1143
- await app.close();
1144
- const response = await app.inject({ method, url, headers?, body? });
1145
- ```
1146
-
1147
- Resposta de `inject()`:
1148
-
1149
- ```js
1150
- {
1151
- (statusCode, headers, body, json()); // helper para JSON.parse(body)
1152
- }
1153
- ```
1154
-
1155
- ---
1156
-
1157
- ## Exemplos de Uso
1158
-
1159
- ### Exemplos executáveis (Fase 5)
1160
-
1161
- Os exemplos abaixo já estão prontos na pasta `examples/`:
18
+ ## Instalação
1162
19
 
1163
20
  ```bash
1164
- node examples/hello-world.mjs
1165
- node examples/rest-api.mjs
1166
- node examples/with-plugins.mjs
21
+ npm install @zentjs/zentjs
1167
22
  ```
1168
23
 
1169
- Cada exemplo sobe o servidor em `127.0.0.1:3000`.
1170
-
1171
- Benchmark básico (Fase 10):
1172
-
1173
- ```bash
1174
- npm run bench
1175
- npm run bench:save-baseline
1176
- ```
1177
-
1178
- ### Guias Práticos (Etapa 5)
1179
-
1180
- - CRUD básico: seção **Guia: Primeira API (CRUD básico)**
1181
- - Autenticação por escopo: seção **Guia: Autenticação por plugin**
1182
- - Métricas por requisição: seção **Guia: Métricas com requestMetrics**
1183
- - Testes sem rede: seção **Guia: Testes com inject + Vitest**
1184
-
1185
- ### Hello World
24
+ ## Exemplo rápido
1186
25
 
1187
26
  ```js
1188
- import { zent } from 'zentjs';
27
+ import { zent } from '@zentjs/zentjs';
1189
28
 
1190
29
  const app = zent();
1191
30
 
@@ -1196,389 +35,33 @@ app.get('/', (ctx) => {
1196
35
  app.listen({ port: 3000 });
1197
36
  ```
1198
37
 
1199
- ### Guia: Primeira API (CRUD básico)
38
+ ## Scripts úteis (desenvolvimento)
1200
39
 
1201
- ```js
1202
- import { zent, bodyParser, NotFoundError } from 'zentjs';
1203
-
1204
- const app = zent();
1205
-
1206
- // Middleware global para parsear body
1207
- app.use(bodyParser());
1208
-
1209
- // In-memory store
1210
- const users = new Map();
1211
- let nextId = 1;
1212
-
1213
- app.get('/users', (ctx) => {
1214
- return ctx.res.json([...users.values()]);
1215
- });
1216
-
1217
- app.get('/users/:id', (ctx) => {
1218
- const user = users.get(Number(ctx.req.params.id));
1219
- if (!user) throw new NotFoundError('User not found');
1220
- return ctx.res.json(user);
1221
- });
1222
-
1223
- app.post('/users', (ctx) => {
1224
- const { name, email } = ctx.req.body;
1225
- const user = { id: nextId++, name, email };
1226
- users.set(user.id, user);
1227
- return ctx.res.status(201).json(user);
1228
- });
1229
-
1230
- app.put('/users/:id', (ctx) => {
1231
- const id = Number(ctx.req.params.id);
1232
- if (!users.has(id)) throw new NotFoundError('User not found');
1233
-
1234
- const { name, email } = ctx.req.body;
1235
- const user = { id, name, email };
1236
- users.set(id, user);
1237
- return ctx.res.json(user);
1238
- });
1239
-
1240
- app.delete('/users/:id', (ctx) => {
1241
- const id = Number(ctx.req.params.id);
1242
- if (!users.has(id)) throw new NotFoundError('User not found');
1243
-
1244
- users.delete(id);
1245
- return ctx.res.empty();
1246
- });
1247
-
1248
- app.listen({ port: 3000 });
1249
- ```
1250
-
1251
- ### Guia: Autenticação por plugin
1252
-
1253
- ```js
1254
- import { UnauthorizedError, zent } from 'zentjs';
1255
-
1256
- // Plugin de autenticação
1257
- async function authPlugin(app, opts) {
1258
- app.decorate('authenticate', async (ctx) => {
1259
- const token = ctx.req.get('authorization');
1260
- if (!token) throw new UnauthorizedError('Missing token');
1261
- ctx.state.user = verifyToken(token);
1262
- });
1263
-
1264
- // Aplica hook em todas as rotas dentro deste escopo
1265
- app.addHook('preHandler', async (ctx) => {
1266
- await app.authenticate(ctx);
1267
- });
1268
- }
1269
-
1270
- // Plugin de rotas protegidas
1271
- async function protectedRoutes(app) {
1272
- // Registra o plugin de auth neste escopo
1273
- app.register(authPlugin);
1274
-
1275
- app.get('/profile', (ctx) => {
1276
- return ctx.res.json(ctx.state.user);
1277
- });
1278
- }
1279
-
1280
- // Plugin de rotas públicas
1281
- async function publicRoutes(app) {
1282
- app.get('/health', (ctx) => {
1283
- return ctx.res.json({ status: 'ok' });
1284
- });
1285
- }
1286
-
1287
- const app = zent();
1288
-
1289
- // Rotas públicas (sem auth)
1290
- app.register(publicRoutes, { prefix: '/api' });
1291
-
1292
- // Rotas protegidas (com auth)
1293
- app.register(protectedRoutes, { prefix: '/api' });
1294
-
1295
- app.listen({ port: 3000 });
1296
- ```
1297
-
1298
- ### Guia: Métricas com requestMetrics
1299
-
1300
- ```js
1301
- import { requestMetrics, zent } from 'zentjs';
1302
-
1303
- const app = zent();
1304
-
1305
- const metrics = requestMetrics({
1306
- onRecord: (record) => {
1307
- // { method, path, statusCode, durationMs }
1308
- console.log(record);
1309
- },
1310
- });
1311
-
1312
- app.addHook('onRequest', metrics.onRequest);
1313
- app.addHook('onResponse', metrics.onResponse);
1314
-
1315
- app.get('/health', (ctx) => {
1316
- return ctx.res.json({ status: 'ok' });
1317
- });
1318
-
1319
- app.listen({ port: 3000 });
1320
- ```
1321
-
1322
- ### Guia: Testes com inject + Vitest
1323
-
1324
- ```js
1325
- import { describe, it, expect } from 'vitest';
1326
- import { zent } from 'zentjs';
1327
-
1328
- describe('API', () => {
1329
- it('should return hello world', async () => {
1330
- const app = zent();
1331
-
1332
- app.get('/', (ctx) => {
1333
- return ctx.res.json({ hello: 'world' });
1334
- });
1335
-
1336
- const response = await app.inject({
1337
- method: 'GET',
1338
- url: '/',
1339
- });
1340
-
1341
- expect(response.statusCode).toBe(200);
1342
- expect(response.json()).toEqual({ hello: 'world' });
1343
- });
1344
- });
1345
- ```
1346
-
1347
- ---
1348
-
1349
- ## Roadmap de Implementação
1350
-
1351
- A implementação segue uma ordem lógica de dependências:
1352
-
1353
- ### Fase 1 — Fundação (Core)
1354
-
1355
- | # | Módulo | Prioridade | Dependência | Descrição |
1356
- | --- | -------------- | ---------- | ----------- | ---------------------------------- |
1357
- | 1 | `HttpError` | Alta | Nenhuma | Classes de erro HTTP |
1358
- | 2 | `ZentRequest` | Alta | Nenhuma | Wrapper do IncomingMessage |
1359
- | 3 | `ZentResponse` | Alta | Nenhuma | Wrapper do ServerResponse |
1360
- | 4 | `Context` | Alta | 2, 3 | Objeto de contexto (req + res) |
1361
- | 5 | `RadixTree` | Alta | Nenhuma | Estrutura de dados para roteamento |
1362
- | 6 | `Router` | Alta | 5 | API pública do router |
1363
-
1364
- ### Fase 2 — Pipeline
1365
-
1366
- | # | Módulo | Prioridade | Dependência | Descrição |
1367
- | --- | -------------- | ---------- | ----------- | --------------------------------- |
1368
- | 7 | `Pipeline` | Alta | 4 | Executor de middlewares (compose) |
1369
- | 8 | `Lifecycle` | Alta | 7 | Gerenciador de hooks |
1370
- | 9 | `ErrorHandler` | Alta | 1, 4 | Handler global de erros |
1371
-
1372
- ### Fase 3 — Aplicação
1373
-
1374
- | # | Módulo | Prioridade | Dependência | Descrição |
1375
- | --- | ------------- | ---------- | ------------- | ------------------------------------------ |
1376
- | 10 | `HttpServer` | Alta | 4, 6, 7, 8, 9 | Servidor HTTP + request dispatch |
1377
- | 11 | `Application` | Alta | 10 | Classe principal Zent |
1378
- | 12 | `inject()` | Média | 10, 11 | Light-weight request injection para testes |
1379
-
1380
- ### Fase 4 — Plugins e Extras
1381
-
1382
- | # | Módulo | Prioridade | Dependência | Descrição |
1383
- | --- | --------------- | ---------- | ----------- | ----------------------------------------------- |
1384
- | 13 | `PluginManager` | Média | 11 | Sistema de registro e encapsulamento de plugins |
1385
- | 14 | `bodyParser` | Média | Nenhuma | Middleware built-in para parsing de body |
1386
- | 15 | `cors` | Baixa | Nenhuma | Middleware built-in para CORS |
1387
-
1388
- ### Fase 5 — Polish
1389
-
1390
- | # | Módulo | Prioridade | Descrição |
1391
- | --- | -------------------- | ---------- | ----------------------------------- |
1392
- | 16 | Testes de integração | Alta | Testes end-to-end HTTP reais |
1393
- | 17 | JSDoc + tipos | Média | Documentação inline e type hints |
1394
- | 18 | Exemplos | Baixa | Exemplos executáveis em `examples/` |
1395
-
1396
- Status atual: fases 1–5 implementadas.
1397
-
1398
- Métricas atuais (03/03/2026):
1399
-
1400
- - Testes: `322/322` passando (`14` arquivos de teste)
1401
- - Cobertura geral: `99.62%` statements · `96.79%` branches · `100%` functions · `99.61%` lines
1402
-
1403
- ### Novo ciclo (pós-fases iniciais)
1404
-
1405
- Com as fases 1–5 concluídas, o próximo ciclo passa a ser guiado por **entregas pequenas e testáveis**, sempre com:
1406
-
1407
- 1. Escopo fechado por fase
1408
- 2. Testes unitários obrigatórios
1409
- 3. Testes de integração quando houver impacto no fluxo HTTP real, plugins ou encapsulamento
1410
-
1411
- ### Fase 6 — Paridade API x Runtime
1412
-
1413
- Objetivo: alinhar comportamento real com a API pública/documentação.
1414
-
1415
- | Item | Escopo |
1416
- | ---- | -------------------------------------------------------------------------------- |
1417
- | 6.1 | Executar `onSend` no dispatch da requisição (incluindo transformação de payload) |
1418
- | 6.2 | Suporte completo a `app.use('/prefix', middleware)` |
1419
- | 6.3 | Implementar `setNotFoundHandler()` na aplicação |
1420
- | 6.4 | Garantir hooks de rota além de `preHandler` conforme contrato público |
1421
-
1422
- **Testes da fase 6:**
1423
-
1424
- - Unitários para `application`, `lifecycle`, `router`
1425
- - Integração para validar `onSend`, middleware com prefixo e 404 customizado
1426
-
1427
- ### Fase 7 — Encapsulamento real de plugins
1428
-
1429
- Objetivo: garantir isolamento entre escopos pai/filho/irmãos.
1430
-
1431
- | Item | Escopo |
1432
- | ---- | ------------------------------------------------------------- |
1433
- | 7.1 | Isolar decorators por escopo de plugin |
1434
- | 7.2 | Isolar hooks e middlewares com herança controlada pai → filho |
1435
- | 7.3 | Validar que plugins irmãos não compartilham estado interno |
1436
- | 7.4 | Fortalecer contratos de registro/carregamento em cascata |
1437
-
1438
- **Testes da fase 7:**
1439
-
1440
- - Unitários para `plugin-manager` e criação de escopo
1441
- - Integração com plugins aninhados e cenários de não-vazamento
1442
-
1443
- ### Fase 8 — Robustez de HTTP/erros
1444
-
1445
- Objetivo: endurecer comportamento em cenários de borda.
1446
-
1447
- | Item | Escopo |
1448
- | ---- | ------------------------------------------------------------------ |
1449
- | 8.1 | Revisar fluxo de erro para evitar respostas duplicadas |
1450
- | 8.2 | Consolidar resposta de parse inválido de body (ex.: JSON inválido) |
1451
- | 8.3 | Melhorar consistência entre `inject()` e servidor real |
1452
- | 8.4 | Cobrir cenários limite de headers/body/status |
1453
-
1454
- **Testes da fase 8:**
1455
-
1456
- - Unitários focados em `error-handler`, `body-parser`, `response`
1457
- - Integração para falhas reais de parsing e serialização
1458
-
1459
- ### Fase 9 — Qualidade de documentação e DX
1460
-
1461
- Objetivo: manter documentação e uso prático sempre sincronizados.
1462
-
1463
- | Item | Escopo |
1464
- | ---- | --------------------------------------------------------------- |
1465
- | 9.1 | Corrigir lint de markdown (fenced blocks com linguagem) |
1466
- | 9.2 | Revisar README para refletir somente comportamento implementado |
1467
- | 9.3 | Padronizar exemplos para cobrir APIs críticas do ciclo 6–8 |
1468
-
1469
- **Testes/validações da fase 9:**
1470
-
1471
- - `npm run lint`
1472
- - Execução dos exemplos e smoke tests de rotas principais
1473
-
1474
- ### Fase 10 — Performance e observabilidade mínima
1475
-
1476
- Objetivo: preparar baseline para evolução com segurança.
1477
-
1478
- | Item | Escopo |
1479
- | ---- | ---------------------------------------------------------- |
1480
- | 10.1 | Benchmark básico de roteamento e pipeline |
1481
- | 10.2 | Métricas mínimas por requisição (tempo e status) via hooks |
1482
- | 10.3 | Cenários de carga leve para regressão de performance |
1483
-
1484
- **Testes/validações da fase 10:**
1485
-
1486
- - Benchmarks reproduzíveis versionados no repositório
1487
- - Regressão comparativa entre versões do core
1488
-
1489
- ---
1490
-
1491
- ## Decisões Técnicas (ADRs)
1492
-
1493
- ### ADR-001: ESM Only
1494
-
1495
- **Contexto:** Node.js suporta CommonJS e ESM.
1496
- **Decisão:** Usar exclusivamente ESM (`.mjs` ou `"type": "module"`).
1497
- **Motivo:** ESM é o padrão do futuro, permite top-level await, tree-shaking nativo, e importações estáticas para analysis.
1498
-
1499
- ### ADR-002: Zero Dependências de Runtime
1500
-
1501
- **Contexto:** Frameworks como Express dependem de dezenas de pacotes.
1502
- **Decisão:** Nenhuma dependência no `dependencies` do package.json.
1503
- **Motivo:** Reduz supply chain risk, tamanho do `node_modules`, e garante total controle sobre o código.
1504
-
1505
- ### ADR-003: Radix Tree para Roteamento
1506
-
1507
- **Contexto:** Express usa array linear O(n), o que não escala.
1508
- **Decisão:** Implementar Radix Tree customizada.
1509
- **Motivo:** Lookup em O(k) onde k = comprimento do path. Performance independente do número de rotas.
1510
-
1511
- ### ADR-004: Context Object (ctx)
1512
-
1513
- **Contexto:** Express passa `(req, res, next)`, Fastify passa `(request, reply)`.
1514
- **Decisão:** Usar um único objeto `ctx` que contém `req`, `res`, `state` e `app`.
1515
- **Motivo:** Simplifica a signature dos handlers, facilita extensão via `state`, e permite tipagem mais clara.
1516
-
1517
- ### ADR-005: Async-first
1518
-
1519
- **Contexto:** Express não trata promises automaticamente.
1520
- **Decisão:** Todos os handlers e middlewares são tratados como async por padrão.
1521
- **Motivo:** Elimina a necessidade de `try/catch` manual e `next(err)`. Erros em async handlers são capturados automaticamente.
1522
-
1523
- ### ADR-006: Plugin Encapsulation
1524
-
1525
- **Contexto:** Em Express, todos os middlewares são globais.
1526
- **Decisão:** Plugins criam escopos encapsulados (inspirado no Fastify).
1527
- **Motivo:** Evita efeitos colaterais entre módulos, facilita composição de aplicações grandes.
1528
-
1529
- ### ADR-007: Lazy Body Parsing
1530
-
1531
- **Contexto:** Fastify parseia body apenas quando necessário.
1532
- **Decisão:** Body não é parseado automaticamente — exige middleware explícito.
1533
- **Motivo:** Zero overhead para rotas que não precisam de body (GET, DELETE, health checks).
1534
-
1535
- ---
1536
-
1537
- ## Contribuição
1538
-
1539
- Este projeto valida commits com **Conventional Commits** via `commitlint`.
1540
-
1541
- Formato esperado:
1542
-
1543
- ```text
1544
- tipo(escopo-opcional): descrição
40
+ ```bash
41
+ npm test
42
+ npm run lint
43
+ npm run test:coverage
1545
44
  ```
1546
45
 
1547
- Tipos aceitos pelo projeto:
1548
-
1549
- - `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test`
46
+ ## Documentação completa
1550
47
 
1551
- Exemplos válidos:
48
+ A documentação detalhada foi separada na pasta `docs/`:
1552
49
 
1553
- - `feat(router): adiciona suporte a wildcard`
1554
- - `fix(cors): corrige headers do preflight`
1555
- - `docs: atualiza README com badges`
1556
- - `test: cobre fluxo de plugins`
1557
- - `chore: ajusta workflow de release`
50
+ - [Hub da documentação](docs/README.md)
51
+ - [Arquitetura](docs/architecture.md)
52
+ - [API pública](docs/api.md)
53
+ - [Guias práticos](docs/guides.md)
54
+ - [Roadmap e ADRs](docs/roadmap.md)
55
+ - [Checklist de evolução da documentação](docs/DOCUMENTATION_TODO.md)
1558
56
 
1559
- Para mudanças incompatíveis, use `!` após o tipo/escopo:
57
+ ## Exemplos executáveis
1560
58
 
1561
- - `feat!: remove API legada de register`
1562
-
1563
- ou informe no corpo do commit:
1564
-
1565
- ```text
1566
- BREAKING CHANGE: descrição da mudança incompatível
59
+ ```bash
60
+ node examples/hello-world.mjs
61
+ node examples/rest-api.mjs
62
+ node examples/with-plugins.mjs
1567
63
  ```
1568
64
 
1569
- ---
1570
-
1571
- ## Referências
1572
-
1573
- - [Node.js HTTP Module](https://nodejs.org/api/http.html)
1574
- - [Express.js Source Code](https://github.com/expressjs/express)
1575
- - [Fastify Architecture](https://fastify.dev/docs/latest/Reference/Architecture/)
1576
- - [Radix Tree (Wikipedia)](https://en.wikipedia.org/wiki/Radix_tree)
1577
- - [find-my-way (Fastify Router)](https://github.com/delvedor/find-my-way)
1578
- - [Koa Compose (Middleware)](https://github.com/koajs/compose)
1579
-
1580
- ---
1581
-
1582
65
  ## Licença
1583
66
 
1584
67
  [BSD-3-Clause](LICENSE)