sashiko 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +398 -2
  3. package/dist/core/app.d.ts +104 -0
  4. package/dist/core/app.d.ts.map +1 -0
  5. package/dist/core/app.js +202 -0
  6. package/dist/core/app.js.map +1 -0
  7. package/dist/core/body-parser.d.ts +4 -0
  8. package/dist/core/body-parser.d.ts.map +1 -0
  9. package/dist/core/body-parser.js +38 -0
  10. package/dist/core/body-parser.js.map +1 -0
  11. package/dist/core/compiler.d.ts +52 -0
  12. package/dist/core/compiler.d.ts.map +1 -0
  13. package/dist/core/compiler.js +74 -0
  14. package/dist/core/compiler.js.map +1 -0
  15. package/dist/core/compose.d.ts +4 -0
  16. package/dist/core/compose.d.ts.map +1 -0
  17. package/dist/core/compose.js +11 -0
  18. package/dist/core/compose.js.map +1 -0
  19. package/dist/core/context.d.ts +84 -0
  20. package/dist/core/context.d.ts.map +1 -0
  21. package/dist/core/context.js +121 -0
  22. package/dist/core/context.js.map +1 -0
  23. package/dist/core/errors.d.ts +24 -0
  24. package/dist/core/errors.d.ts.map +1 -0
  25. package/dist/core/errors.js +23 -0
  26. package/dist/core/errors.js.map +1 -0
  27. package/dist/core/guard.d.ts +10 -0
  28. package/dist/core/guard.d.ts.map +1 -0
  29. package/dist/core/guard.js +12 -0
  30. package/dist/core/guard.js.map +1 -0
  31. package/dist/core/index.d.ts +15 -0
  32. package/dist/core/index.d.ts.map +1 -0
  33. package/dist/core/index.js +8 -0
  34. package/dist/core/index.js.map +1 -0
  35. package/dist/core/infer.d.ts +36 -0
  36. package/dist/core/infer.d.ts.map +1 -0
  37. package/dist/core/infer.js +2 -0
  38. package/dist/core/infer.js.map +1 -0
  39. package/dist/core/inject.d.ts +27 -0
  40. package/dist/core/inject.d.ts.map +1 -0
  41. package/dist/core/inject.js +72 -0
  42. package/dist/core/inject.js.map +1 -0
  43. package/dist/core/logger.d.ts +28 -0
  44. package/dist/core/logger.d.ts.map +1 -0
  45. package/dist/core/logger.js +27 -0
  46. package/dist/core/logger.js.map +1 -0
  47. package/dist/core/metadata.d.ts +59 -0
  48. package/dist/core/metadata.d.ts.map +1 -0
  49. package/dist/core/metadata.js +83 -0
  50. package/dist/core/metadata.js.map +1 -0
  51. package/dist/core/mounted-group.d.ts +41 -0
  52. package/dist/core/mounted-group.d.ts.map +1 -0
  53. package/dist/core/mounted-group.js +43 -0
  54. package/dist/core/mounted-group.js.map +1 -0
  55. package/dist/core/request-validation.d.ts +11 -0
  56. package/dist/core/request-validation.d.ts.map +1 -0
  57. package/dist/core/request-validation.js +30 -0
  58. package/dist/core/request-validation.js.map +1 -0
  59. package/dist/core/response.d.ts +16 -0
  60. package/dist/core/response.d.ts.map +1 -0
  61. package/dist/core/response.js +49 -0
  62. package/dist/core/response.js.map +1 -0
  63. package/dist/core/route-table.d.ts +29 -0
  64. package/dist/core/route-table.d.ts.map +1 -0
  65. package/dist/core/route-table.js +41 -0
  66. package/dist/core/route-table.js.map +1 -0
  67. package/dist/decorators/http.d.ts +16 -0
  68. package/dist/decorators/http.d.ts.map +1 -0
  69. package/dist/decorators/http.js +27 -0
  70. package/dist/decorators/http.js.map +1 -0
  71. package/dist/decorators/index.d.ts +6 -0
  72. package/dist/decorators/index.d.ts.map +1 -0
  73. package/dist/decorators/index.js +6 -0
  74. package/dist/decorators/index.js.map +1 -0
  75. package/dist/decorators/log.d.ts +10 -0
  76. package/dist/decorators/log.d.ts.map +1 -0
  77. package/dist/decorators/log.js +32 -0
  78. package/dist/decorators/log.js.map +1 -0
  79. package/dist/decorators/middlewares.d.ts +28 -0
  80. package/dist/decorators/middlewares.d.ts.map +1 -0
  81. package/dist/decorators/middlewares.js +40 -0
  82. package/dist/decorators/middlewares.js.map +1 -0
  83. package/dist/decorators/router.d.ts +12 -0
  84. package/dist/decorators/router.d.ts.map +1 -0
  85. package/dist/decorators/router.js +19 -0
  86. package/dist/decorators/router.js.map +1 -0
  87. package/dist/decorators/schema.d.ts +8 -0
  88. package/dist/decorators/schema.d.ts.map +1 -0
  89. package/dist/decorators/schema.js +13 -0
  90. package/dist/decorators/schema.js.map +1 -0
  91. package/dist/index.d.ts +5 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +7 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/middlewares/cors.d.ts +24 -0
  96. package/dist/middlewares/cors.d.ts.map +1 -0
  97. package/dist/middlewares/cors.js +63 -0
  98. package/dist/middlewares/cors.js.map +1 -0
  99. package/dist/middlewares/index.d.ts +4 -0
  100. package/dist/middlewares/index.d.ts.map +1 -0
  101. package/dist/middlewares/index.js +4 -0
  102. package/dist/middlewares/index.js.map +1 -0
  103. package/dist/middlewares/rate-limit.d.ts +35 -0
  104. package/dist/middlewares/rate-limit.d.ts.map +1 -0
  105. package/dist/middlewares/rate-limit.js +45 -0
  106. package/dist/middlewares/rate-limit.js.map +1 -0
  107. package/dist/middlewares/security-headers.d.ts +26 -0
  108. package/dist/middlewares/security-headers.d.ts.map +1 -0
  109. package/dist/middlewares/security-headers.js +34 -0
  110. package/dist/middlewares/security-headers.js.map +1 -0
  111. package/dist/schema/serialization.d.ts +2 -0
  112. package/dist/schema/serialization.d.ts.map +1 -0
  113. package/dist/schema/serialization.js +11 -0
  114. package/dist/schema/serialization.js.map +1 -0
  115. package/dist/schema/validation.d.ts +11 -0
  116. package/dist/schema/validation.d.ts.map +1 -0
  117. package/dist/schema/validation.js +48 -0
  118. package/dist/schema/validation.js.map +1 -0
  119. package/dist/shims/symbol-metadata.d.ts +2 -0
  120. package/dist/shims/symbol-metadata.d.ts.map +1 -0
  121. package/dist/shims/symbol-metadata.js +8 -0
  122. package/dist/shims/symbol-metadata.js.map +1 -0
  123. package/dist/utils/content-type.d.ts +5 -0
  124. package/dist/utils/content-type.d.ts.map +1 -0
  125. package/dist/utils/content-type.js +10 -0
  126. package/dist/utils/content-type.js.map +1 -0
  127. package/dist/utils/index.d.ts +4 -0
  128. package/dist/utils/index.d.ts.map +1 -0
  129. package/dist/utils/index.js +4 -0
  130. package/dist/utils/index.js.map +1 -0
  131. package/dist/utils/path.d.ts +3 -0
  132. package/dist/utils/path.d.ts.map +1 -0
  133. package/dist/utils/path.js +7 -0
  134. package/dist/utils/path.js.map +1 -0
  135. package/dist/utils/query.d.ts +13 -0
  136. package/dist/utils/query.d.ts.map +1 -0
  137. package/dist/utils/query.js +30 -0
  138. package/dist/utils/query.js.map +1 -0
  139. package/package.json +74 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,4BAA4B,CAAC;AAEpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // Ponto de entrada público do Sashiko.
