sashiko 0.2.0 → 0.3.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.
Files changed (51) hide show
  1. package/README.md +97 -15
  2. package/dist/core/app.d.ts +35 -3
  3. package/dist/core/app.d.ts.map +1 -1
  4. package/dist/core/app.js +70 -5
  5. package/dist/core/app.js.map +1 -1
  6. package/dist/core/context.d.ts +60 -8
  7. package/dist/core/context.d.ts.map +1 -1
  8. package/dist/core/context.js +77 -16
  9. package/dist/core/context.js.map +1 -1
  10. package/dist/core/index.d.ts +4 -2
  11. package/dist/core/index.d.ts.map +1 -1
  12. package/dist/core/index.js +1 -1
  13. package/dist/core/index.js.map +1 -1
  14. package/dist/core/infer.d.ts +9 -1
  15. package/dist/core/infer.d.ts.map +1 -1
  16. package/dist/core/metadata.d.ts +17 -1
  17. package/dist/core/metadata.d.ts.map +1 -1
  18. package/dist/core/metadata.js +6 -0
  19. package/dist/core/metadata.js.map +1 -1
  20. package/dist/core/response.d.ts +22 -8
  21. package/dist/core/response.d.ts.map +1 -1
  22. package/dist/core/response.js +93 -15
  23. package/dist/core/response.js.map +1 -1
  24. package/dist/core/shutdown.d.ts +36 -0
  25. package/dist/core/shutdown.d.ts.map +1 -0
  26. package/dist/core/shutdown.js +53 -0
  27. package/dist/core/shutdown.js.map +1 -0
  28. package/dist/middlewares/index.d.ts +1 -1
  29. package/dist/middlewares/index.d.ts.map +1 -1
  30. package/dist/middlewares/index.js.map +1 -1
  31. package/dist/middlewares/security-headers.d.ts +4 -2
  32. package/dist/middlewares/security-headers.d.ts.map +1 -1
  33. package/dist/middlewares/security-headers.js +10 -2
  34. package/dist/middlewares/security-headers.js.map +1 -1
  35. package/dist/schema/validation.d.ts +11 -0
  36. package/dist/schema/validation.d.ts.map +1 -1
  37. package/dist/schema/validation.js +18 -0
  38. package/dist/schema/validation.js.map +1 -1
  39. package/dist/utils/content-type.d.ts +8 -0
  40. package/dist/utils/content-type.d.ts.map +1 -1
  41. package/dist/utils/content-type.js +42 -0
  42. package/dist/utils/content-type.js.map +1 -1
  43. package/dist/utils/index.d.ts +2 -1
  44. package/dist/utils/index.d.ts.map +1 -1
  45. package/dist/utils/index.js +2 -1
  46. package/dist/utils/index.js.map +1 -1
  47. package/dist/utils/request-target.d.ts +19 -0
  48. package/dist/utils/request-target.d.ts.map +1 -0
  49. package/dist/utils/request-target.js +49 -0
  50. package/dist/utils/request-target.js.map +1 -0
  51. package/package.json +1 -1
@@ -18,9 +18,11 @@ export interface SecurityHeadersOptions {
18
18
  preload?: boolean;
19
19
  };
20
20
  }
21
+ /** `'api'`: restritivo, para APIs (padrão). `'web'`: permite recursos do próprio site (F-31). */
22
+ export type SecurityPreset = 'api' | 'web';
21
23
  /**
22
- * Headers de segurança padrão (S-04) e proteções contra XSS em APIs JSON (S-02).
24
+ * Headers de segurança padrão (S-04) e proteções contra XSS (S-02).
23
25
  * Retorna uma função que aplica os headers pré-calculados na resposta.
24
26
  */
