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,123 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const Fastify = require('../..')
5
+ const statusCodes = require('node:http').STATUS_CODES
6
+ const diagnostics = require('node:diagnostics_channel')
7
+
8
+ test('diagnostics channel error event should report correct status code', async (t) => {
9
+ t.plan(3)
10
+ const fastify = Fastify()
11
+ t.after(() => fastify.close())
12
+
13
+ let diagnosticsStatusCode
14
+
15
+ const channel = diagnostics.channel('tracing:fastify.request.handler:error')
16
+ const handler = (msg) => {
17
+ diagnosticsStatusCode = msg.reply.statusCode
18
+ }
19
+ channel.subscribe(handler)
20
+ t.after(() => channel.unsubscribe(handler))
21
+
22
+ fastify.get('/', async () => {
23
+ const err = new Error('test error')
24
+ err.statusCode = 503
25
+ throw err
26
+ })
27
+
28
+ const res = await fastify.inject('/')
29
+
30
+ t.assert.strictEqual(res.statusCode, 503)
31
+ t.assert.strictEqual(diagnosticsStatusCode, 503, 'diagnostics channel should report correct status code')
32
+ t.assert.strictEqual(diagnosticsStatusCode, res.statusCode, 'diagnostics status should match response status')
33
+ })
34
+
35
+ test('diagnostics channel error event should report 500 for errors without status', async (t) => {
36
+ t.plan(3)
37
+ const fastify = Fastify()
38
+ t.after(() => fastify.close())
39
+
40
+ let diagnosticsStatusCode
41
+
42
+ const channel = diagnostics.channel('tracing:fastify.request.handler:error')
43
+ const handler = (msg) => {
44
+ diagnosticsStatusCode = msg.reply.statusCode
45
+ }
46
+ channel.subscribe(handler)
47
+ t.after(() => channel.unsubscribe(handler))
48
+
49
+ fastify.get('/', async () => {
50
+ throw new Error('plain error without status')
51
+ })
52
+
53
+ const res = await fastify.inject('/')
54
+
55
+ t.assert.strictEqual(res.statusCode, 500)
56
+ t.assert.strictEqual(diagnosticsStatusCode, 500, 'diagnostics channel should report 500 for plain errors')
57
+ t.assert.strictEqual(diagnosticsStatusCode, res.statusCode, 'diagnostics status should match response status')
58
+ })
59
+
60
+ test('diagnostics channel error event should report correct status with custom error handler', async (t) => {
61
+ t.plan(3)
62
+ const fastify = Fastify()
63
+ t.after(() => fastify.close())
64
+
65
+ let diagnosticsStatusCode
66
+
67
+ const channel = diagnostics.channel('tracing:fastify.request.handler:error')
68
+ const handler = (msg) => {
69
+ diagnosticsStatusCode = msg.reply.statusCode
70
+ }
71
+ channel.subscribe(handler)
72
+ t.after(() => channel.unsubscribe(handler))
73
+
74
+ fastify.setErrorHandler((error, request, reply) => {
75
+ reply.status(503).send({ error: error.message })
76
+ })
77
+
78
+ fastify.get('/', async () => {
79
+ throw new Error('handler error')
80
+ })
81
+
82
+ const res = await fastify.inject('/')
83
+
84
+ // Note: The diagnostics channel fires before the custom error handler runs,
85
+ // so it reports 500 (default) rather than 503 (set by custom handler).
86
+ // This is expected behavior - the error channel reports the initial error state.
87
+ t.assert.strictEqual(res.statusCode, 503)
88
+ t.assert.strictEqual(diagnosticsStatusCode, 500, 'diagnostics channel reports status before custom handler')
89
+ t.assert.notStrictEqual(diagnosticsStatusCode, res.statusCode, 'custom handler can change status after diagnostics')
90
+ })
91
+
92
+ test('Error.status property support', (t, done) => {
93
+ t.plan(4)
94
+ const fastify = Fastify()
95
+ t.after(() => fastify.close())
96
+ const err = new Error('winter is coming')
97
+ err.status = 418
98
+
99
+ diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => {
100
+ t.assert.strictEqual(msg.error.message, 'winter is coming')
101
+ })
102
+
103
+ fastify.get('/', () => {
104
+ return Promise.reject(err)
105
+ })
106
+
107
+ fastify.inject({
108
+ method: 'GET',
109
+ url: '/'
110
+ }, (error, res) => {
111
+ t.assert.ifError(error)
112
+ t.assert.strictEqual(res.statusCode, 418)
113
+ t.assert.deepStrictEqual(
114
+ {
115
+ error: statusCodes['418'],
116
+ message: err.message,
117
+ statusCode: 418
118
+ },
119
+ JSON.parse(res.payload)
120
+ )
121
+ done()
122
+ })
123
+ })
@@ -0,0 +1,50 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const proxyquire = require('proxyquire')
5
+
6
+ test('diagnostics_channel when present and subscribers', t => {
7
+ t.plan(3)
8
+
9
+ let fastifyInHook
10
+
11
+ const diagnostics = {
12
+ channel (name) {
13
+ t.assert.strictEqual(name, 'fastify.initialization')
14
+ return {
15
+ hasSubscribers: true,
16
+ publish (event) {
17
+ t.assert.ok(event.fastify)
18
+ fastifyInHook = event.fastify
19
+ }
20
+ }
21
+ },
22
+ '@noCallThru': true
23
+ }
24
+
25
+ const fastify = proxyquire('../../fastify', {
26
+ 'node:diagnostics_channel': diagnostics
27
+ })()
28
+ t.assert.strictEqual(fastifyInHook, fastify)
29
+ })
30
+
31
+ test('diagnostics_channel when present and no subscribers', t => {
32
+ t.plan(1)
33
+
34
+ const diagnostics = {
35
+ channel (name) {
36
+ t.assert.strictEqual(name, 'fastify.initialization')
37
+ return {
38
+ hasSubscribers: false,
39
+ publish () {
40
+ t.assert.fail('publish should not be called')
41
+ }
42
+ }
43
+ },
44
+ '@noCallThru': true
45
+ }
46
+
47
+ proxyquire('../../fastify', {
48
+ 'node:diagnostics_channel': diagnostics
49
+ })()
50
+ })
@@ -0,0 +1,49 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const diagnostics = require('node:diagnostics_channel')
5
+ const Fastify = require('../..')
6
+ const Request = require('../../lib/request')
7
+ const Reply = require('../../lib/reply')
8
+
9
+ test('diagnostics channel sync events fire in expected order', async t => {
10
+ t.plan(10)
11
+ let callOrder = 0
12
+ let firstEncounteredMessage
13
+
14
+ diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => {
15
+ t.assert.strictEqual(callOrder++, 0)
16
+ firstEncounteredMessage = msg
17
+ t.assert.ok(msg.request instanceof Request)
18
+ t.assert.ok(msg.reply instanceof Reply)
19
+ })
20
+
21
+ diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => {
22
+ t.assert.ok(msg.request instanceof Request)
23
+ t.assert.ok(msg.reply instanceof Reply)
24
+ t.assert.strictEqual(callOrder++, 1)
25
+ t.assert.strictEqual(msg, firstEncounteredMessage)
26
+ })
27
+
28
+ diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => {
29
+ t.assert.fail('should not trigger error channel')
30
+ })
31
+
32
+ const fastify = Fastify()
33
+ fastify.route({
34
+ method: 'GET',
35
+ url: '/',
36
+ handler: function (req, reply) {
37
+ setImmediate(() => reply.send({ hello: 'world' }))
38
+ }
39
+ })
40
+
41
+ t.after(() => { fastify.close() })
42
+
43
+ const fastifyServer = await fastify.listen({ port: 0 })
44
+
45
+ const result = await fetch(fastifyServer + '/')
46
+ t.assert.ok(result.ok)
47
+ t.assert.strictEqual(result.status, 200)
48
+ t.assert.deepStrictEqual(await result.json(), { hello: 'world' })
49
+ })
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const diagnostics = require('node:diagnostics_channel')
5
+ const Fastify = require('../..')
6
+ const Request = require('../../lib/request')
7
+ const Reply = require('../../lib/reply')
8
+
9
+ test('diagnostics channel sync events fire in expected order', async t => {
10
+ t.plan(10)
11
+ let callOrder = 0
12
+ let firstEncounteredMessage
13
+
14
+ diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => {
15
+ t.assert.strictEqual(callOrder++, 0)
16
+ firstEncounteredMessage = msg
17
+ t.assert.ok(msg.request instanceof Request)
18
+ t.assert.ok(msg.reply instanceof Reply)
19
+ })
20
+
21
+ diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => {
22
+ t.assert.ok(msg.request instanceof Request)
23
+ t.assert.ok(msg.reply instanceof Reply)
24
+ t.assert.strictEqual(callOrder++, 1)
25
+ t.assert.strictEqual(msg, firstEncounteredMessage)
26
+ })
27
+
28
+ diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => {
29
+ t.assert.fail('should not trigger error channel')
30
+ })
31
+
32
+ const fastify = Fastify()
33
+ fastify.route({
34
+ method: 'GET',
35
+ url: '/',
36
+ handler: function (req, reply) {
37
+ reply.send({ hello: 'world' })
38
+ }
39
+ })
40
+
41
+ const fastifyServer = await fastify.listen({ port: 0 })
42
+ t.after(() => { fastify.close() })
43
+
44
+ const response = await fetch(fastifyServer, {
45
+ method: 'GET'
46
+ })
47
+ t.assert.ok(response.ok)
48
+ t.assert.strictEqual(response.status, 200)
49
+ const body = await response.text()
50
+ t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' })
51
+ })
@@ -0,0 +1,54 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const diagnostics = require('node:diagnostics_channel')
5
+ const Fastify = require('../..')
6
+ const Request = require('../../lib/request')
7
+ const Reply = require('../../lib/reply')
8
+
9
+ test('diagnostics channel sync events fire in expected order', async t => {
10
+ t.plan(13)
11
+ let callOrder = 0
12
+ let firstEncounteredMessage
13
+
14
+ diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => {
15
+ t.assert.strictEqual(callOrder++, 0)
16
+ firstEncounteredMessage = msg
17
+ t.assert.ok(msg.request instanceof Request)
18
+ t.assert.ok(msg.reply instanceof Reply)
19
+ t.assert.ok(msg.route)
20
+ t.assert.strictEqual(msg.route.url, '/:id')
21
+ t.assert.strictEqual(msg.route.method, 'GET')
22
+ })
23
+
24
+ diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => {
25
+ t.assert.ok(msg.request instanceof Request)
26
+ t.assert.ok(msg.reply instanceof Reply)
27
+ t.assert.strictEqual(callOrder++, 1)
28
+ t.assert.strictEqual(msg, firstEncounteredMessage)
29
+ })
30
+
31
+ diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => {
32
+ t.assert.fail('should not trigger error channel')
33
+ })
34
+
35
+ const fastify = Fastify()
36
+ fastify.route({
37
+ method: 'GET',
38
+ url: '/:id',
39
+ handler: function (req, reply) {
40
+ return { hello: 'world' }
41
+ }
42
+ })
43
+
44
+ const fastifyServer = await fastify.listen({ port: 0 })
45
+ t.after(() => { fastify.close() })
46
+
47
+ const response = await fetch(fastifyServer + '/7', {
48
+ method: 'GET'
49
+ })
50
+ t.assert.ok(response.ok)
51
+ t.assert.strictEqual(response.status, 200)
52
+ const body = await response.text()
53
+ t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' })
54
+ })
@@ -0,0 +1,69 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const Fastify = require('..')
5
+ const fp = require('fastify-plugin')
6
+
7
+ test('encapsulates an child logger factory', async t => {
8
+ t.plan(4)
9
+
10
+ const fastify = Fastify()
11
+ fastify.register(async function (fastify) {
12
+ fastify.setChildLoggerFactory(function pluginFactory (logger, bindings, opts) {
13
+ const child = logger.child(bindings, opts)
14
+ child.customLog = function (message) {
15
+ t.assert.strictEqual(message, 'custom')
16
+ }
17
+ return child
18
+ })
19
+ fastify.get('/encapsulated', async (req) => {
20
+ req.log.customLog('custom')
21
+ })
22
+ })
23
+
24
+ fastify.setChildLoggerFactory(function globalFactory (logger, bindings, opts) {
25
+ const child = logger.child(bindings, opts)
26
+ child.globalLog = function (message) {
27
+ t.assert.strictEqual(message, 'global')
28
+ }
29
+ return child
30
+ })
31
+ fastify.get('/not-encapsulated', async (req) => {
32
+ req.log.globalLog('global')
33
+ })
34
+
35
+ const res1 = await fastify.inject('/encapsulated')
36
+ t.assert.strictEqual(res1.statusCode, 200)
37
+
38
+ const res2 = await fastify.inject('/not-encapsulated')
39
+ t.assert.strictEqual(res2.statusCode, 200)
40
+ })
41
+
42
+ test('child logger factory set on root scope when using fastify-plugin', async t => {
43
+ t.plan(4)
44
+
45
+ const fastify = Fastify()
46
+ fastify.register(fp(async function (fastify) {
47
+ // Using fastify-plugin, the factory should be set on the root scope
48
+ fastify.setChildLoggerFactory(function pluginFactory (logger, bindings, opts) {
49
+ const child = logger.child(bindings, opts)
50
+ child.customLog = function (message) {
51
+ t.assert.strictEqual(message, 'custom')
52
+ }
53
+ return child
54
+ })
55
+ fastify.get('/not-encapsulated-1', async (req) => {
56
+ req.log.customLog('custom')
57
+ })
58
+ }))
59
+
60
+ fastify.get('/not-encapsulated-2', async (req) => {
61
+ req.log.customLog('custom')
62
+ })
63
+
64
+ const res1 = await fastify.inject('/not-encapsulated-1')
65
+ t.assert.strictEqual(res1.statusCode, 200)
66
+
67
+ const res2 = await fastify.inject('/not-encapsulated-2')
68
+ t.assert.strictEqual(res2.statusCode, 200)
69
+ })
@@ -0,0 +1,237 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const Fastify = require('..')
5
+
6
+ // Because of how error handlers wrap things, following the control flow can be tricky
7
+ // In this test file numbered comments indicate the order statements are expected to execute
8
+
9
+ test('encapsulates an asynchronous error handler', async t => {
10
+ t.plan(3)
11
+
12
+ const fastify = Fastify()
13
+ fastify.register(async function (fastify) {
14
+ fastify.setErrorHandler(async function a (err) {
15
+ // 3. the inner error handler catches the error, and throws a new error
16
+ t.assert.strictEqual(err.message, 'from_endpoint')
17
+ throw new Error('from_inner')
18
+ })
19
+ fastify.get('/encapsulated', async () => {
20
+ // 2. the endpoint throws an error
21
+ throw new Error('from_endpoint')
22
+ })
23
+ })
24
+
25
+ fastify.setErrorHandler(async function b (err) {
26
+ // 4. the outer error handler catches the error thrown by the inner error handler
27
+ t.assert.strictEqual(err.message, 'from_inner')
28
+ // 5. the outer error handler throws a new error
29
+ throw new Error('from_outer')
30
+ })
31
+
32
+ // 1. the endpoint is called
33
+ const res = await fastify.inject('/encapsulated')
34
+ // 6. the default error handler returns the error from the outer error handler
35
+ t.assert.strictEqual(res.json().message, 'from_outer')
36
+ })
37
+
38
+ // See discussion in https://github.com/fastify/fastify/pull/5222#discussion_r1432573655
39
+ test('encapsulates a synchronous error handler', async t => {
40
+ t.plan(3)
41
+
42
+ const fastify = Fastify()
43
+ fastify.register(async function (fastify) {
44
+ fastify.setErrorHandler(function a (err) {
45
+ // 3. the inner error handler catches the error, and throws a new error
46
+ t.assert.strictEqual(err.message, 'from_endpoint')
47
+ throw new Error('from_inner')
48
+ })
49
+ fastify.get('/encapsulated', async () => {
50
+ // 2. the endpoint throws an error
51
+ throw new Error('from_endpoint')
52
+ })
53
+ })
54
+
55
+ fastify.setErrorHandler(async function b (err) {
56
+ // 4. the outer error handler catches the error thrown by the inner error handler
57
+ t.assert.strictEqual(err.message, 'from_inner')
58
+ // 5. the outer error handler throws a new error
59
+ throw new Error('from_outer')
60
+ })
61
+
62
+ // 1. the endpoint is called
63
+ const res = await fastify.inject('/encapsulated')
64
+ // 6. the default error handler returns the error from the outer error handler
65
+ t.assert.strictEqual(res.json().message, 'from_outer')
66
+ })
67
+
68
+ test('onError hook nested', async t => {
69
+ t.plan(4)
70
+
71
+ const fastify = Fastify()
72
+ fastify.register(async function (fastify) {
73
+ fastify.setErrorHandler(async function a (err) {
74
+ // 4. the inner error handler catches the error, and throws a new error
75
+ t.assert.strictEqual(err.message, 'from_endpoint')
76
+ throw new Error('from_inner')
77
+ })
78
+ fastify.get('/encapsulated', async () => {
79
+ // 2. the endpoint throws an error
80
+ throw new Error('from_endpoint')
81
+ })
82
+ })
83
+
84
+ fastify.setErrorHandler(async function b (err) {
85
+ // 5. the outer error handler catches the error thrown by the inner error handler
86
+ t.assert.strictEqual(err.message, 'from_inner')
87
+ // 6. the outer error handler throws a new error
88
+ throw new Error('from_outer')
89
+ })
90
+
91
+ fastify.addHook('onError', async function (request, reply, err) {
92
+ // 3. the hook receives the error
93
+ t.assert.strictEqual(err.message, 'from_endpoint')
94
+ })
95
+
96
+ // 1. the endpoint is called
97
+ const res = await fastify.inject('/encapsulated')
98
+ // 7. the default error handler returns the error from the outer error handler
99
+ t.assert.strictEqual(res.json().message, 'from_outer')
100
+ })
101
+
102
+ // See https://github.com/fastify/fastify/issues/5220
103
+ test('encapuslates an error handler, for errors thrown in hooks', async t => {
104
+ t.plan(3)
105
+
106
+ const fastify = Fastify()
107
+ fastify.register(async function (fastify) {
108
+ fastify.setErrorHandler(function a (err) {
109
+ // 3. the inner error handler catches the error, and throws a new error
110
+ t.assert.strictEqual(err.message, 'from_hook')
111
+ throw new Error('from_inner')
112
+ })
113
+ fastify.addHook('onRequest', async () => {
114
+ // 2. the hook throws an error
115
+ throw new Error('from_hook')
116
+ })
117
+ fastify.get('/encapsulated', async () => {})
118
+ })
119
+
120
+ fastify.setErrorHandler(function b (err) {
121
+ // 4. the outer error handler catches the error thrown by the inner error handler
122
+ t.assert.strictEqual(err.message, 'from_inner')
123
+ // 5. the outer error handler throws a new error
124
+ throw new Error('from_outer')
125
+ })
126
+
127
+ // 1. the endpoint is called
128
+ const res = await fastify.inject('/encapsulated')
129
+ // 6. the default error handler returns the error from the outer error handler
130
+ t.assert.strictEqual(res.json().message, 'from_outer')
131
+ })
132
+
133
+ // See https://github.com/fastify/fastify/issues/5220
134
+ test('encapuslates many synchronous error handlers that rethrow errors', async t => {
135
+ const DEPTH = 100
136
+ t.plan(DEPTH + 2)
137
+
138
+ /**
139
+ * This creates a very nested set of error handlers, that looks like:
140
+ * plugin
141
+ * - error handler
142
+ * - plugin
143
+ * - error handler
144
+ * - plugin
145
+ * ... {to DEPTH levels}
146
+ * - plugin
147
+ * - error handler
148
+ * - GET /encapsulated
149
+ */
150
+ const createNestedRoutes = (fastify, depth) => {
151
+ if (depth < 0) {
152
+ throw new Error('Expected depth >= 0')
153
+ } else if (depth === 0) {
154
+ fastify.setErrorHandler(function a (err) {
155
+ // 3. innermost error handler catches the error, and throws a new error
156
+ t.assert.strictEqual(err.message, 'from_route')
157
+ throw new Error(`from_handler_${depth}`)
158
+ })
159
+ fastify.get('/encapsulated', async () => {
160
+ // 2. the endpoint throws an error
161
+ throw new Error('from_route')
162
+ })
163
+ } else {
164
+ fastify.setErrorHandler(function d (err) {
165
+ // 4 to {DEPTH+4}. error handlers each catch errors, and then throws a new error
166
+ t.assert.strictEqual(err.message, `from_handler_${depth - 1}`)
167
+ throw new Error(`from_handler_${depth}`)
168
+ })
169
+
170
+ fastify.register(async function (fastify) {
171
+ createNestedRoutes(fastify, depth - 1)
172
+ })
173
+ }
174
+ }
175
+
176
+ const fastify = Fastify()
177
+ createNestedRoutes(fastify, DEPTH)
178
+
179
+ // 1. the endpoint is called
180
+ const res = await fastify.inject('/encapsulated')
181
+ // {DEPTH+5}. the default error handler returns the error from the outermost error handler
182
+ t.assert.strictEqual(res.json().message, `from_handler_${DEPTH}`)
183
+ })
184
+
185
+ // See https://github.com/fastify/fastify/issues/5220
186
+ // This was not failing previously, but we want to make sure the behavior continues to work in the same way across async and sync handlers
187
+ // Plus, the current setup is somewhat fragile to tweaks to wrapThenable as that's what retries (by calling res.send(err) again)
188
+ test('encapuslates many asynchronous error handlers that rethrow errors', async t => {
189
+ const DEPTH = 100
190
+ t.plan(DEPTH + 2)
191
+
192
+ /**
193
+ * This creates a very nested set of error handlers, that looks like:
194
+ * plugin
195
+ * - error handler
196
+ * - plugin
197
+ * - error handler
198
+ * - plugin
199
+ * ... {to DEPTH levels}
200
+ * - plugin
201
+ * - error handler
202
+ * - GET /encapsulated
203
+ */
204
+ const createNestedRoutes = (fastify, depth) => {
205
+ if (depth < 0) {
206
+ throw new Error('Expected depth >= 0')
207
+ } else if (depth === 0) {
208
+ fastify.setErrorHandler(async function a (err) {
209
+ // 3. innermost error handler catches the error, and throws a new error
210
+ t.assert.strictEqual(err.message, 'from_route')
211
+ throw new Error(`from_handler_${depth}`)
212
+ })
213
+ fastify.get('/encapsulated', async () => {
214
+ // 2. the endpoint throws an error
215
+ throw new Error('from_route')
216
+ })
217
+ } else {
218
+ fastify.setErrorHandler(async function m (err) {
219
+ // 4 to {DEPTH+4}. error handlers each catch errors, and then throws a new error
220
+ t.assert.strictEqual(err.message, `from_handler_${depth - 1}`)
221
+ throw new Error(`from_handler_${depth}`)
222
+ })
223
+
224
+ fastify.register(async function (fastify) {
225
+ createNestedRoutes(fastify, depth - 1)
226
+ })
227
+ }
228
+ }
229
+
230
+ const fastify = Fastify()
231
+ createNestedRoutes(fastify, DEPTH)
232
+
233
+ // 1. the endpoint is called
234
+ const res = await fastify.inject('/encapsulated')
235
+ // {DEPTH+5}. the default error handler returns the error from the outermost error handler
236
+ t.assert.strictEqual(res.json().message, `from_handler_${DEPTH}`)
237
+ })
@@ -0,0 +1,10 @@
1
+ import { errorCodes } from '../../fastify.js'
2
+ import { test } from 'node:test'
3
+
4
+ test('errorCodes in ESM', async t => {
5
+ // test a custom fastify error using errorCodes with ESM
6
+ const customError = errorCodes.FST_ERR_VALIDATION('custom error message')
7
+ t.assert.ok(typeof customError !== 'undefined')
8
+ t.assert.ok(customError instanceof errorCodes.FST_ERR_VALIDATION)
9
+ t.assert.strictEqual(customError.message, 'custom error message')
10
+ })
@@ -0,0 +1,13 @@
1
+ import { test } from 'node:test'
2
+ import Fastify from '../../fastify.js'
3
+
4
+ test('esm support', async t => {
5
+ const fastify = Fastify()
6
+
7
+ fastify.register(import('./plugin.mjs'), { foo: 'bar' })
8
+ fastify.register(import('./other.mjs'))
9
+
10
+ await fastify.ready()
11
+
12
+ t.assert.strictEqual(fastify.foo, 'bar')
13
+ })
@@ -0,0 +1,8 @@
1
+ 'use strict'
2
+
3
+ import('./named-exports.mjs')
4
+ .catch(err => {
5
+ process.nextTick(() => {
6
+ throw err
7
+ })
8
+ })
@@ -0,0 +1,14 @@
1
+ import { test } from 'node:test'
2
+ import { fastify } from '../../fastify.js'
3
+
4
+ // This test is executed in index.test.js
5
+ test('named exports support', async t => {
6
+ const app = fastify()
7
+
8
+ app.register(import('./plugin.mjs'), { foo: 'bar' })
9
+ app.register(import('./other.mjs'))
10
+
11
+ await app.ready()
12
+
13
+ t.assert.strictEqual(app.foo, 'bar')
14
+ })