flowingts 0.0.0 → 0.19.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 (259) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +74 -3
  3. package/dist/core/abort.d.ts +23 -0
  4. package/dist/core/abort.d.ts.map +1 -0
  5. package/dist/core/abort.js +23 -0
  6. package/dist/core/abort.js.map +1 -0
  7. package/dist/core/app/core.d.ts +35 -0
  8. package/dist/core/app/core.d.ts.map +1 -0
  9. package/dist/core/app/core.js +2 -0
  10. package/dist/core/app/core.js.map +1 -0
  11. package/dist/core/app/options.d.ts +178 -0
  12. package/dist/core/app/options.d.ts.map +1 -0
  13. package/dist/core/app/options.js +2 -0
  14. package/dist/core/app/options.js.map +1 -0
  15. package/dist/core/app.d.ts +143 -0
  16. package/dist/core/app.d.ts.map +1 -0
  17. package/dist/core/app.js +283 -0
  18. package/dist/core/app.js.map +1 -0
  19. package/dist/core/body-parser.d.ts +14 -0
  20. package/dist/core/body-parser.d.ts.map +1 -0
  21. package/dist/core/body-parser.js +76 -0
  22. package/dist/core/body-parser.js.map +1 -0
  23. package/dist/core/compiler.d.ts +104 -0
  24. package/dist/core/compiler.d.ts.map +1 -0
  25. package/dist/core/compiler.js +151 -0
  26. package/dist/core/compiler.js.map +1 -0
  27. package/dist/core/compose.d.ts +16 -0
  28. package/dist/core/compose.d.ts.map +1 -0
  29. package/dist/core/compose.js +41 -0
  30. package/dist/core/compose.js.map +1 -0
  31. package/dist/core/context.d.ts +290 -0
  32. package/dist/core/context.d.ts.map +1 -0
  33. package/dist/core/context.js +460 -0
  34. package/dist/core/context.js.map +1 -0
  35. package/dist/core/contracts.d.ts +76 -0
  36. package/dist/core/contracts.d.ts.map +1 -0
  37. package/dist/core/contracts.js +87 -0
  38. package/dist/core/contracts.js.map +1 -0
  39. package/dist/core/errors.d.ts +50 -0
  40. package/dist/core/errors.d.ts.map +1 -0
  41. package/dist/core/errors.js +25 -0
  42. package/dist/core/errors.js.map +1 -0
  43. package/dist/core/guard.d.ts +10 -0
  44. package/dist/core/guard.d.ts.map +1 -0
  45. package/dist/core/guard.js +14 -0
  46. package/dist/core/guard.js.map +1 -0
  47. package/dist/core/health.d.ts +37 -0
  48. package/dist/core/health.d.ts.map +1 -0
  49. package/dist/core/health.js +68 -0
  50. package/dist/core/health.js.map +1 -0
  51. package/dist/core/hooks.d.ts +34 -0
  52. package/dist/core/hooks.d.ts.map +1 -0
  53. package/dist/core/hooks.js +27 -0
  54. package/dist/core/hooks.js.map +1 -0
  55. package/dist/core/index.d.ts +30 -0
  56. package/dist/core/index.d.ts.map +1 -0
  57. package/dist/core/index.js +11 -0
  58. package/dist/core/index.js.map +1 -0
  59. package/dist/core/infer.d.ts +68 -0
  60. package/dist/core/infer.d.ts.map +1 -0
  61. package/dist/core/infer.js +2 -0
  62. package/dist/core/infer.js.map +1 -0
  63. package/dist/core/inject.d.ts +27 -0
  64. package/dist/core/inject.d.ts.map +1 -0
  65. package/dist/core/inject.js +85 -0
  66. package/dist/core/inject.js.map +1 -0
  67. package/dist/core/logger.d.ts +57 -0
  68. package/dist/core/logger.d.ts.map +1 -0
  69. package/dist/core/logger.js +134 -0
  70. package/dist/core/logger.js.map +1 -0
  71. package/dist/core/metadata.d.ts +83 -0
  72. package/dist/core/metadata.d.ts.map +1 -0
  73. package/dist/core/metadata.js +91 -0
  74. package/dist/core/metadata.js.map +1 -0
  75. package/dist/core/mounted-group.d.ts +77 -0
  76. package/dist/core/mounted-group.d.ts.map +1 -0
  77. package/dist/core/mounted-group.js +131 -0
  78. package/dist/core/mounted-group.js.map +1 -0
  79. package/dist/core/registry/framework-routes.d.ts +28 -0
  80. package/dist/core/registry/framework-routes.d.ts.map +1 -0
  81. package/dist/core/registry/framework-routes.js +83 -0
  82. package/dist/core/registry/framework-routes.js.map +1 -0
  83. package/dist/core/registry/registry.d.ts +58 -0
  84. package/dist/core/registry/registry.d.ts.map +1 -0
  85. package/dist/core/registry/registry.js +208 -0
  86. package/dist/core/registry/registry.js.map +1 -0
  87. package/dist/core/registry/route-program.d.ts +16 -0
  88. package/dist/core/registry/route-program.d.ts.map +1 -0
  89. package/dist/core/registry/route-program.js +62 -0
  90. package/dist/core/registry/route-program.js.map +1 -0
  91. package/dist/core/request-validation.d.ts +15 -0
  92. package/dist/core/request-validation.d.ts.map +1 -0
  93. package/dist/core/request-validation.js +67 -0
  94. package/dist/core/request-validation.js.map +1 -0
  95. package/dist/core/response/pipeline.d.ts +51 -0
  96. package/dist/core/response/pipeline.d.ts.map +1 -0
  97. package/dist/core/response/pipeline.js +169 -0
  98. package/dist/core/response/pipeline.js.map +1 -0
  99. package/dist/core/route-table.d.ts +58 -0
  100. package/dist/core/route-table.d.ts.map +1 -0
  101. package/dist/core/route-table.js +108 -0
  102. package/dist/core/route-table.js.map +1 -0
  103. package/dist/core/runtime/observer.d.ts +47 -0
  104. package/dist/core/runtime/observer.d.ts.map +1 -0
  105. package/dist/core/runtime/observer.js +125 -0
  106. package/dist/core/runtime/observer.js.map +1 -0
  107. package/dist/core/runtime/request-handler.d.ts +62 -0
  108. package/dist/core/runtime/request-handler.d.ts.map +1 -0
  109. package/dist/core/runtime/request-handler.js +256 -0
  110. package/dist/core/runtime/request-handler.js.map +1 -0
  111. package/dist/core/runtime/server.d.ts +59 -0
  112. package/dist/core/runtime/server.d.ts.map +1 -0
  113. package/dist/core/runtime/server.js +180 -0
  114. package/dist/core/runtime/server.js.map +1 -0
  115. package/dist/core/shutdown.d.ts +41 -0
  116. package/dist/core/shutdown.d.ts.map +1 -0
  117. package/dist/core/shutdown.js +53 -0
  118. package/dist/core/shutdown.js.map +1 -0
  119. package/dist/core/streaming/sse.d.ts +35 -0
  120. package/dist/core/streaming/sse.d.ts.map +1 -0
  121. package/dist/core/streaming/sse.js +121 -0
  122. package/dist/core/streaming/sse.js.map +1 -0
  123. package/dist/core/symbols.d.ts +28 -0
  124. package/dist/core/symbols.d.ts.map +1 -0
  125. package/dist/core/symbols.js +28 -0
  126. package/dist/core/symbols.js.map +1 -0
  127. package/dist/core/telemetry.d.ts +83 -0
  128. package/dist/core/telemetry.d.ts.map +1 -0
  129. package/dist/core/telemetry.js +96 -0
  130. package/dist/core/telemetry.js.map +1 -0
  131. package/dist/core/uploads/receive.d.ts +63 -0
  132. package/dist/core/uploads/receive.d.ts.map +1 -0
  133. package/dist/core/uploads/receive.js +342 -0
  134. package/dist/core/uploads/receive.js.map +1 -0
  135. package/dist/core/uploads/types.d.ts +79 -0
  136. package/dist/core/uploads/types.d.ts.map +1 -0
  137. package/dist/core/uploads/types.js +2 -0
  138. package/dist/core/uploads/types.js.map +1 -0
  139. package/dist/decorators/hooks.d.ts +25 -0
  140. package/dist/decorators/hooks.d.ts.map +1 -0
  141. package/dist/decorators/hooks.js +36 -0
  142. package/dist/decorators/hooks.js.map +1 -0
  143. package/dist/decorators/http.d.ts +21 -0
  144. package/dist/decorators/http.d.ts.map +1 -0
  145. package/dist/decorators/http.js +21 -0
  146. package/dist/decorators/http.js.map +1 -0
  147. package/dist/decorators/index.d.ts +7 -0
  148. package/dist/decorators/index.d.ts.map +1 -0
  149. package/dist/decorators/index.js +7 -0
  150. package/dist/decorators/index.js.map +1 -0
  151. package/dist/decorators/log.d.ts +10 -0
  152. package/dist/decorators/log.d.ts.map +1 -0
  153. package/dist/decorators/log.js +32 -0
  154. package/dist/decorators/log.js.map +1 -0
  155. package/dist/decorators/middlewares.d.ts +28 -0
  156. package/dist/decorators/middlewares.d.ts.map +1 -0
  157. package/dist/decorators/middlewares.js +40 -0
  158. package/dist/decorators/middlewares.js.map +1 -0
  159. package/dist/decorators/router.d.ts +12 -0
  160. package/dist/decorators/router.d.ts.map +1 -0
  161. package/dist/decorators/router.js +20 -0
  162. package/dist/decorators/router.js.map +1 -0
  163. package/dist/decorators/schema.d.ts +8 -0
  164. package/dist/decorators/schema.d.ts.map +1 -0
  165. package/dist/decorators/schema.js +14 -0
  166. package/dist/decorators/schema.js.map +1 -0
  167. package/dist/index.d.ts +7 -0
  168. package/dist/index.d.ts.map +1 -0
  169. package/dist/index.js +10 -0
  170. package/dist/index.js.map +1 -0
  171. package/dist/middlewares/auth.d.ts +41 -0
  172. package/dist/middlewares/auth.d.ts.map +1 -0
  173. package/dist/middlewares/auth.js +74 -0
  174. package/dist/middlewares/auth.js.map +1 -0
  175. package/dist/middlewares/cors.d.ts +30 -0
  176. package/dist/middlewares/cors.d.ts.map +1 -0
  177. package/dist/middlewares/cors.js +69 -0
  178. package/dist/middlewares/cors.js.map +1 -0
  179. package/dist/middlewares/csrf.d.ts +42 -0
  180. package/dist/middlewares/csrf.d.ts.map +1 -0
  181. package/dist/middlewares/csrf.js +70 -0
  182. package/dist/middlewares/csrf.js.map +1 -0
  183. package/dist/middlewares/index.d.ts +6 -0
  184. package/dist/middlewares/index.d.ts.map +1 -0
  185. package/dist/middlewares/index.js +6 -0
  186. package/dist/middlewares/index.js.map +1 -0
  187. package/dist/middlewares/rate-limit.d.ts +35 -0
  188. package/dist/middlewares/rate-limit.d.ts.map +1 -0
  189. package/dist/middlewares/rate-limit.js +45 -0
  190. package/dist/middlewares/rate-limit.js.map +1 -0
  191. package/dist/middlewares/security-headers.d.ts +28 -0
  192. package/dist/middlewares/security-headers.d.ts.map +1 -0
  193. package/dist/middlewares/security-headers.js +42 -0
  194. package/dist/middlewares/security-headers.js.map +1 -0
  195. package/dist/openapi/generate.d.ts +51 -0
  196. package/dist/openapi/generate.d.ts.map +1 -0
  197. package/dist/openapi/generate.js +175 -0
  198. package/dist/openapi/generate.js.map +1 -0
  199. package/dist/schema/serialization.d.ts +2 -0
  200. package/dist/schema/serialization.d.ts.map +1 -0
  201. package/dist/schema/serialization.js +11 -0
  202. package/dist/schema/serialization.js.map +1 -0
  203. package/dist/schema/validation.d.ts +22 -0
  204. package/dist/schema/validation.d.ts.map +1 -0
  205. package/dist/schema/validation.js +68 -0
  206. package/dist/schema/validation.js.map +1 -0
  207. package/dist/server/static.d.ts +45 -0
  208. package/dist/server/static.d.ts.map +1 -0
  209. package/dist/server/static.js +204 -0
  210. package/dist/server/static.js.map +1 -0
  211. package/dist/server/tls.d.ts +48 -0
  212. package/dist/server/tls.d.ts.map +1 -0
  213. package/dist/server/tls.js +60 -0
  214. package/dist/server/tls.js.map +1 -0
  215. package/dist/shims/symbol-metadata.d.ts +2 -0
  216. package/dist/shims/symbol-metadata.d.ts.map +1 -0
  217. package/dist/shims/symbol-metadata.js +8 -0
  218. package/dist/shims/symbol-metadata.js.map +1 -0
  219. package/dist/utils/content-type.d.ts +13 -0
  220. package/dist/utils/content-type.d.ts.map +1 -0
  221. package/dist/utils/content-type.js +75 -0
  222. package/dist/utils/content-type.js.map +1 -0
  223. package/dist/utils/cookie.d.ts +34 -0
  224. package/dist/utils/cookie.d.ts.map +1 -0
  225. package/dist/utils/cookie.js +94 -0
  226. package/dist/utils/cookie.js.map +1 -0
  227. package/dist/utils/index.d.ts +7 -0
  228. package/dist/utils/index.d.ts.map +1 -0
  229. package/dist/utils/index.js +7 -0
  230. package/dist/utils/index.js.map +1 -0
  231. package/dist/utils/mime.d.ts +2 -0
  232. package/dist/utils/mime.d.ts.map +1 -0
  233. package/dist/utils/mime.js +52 -0
  234. package/dist/utils/mime.js.map +1 -0
  235. package/dist/utils/origin-match.d.ts +58 -0
  236. package/dist/utils/origin-match.d.ts.map +1 -0
  237. package/dist/utils/origin-match.js +194 -0
  238. package/dist/utils/origin-match.js.map +1 -0
  239. package/dist/utils/path.d.ts +10 -0
  240. package/dist/utils/path.d.ts.map +1 -0
  241. package/dist/utils/path.js +60 -0
  242. package/dist/utils/path.js.map +1 -0
  243. package/dist/utils/query.d.ts +28 -0
  244. package/dist/utils/query.d.ts.map +1 -0
  245. package/dist/utils/query.js +67 -0
  246. package/dist/utils/query.js.map +1 -0
  247. package/dist/utils/request-target.d.ts +31 -0
  248. package/dist/utils/request-target.d.ts.map +1 -0
  249. package/dist/utils/request-target.js +80 -0
  250. package/dist/utils/request-target.js.map +1 -0
  251. package/dist/utils/safe-json.d.ts +24 -0
  252. package/dist/utils/safe-json.d.ts.map +1 -0
  253. package/dist/utils/safe-json.js +83 -0
  254. package/dist/utils/safe-json.js.map +1 -0
  255. package/dist/utils/trace-context.d.ts +17 -0
  256. package/dist/utils/trace-context.d.ts.map +1 -0
  257. package/dist/utils/trace-context.js +30 -0
  258. package/dist/utils/trace-context.js.map +1 -0
  259. package/package.json +85 -6