25
- export declare function buildSecurityHeaders(opts?: SecurityHeadersOptions): (res: ServerResponse) => void;
27
+ export declare function buildSecurityHeaders(input?: SecurityHeadersOptions | SecurityPreset): (res: ServerResponse) => void;
26
28
  //# sourceMappingURL=security-headers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../../src/middlewares/security-headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,MAAM,WAAW,sBAAsB;IACrC,uGAAuG;IACvG,qBAAqB,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACvC,6BAA6B;IAC7B,cAAc,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAChC,sBAAsB;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,KAAK,CAAC;IAC7C,6BAA6B;IAC7B,yBAAyB,CAAC,EAAE,aAAa,GAAG,WAAW,GAAG,cAAc,GAAG,KAAK,CAAC;IACjF;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACxG;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,sBAA2B,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CA2BrG"}
1
+ {"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../../src/middlewares/security-headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,MAAM,WAAW,sBAAsB;IACrC,uGAAuG;IACvG,qBAAqB,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACvC,6BAA6B;IAC7B,cAAc,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAChC,sBAAsB;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,KAAK,CAAC;IAC7C,6BAA6B;IAC7B,yBAAyB,CAAC,EAAE,aAAa,GAAG,WAAW,GAAG,cAAc,GAAG,KAAK,CAAC;IACjF;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACxG;AAED,iGAAiG;AACjG,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,KAAK,CAAC;AAU3C;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,GAAE,sBAAsB,GAAG,cAAmB,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CA4BvH"}
@@ -1,8 +1,16 @@
1
+ const PRESETS = {
2
+ api: {},
3
+ web: {
4
+ contentSecurityPolicy: "default-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'",
5
+ frameOptions: 'SAMEORIGIN',
6
+ },
7
+ };
1
8
  /**
2
- * Headers de segurança padrão (S-04) e proteções contra XSS em APIs JSON (S-02).
9
+ * Headers de segurança padrão (S-04) e proteções contra XSS (S-02).
3
10
  * Retorna uma função que aplica os headers pré-calculados na resposta.
4
11
  */
