node-fastify 5.8.3

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 (354) hide show
  1. package/.borp.yaml +3 -0
  2. package/.markdownlint-cli2.yaml +22 -0
  3. package/.prettierignore +1 -0
  4. package/GOVERNANCE.md +4 -0
  5. package/LICENSE +21 -0
  6. package/PROJECT_CHARTER.md +126 -0
  7. package/README.md +423 -0
  8. package/SECURITY.md +220 -0
  9. package/SPONSORS.md +24 -0
  10. package/build/build-error-serializer.js +35 -0
  11. package/build/build-validation.js +169 -0
  12. package/build/sync-version.js +11 -0
  13. package/docs/Guides/Benchmarking.md +60 -0
  14. package/docs/Guides/Database.md +321 -0
  15. package/docs/Guides/Delay-Accepting-Requests.md +608 -0
  16. package/docs/Guides/Detecting-When-Clients-Abort.md +172 -0
  17. package/docs/Guides/Ecosystem.md +726 -0
  18. package/docs/Guides/Fluent-Schema.md +127 -0
  19. package/docs/Guides/Getting-Started.md +620 -0
  20. package/docs/Guides/Index.md +43 -0
  21. package/docs/Guides/Migration-Guide-V3.md +287 -0
  22. package/docs/Guides/Migration-Guide-V4.md +267 -0
  23. package/docs/Guides/Migration-Guide-V5.md +727 -0
  24. package/docs/Guides/Plugins-Guide.md +520 -0
  25. package/docs/Guides/Prototype-Poisoning.md +383 -0
  26. package/docs/Guides/Recommendations.md +378 -0
  27. package/docs/Guides/Serverless.md +604 -0
  28. package/docs/Guides/Style-Guide.md +246 -0
  29. package/docs/Guides/Testing.md +481 -0
  30. package/docs/Guides/Write-Plugin.md +103 -0
  31. package/docs/Guides/Write-Type-Provider.md +34 -0
  32. package/docs/Reference/ContentTypeParser.md +271 -0
  33. package/docs/Reference/Decorators.md +436 -0
  34. package/docs/Reference/Encapsulation.md +194 -0
  35. package/docs/Reference/Errors.md +377 -0
  36. package/docs/Reference/HTTP2.md +94 -0
  37. package/docs/Reference/Hooks.md +958 -0
  38. package/docs/Reference/Index.md +73 -0
  39. package/docs/Reference/LTS.md +86 -0
  40. package/docs/Reference/Lifecycle.md +99 -0
  41. package/docs/Reference/Logging.md +268 -0
  42. package/docs/Reference/Middleware.md +79 -0
  43. package/docs/Reference/Plugins.md +245 -0
  44. package/docs/Reference/Principles.md +73 -0
  45. package/docs/Reference/Reply.md +1001 -0
  46. package/docs/Reference/Request.md +295 -0
  47. package/docs/Reference/Routes.md +802 -0
  48. package/docs/Reference/Server.md +2389 -0
  49. package/docs/Reference/Type-Providers.md +256 -0
  50. package/docs/Reference/TypeScript.md +1729 -0
  51. package/docs/Reference/Validation-and-Serialization.md +1130 -0
  52. package/docs/Reference/Warnings.md +58 -0
  53. package/docs/index.md +24 -0
  54. package/docs/resources/encapsulation_context.drawio +1 -0
  55. package/docs/resources/encapsulation_context.svg +3 -0
  56. package/eslint.config.js +35 -0
  57. package/examples/asyncawait.js +38 -0
  58. package/examples/benchmark/body.json +3 -0
  59. package/examples/benchmark/hooks-benchmark-async-await.js +44 -0
  60. package/examples/benchmark/hooks-benchmark.js +52 -0
  61. package/examples/benchmark/parser.js +47 -0
  62. package/examples/benchmark/simple.js +30 -0
  63. package/examples/benchmark/webstream.js +27 -0
  64. package/examples/hooks.js +91 -0
  65. package/examples/http2.js +39 -0
  66. package/examples/https.js +38 -0
  67. package/examples/parser.js +53 -0
  68. package/examples/plugin.js +12 -0
  69. package/examples/route-prefix.js +38 -0
  70. package/examples/shared-schema.js +38 -0
  71. package/examples/simple-stream.js +20 -0
  72. package/examples/simple.js +32 -0
  73. package/examples/simple.mjs +27 -0
  74. package/examples/typescript-server.ts +79 -0
  75. package/examples/use-plugin.js +29 -0
  76. package/fastify.d.ts +253 -0
  77. package/fastify.js +985 -0
  78. package/integration/server.js +29 -0
  79. package/integration/test.sh +23 -0
  80. package/lib/config-validator.js +1266 -0
  81. package/lib/content-type-parser.js +413 -0
  82. package/lib/content-type.js +160 -0
  83. package/lib/context.js +98 -0
  84. package/lib/decorate.js +152 -0
  85. package/lib/error-handler.js +173 -0
  86. package/lib/error-serializer.js +134 -0
  87. package/lib/error-status.js +14 -0
  88. package/lib/errors.js +516 -0
  89. package/lib/four-oh-four.js +190 -0
  90. package/lib/handle-request.js +195 -0
  91. package/lib/head-route.js +45 -0
  92. package/lib/hooks.js +429 -0
  93. package/lib/initial-config-validation.js +37 -0
  94. package/lib/logger-factory.js +136 -0
  95. package/lib/logger-pino.js +68 -0
  96. package/lib/noop-set.js +10 -0
  97. package/lib/plugin-override.js +90 -0
  98. package/lib/plugin-utils.js +169 -0
  99. package/lib/promise.js +23 -0
  100. package/lib/reply.js +1030 -0
  101. package/lib/req-id-gen-factory.js +52 -0
  102. package/lib/request.js +391 -0
  103. package/lib/route.js +686 -0
  104. package/lib/schema-controller.js +164 -0
  105. package/lib/schemas.js +207 -0
  106. package/lib/server.js +441 -0
  107. package/lib/symbols.js +71 -0
  108. package/lib/validation.js +280 -0
  109. package/lib/warnings.js +57 -0
  110. package/lib/wrap-thenable.js +84 -0
  111. package/package.json +225 -0
  112. package/scripts/validate-ecosystem-links.js +179 -0
  113. package/test/404s.test.js +2035 -0
  114. package/test/500s.test.js +422 -0
  115. package/test/allow-unsafe-regex.test.js +92 -0
  116. package/test/als.test.js +65 -0
  117. package/test/async-await.test.js +705 -0
  118. package/test/async-dispose.test.js +20 -0
  119. package/test/async_hooks.test.js +52 -0
  120. package/test/body-limit.test.js +224 -0
  121. package/test/buffer.test.js +74 -0
  122. package/test/build/error-serializer.test.js +36 -0
  123. package/test/build/version.test.js +14 -0
  124. package/test/build-certificate.js +109 -0
  125. package/test/bundler/README.md +29 -0
  126. package/test/bundler/esbuild/bundler-test.js +32 -0
  127. package/test/bundler/esbuild/package.json +10 -0
  128. package/test/bundler/esbuild/src/fail-plugin-version.js +14 -0
  129. package/test/bundler/esbuild/src/index.js +9 -0
  130. package/test/bundler/webpack/bundler-test.js +32 -0
  131. package/test/bundler/webpack/package.json +11 -0
  132. package/test/bundler/webpack/src/fail-plugin-version.js +14 -0
  133. package/test/bundler/webpack/src/index.js +9 -0
  134. package/test/bundler/webpack/webpack.config.js +15 -0
  135. package/test/case-insensitive.test.js +102 -0
  136. package/test/chainable.test.js +40 -0
  137. package/test/child-logger-factory.test.js +128 -0
  138. package/test/client-timeout.test.js +38 -0
  139. package/test/close-pipelining.test.js +78 -0
  140. package/test/close.test.js +706 -0
  141. package/test/conditional-pino.test.js +47 -0
  142. package/test/connection-timeout.test.js +42 -0
  143. package/test/constrained-routes.test.js +1138 -0
  144. package/test/content-length.test.js +174 -0
  145. package/test/content-parser.test.js +739 -0
  146. package/test/content-type.test.js +181 -0
  147. package/test/context-config.test.js +164 -0
  148. package/test/custom-http-server.test.js +118 -0
  149. package/test/custom-parser-async.test.js +59 -0
  150. package/test/custom-parser.0.test.js +701 -0
  151. package/test/custom-parser.1.test.js +266 -0
  152. package/test/custom-parser.2.test.js +91 -0
  153. package/test/custom-parser.3.test.js +208 -0
  154. package/test/custom-parser.4.test.js +218 -0
  155. package/test/custom-parser.5.test.js +130 -0
  156. package/test/custom-querystring-parser.test.js +129 -0
  157. package/test/decorator.test.js +1330 -0
  158. package/test/delete.test.js +344 -0
  159. package/test/diagnostics-channel/404.test.js +49 -0
  160. package/test/diagnostics-channel/async-delay-request.test.js +65 -0
  161. package/test/diagnostics-channel/async-request.test.js +64 -0
  162. package/test/diagnostics-channel/error-before-handler.test.js +35 -0
  163. package/test/diagnostics-channel/error-request.test.js +53 -0
  164. package/test/diagnostics-channel/error-status.test.js +123 -0
  165. package/test/diagnostics-channel/init.test.js +50 -0
  166. package/test/diagnostics-channel/sync-delay-request.test.js +49 -0
  167. package/test/diagnostics-channel/sync-request-reply.test.js +51 -0
  168. package/test/diagnostics-channel/sync-request.test.js +54 -0
  169. package/test/encapsulated-child-logger-factory.test.js +69 -0
  170. package/test/encapsulated-error-handler.test.js +237 -0
  171. package/test/esm/errorCodes.test.mjs +10 -0
  172. package/test/esm/esm.test.mjs +13 -0
  173. package/test/esm/index.test.js +8 -0
  174. package/test/esm/named-exports.mjs +14 -0
  175. package/test/esm/other.mjs +8 -0
  176. package/test/esm/plugin.mjs +8 -0
  177. package/test/fastify-instance.test.js +300 -0
  178. package/test/find-route.test.js +152 -0
  179. package/test/fluent-schema.test.js +209 -0
  180. package/test/genReqId.test.js +426 -0
  181. package/test/handler-context.test.js +45 -0
  182. package/test/handler-timeout.test.js +367 -0
  183. package/test/has-route.test.js +88 -0
  184. package/test/header-overflow.test.js +55 -0
  185. package/test/helper.js +496 -0
  186. package/test/hooks-async.test.js +1099 -0
  187. package/test/hooks.on-listen.test.js +1162 -0
  188. package/test/hooks.on-ready.test.js +421 -0
  189. package/test/hooks.test.js +3578 -0
  190. package/test/http-methods/copy.test.js +35 -0
  191. package/test/http-methods/custom-http-methods.test.js +114 -0
  192. package/test/http-methods/get.test.js +412 -0
  193. package/test/http-methods/head.test.js +263 -0
  194. package/test/http-methods/lock.test.js +108 -0
  195. package/test/http-methods/mkcalendar.test.js +143 -0
  196. package/test/http-methods/mkcol.test.js +35 -0
  197. package/test/http-methods/move.test.js +42 -0
  198. package/test/http-methods/propfind.test.js +136 -0
  199. package/test/http-methods/proppatch.test.js +105 -0
  200. package/test/http-methods/report.test.js +142 -0
  201. package/test/http-methods/search.test.js +233 -0
  202. package/test/http-methods/trace.test.js +21 -0
  203. package/test/http-methods/unlock.test.js +38 -0
  204. package/test/http2/closing.test.js +270 -0
  205. package/test/http2/constraint.test.js +109 -0
  206. package/test/http2/head.test.js +34 -0
  207. package/test/http2/plain.test.js +68 -0
  208. package/test/http2/secure-with-fallback.test.js +113 -0
  209. package/test/http2/secure.test.js +67 -0
  210. package/test/http2/unknown-http-method.test.js +34 -0
  211. package/test/https/custom-https-server.test.js +58 -0
  212. package/test/https/https.test.js +136 -0
  213. package/test/imports.test.js +17 -0
  214. package/test/inject.test.js +502 -0
  215. package/test/input-validation.js +335 -0
  216. package/test/internals/all.test.js +38 -0
  217. package/test/internals/content-type-parser.test.js +111 -0
  218. package/test/internals/context.test.js +31 -0
  219. package/test/internals/decorator.test.js +156 -0
  220. package/test/internals/errors.test.js +982 -0
  221. package/test/internals/handle-request.test.js +270 -0
  222. package/test/internals/hook-runner.test.js +449 -0
  223. package/test/internals/hooks.test.js +96 -0
  224. package/test/internals/initial-config.test.js +383 -0
  225. package/test/internals/logger.test.js +163 -0
  226. package/test/internals/plugin.test.js +170 -0
  227. package/test/internals/promise.test.js +63 -0
  228. package/test/internals/reply-serialize.test.js +714 -0
  229. package/test/internals/reply.test.js +1920 -0
  230. package/test/internals/req-id-gen-factory.test.js +133 -0
  231. package/test/internals/request-validate.test.js +1402 -0
  232. package/test/internals/request.test.js +506 -0
  233. package/test/internals/schema-controller-perf.test.js +40 -0
  234. package/test/internals/server.test.js +91 -0
  235. package/test/internals/validation.test.js +352 -0
  236. package/test/issue-4959.test.js +118 -0
  237. package/test/keep-alive-timeout.test.js +42 -0
  238. package/test/listen.1.test.js +154 -0
  239. package/test/listen.2.test.js +113 -0
  240. package/test/listen.3.test.js +83 -0
  241. package/test/listen.4.test.js +168 -0
  242. package/test/listen.5.test.js +122 -0
  243. package/test/logger/instantiation.test.js +341 -0
  244. package/test/logger/logger-test-utils.js +47 -0
  245. package/test/logger/logging.test.js +460 -0
  246. package/test/logger/options.test.js +579 -0
  247. package/test/logger/request.test.js +292 -0
  248. package/test/logger/response.test.js +183 -0
  249. package/test/logger/tap-parallel-not-ok +0 -0
  250. package/test/max-requests-per-socket.test.js +113 -0
  251. package/test/middleware.test.js +37 -0
  252. package/test/noop-set.test.js +19 -0
  253. package/test/nullable-validation.test.js +187 -0
  254. package/test/options.error-handler.test.js +5 -0
  255. package/test/options.test.js +5 -0
  256. package/test/output-validation.test.js +140 -0
  257. package/test/patch.error-handler.test.js +5 -0
  258. package/test/patch.test.js +5 -0
  259. package/test/plugin.1.test.js +230 -0
  260. package/test/plugin.2.test.js +314 -0
  261. package/test/plugin.3.test.js +287 -0
  262. package/test/plugin.4.test.js +504 -0
  263. package/test/plugin.helper.js +8 -0
  264. package/test/plugin.name.display.js +10 -0
  265. package/test/post-empty-body.test.js +38 -0
  266. package/test/pretty-print.test.js +366 -0
  267. package/test/promises.test.js +125 -0
  268. package/test/proto-poisoning.test.js +145 -0
  269. package/test/put.error-handler.test.js +5 -0
  270. package/test/put.test.js +5 -0
  271. package/test/register.test.js +184 -0
  272. package/test/reply-code.test.js +148 -0
  273. package/test/reply-early-hints.test.js +100 -0
  274. package/test/reply-error.test.js +815 -0
  275. package/test/reply-trailers.test.js +445 -0
  276. package/test/reply-web-stream-locked.test.js +37 -0
  277. package/test/request-error.test.js +624 -0
  278. package/test/request-header-host.test.js +339 -0
  279. package/test/request-id.test.js +118 -0
  280. package/test/request-timeout.test.js +53 -0
  281. package/test/route-hooks.test.js +635 -0
  282. package/test/route-prefix.test.js +904 -0
  283. package/test/route-shorthand.test.js +48 -0
  284. package/test/route.1.test.js +259 -0
  285. package/test/route.2.test.js +100 -0
  286. package/test/route.3.test.js +213 -0
  287. package/test/route.4.test.js +127 -0
  288. package/test/route.5.test.js +211 -0
  289. package/test/route.6.test.js +306 -0
  290. package/test/route.7.test.js +406 -0
  291. package/test/route.8.test.js +225 -0
  292. package/test/router-options.test.js +1108 -0
  293. package/test/same-shape.test.js +124 -0
  294. package/test/schema-examples.test.js +661 -0
  295. package/test/schema-feature.test.js +2198 -0
  296. package/test/schema-serialization.test.js +1171 -0
  297. package/test/schema-special-usage.test.js +1348 -0
  298. package/test/schema-validation.test.js +1572 -0
  299. package/test/scripts/validate-ecosystem-links.test.js +339 -0
  300. package/test/serialize-response.test.js +186 -0
  301. package/test/server.test.js +347 -0
  302. package/test/set-error-handler.test.js +69 -0
  303. package/test/skip-reply-send.test.js +317 -0
  304. package/test/stream-serializers.test.js +40 -0
  305. package/test/stream.1.test.js +94 -0
  306. package/test/stream.2.test.js +129 -0
  307. package/test/stream.3.test.js +198 -0
  308. package/test/stream.4.test.js +176 -0
  309. package/test/stream.5.test.js +188 -0
  310. package/test/sync-routes.test.js +32 -0
  311. package/test/throw.test.js +359 -0
  312. package/test/toolkit.js +63 -0
  313. package/test/trust-proxy.test.js +162 -0
  314. package/test/type-provider.test.js +22 -0
  315. package/test/types/content-type-parser.test-d.ts +72 -0
  316. package/test/types/decorate-request-reply.test-d.ts +18 -0
  317. package/test/types/dummy-plugin.ts +9 -0
  318. package/test/types/errors.test-d.ts +90 -0
  319. package/test/types/fastify.test-d.ts +352 -0
  320. package/test/types/hooks.test-d.ts +550 -0
  321. package/test/types/import.ts +2 -0
  322. package/test/types/instance.test-d.ts +588 -0
  323. package/test/types/logger.test-d.ts +277 -0
  324. package/test/types/plugin.test-d.ts +97 -0
  325. package/test/types/register.test-d.ts +237 -0
  326. package/test/types/reply.test-d.ts +254 -0
  327. package/test/types/request.test-d.ts +188 -0
  328. package/test/types/route.test-d.ts +553 -0
  329. package/test/types/schema.test-d.ts +135 -0
  330. package/test/types/serverFactory.test-d.ts +37 -0
  331. package/test/types/type-provider.test-d.ts +1213 -0
  332. package/test/types/using.test-d.ts +17 -0
  333. package/test/upgrade.test.js +52 -0
  334. package/test/url-rewriting.test.js +122 -0
  335. package/test/use-semicolon-delimiter.test.js +168 -0
  336. package/test/validation-error-handling.test.js +900 -0
  337. package/test/versioned-routes.test.js +603 -0
  338. package/test/web-api.test.js +616 -0
  339. package/test/wrap-thenable.test.js +30 -0
  340. package/types/content-type-parser.d.ts +75 -0
  341. package/types/context.d.ts +22 -0
  342. package/types/errors.d.ts +92 -0
  343. package/types/hooks.d.ts +875 -0
  344. package/types/instance.d.ts +609 -0
  345. package/types/logger.d.ts +107 -0
  346. package/types/plugin.d.ts +44 -0
  347. package/types/register.d.ts +42 -0
  348. package/types/reply.d.ts +81 -0
  349. package/types/request.d.ts +95 -0
  350. package/types/route.d.ts +199 -0
  351. package/types/schema.d.ts +61 -0
  352. package/types/server-factory.d.ts +19 -0
  353. package/types/type-provider.d.ts +130 -0
  354. package/types/utils.d.ts +98 -0