2
+ // O shim precisa ser avaliado antes de qualquer decorator rodar.
3
+ import './shims/symbol-metadata.js';
4
+ export * from './core/index.js';
5
+ export * from './decorators/index.js';
6
+ export * from './middlewares/index.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,iEAAiE;AACjE,OAAO,4BAA4B,CAAC;AAEpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { Middleware } from '../core/compose.ts';
2
+ export type CorsOrigin = '*' | string | RegExp | readonly (string | RegExp)[] | ((origin: string) => boolean | Promise<boolean>);
3
+ export interface CorsOptions {
4
+ /** Origens permitidas. Obrigatório: não existe default permissivo. */
5
+ origin: CorsOrigin;
6
+ methods?: readonly string[];
7
+ allowedHeaders?: readonly string[];
8
+ exposedHeaders?: readonly string[];
9
+ /** Envia `Access-Control-Allow-Credentials: true`. Proibido com `origin: '*'`. */
10
+ credentials?: boolean;
11
+ /** Cache do preflight em segundos. Padrão: 600. */
12
+ maxAge?: number;
13
+ }
14
+ /**
15
+ * Middleware de CORS (S-01). Uso: `app.use(cors({ origin: 'https://app.exemplo.com' }))`.
16
+ *
17
+ * - Preflight (`OPTIONS` + `Access-Control-Request-Method`) é respondido com 204.
18
+ * - A origem só é refletida se estiver na lista permitida; `Vary: Origin` sempre que
19
+ * a resposta depende da origem.
20
+ * - `origin: '*'` + `credentials: true` é rejeitado na criação (o navegador também recusaria,
21
+ * e refletir qualquer origem com credenciais permitiria que qualquer site lesse dados do usuário).
22
+ */
23
+ export declare function cors(opts: CorsOptions): Middleware;
24
+ //# sourceMappingURL=cors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../src/middlewares/cors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,MAAM,UAAU,GAClB,GAAG,GACH,MAAM,GACN,MAAM,GACN,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAC5B,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,kFAAkF;IAClF,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD;;;;;;;;GAQG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,CAyClD"}
@@ -0,0 +1,63 @@
1
+ const DEFAULT_METHODS = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'];
2
+ const DEFAULT_HEADERS = ['Content-Type', 'Authorization'];
3
+ /**
4
+ * Middleware de CORS (S-01). Uso: `app.use(cors({ origin: 'https://app.exemplo.com' }))`.
5
+ *
6
+ * - Preflight (`OPTIONS` + `Access-Control-Request-Method`) é respondido com 204.
7
+ * - A origem só é refletida se estiver na lista permitida; `Vary: Origin` sempre que
8
+ * a resposta depende da origem.
9
+ * - `origin: '*'` + `credentials: true` é rejeitado na criação (o navegador também recusaria,
10
+ * e refletir qualquer origem com credenciais permitiria que qualquer site lesse dados do usuário).
11
+ */
12
+ export function cors(opts) {
13
+ if (opts.origin === '*' && opts.credentials) {
14
+ throw new Error("cors: `origin: '*'` cannot be combined with `credentials: true`. List the allowed origins.");
15
+ }
16
+ const wildcard = opts.origin === '*';
17
+ const isAllowed = buildMatcher(opts.origin);
18
+ const methods = (opts.methods ?? DEFAULT_METHODS).join(', ');
19
+ const allowedHeaders = opts.allowedHeaders?.join(', ');
20
+ const exposed = opts.exposedHeaders?.join(', ');
21
+ const maxAge = String(opts.maxAge ?? 600);
22
+ return async (ctx, next) => {
23
+ const origin = ctx.req.headers.origin;
24
+ const res = ctx.res;
25
+ if (!wildcard)
26
+ res.appendHeader('Vary', 'Origin');
27
+ const preflight = ctx.method === 'OPTIONS' && ctx.req.headers['access-control-request-method'] !== undefined;
28
+ if (!origin)
29
+ return preflight ? void ctx.status(204).res.end() : next();
30
+ const allowed = wildcard || (await isAllowed(origin));
31
+ if (!allowed) {
32
+ // Sem headers CORS o navegador bloqueia a leitura; a requisição em si segue normalmente.
33
+ return preflight ? void ctx.status(204).res.end() : next();
34
+ }
35
+ res.setHeader('Access-Control-Allow-Origin', wildcard ? '*' : origin);
36
+ if (opts.credentials)
37
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
38
+ if (preflight) {
39
+ res.setHeader('Access-Control-Allow-Methods', methods);
40
+ const requested = ctx.req.headers['access-control-request-headers'];
41
+ const headers = allowedHeaders ?? (typeof requested === 'string' ? filterHeaders(requested) : DEFAULT_HEADERS.join(', '));
42
+ if (headers)
43
+ res.setHeader('Access-Control-Allow-Headers', headers);
44
+ res.setHeader('Access-Control-Max-Age', maxAge);
45
+ return void ctx.status(204).res.end();
46
+ }
47
+ if (exposed)
48
+ res.setHeader('Access-Control-Expose-Headers', exposed);
49
+ await next();
50
+ };
51
+ }
52
+ /** Sem lista explícita, aceita apenas os headers padrão entre os pedidos pelo navegador. */
53
+ function filterHeaders(requested) {
54
+ const safe = new Set(DEFAULT_HEADERS.map((h) => h.toLowerCase()));
55
+ return requested.split(',').map((h) => h.trim()).filter((h) => safe.has(h.toLowerCase())).join(', ');
56
+ }
57
+ function buildMatcher(origin) {
58
+ if (typeof origin === 'function')
59
+ return origin;
60
+ const list = Array.isArray(origin) ? origin : [origin];
61
+ return (o) => list.some((rule) => (typeof rule === 'string' ? rule === o : rule.test(o)));
62
+ }
63
+ //# sourceMappingURL=cors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors.js","sourceRoot":"","sources":["../../src/middlewares/cors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { cors, type CorsOptions, type CorsOrigin } from './cors.ts';
2
+ export { MemoryRateLimitStore, rateLimit, type RateLimitOptions, type RateLimitStore } from './rate-limit.ts';
3
+ export { buildSecurityHeaders, type SecurityHeadersOptions } from './security-headers.ts';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,oBAAoB,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { cors } from './cors.js';
2
+ export { MemoryRateLimitStore, rateLimit } from './rate-limit.js';
3
+ export { buildSecurityHeaders } from './security-headers.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAqC,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAA8C,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,oBAAoB,EAA+B,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,35 @@
1
+ import type { Context } from '../core/context.ts';
2
+ import type { Middleware } from '../core/compose.ts';
3
+ export interface RateLimitStore {
4
+ /** Incrementa o contador da chave e retorna o total e quando a janela reinicia (epoch ms). */
5
+ hit(key: string, windowMs: number): {
6
+ count: number;
7
+ resetAt: number;
8
+ } | Promise<{
9
+ count: number;
10
+ resetAt: number;
11
+ }>;
12
+ }
13
+ export interface RateLimitOptions {
14
+ /** Máximo de requisições por janela. */
15
+ max: number;
16
+ /** Tamanho da janela em ms. Padrão: 60_000. */
17
+ windowMs?: number;
18
+ /** Chave de agrupamento. Padrão: IP do socket (`ctx.ip`). */
19
+ key?: (ctx: Context) => string;
20
+ /** Store plugável (Redis etc.). Padrão: memória local. */
21
+ store?: RateLimitStore;
22
+ }
23
+ /** Store em memória, janela fixa. Adequado para uma única instância. */
24
+ export declare class MemoryRateLimitStore implements RateLimitStore {
25
+ private hits;
26
+ private sweeper;
27
+ constructor(sweepEveryMs?: number);
28
+ hit(key: string, windowMs: number): {
29
+ count: number;
30
+ resetAt: number;
31
+ };
32
+ }
33
+ /** Rate limiting por chave (S-06). Responde 429 com `Retry-After` ao exceder. */
34
+ export declare function rateLimit(opts: RateLimitOptions): Middleware;
35
+ //# sourceMappingURL=rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/middlewares/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGrD,MAAM,WAAW,cAAc;IAC7B,8FAA8F;IAC9F,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtH;AAED,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IAC/B,0DAA0D;IAC1D,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAED,wEAAwE;AACxE,qBAAa,oBAAqB,YAAW,cAAc;IACzD,OAAO,CAAC,IAAI,CAAyD;IACrE,OAAO,CAAC,OAAO,CAAiB;IAEhC,YAAY,YAAY,SAAS,EAMhC;IAED,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;eAXO,MAAM;iBAAW,MAAM;MAoB9D;CACF;AAED,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,UAAU,CAkB5D"}
@@ -0,0 +1,45 @@
1
+ import { HttpError } from '../core/errors.js';
2
+ /** Store em memória, janela fixa. Adequado para uma única instância. */
3
+ export class MemoryRateLimitStore {
4
+ hits = new Map();
5
+ sweeper;
6
+ constructor(sweepEveryMs = 60_000) {
7
+ this.sweeper = setInterval(() => {
8
+ const now = Date.now();
9
+ for (const [k, v] of this.hits)
10
+ if (v.resetAt <= now)
11
+ this.hits.delete(k);
12
+ }, sweepEveryMs);
13
+ this.sweeper.unref();
14
+ }
15
+ hit(key, windowMs) {
16
+ const now = Date.now();
17
+ let entry = this.hits.get(key);
18
+ if (!entry || entry.resetAt <= now) {
19
+ entry = { count: 0, resetAt: now + windowMs };
20
+ this.hits.set(key, entry);
21
+ }
22
+ entry.count++;
23
+ return { ...entry };
24
+ }
25
+ }
26
+ /** Rate limiting por chave (S-06). Responde 429 com `Retry-After` ao exceder. */
27
+ export function rateLimit(opts) {
28
+ const windowMs = opts.windowMs ?? 60_000;
29
+ const store = opts.store ?? new MemoryRateLimitStore();
30
+ const keyOf = opts.key ?? ((ctx) => ctx.ip);
31
+ return async (ctx, next) => {
32
+ const { count, resetAt } = await store.hit(keyOf(ctx), windowMs);
33
+ const remaining = Math.max(0, opts.max - count);
34
+ const resetSec = Math.max(0, Math.ceil((resetAt - Date.now()) / 1000));
35
+ ctx.res.setHeader('RateLimit-Limit', String(opts.max));
36
+ ctx.res.setHeader('RateLimit-Remaining', String(remaining));
37
+ ctx.res.setHeader('RateLimit-Reset', String(resetSec));
38
+ if (count > opts.max) {
39
+ ctx.res.setHeader('Retry-After', String(resetSec));
40
+ throw new HttpError(429, 'Too Many Requests', 'RATE_LIMITED');
41
+ }
42
+ await next();
43
+ };
44
+ }
45
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/middlewares/rate-limit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { ServerResponse } from 'node:http';
2
+ export interface SecurityHeadersOptions {
3
+ /** Padrão: `default-src 'none'; frame-ancestors 'none'` (adequado para APIs JSON). `false` desliga. */
4
+ contentSecurityPolicy?: string | false;
5
+ /** Padrão: `no-referrer`. */
6
+ referrerPolicy?: string | false;
7
+ /** Padrão: `DENY`. */
8
+ frameOptions?: 'DENY' | 'SAMEORIGIN' | false;
9
+ /** Padrão: `same-origin`. */
10
+ crossOriginResourcePolicy?: 'same-origin' | 'same-site' | 'cross-origin' | false;
11
+ /**
12
+ * HSTS é opt-in: só faz sentido atrás de HTTPS.
13
+ * `true` = `max-age=15552000; includeSubDomains` (180 dias).
14
+ */
15
+ strictTransportSecurity?: boolean | {
16
+ maxAge: number;
17
+ includeSubDomains?: boolean;
18
+ preload?: boolean;
19
+ };
20
+ }
21
+ /**
22
+ * Headers de segurança padrão (S-04) e proteções contra XSS em APIs JSON (S-02).
23
+ * Retorna uma função que aplica os headers pré-calculados na resposta.
24
+ */
25
+ export declare function buildSecurityHeaders(opts?: SecurityHeadersOptions): (res: ServerResponse) => void;
26
+ //# sourceMappingURL=security-headers.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Headers de segurança padrão (S-04) e proteções contra XSS em APIs JSON (S-02).
3
+ * Retorna uma função que aplica os headers pré-calculados na resposta.
4
+ */
5
+ export function buildSecurityHeaders(opts = {}) {
6
+ const headers = [['X-Content-Type-Options', 'nosniff']];
7
+ const csp = opts.contentSecurityPolicy ?? "default-src 'none'; frame-ancestors 'none'";
8
+ if (csp)
9
+ headers.push(['Content-Security-Policy', csp]);
10
+ const referrer = opts.referrerPolicy ?? 'no-referrer';
11
+ if (referrer)
12
+ headers.push(['Referrer-Policy', referrer]);
13
+ const frame = opts.frameOptions ?? 'DENY';
14
+ if (frame)
15
+ headers.push(['X-Frame-Options', frame]);
16
+ const corp = opts.crossOriginResourcePolicy ?? 'same-origin';
17
+ if (corp)
18
+ headers.push(['Cross-Origin-Resource-Policy', corp]);
19
+ const hsts = opts.strictTransportSecurity;
20
+ if (hsts) {
21
+ const cfg = hsts === true ? { maxAge: 15552000, includeSubDomains: true } : hsts;
22
+ let value = `max-age=${cfg.maxAge}`;
23
+ if (cfg.includeSubDomains)
24
+ value += '; includeSubDomains';
25
+ if ('preload' in cfg && cfg.preload)
26
+ value += '; preload';
27
+ headers.push(['Strict-Transport-Security', value]);
28
+ }
29
+ return (res) => {
30
+ for (const [name, value] of headers)
31
+ res.setHeader(name, value);
32
+ };
33
+ }
34
+ //# sourceMappingURL=security-headers.js.map
@@ -0,0 +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"}
@@ -0,0 +1,2 @@
1
+ export declare function serialize(schema: object, data: unknown): string;
2
+ //# sourceMappingURL=serialization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.d.ts","sourceRoot":"","sources":["../../src/schema/serialization.ts"],"names":[],"mappings":"AAIA,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAO/D"}
@@ -0,0 +1,11 @@
1
+ import fastJson from 'fast-json-stringify';
2
+ const cache = new WeakMap();
3
+ export function serialize(schema, data) {
4
+ let fn = cache.get(schema);
5
+ if (!fn) {
6
+ fn = fastJson(schema);
7
+ cache.set(schema, fn);
8
+ }
9
+ return fn(data);
10
+ }
11
+ //# sourceMappingURL=serialization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.js","sourceRoot":"","sources":["../../src/schema/serialization.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,qBAAqB,CAAC;AAE3C,MAAM,KAAK,GAAG,IAAI,OAAO,EAAqC,CAAC;AAE/D,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,IAAa;IACrD,IAAI,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,EAAE,GAAG,QAAQ,CAAC,MAAwC,CAAC,CAAC;QACxD,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;AAClB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { type ErrorObject } from 'ajv';
2
+ export type ValidationTarget = 'params' | 'query' | 'headers' | 'body';
3
+ export interface ValidationIssue {
4
+ /** JSON Pointer do campo inválido (`/email`, `/items/0/id`). `/` = raiz. */
5
+ path: string;
6
+ message: string;
7
+ }
8
+ /** Valida `data` contra `schema`. Retorna `null` se válido, ou a lista de problemas (B-02). */
9
+ export declare function validate(schema: object, data: unknown, target: ValidationTarget): ValidationIssue[] | null;
10
+ export declare function formatErrors(errors: ErrorObject[]): ValidationIssue[];
11
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,48 @@
1
+ import AjvModule, {} from 'ajv';
2
+ import addFormatsModule from 'ajv-formats';
3
+ // Interop explícito: o TS em ESM + verbatimModuleSyntax resolve o default
4
+ // como namespace; pegamos o `.default` real em runtime (que o Node injeta
5
+ // via CJS interop) e caímos no próprio módulo se ele já for o valor.
6
+ const Ajv = (AjvModule.default ?? AjvModule);
7
+ const addFormats = (addFormatsModule.default ?? addFormatsModule);
8
+ function createAjv(coerceTypes) {
9
+ const ajv = new Ajv({ allErrors: true, coerceTypes, useDefaults: true, removeAdditional: false });
10
+ addFormats(ajv);
11
+ return ajv;
12
+ }
13
+ /**
14
+ * Duas instâncias (B-06):
15
+ * - `coercing`: params/query/headers chegam sempre como string, então convertemos
16
+ * para o tipo declarado (`"5"` → `5`).
17
+ * - `strict`: o body já é JSON tipado; coerção mascararia erros do cliente
18
+ * (`"30"` virar `30`, `""` virar `0`).
19
+ */
20
+ // 'array' também converte valor único em array quando o schema pede array (`?tag=a` → `['a']`, B-07)
21
+ const coercing = createAjv('array');
22
+ const strict = createAjv(false);
23
+ const caches = new Map([
24
+ [coercing, new WeakMap()],
25
+ [strict, new WeakMap()],
26
+ ]);
27
+ function compile(schema, target) {
28
+ const ajv = target === 'body' ? strict : coercing;
29
+ const cache = caches.get(ajv);
30
+ let fn = cache.get(schema);
31
+ if (!fn) {
32
+ fn = ajv.compile(schema);
33
+ cache.set(schema, fn);
34
+ }
35
+ return fn;
36
+ }
37
+ /** Valida `data` contra `schema`. Retorna `null` se válido, ou a lista de problemas (B-02). */
38
+ export function validate(schema, data, target) {
39
+ const fn = compile(schema, target);
40
+ return fn(data) ? null : formatErrors(fn.errors ?? []);
41
+ }
42
+ export function formatErrors(errors) {
43
+ return errors.map((e) => {
44
+ const missing = e.keyword === 'required' ? `/${e.params.missingProperty}` : '';
45
+ return { path: `${e.instancePath}${missing}` || '/', message: e.message ?? 'invalid' };
46
+ });
47
+ }
48
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=symbol-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbol-metadata.d.ts","sourceRoot":"","sources":["../../src/shims/symbol-metadata.ts"],"names":[],"mappings":""}
@@ -0,0 +1,8 @@
1
+ // TC39 não cria Symbol.metadata automaticamente em todo runtime.
2
+ // Este arquivo deve ser importado ANTES de qualquer decorator rodar.
3
+ const S = Symbol;
4
+ if (!S.metadata) {
5
+ S.metadata = Symbol.for('Symbol.metadata');
6
+ }
7
+ export {};
8
+ //# sourceMappingURL=symbol-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbol-metadata.js","sourceRoot":"","sources":["../../src/shims/symbol-metadata.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,qEAAqE;AACrE,MAAM,CAAC,GAAG,MAA+B,CAAC;AAC1C,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAChB,CAAC,CAAC,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Media type sem parâmetros e em minúsculas: `"Application/JSON; charset=utf-8"` → `"application/json"`. */
2
+ export declare function mediaType(header: string | undefined): string;
3
+ /** `application/json` ou `application/*+json`, com parâmetros opcionais (B-11). */
4
+ export declare function isJsonContentType(header: string | undefined): boolean;
5
+ //# sourceMappingURL=content-type.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,10 @@
1
+ /** Media type sem parâmetros e em minúsculas: `"Application/JSON; charset=utf-8"` → `"application/json"`. */
2
+ export function mediaType(header) {
3
+ return header ? header.split(';', 1)[0].trim().toLowerCase() : '';
4
+ }
5
+ /** `application/json` ou `application/*+json`, com parâmetros opcionais (B-11). */
6
+ export function isJsonContentType(header) {
7
+ const type = mediaType(header);
8
+ return type === 'application/json' || /^application\/[a-z0-9!#$&^_.-]+\+json$/.test(type);
9
+ }
10
+ //# sourceMappingURL=content-type.js.map
@@ -0,0 +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"}
@@ -0,0 +1,4 @@
1
+ export { isJsonContentType, mediaType } from './content-type.ts';
2
+ export { joinPaths } from './path.ts';
3
+ export { DEFAULT_MAX_QUERY_KEYS, parseQuery, type ParsedQuery } from './query.ts';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,4 @@
1
+ export { isJsonContentType, mediaType } from './content-type.js';
2
+ export { joinPaths } from './path.js';
3
+ export { DEFAULT_MAX_QUERY_KEYS, parseQuery } from './query.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"}
@@ -0,0 +1,3 @@
1
+ /** Junta prefixo e caminho com exatamente uma barra entre eles: `('/users/', 'id')` → `/users/id`. */
2
+ export declare function joinPaths(prefix: string, path: string): string;
3
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9D"}
@@ -0,0 +1,7 @@
1
+ /** Junta prefixo e caminho com exatamente uma barra entre eles: `('/users/', 'id')` → `/users/id`. */
2
+ export function joinPaths(prefix, path) {
3
+ const p = prefix.replace(/\/$/, '');
4
+ const s = path.startsWith('/') ? path : `/${path}`;
5
+ return `${p}${s}` || '/';
6
+ }
7
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,IAAY;IACpD,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACpC,MAAM,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;IACnD,OAAO,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,13 @@
1
+ /** Padrão do `node:querystring`: limita o custo de queries gigantes (S-06). */
2
+ export declare const DEFAULT_MAX_QUERY_KEYS = 1000;
3
+ export type ParsedQuery = Record<string, string | string[]>;
4
+ /**
5
+ * Converte `URLSearchParams` em objeto (B-07):
6
+ * - chaves repetidas viram array (`?tag=a&tag=b` → `{ tag: ['a', 'b'] }`);
7
+ * - `__proto__`, `constructor` e `prototype` são descartadas (S-08);
8
+ * - pares além de `maxKeys` são ignorados (S-06).
9
+ *
10
+ * Usa `URLSearchParams` (WHATWG) em vez do `node:querystring`, que é legado.
11
+ */
12
+ export declare function parseQuery(params: URLSearchParams, maxKeys?: number): ParsedQuery;
13
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/utils/query.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;AAE5D;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,SAAyB,GAAG,WAAW,CAYjG"}
@@ -0,0 +1,30 @@
1
+ const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
2
+ /** Padrão do `node:querystring`: limita o custo de queries gigantes (S-06). */
3
+ export const DEFAULT_MAX_QUERY_KEYS = 1000;
4
+ /**
5
+ * Converte `URLSearchParams` em objeto (B-07):
6
+ * - chaves repetidas viram array (`?tag=a&tag=b` → `{ tag: ['a', 'b'] }`);
7
+ * - `__proto__`, `constructor` e `prototype` são descartadas (S-08);
8
+ * - pares além de `maxKeys` são ignorados (S-06).
9
+ *
10
+ * Usa `URLSearchParams` (WHATWG) em vez do `node:querystring`, que é legado.
11
+ */
12
+ export function parseQuery(params, maxKeys = DEFAULT_MAX_QUERY_KEYS) {
13
+ const out = {};
14
+ let count = 0;
15
+ for (const [key, value] of params) {
16
+ if (count++ >= maxKeys)
17
+ break;
18
+ if (UNSAFE_KEYS.has(key))
19
+ continue;
20
+ const prev = out[key];
21
+ if (prev === undefined)
22
+ out[key] = value;
23
+ else if (Array.isArray(prev))
24
+ prev.push(value);
25
+ else
26
+ out[key] = [prev, value];
27
+ }
28
+ return out;
29
+ }
30
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/utils/query.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC;AAEvE,+EAA+E;AAC/E,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAI3C;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,MAAuB,EAAE,OAAO,GAAG,sBAAsB;IAClF,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QAClC,IAAI,KAAK,EAAE,IAAI,OAAO;YAAE,MAAM;QAC9B,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QACnC,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,IAAI,KAAK,SAAS;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;aACpC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;YAC1C,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}