@@ -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,28 @@
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
+ /** `'api'`: restritivo, para APIs (padrão). `'web'`: permite recursos do próprio site (F-31). */
22
+ export type SecurityPreset = 'api' | 'web';
23
+ /**
24
+ * Headers de segurança padrão (S-04) e proteções contra XSS (S-02).
25
+ * Retorna uma função que aplica os headers pré-calculados na resposta.
26
+ */
27
+ export declare function buildSecurityHeaders(input?: SecurityHeadersOptions | SecurityPreset): (res: ServerResponse) => void;
28
+ //# 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,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"}
@@ -0,0 +1,42 @@
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
+ };
8
+ /**
9
+ * Headers de segurança padrão (S-04) e proteções contra XSS (S-02).
10
+ * Retorna uma função que aplica os headers pré-calculados na resposta.
11
+ */
12
+ export function buildSecurityHeaders(input = {}) {
13
+ const opts = typeof input === 'string' ? PRESETS[input] : input;
14
+ const headers = [['X-Content-Type-Options', 'nosniff']];
15
+ const csp = opts.contentSecurityPolicy ?? "default-src 'none'; frame-ancestors 'none'";
16
+ if (csp)
17
+ headers.push(['Content-Security-Policy', csp]);
18
+ const referrer = opts.referrerPolicy ?? 'no-referrer';
19
+ if (referrer)
20
+ headers.push(['Referrer-Policy', referrer]);
21
+ const frame = opts.frameOptions ?? 'DENY';
22
+ if (frame)
23
+ headers.push(['X-Frame-Options', frame]);
24
+ const corp = opts.crossOriginResourcePolicy ?? 'same-origin';
25
+ if (corp)
26
+ headers.push(['Cross-Origin-Resource-Policy', corp]);
27
+ const hsts = opts.strictTransportSecurity;
28
+ if (hsts) {
29
+ const cfg = hsts === true ? { maxAge: 15552000, includeSubDomains: true } : hsts;
30
+ let value = `max-age=${cfg.maxAge}`;
31
+ if (cfg.includeSubDomains)
32
+ value += '; includeSubDomains';
33
+ if ('preload' in cfg && cfg.preload)
34
+ value += '; preload';
35
+ headers.push(['Strict-Transport-Security', value]);
36
+ }
37
+ return (res) => {
38
+ for (const [name, value] of headers)
39
+ res.setHeader(name, value);
40
+ };
41
+ }
42
+ //# sourceMappingURL=security-headers.js.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,51 @@
1
+ import type { CompiledRoute } from '../core/compiler.ts';
2
+ import type { Middleware } from '../core/compose.ts';
3
+ import type { Guard } from '../core/guard.ts';
4
+ /** Opções de `app.openapi()` (F-16). */
5
+ export interface OpenAPIOptions {
6
+ /** Padrão: `{ title: 'API', version: '0.0.0' }`. */
7
+ info?: {
8
+ title: string;
9
+ version: string;
10
+ description?: string;
11
+ [key: string]: unknown;
12
+ };
13
+ servers?: readonly {
14
+ url: string;
15
+ description?: string;
16
+ }[];
17
+ /**
18
+ * Só no `openapiDocument()`: documenta as rotas deste host (virtual hosts) mais as sem host (F-35).
19
+ * Sem `host`, entram só as rotas sem host. O `/openapi.json` servido usa o host da requisição.
20
+ */
21
+ host?: string;
22
+ /** Onde servir o documento JSON. Padrão: `'/openapi.json'`. */
23
+ path?: string;
24
+ /** Swagger UI. Desligado por padrão: `true` serve em `'/docs'`, uma string escolhe o path. */
25
+ docs?: string | boolean;
26
+ /** Rodam antes de servir o documento e a página, depois dos middlewares globais. */
27
+ middlewares?: readonly Middleware[];
28
+ /** Rodam depois dos `middlewares`, como nas rotas (401/403 antes de gerar o documento). */
29
+ guards?: readonly Guard[];
30
+ }
31
+ type Json = Record<string, unknown>;
32
+ /** Converte um schema de contrato em JSON Schema (F-27); `undefined` quando o motor não sabe converter. */
33
+ type ToJsonSchema = (schema: object) => object | undefined;
34
+ /** `/:org/users/:id(^\\d+)` → `/{org}/users/{id}`; `::` → `:`; `*` → `{*}`; sem barra final (o roteador a ignora). */
35
+ export declare function toOpenAPIPath(path: string): string;
36
+ /**
37
+ * Gera o documento OpenAPI 3.1 a partir das rotas registradas (F-16). Os schemas TypeBox já são
38
+ * JSON Schema, então entram como estão. Com outro motor de contratos (F-27), `toJson` os converte; sem conversão,
39
+ * o schema sai como `{}` (qualquer valor).
40
+ */
41
+ export declare function generateOpenAPI(routes: readonly CompiledRoute[], opts: OpenAPIOptions, problemErrors: boolean, toJson?: ToJsonSchema): Json;
42
+ /**
43
+ * Página do Swagger UI (versão fixada, com SRI) e a CSP própria dela: scripts só do CDN e o inline com
44
+ * `nonce`, `connect-src 'self'` para buscar o documento.
45
+ */
46
+ export declare function docsPage(specUrl: string, title: string, nonce: string): {
47
+ html: string;
48
+ csp: string;
49
+ };
50
+ export {};
51
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/openapi/generate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAG9C,wCAAwC;AACxC,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IACxF,OAAO,CAAC,EAAE,SAAS;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3D;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,oFAAoF;IACpF,WAAW,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IACpC,2FAA2F;IAC3F,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,CAAC;CAC3B;AAED,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAKpC,2GAA2G;AAC3G,KAAK,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAQ3D,sHAAsH;AACtH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUlD;AA6FD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,IAAI,EAAE,cAAc,EACpB,aAAa,EAAE,OAAO,EACtB,MAAM,GAAE,YAAyB,GAChC,IAAI,CAqBN;AASD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CA2BrG"}
@@ -0,0 +1,175 @@
1
+ import { STATUS_CODES } from 'node:http';
2
+ import { isResponseContent } from '../core/metadata.js';
3
+ /** Cópia em JSON puro: tira as chaves `Symbol` do TypeBox e qualquer coisa não serializável. */
4
+ const plain = (schema) => JSON.parse(JSON.stringify(schema));
5
+ const nativeJson = (schema) => schema;
6
+ /** O schema em JSON puro, ou `{}` (qualquer valor) quando não há conversão. */
7
+ const asJson = (schema, toJson) => {
8
+ const converted = toJson(schema);
9
+ return converted ? plain(converted) : {};
10
+ };
11
+ /** `/:org/users/:id(^\\d+)` → `/{org}/users/{id}`; `::` → `:`; `*` → `{*}`; sem barra final (o roteador a ignora). */
12
+ export function toOpenAPIPath(path) {
13
+ return (path
14
+ .replace(/::/g, '\u0000')
15
+ .replace(/:([^/\-.(]+)(\((?:\\.|[^\\])*?\))?/g, '{$1}')
16
+ .replace(/\*$/, '{*}')
17
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: desfaz o marcador acima.
18
+ .replace(/\u0000/g, ':')
19
+ .replace(/(.)\/$/, '$1'));
20
+ }
21
+ /** Problem Details (RFC 9457), o corpo padrão de todos os erros (F-15). */
22
+ const PROBLEM_SCHEMA = {
23
+ type: 'object',
24
+ required: ['type', 'title', 'status'],
25
+ properties: {
26
+ type: { type: 'string' },
27
+ title: { type: 'string' },
28
+ status: { type: 'integer' },
29
+ detail: { type: 'string' },
30
+ code: { type: 'string' },
31
+ details: {},
32
+ },
33
+ };
34
+ function parameters(where, schema, toJson) {
35
+ const json = schema ? asJson(schema, toJson) : undefined;
36
+ const props = json?.properties;
37
+ if (!props)
38
+ return [];
39
+ const required = new Set(json.required ?? []);
40
+ return Object.entries(props).map(([name, s]) => ({
41
+ name,
42
+ in: where,
43
+ required: where === 'path' || required.has(name),
44
+ schema: s,
45
+ }));
46
+ }
47
+ function responseContent(entry, toJson) {
48
+ if (isResponseContent(entry)) {
49
+ const types = typeof entry.contentType === 'string' ? [entry.contentType] : entry.contentType;
50
+ return Object.fromEntries(types.map((t) => [t, entry.schema ? { schema: asJson(entry.schema, toJson) } : {}]));
51
+ }
52
+ return { 'application/json': { schema: asJson(entry, toJson) } };
53
+ }
54
+ function operation(route, problemErrors, toJson) {
55
+ const { schemas } = route;
56
+ const params = [
57
+ ...parameters('path', schemas.params, toJson),
58
+ ...parameters('query', schemas.query, toJson),
59
+ ...parameters('header', schemas.headers, toJson),
60
+ ];
61
+ const responses = {};
62
+ for (const [status, entry] of Object.entries(schemas.response ?? {})) {
63
+ const content = Number(status) === 204 ? undefined : responseContent(entry, toJson);
64
+ responses[status] = { description: STATUS_CODES[Number(status)] ?? 'Response', ...(content ? { content } : {}) };
65
+ }
66
+ if (Object.keys(responses).length === 0)
67
+ responses['200'] = { description: 'OK' };
68
+ responses.default = problemErrors
69
+ ? { description: 'Error', content: { 'application/problem+json': { schema: { $ref: '#/components/schemas/ProblemDetails' } } } }
70
+ : { description: 'Error' };
71
+ return {
72
+ operationId: `${route.owner}.${route.handlerName}`,
73
+ tags: [route.owner],
74
+ ...(params.length ? { parameters: params } : {}),
75
+ ...(schemas.files
76
+ ? {
77
+ requestBody: {
78
+ required: true,
79
+ content: { 'multipart/form-data': multipartSchema(schemas.files, schemas.body ? asJson(schemas.body, toJson) : undefined) },
80
+ },
81
+ }
82
+ : schemas.body
83
+ ? { requestBody: { required: true, content: { 'application/json': { schema: asJson(schemas.body, toJson) } } } }
84
+ : {}),
85
+ responses,
86
+ };
87
+ }
88
+ /**
89
+ * Upload (F-46): os campos de texto do `body` e os arquivos do `files` num único objeto, com `format: binary`.
90
+ * `maxCount` maior que 1 vira um array; os tipos aceitos vão no `encoding.contentType`.
91
+ */
92
+ function multipartSchema(files, body) {
93
+ const properties = { ...(body?.properties ?? {}) };
94
+ const required = [...(body?.required ?? [])];
95
+ const encoding = {};
96
+ for (const [name, rule] of Object.entries(files)) {
97
+ const binary = { type: 'string', format: 'binary' };
98
+ const max = rule.maxCount ?? 1;
99
+ properties[name] = max > 1 ? { type: 'array', items: binary, maxItems: max } : binary;
100
+ if (rule.required)
101
+ required.push(name);
102
+ if (rule.types)
103
+ encoding[name] = { contentType: rule.types.join(', ') };
104
+ }
105
+ return {
106
+ schema: { type: 'object', properties, ...(required.length ? { required } : {}) },
107
+ ...(Object.keys(encoding).length ? { encoding } : {}),
108
+ };
109
+ }
110
+ /**
111
+ * Gera o documento OpenAPI 3.1 a partir das rotas registradas (F-16). Os schemas TypeBox já são
112
+ * JSON Schema, então entram como estão. Com outro motor de contratos (F-27), `toJson` os converte; sem conversão,
113
+ * o schema sai como `{}` (qualquer valor).
114
+ */
115
+ export function generateOpenAPI(routes, opts, problemErrors, toJson = nativeJson) {
116
+ const paths = {};
117
+ const ids = new Map();
118
+ for (const route of routes) {
119
+ if (route.internal)
120
+ continue;
121
+ const path = toOpenAPIPath(route.path);
122
+ const op = operation(route, problemErrors, toJson);
123
+ // A mesma classe montada em dois prefixos gera o mesmo id; o OpenAPI exige ids únicos.
124
+ const base = op.operationId;
125
+ const n = (ids.get(base) ?? 0) + 1;
126
+ ids.set(base, n);
127
+ if (n > 1)
128
+ op.operationId = `${base}_${n}`;
129
+ (paths[path] ??= {})[route.method.toLowerCase()] = op;
130
+ }
131
+ return {
132
+ openapi: '3.1.0',
133
+ info: opts.info ?? { title: 'API', version: '0.0.0' },
134
+ ...(opts.servers ? { servers: opts.servers } : {}),
135
+ paths,
136
+ ...(problemErrors ? { components: { schemas: { ProblemDetails: PROBLEM_SCHEMA } } } : {}),
137
+ };
138
+ }
139
+ const SWAGGER = 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.34.1';
140
+ const SWAGGER_JS_SRI = 'sha384-XFYN47VzPiDY6qPtxYKg0nqUeW/upo20xf1j2SUE7gT/i35FJB4cuS/71Iegzk8Z';
141
+ const SWAGGER_CSS_SRI = 'sha384-4c3vsE5f+trhFyHrFrLM1fFGUn531NIaC7dcqEWIJdoRQJ5CkJbSuSCwXLpaS1KF';
142
+ const escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);
143
+ /**
144
+ * Página do Swagger UI (versão fixada, com SRI) e a CSP própria dela: scripts só do CDN e o inline com
145
+ * `nonce`, `connect-src 'self'` para buscar o documento.
146
+ */
147
+ export function docsPage(specUrl, title, nonce) {
148
+ const html = `<!DOCTYPE html>
149
+ <html lang="en">
150
+ <head>
151
+ <meta charset="utf-8">
152
+ <meta name="viewport" content="width=device-width, initial-scale=1">
153
+ <title>${escapeHtml(title)}</title>
154
+ <link rel="stylesheet" href="${SWAGGER}/swagger-ui.css" integrity="${SWAGGER_CSS_SRI}" crossorigin="anonymous">
155
+ </head>
156
+ <body>
157
+ <div id="swagger-ui"></div>
158
+ <script src="${SWAGGER}/swagger-ui-bundle.js" integrity="${SWAGGER_JS_SRI}" crossorigin="anonymous"></script>
159
+ <script nonce="${nonce}">SwaggerUIBundle({ url: ${JSON.stringify(specUrl).replace(/</g, '\\u003c')}, dom_id: '#swagger-ui' });</script>
160
+ </body>
161
+ </html>
162
+ `;
163
+ const csp = [
164
+ "default-src 'none'",
165
+ `script-src https://cdn.jsdelivr.net 'nonce-${nonce}'`,
166
+ "style-src https://cdn.jsdelivr.net 'unsafe-inline'",
167
+ "img-src 'self' data: https:",
168
+ "connect-src 'self'",
169
+ "frame-ancestors 'none'",
170
+ "base-uri 'none'",
171
+ "form-action 'none'",
172
+ ].join('; ');
173
+ return { html, csp };
174
+ }
175
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/openapi/generate.ts"],"names":[],"mappings":"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"}
@@ -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,22 @@
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
+ /**
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
+ };
19
+ /** Valida `data` contra `schema`. Retorna `null` se válido, ou a lista de problemas (B-02). */
20
+ export declare function validate(schema: object, data: unknown, target: ValidationTarget): ValidationIssue[] | null;
21
+ export declare function formatErrors(errors: ErrorObject[]): ValidationIssue[];
22
+ //# 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;AAczE,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"}
@@ -0,0 +1,68 @@
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
+ // biome-ignore lint/suspicious/noExplicitAny: interop CJS/ESM do ajv, resolvido em runtime.
7
+ const Ajv = (AjvModule.default ?? AjvModule);
8
+ // biome-ignore lint/suspicious/noExplicitAny: interop CJS/ESM do ajv-formats, resolvido em runtime.
9
+ const addFormats = (addFormatsModule.default ?? addFormatsModule);
10
+ function createAjv(coerceTypes) {
11
+ const ajv = new Ajv({ allErrors: true, coerceTypes, useDefaults: true, removeAdditional: false });
12
+ addFormats(ajv);
13
+ return ajv;
14
+ }
15
+ /**
16
+ * Duas instâncias (B-06):
17
+ * - `coercing`: params/query/headers chegam sempre como string, então convertemos
18
+ * para o tipo declarado (`"5"` → `5`).
19
+ * - `strict`: o body já é JSON tipado; coerção mascararia erros do cliente
20
+ * (`"30"` virar `30`, `""` virar `0`).
21
+ */
22
+ // 'array' também converte valor único em array quando o schema pede array (`?tag=a` → `['a']`, B-07)
23
+ const coercing = createAjv('array');
24
+ const strict = createAjv(false);
25
+ // F-31: para respostas em formatos não JSON, remove TODOS os campos não declarados (como o
26
+ // fast-json-stringify faz no JSON) antes do serializador, sem alterar o objeto do handler.
27
+ const stripper = new Ajv({ allErrors: true, coerceTypes: false, useDefaults: false, removeAdditional: 'all' });
28
+ addFormats(stripper);
29
+ const stripCache = new WeakMap();
30
+ /**
31
+ * Valida `data` contra `schema` sobre uma cópia, removendo campos não declarados.
32
+ * Retorna a cópia filtrada, ou os problemas encontrados.
33
+ */
34
+ export function stripToSchema(schema, data) {
35
+ let fn = stripCache.get(schema);
36
+ if (!fn) {
37
+ fn = stripper.compile(schema);
38
+ stripCache.set(schema, fn);
39
+ }
40
+ const copy = data === undefined ? undefined : JSON.parse(JSON.stringify(data));
41
+ return fn(copy) ? { ok: true, value: copy } : { ok: false, issues: formatErrors(fn.errors ?? []) };
42
+ }
43
+ const caches = new Map([
44
+ [coercing, new WeakMap()],
45
+ [strict, new WeakMap()],
46
+ ]);
47
+ function compile(schema, target) {
48
+ const ajv = target === 'body' ? strict : coercing;
49
+ const cache = caches.get(ajv);
50
+ let fn = cache.get(schema);
51
+ if (!fn) {
52
+ fn = ajv.compile(schema);
53
+ cache.set(schema, fn);
54
+ }
55
+ return fn;
56
+ }
57
+ /** Valida `data` contra `schema`. Retorna `null` se válido, ou a lista de problemas (B-02). */
58
+ export function validate(schema, data, target) {
59
+ const fn = compile(schema, target);
60
+ return fn(data) ? null : formatErrors(fn.errors ?? []);
61
+ }
62
+ export function formatErrors(errors) {
63
+ return errors.map((e) => {
64
+ const missing = e.keyword === 'required' ? `/${e.params.missingProperty}` : '';
65
+ return { path: `${e.instancePath}${missing}` || '/', message: e.message ?? 'invalid' };
66
+ });
67
+ }
68
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/schema/validation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { type Stats } from 'node:fs';
2
+ import type { Context } from '../core/context.ts';
3
+ /** Opções comuns a `app.static()` e `ctx.sendFile()` (F-13). */
4
+ export interface FileOptions {
5
+ /** `Cache-Control: max-age`, em segundos ou como `'7d'`, `'12h'`, `'30m'`, `'10s'`. Padrão: 0 (revalida sempre). */
6
+ maxAge?: number | string;
7
+ /** Acrescenta `immutable` ao `Cache-Control`, para arquivos com hash no nome. */
8
+ immutable?: boolean;
9
+ /** `ETag` fraco (tamanho + data de modificação), com 304 para `If-None-Match`. Padrão: `true`. */
10
+ etag?: boolean;
11
+ /** `Last-Modified`, com 304 para `If-Modified-Since`. Padrão: `true`. */
12
+ lastModified?: boolean;
13
+ /** Tipos por extensão, somados à tabela interna: `{ glb: 'model/gltf-binary' }`. */
14
+ mimeTypes?: Readonly<Record<string, string>>;
15
+ }
16
+ export interface StaticOptions extends FileOptions {
17
+ /** Arquivo servido para um diretório. Padrão: `'index.html'`; `false` desliga. Diretórios nunca são listados. */
18
+ index?: string | false;
19
+ /**
20
+ * SPA: arquivo (relativo à raiz) servido quando o path pedido não existe **e não tem extensão**
21
+ * (`/pedidos/42` → `index.html`, mas `/app.js` inexistente continua 404). Sai com `no-cache`.
22
+ */
23
+ fallback?: string;
24
+ /** Arquivos e pastas que começam com `.` (`.env`, `.git`): `'deny'` responde 404 (padrão) ou `'allow'`. */
25
+ dotfiles?: 'deny' | 'allow';
26
+ }
27
+ export interface SendFileOptions extends FileOptions {
28
+ /** Raiz que contém o arquivo; com ela, o path é relativo e não pode sair dela. Sem `root`, o path precisa ser absoluto. */
29
+ root?: string;
30
+ /** `Content-Disposition: attachment`. `true` usa o nome do arquivo; uma string define o nome do download. */
31
+ download?: boolean | string;
32
+ }
33
+ export declare function parseMaxAge(value: number | string | undefined): number;
34
+ /** Raiz real (symlinks resolvidos), conferida na configuração: um diretório que não existe falha cedo. */
35
+ export declare function resolveRoot(root: string): string;
36
+ /** Envia um arquivo já resolvido e conferido, com cache condicional e `Range` (F-13). */
37
+ export declare function sendResolvedFile(ctx: Context, path: string, s: Stats, opts: FileOptions & {
38
+ download?: boolean | string;
39
+ noCache?: boolean;
40
+ }): Promise<void>;
41
+ /** `ctx.sendFile()`: confere o caminho e envia (F-13). */
42
+ export declare function sendFile(ctx: Context, path: string, opts?: SendFileOptions): Promise<void>;
43
+ /** Handler de `app.static()`: resolve o resto do path dentro da raiz, com índice e fallback de SPA. */
44
+ export declare function staticHandler(rootInput: string, opts: StaticOptions): (ctx: Context) => Promise<void>;
45
+ //# sourceMappingURL=static.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../src/server/static.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,KAAK,EAAY,MAAM,SAAS,CAAC;AAG/E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAKlD,gEAAgE;AAChE,MAAM,WAAW,WAAW;IAC1B,oHAAoH;IACpH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kGAAkG;IAClG,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,yEAAyE;IACzE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oFAAoF;IACpF,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,iHAAiH;IACjH,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2GAA2G;IAC3G,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,2HAA2H;IAC3H,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6GAA6G;IAC7G,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC7B;AAID,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAStE;AAED,0GAA0G;AAC1G,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAShD;AAgED,yFAAyF;AACzF,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,KAAK,EACR,IAAI,EAAE,WAAW,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACrE,OAAO,CAAC,IAAI,CAAC,CA0Df;AAED,0DAA0D;AAC1D,wBAAsB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAapG;AAED,uGAAuG;AACvG,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAwBrG"}