@@ -0,0 +1,616 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const Fastify = require('../fastify')
5
+ const fs = require('node:fs')
6
+ const { Readable } = require('node:stream')
7
+ const { fetch: undiciFetch } = require('undici')
8
+ const http = require('node:http')
9
+ const { setTimeout: sleep } = require('node:timers/promises')
10
+
11
+ test('should response with a ReadableStream', async (t) => {
12
+ t.plan(2)
13
+
14
+ const fastify = Fastify()
15
+
16
+ fastify.get('/', function (request, reply) {
17
+ const stream = fs.createReadStream(__filename)
18
+ reply.code(200).send(Readable.toWeb(stream))
19
+ })
20
+
21
+ const {
22
+ statusCode,
23
+ body
24
+ } = await fastify.inject({ method: 'GET', path: '/' })
25
+
26
+ const expected = await fs.promises.readFile(__filename)
27
+
28
+ t.assert.strictEqual(statusCode, 200)
29
+ t.assert.strictEqual(expected.toString(), body.toString())
30
+ })
31
+
32
+ test('should response with a Response', async (t) => {
33
+ t.plan(3)
34
+
35
+ const fastify = Fastify()
36
+
37
+ fastify.get('/', function (request, reply) {
38
+ const stream = fs.createReadStream(__filename)
39
+ reply.send(new Response(Readable.toWeb(stream), {
40
+ status: 200,
41
+ headers: {
42
+ hello: 'world'
43
+ }
44
+ }))
45
+ })
46
+
47
+ const {
48
+ statusCode,
49
+ headers,
50
+ body
51
+ } = await fastify.inject({ method: 'GET', path: '/' })
52
+
53
+ const expected = await fs.promises.readFile(__filename)
54
+
55
+ t.assert.strictEqual(statusCode, 200)
56
+ t.assert.strictEqual(expected.toString(), body.toString())
57
+ t.assert.strictEqual(headers.hello, 'world')
58
+ })
59
+
60
+ test('should response with a Response 204', async (t) => {
61
+ t.plan(3)
62
+
63
+ const fastify = Fastify()
64
+
65
+ fastify.get('/', function (request, reply) {
66
+ reply.send(new Response(null, {
67
+ status: 204,
68
+ headers: {
69
+ hello: 'world'
70
+ }
71
+ }))
72
+ })
73
+
74
+ const {
75
+ statusCode,
76
+ headers,
77
+ body
78
+ } = await fastify.inject({ method: 'GET', path: '/' })
79
+
80
+ t.assert.strictEqual(statusCode, 204)
81
+ t.assert.strictEqual(body, '')
82
+ t.assert.strictEqual(headers.hello, 'world')
83
+ })
84
+
85
+ test('should response with a Response 304', async (t) => {
86
+ t.plan(3)
87
+
88
+ const fastify = Fastify()
89
+
90
+ fastify.get('/', function (request, reply) {
91
+ reply.send(new Response(null, {
92
+ status: 304,
93
+ headers: {
94
+ hello: 'world'
95
+ }
96
+ }))
97
+ })
98
+
99
+ const {
100
+ statusCode,
101
+ headers,
102
+ body
103
+ } = await fastify.inject({ method: 'GET', path: '/' })
104
+
105
+ t.assert.strictEqual(statusCode, 304)
106
+ t.assert.strictEqual(body, '')
107
+ t.assert.strictEqual(headers.hello, 'world')
108
+ })
109
+
110
+ test('should response with a Response without body', async (t) => {
111
+ t.plan(3)
112
+
113
+ const fastify = Fastify()
114
+
115
+ fastify.get('/', function (request, reply) {
116
+ reply.send(new Response(null, {
117
+ status: 200,
118
+ headers: {
119
+ hello: 'world'
120
+ }
121
+ }))
122
+ })
123
+
124
+ const {
125
+ statusCode,
126
+ headers,
127
+ body
128
+ } = await fastify.inject({ method: 'GET', path: '/' })
129
+
130
+ t.assert.strictEqual(statusCode, 200)
131
+ t.assert.strictEqual(body, '')
132
+ t.assert.strictEqual(headers.hello, 'world')
133
+ })
134
+
135
+ test('able to use in onSend hook - ReadableStream', async (t) => {
136
+ t.plan(4)
137
+
138
+ const fastify = Fastify()
139
+
140
+ fastify.get('/', function (request, reply) {
141
+ const stream = fs.createReadStream(__filename)
142
+ reply.code(500).send(Readable.toWeb(stream))
143
+ })
144
+
145
+ fastify.addHook('onSend', (request, reply, payload, done) => {
146
+ t.assert.strictEqual(Object.prototype.toString.call(payload), '[object ReadableStream]')
147
+ done(null, new Response(payload, {
148
+ status: 200,
149
+ headers: {
150
+ hello: 'world'
151
+ }
152
+ }))
153
+ })
154
+
155
+ const {
156
+ statusCode,
157
+ headers,
158
+ body
159
+ } = await fastify.inject({ method: 'GET', path: '/' })
160
+
161
+ const expected = await fs.promises.readFile(__filename)
162
+
163
+ t.assert.strictEqual(statusCode, 200)
164
+ t.assert.strictEqual(expected.toString(), body.toString())
165
+ t.assert.strictEqual(headers.hello, 'world')
166
+ })
167
+
168
+ test('able to use in onSend hook - Response', async (t) => {
169
+ t.plan(4)
170
+
171
+ const fastify = Fastify()
172
+
173
+ fastify.get('/', function (request, reply) {
174
+ const stream = fs.createReadStream(__filename)
175
+ reply.send(new Response(Readable.toWeb(stream), {
176
+ status: 500,
177
+ headers: {
178
+ hello: 'world'
179
+ }
180
+ }))
181
+ })
182
+
183
+ fastify.addHook('onSend', (request, reply, payload, done) => {
184
+ t.assert.strictEqual(Object.prototype.toString.call(payload), '[object Response]')
185
+ done(null, new Response(payload.body, {
186
+ status: 200,
187
+ headers: payload.headers
188
+ }))
189
+ })
190
+
191
+ const {
192
+ statusCode,
193
+ headers,
194
+ body
195
+ } = await fastify.inject({ method: 'GET', path: '/' })
196
+
197
+ const expected = await fs.promises.readFile(__filename)
198
+
199
+ t.assert.strictEqual(statusCode, 200)
200
+ t.assert.strictEqual(expected.toString(), body.toString())
201
+ t.assert.strictEqual(headers.hello, 'world')
202
+ })
203
+
204
+ test('Error when Response.bodyUsed', async (t) => {
205
+ t.plan(4)
206
+
207
+ const expected = await fs.promises.readFile(__filename)
208
+
209
+ const fastify = Fastify()
210
+
211
+ fastify.get('/', async function (request, reply) {
212
+ const stream = fs.createReadStream(__filename)
213
+ const response = new Response(Readable.toWeb(stream), {
214
+ status: 200,
215
+ headers: {
216
+ hello: 'world'
217
+ }
218
+ })
219
+ const file = await response.text()
220
+ t.assert.strictEqual(expected.toString(), file)
221
+ t.assert.strictEqual(response.bodyUsed, true)
222
+ return reply.send(response)
223
+ })
224
+
225
+ const response = await fastify.inject({ method: 'GET', path: '/' })
226
+
227
+ t.assert.strictEqual(response.statusCode, 500)
228
+ const body = response.json()
229
+ t.assert.strictEqual(body.code, 'FST_ERR_REP_RESPONSE_BODY_CONSUMED')
230
+ })
231
+
232
+ test('Error when Response.body.locked', async (t) => {
233
+ t.plan(3)
234
+
235
+ const fastify = Fastify()
236
+
237
+ fastify.get('/', async function (request, reply) {
238
+ const stream = Readable.toWeb(fs.createReadStream(__filename))
239
+ const response = new Response(stream, {
240
+ status: 200,
241
+ headers: {
242
+ hello: 'world'
243
+ }
244
+ })
245
+ stream.getReader()
246
+ t.assert.strictEqual(stream.locked, true)
247
+ return reply.send(response)
248
+ })
249
+
250
+ const response = await fastify.inject({ method: 'GET', path: '/' })
251
+
252
+ t.assert.strictEqual(response.statusCode, 500)
253
+ const body = response.json()
254
+ t.assert.strictEqual(body.code, 'FST_ERR_REP_READABLE_STREAM_LOCKED')
255
+ })
256
+
257
+ test('Error when ReadableStream.locked', async (t) => {
258
+ t.plan(3)
259
+
260
+ const fastify = Fastify()
261
+
262
+ fastify.get('/', async function (request, reply) {
263
+ const stream = Readable.toWeb(fs.createReadStream(__filename))
264
+ stream.getReader()
265
+ t.assert.strictEqual(stream.locked, true)
266
+ return reply.send(stream)
267
+ })
268
+
269
+ const response = await fastify.inject({ method: 'GET', path: '/' })
270
+
271
+ t.assert.strictEqual(response.statusCode, 500)
272
+ const body = response.json()
273
+ t.assert.strictEqual(body.code, 'FST_ERR_REP_READABLE_STREAM_LOCKED')
274
+ })
275
+
276
+ test('allow to pipe with fetch', async (t) => {
277
+ t.plan(2)
278
+ const abortController = new AbortController()
279
+ const { signal } = abortController
280
+
281
+ const fastify = Fastify()
282
+ t.after(() => {
283
+ fastify.close()
284
+ abortController.abort()
285
+ })
286
+
287
+ fastify.get('/', function (request, reply) {
288
+ return fetch(`${fastify.listeningOrigin}/fetch`, {
289
+ method: 'GET',
290
+ signal
291
+ })
292
+ })
293
+
294
+ fastify.get('/fetch', function async (request, reply) {
295
+ reply.code(200).send({ ok: true })
296
+ })
297
+
298
+ await fastify.listen()
299
+
300
+ const response = await fastify.inject({ method: 'GET', path: '/' })
301
+
302
+ t.assert.strictEqual(response.statusCode, 200)
303
+ t.assert.deepStrictEqual(response.json(), { ok: true })
304
+ })
305
+
306
+ test('allow to pipe with undici.fetch', async (t) => {
307
+ t.plan(2)
308
+ const abortController = new AbortController()
309
+ const { signal } = abortController
310
+
311
+ const fastify = Fastify()
312
+ t.after(() => {
313
+ fastify.close()
314
+ abortController.abort()
315
+ })
316
+
317
+ fastify.get('/', function (request, reply) {
318
+ return undiciFetch(`${fastify.listeningOrigin}/fetch`, {
319
+ method: 'GET',
320
+ signal
321
+ })
322
+ })
323
+
324
+ fastify.get('/fetch', function (request, reply) {
325
+ reply.code(200).send({ ok: true })
326
+ })
327
+
328
+ await fastify.listen()
329
+
330
+ const response = await fastify.inject({ method: 'GET', path: '/' })
331
+
332
+ t.assert.strictEqual(response.statusCode, 200)
333
+ t.assert.deepStrictEqual(response.json(), { ok: true })
334
+ })
335
+
336
+ test('WebStream error before headers sent should trigger error handler', async (t) => {
337
+ t.plan(2)
338
+
339
+ const fastify = Fastify()
340
+
341
+ fastify.get('/', function (request, reply) {
342
+ const stream = new ReadableStream({
343
+ start (controller) {
344
+ controller.error(new Error('stream error'))
345
+ }
346
+ })
347
+ reply.send(stream)
348
+ })
349
+
350
+ const response = await fastify.inject({ method: 'GET', path: '/' })
351
+
352
+ t.assert.strictEqual(response.statusCode, 500)
353
+ t.assert.strictEqual(response.json().message, 'stream error')
354
+ })
355
+
356
+ test('WebStream error after headers sent should destroy response', (t, done) => {
357
+ t.plan(2)
358
+
359
+ const fastify = Fastify()
360
+ t.after(() => fastify.close())
361
+
362
+ fastify.get('/', function (request, reply) {
363
+ const stream = new ReadableStream({
364
+ start (controller) {
365
+ controller.enqueue('hello')
366
+ },
367
+ pull (controller) {
368
+ setTimeout(() => {
369
+ controller.error(new Error('stream error'))
370
+ }, 10)
371
+ }
372
+ })
373
+ reply.header('content-type', 'text/plain').send(stream)
374
+ })
375
+
376
+ fastify.listen({ port: 0 }, err => {
377
+ t.assert.ifError(err)
378
+
379
+ let finished = false
380
+ http.get(`http://localhost:${fastify.server.address().port}`, (res) => {
381
+ res.on('close', () => {
382
+ if (!finished) {
383
+ finished = true
384
+ t.assert.ok('response closed')
385
+ done()
386
+ }
387
+ })
388
+ res.resume()
389
+ })
390
+ })
391
+ })
392
+
393
+ test('WebStream should cancel reader when response is destroyed', (t, done) => {
394
+ t.plan(2)
395
+
396
+ const fastify = Fastify()
397
+ t.after(() => fastify.close())
398
+
399
+ let readerCancelled = false
400
+
401
+ fastify.get('/', function (request, reply) {
402
+ const stream = new ReadableStream({
403
+ start (controller) {
404
+ controller.enqueue('hello')
405
+ },
406
+ pull (controller) {
407
+ return new Promise(() => {})
408
+ },
409
+ cancel () {
410
+ readerCancelled = true
411
+ }
412
+ })
413
+ reply.header('content-type', 'text/plain').send(stream)
414
+ })
415
+
416
+ fastify.listen({ port: 0 }, err => {
417
+ t.assert.ifError(err)
418
+
419
+ const req = http.get(`http://localhost:${fastify.server.address().port}`, (res) => {
420
+ res.once('data', () => {
421
+ req.destroy()
422
+ setTimeout(() => {
423
+ t.assert.strictEqual(readerCancelled, true)
424
+ done()
425
+ }, 50)
426
+ })
427
+ })
428
+ })
429
+ })
430
+
431
+ test('WebStream should respect backpressure', async (t) => {
432
+ t.plan(3)
433
+
434
+ const fastify = Fastify()
435
+ t.after(() => fastify.close())
436
+
437
+ let drainEmittedAt = 0
438
+ let secondWriteAt = 0
439
+ let resolveSecondWrite
440
+ const secondWrite = new Promise((resolve) => {
441
+ resolveSecondWrite = resolve
442
+ })
443
+
444
+ fastify.get('/', function (request, reply) {
445
+ const raw = reply.raw
446
+ const originalWrite = raw.write.bind(raw)
447
+ const bufferedChunks = []
448
+ let wroteFirstChunk = false
449
+
450
+ raw.once('drain', () => {
451
+ for (const bufferedChunk of bufferedChunks) {
452
+ originalWrite(bufferedChunk)
453
+ }
454
+ })
455
+
456
+ raw.write = function (chunk, encoding, cb) {
457
+ if (!wroteFirstChunk) {
458
+ wroteFirstChunk = true
459
+ bufferedChunks.push(Buffer.from(chunk))
460
+ sleep(100).then(() => {
461
+ drainEmittedAt = Date.now()
462
+ raw.emit('drain')
463
+ })
464
+ if (typeof cb === 'function') {
465
+ cb()
466
+ }
467
+ return false
468
+ }
469
+ if (!secondWriteAt) {
470
+ secondWriteAt = Date.now()
471
+ resolveSecondWrite()
472
+ }
473
+ return originalWrite(chunk, encoding, cb)
474
+ }
475
+
476
+ const stream = new ReadableStream({
477
+ start (controller) {
478
+ controller.enqueue(Buffer.from('chunk-1'))
479
+ },
480
+ pull (controller) {
481
+ controller.enqueue(Buffer.from('chunk-2'))
482
+ controller.close()
483
+ }
484
+ })
485
+
486
+ reply.header('content-type', 'text/plain').send(stream)
487
+ })
488
+
489
+ await fastify.listen({ port: 0 })
490
+
491
+ const response = await undiciFetch(`http://localhost:${fastify.server.address().port}/`)
492
+ const bodyPromise = response.text()
493
+
494
+ await secondWrite
495
+ await sleep(120)
496
+ const body = await bodyPromise
497
+
498
+ t.assert.strictEqual(response.status, 200)
499
+ t.assert.strictEqual(body, 'chunk-1chunk-2')
500
+ t.assert.ok(secondWriteAt >= drainEmittedAt)
501
+ })
502
+
503
+ test('WebStream should stop reading on drain after response destroy', async (t) => {
504
+ t.plan(2)
505
+
506
+ const fastify = Fastify()
507
+ t.after(() => fastify.close())
508
+
509
+ let cancelCalled = false
510
+ let resolveCancel
511
+ const cancelPromise = new Promise((resolve) => {
512
+ resolveCancel = resolve
513
+ })
514
+
515
+ fastify.get('/', function (request, reply) {
516
+ const raw = reply.raw
517
+ const originalWrite = raw.write.bind(raw)
518
+ let firstWrite = true
519
+
520
+ raw.write = function (chunk, encoding, cb) {
521
+ if (firstWrite) {
522
+ firstWrite = false
523
+ if (typeof cb === 'function') {
524
+ cb()
525
+ }
526
+ queueMicrotask(() => {
527
+ raw.destroy()
528
+ raw.emit('drain')
529
+ })
530
+ return false
531
+ }
532
+ return originalWrite(chunk, encoding, cb)
533
+ }
534
+
535
+ const stream = new ReadableStream({
536
+ start (controller) {
537
+ controller.enqueue(Buffer.from('chunk-1'))
538
+ },
539
+ pull (controller) {
540
+ controller.enqueue(Buffer.from('chunk-2'))
541
+ controller.close()
542
+ },
543
+ cancel () {
544
+ cancelCalled = true
545
+ resolveCancel()
546
+ }
547
+ })
548
+
549
+ reply.header('content-type', 'text/plain').send(stream)
550
+ })
551
+
552
+ await new Promise((resolve, reject) => {
553
+ fastify.listen({ port: 0 }, err => {
554
+ if (err) return reject(err)
555
+ resolve()
556
+ })
557
+ })
558
+
559
+ await new Promise((resolve, reject) => {
560
+ const req = http.get(`http://localhost:${fastify.server.address().port}/`, (res) => {
561
+ res.once('close', resolve)
562
+ res.resume()
563
+ })
564
+ req.once('error', (err) => {
565
+ if (err.code === 'ECONNRESET') {
566
+ resolve()
567
+ } else {
568
+ reject(err)
569
+ }
570
+ })
571
+ })
572
+
573
+ await cancelPromise
574
+ t.assert.ok(true, 'response interrupted as expected')
575
+ t.assert.strictEqual(cancelCalled, true)
576
+ })
577
+
578
+ test('WebStream should warn when headers already sent', async (t) => {
579
+ t.plan(2)
580
+
581
+ let warnCalled = false
582
+ const spyLogger = {
583
+ level: 'warn',
584
+ fatal: () => { },
585
+ error: () => { },
586
+ warn: (msg) => {
587
+ if (typeof msg === 'string' && msg.includes('use res.writeHead in stream mode')) {
588
+ warnCalled = true
589
+ }
590
+ },
591
+ info: () => { },
592
+ debug: () => { },
593
+ trace: () => { },
594
+ child: () => spyLogger
595
+ }
596
+
597
+ const fastify = Fastify({ loggerInstance: spyLogger })
598
+ t.after(() => fastify.close())
599
+
600
+ fastify.get('/', function (request, reply) {
601
+ reply.raw.writeHead(200, { 'content-type': 'text/plain' })
602
+ const stream = new ReadableStream({
603
+ start (controller) {
604
+ controller.enqueue('hello')
605
+ controller.close()
606
+ }
607
+ })
608
+ reply.send(stream)
609
+ })
610
+
611
+ await fastify.listen({ port: 0 })
612
+
613
+ const response = await fetch(`http://localhost:${fastify.server.address().port}/`)
614
+ t.assert.strictEqual(response.status, 200)
615
+ t.assert.strictEqual(warnCalled, true)
616
+ })
@@ -0,0 +1,30 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const { kReplyHijacked } = require('../lib/symbols')
5
+ const wrapThenable = require('../lib/wrap-thenable')
6
+ const Reply = require('../lib/reply')
7
+
8
+ test('should resolve immediately when reply[kReplyHijacked] is true', async t => {
9
+ await new Promise(resolve => {
10
+ const reply = {}
11
+ reply[kReplyHijacked] = true
12
+ const thenable = Promise.resolve()
13
+ wrapThenable(thenable, reply)
14
+ resolve()
15
+ })
16
+ })
17
+
18
+ test('should reject immediately when reply[kReplyHijacked] is true', t => {
19
+ t.plan(1)
20
+ const reply = new Reply({}, {}, {})
21
+ reply[kReplyHijacked] = true
22
+ reply.log = {
23
+ error: ({ err }) => {
24
+ t.assert.strictEqual(err.message, 'Reply sent already')
25
+ }
26
+ }
27
+
28
+ const thenable = Promise.reject(new Error('Reply sent already'))
29
+ wrapThenable(thenable, reply)
30
+ })
@@ -0,0 +1,75 @@
1
+ import { RawServerBase, RawServerDefault, RawRequestDefaultExpression } from './utils'
2
+ import { FastifyRequest } from './request'
3
+ import { RouteGenericInterface } from './route'
4
+ import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider'
5
+ import { FastifySchema } from './schema'
6
+
7
+ type ContentTypeParserDoneFunction = (err: Error | null, body?: any) => void
8
+
9
+ /**
10
+ * Body parser method that operators on request body
11
+ */
12
+ export type FastifyBodyParser<
13
+ RawBody extends string | Buffer,
14
+ RawServer extends RawServerBase = RawServerDefault,
15
+ RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
16
+ RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
17
+ SchemaCompiler extends FastifySchema = FastifySchema,
18
+ TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
19
+ > = ((request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>, rawBody: RawBody, done: ContentTypeParserDoneFunction) => void)
20
+ | ((request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>, rawBody: RawBody) => Promise<any>)
21
+
22
+ /**
23
+ * Content Type Parser method that operates on request content
24
+ */
25
+ export type FastifyContentTypeParser<
26
+ RawServer extends RawServerBase = RawServerDefault,
27
+ RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
28
+ RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
29
+ SchemaCompiler extends FastifySchema = FastifySchema,
30
+ TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
31
+ > = ((request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>, payload: RawRequest) => Promise<any>)
32
+ | ((request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>, payload: RawRequest, done: ContentTypeParserDoneFunction) => void)
33
+
34
+ /**
35
+ * Natively, Fastify only supports 'application/json' and 'text/plain' content types. The default charset is utf-8. If you need to support different content types, you can use the addContentTypeParser API. The default JSON and/or plain text parser can be changed.
36
+ */
37
+ export interface AddContentTypeParser<
38
+ RawServer extends RawServerBase = RawServerDefault,
39
+ RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
40
+ RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
41
+ SchemaCompiler extends FastifySchema = FastifySchema,
42
+ TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
43
+ > {
44
+ (
45
+ contentType: string | string[] | RegExp,
46
+ opts: {
47
+ bodyLimit?: number;
48
+ },
49
+ parser: FastifyContentTypeParser<RawServer, RawRequest, RouteGeneric, SchemaCompiler, TypeProvider>
50
+ ): void;
51
+ (contentType: string | string[] | RegExp, parser: FastifyContentTypeParser<RawServer, RawRequest, RouteGeneric, SchemaCompiler, TypeProvider>): void;
52
+ <parseAs extends string | Buffer>(
53
+ contentType: string | string[] | RegExp,
54
+ opts: {
55
+ parseAs: parseAs extends Buffer ? 'buffer' : 'string';
56
+ bodyLimit?: number;
57
+ },
58
+ parser: FastifyBodyParser<parseAs, RawServer, RawRequest, RouteGeneric, SchemaCompiler, TypeProvider>
59
+ ): void;
60
+ }
61
+
62
+ /**
63
+ * Checks for a type parser of a content type
64
+ */
65
+ export type hasContentTypeParser = (contentType: string | RegExp) => boolean
66
+
67
+ export type ProtoAction = 'error' | 'remove' | 'ignore'
68
+
69
+ export type ConstructorAction = 'error' | 'remove' | 'ignore'
70
+
71
+ export type getDefaultJsonParser = (onProtoPoisoning: ProtoAction, onConstructorPoisoning: ConstructorAction) => FastifyBodyParser<string>
72
+
73
+ export type removeContentTypeParser = (contentType: string | RegExp | (string | RegExp)[]) => void
74
+
75
+ export type removeAllContentTypeParsers = () => void