5
- export function buildSecurityHeaders(opts = {}) {
12
+ export function buildSecurityHeaders(input = {}) {
13
+ const opts = typeof input === 'string' ? PRESETS[input] : input;
6
14
  const headers = [['X-Content-Type-Options', 'nosniff']];
7
15
  const csp = opts.contentSecurityPolicy ?? "default-src 'none'; frame-ancestors 'none'";
8
16
  if (csp)
@@ -1 +1 @@
1
- {"version":3,"file":"security-headers.js","sourceRoot":"","sources":["../../src/middlewares/security-headers.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAI,GAA2B,EAAE;IACpE,MAAM,OAAO,GAAuB,CAAC,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC,CAAC;IAE5E,MAAM,GAAG,GAAG,IAAI,CAAC,qBAAqB,IAAI,4CAA4C,CAAC;IACvF,IAAI,GAAG;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC,CAAC;IAExD,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,IAAI,aAAa,CAAC;IACtD,IAAI,QAAQ;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE1D,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,IAAI,MAAM,CAAC;IAC1C,IAAI,KAAK;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC,CAAC;IAEpD,MAAM,IAAI,GAAG,IAAI,CAAC,yBAAyB,IAAI,aAAa,CAAC;IAC7D,IAAI,IAAI;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,8BAA8B,EAAE,IAAI,CAAC,CAAC,CAAC;IAE/D,MAAM,IAAI,GAAG,IAAI,CAAC,uBAAuB,CAAC;IAC1C,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,GAAG,GAAG,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACjF,IAAI,KAAK,GAAG,WAAW,GAAG,CAAC,MAAM,EAAE,CAAC;QACpC,IAAI,GAAG,CAAC,iBAAiB;YAAE,KAAK,IAAI,qBAAqB,CAAC;QAC1D,IAAI,SAAS,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO;YAAE,KAAK,IAAI,WAAW,CAAC;QAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,EAAE;QACb,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,OAAO;YAAE,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAClE,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"security-headers.js","sourceRoot":"","sources":["../../src/middlewares/security-headers.ts"],"names":[],"mappings":"AAqBA,MAAM,OAAO,GAAmD;IAC9D,GAAG,EAAE,EAAE;IACP,GAAG,EAAE;QACH,qBAAqB,EAAE,iFAAiF;QACxG,YAAY,EAAE,YAAY;KAC3B;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAK,GAA4C,EAAE;IACtF,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAChE,MAAM,OAAO,GAAuB,CAAC,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC,CAAC;IAE5E,MAAM,GAAG,GAAG,IAAI,CAAC,qBAAqB,IAAI,4CAA4C,CAAC;IACvF,IAAI,GAAG;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC,CAAC;IAExD,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,IAAI,aAAa,CAAC;IACtD,IAAI,QAAQ;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE1D,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,IAAI,MAAM,CAAC;IAC1C,IAAI,KAAK;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC,CAAC;IAEpD,MAAM,IAAI,GAAG,IAAI,CAAC,yBAAyB,IAAI,aAAa,CAAC;IAC7D,IAAI,IAAI;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,8BAA8B,EAAE,IAAI,CAAC,CAAC,CAAC;IAE/D,MAAM,IAAI,GAAG,IAAI,CAAC,uBAAuB,CAAC;IAC1C,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,GAAG,GAAG,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACjF,IAAI,KAAK,GAAG,WAAW,GAAG,CAAC,MAAM,EAAE,CAAC;QACpC,IAAI,GAAG,CAAC,iBAAiB;YAAE,KAAK,IAAI,qBAAqB,CAAC;QAC1D,IAAI,SAAS,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO;YAAE,KAAK,IAAI,WAAW,CAAC;QAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,EAAE;QACb,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,OAAO;YAAE,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAClE,CAAC,CAAC;AACJ,CAAC"}
@@ -5,6 +5,17 @@ export interface ValidationIssue {
5
5
  path: string;
6
6
  message: string;
7
7
  }
8
+ /**
9
+ * Valida `data` contra `schema` sobre uma cópia, removendo campos não declarados.
10
+ * Retorna a cópia filtrada, ou os problemas encontrados.
11
+ */
12
+ export declare function stripToSchema(schema: object, data: unknown): {
13
+ ok: true;
14
+ value: unknown;
15
+ } | {
16
+ ok: false;
17
+ issues: ValidationIssue[];
18
+ };
8
19
  /** Valida `data` contra `schema`. Retorna `null` se válido, ou a lista de problemas (B-02). */
9
20
  export declare function validate(schema: object, data: unknown, target: ValidationTarget): ValidationIssue[] | null;
10
21
  export declare function formatErrors(errors: ErrorObject[]): ValidationIssue[];
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/schema/validation.ts"],"names":[],"mappings":"AAAA,OAAkB,EAAE,KAAK,WAAW,EAAyB,MAAM,KAAK,CAAC;AAYzE,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEvE,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAmCD,+FAA+F;AAC/F,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,GAAG,eAAe,EAAE,GAAG,IAAI,CAG1G;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,eAAe,EAAE,CAKrE"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/schema/validation.ts"],"names":[],"mappings":"AAAA,OAAkB,EAAE,KAAK,WAAW,EAAyB,MAAM,KAAK,CAAC;AAYzE,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEvE,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAyBD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,EAAE,CAAA;CAAE,CAQpI;AAkBD,+FAA+F;AAC/F,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,GAAG,eAAe,EAAE,GAAG,IAAI,CAG1G;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,eAAe,EAAE,CAKrE"}
@@ -20,6 +20,24 @@ function createAjv(coerceTypes) {
20
20
  // 'array' também converte valor único em array quando o schema pede array (`?tag=a` → `['a']`, B-07)
21
21
  const coercing = createAjv('array');
22
22
  const strict = createAjv(false);
23
+ // F-31: para respostas em formatos não JSON, remove TODOS os campos não declarados (como o
24
+ // fast-json-stringify faz no JSON) antes do serializador, sem alterar o objeto do handler.
25
+ const stripper = new Ajv({ allErrors: true, coerceTypes: false, useDefaults: false, removeAdditional: 'all' });
26
+ addFormats(stripper);
27
+ const stripCache = new WeakMap();
28
+ /**
29
+ * Valida `data` contra `schema` sobre uma cópia, removendo campos não declarados.
30
+ * Retorna a cópia filtrada, ou os problemas encontrados.
31
+ */
32
+ export function stripToSchema(schema, data) {
33
+ let fn = stripCache.get(schema);
34
+ if (!fn) {
35
+ fn = stripper.compile(schema);
36
+ stripCache.set(schema, fn);
37
+ }
38
+ const copy = data === undefined ? undefined : JSON.parse(JSON.stringify(data));
39
+ return fn(copy) ? { ok: true, value: copy } : { ok: false, issues: formatErrors(fn.errors ?? []) };
40
+ }
23
41
  const caches = new Map([
24
42
  [coercing, new WeakMap()],
25
43
  [strict, new WeakMap()],
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/schema/validation.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,EAAE,EAA2C,MAAM,KAAK,CAAC;AACzE,OAAO,gBAAgB,MAAM,aAAa,CAAC;AAE3C,0EAA0E;AAC1E,0EAA0E;AAC1E,qEAAqE;AACrE,MAAM,GAAG,GAAG,CAAE,SAAiB,CAAC,OAAO,IAAI,SAAS,CAAiC,CAAC;AACtF,MAAM,UAAU,GAAG,CAAE,gBAAwB,CAAC,OAAO,IAAI,gBAAgB,CAGhE,CAAC;AAUV,SAAS,SAAS,CAAC,WAA8B;IAC/C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAC;IAClG,UAAU,CAAC,GAAG,CAAC,CAAC;IAChB,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,qGAAqG;AACrG,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;AACpC,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;AAEhC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC;IACrB,CAAC,QAAQ,EAAE,IAAI,OAAO,EAA4B,CAAC;IACnD,CAAC,MAAM,EAAE,IAAI,OAAO,EAA4B,CAAC;CAClD,CAAC,CAAC;AAEH,SAAS,OAAO,CAAC,MAAc,EAAE,MAAwB;IACvD,MAAM,GAAG,GAAG,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;IAC/B,IAAI,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACzB,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,QAAQ,CAAC,MAAc,EAAE,IAAa,EAAE,MAAwB;IAC9E,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACtB,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,IAAK,CAAC,CAAC,MAAsC,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChH,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,YAAY,GAAG,OAAO,EAAE,IAAI,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,SAAS,EAAE,CAAC;IACzF,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/schema/validation.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,EAAE,EAA2C,MAAM,KAAK,CAAC;AACzE,OAAO,gBAAgB,MAAM,aAAa,CAAC;AAE3C,0EAA0E;AAC1E,0EAA0E;AAC1E,qEAAqE;AACrE,MAAM,GAAG,GAAG,CAAE,SAAiB,CAAC,OAAO,IAAI,SAAS,CAAiC,CAAC;AACtF,MAAM,UAAU,GAAG,CAAE,gBAAwB,CAAC,OAAO,IAAI,gBAAgB,CAGhE,CAAC;AAUV,SAAS,SAAS,CAAC,WAA8B;IAC/C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAC;IAClG,UAAU,CAAC,GAAG,CAAC,CAAC;IAChB,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,qGAAqG;AACrG,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;AACpC,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;AAEhC,2FAA2F;AAC3F,2FAA2F;AAC3F,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAC;AAC/G,UAAU,CAAC,QAAQ,CAAC,CAAC;AACrB,MAAM,UAAU,GAAG,IAAI,OAAO,EAA4B,CAAC;AAE3D;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,IAAa;IACzD,IAAI,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC9B,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC7B,CAAC;IACD,MAAM,IAAI,GAAY,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IACxF,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;AACrG,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC;IACrB,CAAC,QAAQ,EAAE,IAAI,OAAO,EAA4B,CAAC;IACnD,CAAC,MAAM,EAAE,IAAI,OAAO,EAA4B,CAAC;CAClD,CAAC,CAAC;AAEH,SAAS,OAAO,CAAC,MAAc,EAAE,MAAwB;IACvD,MAAM,GAAG,GAAG,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;IAC/B,IAAI,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACzB,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,QAAQ,CAAC,MAAc,EAAE,IAAa,EAAE,MAAwB;IAC9E,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACtB,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,IAAK,CAAC,CAAC,MAAsC,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChH,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,YAAY,GAAG,OAAO,EAAE,IAAI,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,SAAS,EAAE,CAAC;IACzF,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -1,5 +1,13 @@
1
1
  /** Media type sem parâmetros e em minúsculas: `"Application/JSON; charset=utf-8"` → `"application/json"`. */
2
2
  export declare function mediaType(header: string | undefined): string;
3
+ /**
4
+ * Escolhe, entre os tipos `available` (em ordem de preferência do servidor), o melhor para o
5
+ * header `Accept` (F-31). Respeita q-values e curingas (`text/*`, `*\/*`). Sem `Accept`,
6
+ * devolve o primeiro. Retorna `undefined` quando nenhum é aceitável (→ 406).
7
+ */
8
+ export declare function negotiate(accept: string | undefined, available: readonly string[]): string | undefined;
9
+ /** Acrescenta `charset=utf-8` a tipos textuais sem charset. */
10
+ export declare function withCharset(type: string): string;
3
11
  /** `application/json` ou `application/*+json`, com parâmetros opcionais (B-11). */
4
12
  export declare function isJsonContentType(header: string | undefined): boolean;
5
13
  //# sourceMappingURL=content-type.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"content-type.d.ts","sourceRoot":"","sources":["../../src/utils/content-type.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAE5D;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAGrE"}
1
+ {"version":3,"file":"content-type.d.ts","sourceRoot":"","sources":["../../src/utils/content-type.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAiBtG;AAYD,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAGrE"}
@@ -2,6 +2,48 @@
2
2
  export function mediaType(header) {
3
3
  return header ? header.split(';', 1)[0].trim().toLowerCase() : '';
4
4
  }
5
+ /**
6
+ * Escolhe, entre os tipos `available` (em ordem de preferência do servidor), o melhor para o
7
+ * header `Accept` (F-31). Respeita q-values e curingas (`text/*`, `*\/*`). Sem `Accept`,
8
+ * devolve o primeiro. Retorna `undefined` quando nenhum é aceitável (→ 406).
9
+ */
10
+ export function negotiate(accept, available) {
11
+ if (!accept || !accept.trim())
12
+ return available[0];
13
+ const ranges = accept
14
+ .split(',')
15
+ .map((part, index) => {
16
+ const [range = '', ...params] = part.trim().toLowerCase().split(';');
17
+ const q = Number(params.map((p) => p.trim()).find((p) => p.startsWith('q='))?.slice(2) ?? '1');
18
+ return { range: range.trim(), q: Number.isFinite(q) ? q : 1, index };
19
+ })
20
+ .filter((r) => r.range && r.q > 0)
21
+ .sort((a, b) => b.q - a.q || specificity(b.range) - specificity(a.range) || a.index - b.index);
22
+ for (const { range } of ranges) {
23
+ const match = available.find((type) => matches(range, mediaType(type)));
24
+ if (match)
25
+ return match;
26
+ }
27
+ return undefined;
28
+ }
29
+ function specificity(range) {
30
+ if (range === '*/*')
31
+ return 0;
32
+ return range.endsWith('/*') ? 1 : 2;
33
+ }
34
+ function matches(range, type) {
35
+ if (range === '*/*' || range === type)
36
+ return true;
37
+ return range.endsWith('/*') && type.startsWith(range.slice(0, -1));
38
+ }
39
+ /** Acrescenta `charset=utf-8` a tipos textuais sem charset. */
40
+ export function withCharset(type) {
41
+ if (/;\s*charset=/i.test(type))
42
+ return type;
43
+ const base = mediaType(type);
44
+ const textual = base.startsWith('text/') || isJsonContentType(base) || /\+xml$|\/xml$|yaml$/.test(base);
45
+ return textual ? `${type}; charset=utf-8` : type;
46
+ }
5
47
  /** `application/json` ou `application/*+json`, com parâmetros opcionais (B-11). */
6
48
  export function isJsonContentType(header) {
7
49
  const type = mediaType(header);
@@ -1 +1 @@
1
- {"version":3,"file":"content-type.js","sourceRoot":"","sources":["../../src/utils/content-type.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,MAAM,UAAU,SAAS,CAAC,MAA0B;IAClD,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACrE,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,iBAAiB,CAAC,MAA0B;IAC1D,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAC/B,OAAO,IAAI,KAAK,kBAAkB,IAAI,wCAAwC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5F,CAAC"}
1
+ {"version":3,"file":"content-type.js","sourceRoot":"","sources":["../../src/utils/content-type.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- export { isJsonContentType, mediaType } from './content-type.ts';
1
+ export { isJsonContentType, mediaType, negotiate, withCharset } from './content-type.ts';
2
2
  export { joinPaths } from './path.ts';
3
3
  export { DEFAULT_MAX_QUERY_KEYS, parseQuery, type ParsedQuery } from './query.ts';
4
+ export { hostnameOf, isValidHost, parseRequestTarget, pickForwarded } from './request-target.ts';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACzF,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
@@ -1,4 +1,5 @@
1
- export { isJsonContentType, mediaType } from './content-type.js';
1
+ export { isJsonContentType, mediaType, negotiate, withCharset } from './content-type.js';
2
2
  export { joinPaths } from './path.js';
3
3
  export { DEFAULT_MAX_QUERY_KEYS, parseQuery } from './query.js';
4
+ export { hostnameOf, isValidHost, parseRequestTarget, pickForwarded } from './request-target.js';
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAoB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACzF,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAoB,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /** Valida um valor de header `Host`/`X-Forwarded-Host` (S-09). */
2
+ export declare function isValidHost(value: string | undefined): value is string;
3
+ /** `host` sem a porta: `api.exemplo.com:8080` → `api.exemplo.com`, `[::1]:3000` → `[::1]`. */
4
+ export declare function hostnameOf(host: string): string;
5
+ /**
6
+ * Extrai path e query do request-target **sem** deixar o cliente escolher o host (S-09).
7
+ *
8
+ * - `//evil.com/x` continua sendo o path `//evil.com/x`; não vira uma URL relativa ao protocolo.
9
+ * - `http://outro/x` (absolute-form, usado por proxies) só tem o path e a query aproveitados.
10
+ * - Segmentos `.` e `..` são normalizados como no WHATWG URL.
11
+ */
12
+ export declare function parseRequestTarget(raw: string | undefined): URL;
13
+ /**
14
+ * Escolhe o valor de um header encaminhado (`X-Forwarded-For`, `-Host`, `-Proto`) segundo `trustProxy`:
15
+ * - `true`: confia em toda a cadeia e usa o primeiro valor (o cliente original);
16
+ * - `n` (número): confia em `n` proxies e usa o valor adicionado pelo mais externo deles.
17
+ */
18
+ export declare function pickForwarded(header: string | string[] | undefined, trust: boolean | number): string | undefined;
19
+ //# sourceMappingURL=request-target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-target.d.ts","sourceRoot":"","sources":["../../src/utils/request-target.ts"],"names":[],"mappings":"AAGA,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,IAAI,MAAM,CAEtE;AAED,8FAA8F;AAC9F,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,GAAG,CAa/D;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAMhH"}
@@ -0,0 +1,49 @@
1
+ /** `host[:port]`: nome (letras, dígitos, `.` e `-`) ou IPv6 entre colchetes, com porta opcional. */
2
+ const VALID_HOST = /^(?:[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?|\[[0-9A-Fa-f:.]{2,45}\])(?::\d{1,5})?$/;
3
+ /** Valida um valor de header `Host`/`X-Forwarded-Host` (S-09). */
4
+ export function isValidHost(value) {
5
+ return typeof value === 'string' && VALID_HOST.test(value);
6
+ }
7
+ /** `host` sem a porta: `api.exemplo.com:8080` → `api.exemplo.com`, `[::1]:3000` → `[::1]`. */
8
+ export function hostnameOf(host) {
9
+ return host.startsWith('[') ? host.slice(0, host.indexOf(']') + 1) : host.split(':')[0];
10
+ }
11
+ /**
12
+ * Extrai path e query do request-target **sem** deixar o cliente escolher o host (S-09).
13
+ *
14
+ * - `//evil.com/x` continua sendo o path `//evil.com/x`; não vira uma URL relativa ao protocolo.
15
+ * - `http://outro/x` (absolute-form, usado por proxies) só tem o path e a query aproveitados.
16
+ * - Segmentos `.` e `..` são normalizados como no WHATWG URL.
17
+ */
18
+ export function parseRequestTarget(raw) {
19
+ let target = raw || '/';
20
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(target)) {
21
+ try {
22
+ const abs = new URL(target);
23
+ target = abs.pathname + abs.search;
24
+ }
25
+ catch {
26
+ target = '/';
27
+ }
28
+ }
29
+ if (!target.startsWith('/'))
30
+ target = `/${target}`;
31
+ // Base fixa concatenada (e não como segundo argumento): `//x` nunca é lido como host.
32
+ return new URL(`http://localhost${target}`);
33
+ }
34
+ /**
35
+ * Escolhe o valor de um header encaminhado (`X-Forwarded-For`, `-Host`, `-Proto`) segundo `trustProxy`:
36
+ * - `true`: confia em toda a cadeia e usa o primeiro valor (o cliente original);
37
+ * - `n` (número): confia em `n` proxies e usa o valor adicionado pelo mais externo deles.
38
+ */
39
+ export function pickForwarded(header, trust) {
40
+ if (!trust || header === undefined)
41
+ return undefined;
42
+ const list = (Array.isArray(header) ? header.join(',') : header).split(',').map((v) => v.trim()).filter(Boolean);
43
+ if (list.length === 0)
44
+ return undefined;
45
+ if (trust === true)
46
+ return list[0];
47
+ return list[Math.max(0, list.length - trust)];
48
+ }
49
+ //# sourceMappingURL=request-target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-target.js","sourceRoot":"","sources":["../../src/utils/request-target.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,MAAM,UAAU,GAAG,4FAA4F,CAAC;AAEhH,kEAAkE;AAClE,MAAM,UAAU,WAAW,CAAC,KAAyB;IACnD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;AAC3F,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAuB;IACxD,IAAI,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC;IACxB,IAAI,0BAA0B,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;YAC5B,MAAM,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,GAAG,GAAG,CAAC;QACf,CAAC;IACH,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IACnD,sFAAsF;IACtF,OAAO,IAAI,GAAG,CAAC,mBAAmB,MAAM,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,MAAqC,EAAE,KAAuB;IAC1F,IAAI,CAAC,KAAK,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACrD,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjH,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;IACnC,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC;AAChD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sashiko",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Micro-framework HTTP para Node.js com decorators TC39 nativos, schema-first (TypeBox + Ajv) e defaults seguros.",
5
5
  "keywords": [
6
6
  "http",