fastify-ata 0.2.2 → 0.2.4

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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Fastify plugin for [ata-validator](https://ata-validator.com) — JSON Schema validation powered by simdjson.
4
4
 
5
- Replaces the default ajv validator compiler with ata-validator. Beats ajv on every valid-path benchmark.
5
+ Drop-in replacement for Fastify's default ajv validator. Standard Schema V1 compatible.
6
6
 
7
7
  ## Install
8
8
 
@@ -24,40 +24,82 @@ fastify.post('/user', {
24
24
  type: 'object',
25
25
  properties: {
26
26
  name: { type: 'string', minLength: 1 },
27
- age: { type: 'integer', minimum: 0 }
27
+ age: { type: 'integer', minimum: 0 },
28
+ role: { type: 'string', default: 'user' }
28
29
  },
29
30
  required: ['name']
30
31
  }
31
32
  }
32
33
  }, (req, reply) => {
34
+ // req.body.role === 'user' (default applied)
33
35
  reply.send({ ok: true, name: req.body.name })
34
36
  })
35
37
 
36
38
  fastify.listen({ port: 3000 })
37
39
  ```
38
40
 
39
- That's it. All your existing JSON Schema route definitions work as-is.
41
+ All your existing JSON Schema route definitions work as-is.
42
+
43
+ ## Options
44
+
45
+ ```js
46
+ fastify.register(fastifyAta, {
47
+ coerceTypes: true, // convert "42" → 42 for integer fields
48
+ removeAdditional: true, // strip properties not in schema
49
+ })
50
+ ```
51
+
52
+ ## Standard Schema V1
53
+
54
+ ata-validator natively implements [Standard Schema V1](https://github.com/standard-schema/standard-schema) — the emerging standard for TypeScript-first schema libraries.
55
+
56
+ ```js
57
+ const { Validator } = require('ata-validator')
58
+ const v = new Validator(schema)
59
+
60
+ // Standard Schema V1 interface
61
+ const result = v['~standard'].validate(data)
62
+ // { value: data } on success
63
+ // { issues: [{ message, path }] } on failure
64
+ ```
65
+
66
+ Works with Fastify v5's Standard Schema support, tRPC, TanStack Form, Drizzle ORM.
40
67
 
41
68
  ## What it does
42
69
 
43
70
  - Registers a custom `validatorCompiler` using ata-validator
44
- - Caches compiled schemas for reuse across routes
71
+ - Applies `default` values, `coerceTypes`, `removeAdditional` during validation
72
+ - Caches compiled schemas (WeakMap) for reuse across routes
45
73
  - Returns Fastify-compatible validation errors on invalid requests (400)
46
74
  - Works with Fastify v4 and v5
47
75
 
48
- ## Why
76
+ ## Performance
77
+
78
+ ### Real-world HTTP benchmark (autocannon, 10 connections, 5s)
79
+
80
+ | Payload | ata | ajv | |
81
+ |---|---|---|---|
82
+ | 1 user (0.1KB) | 65.7K req/sec | 65.7K req/sec | equal |
83
+ | 10 users (0.9KB) | 57.2K | 55.3K | +3% |
84
+ | 50 users (4.6KB) | 36.0K | 33.8K | +6% |
85
+ | 100 users (9.1KB) | 24.6K | 22.6K | +9% |
86
+
87
+ ### Where ata really shines
88
+
89
+ | Scenario | ata | ajv | |
90
+ |---|---|---|---|
91
+ | **Serverless cold start** (50 schemas) | 7.7ms | 96ms | **12.5x faster** |
92
+ | **ReDoS protection** (catastrophic pattern) | 0.3ms | 765ms | **immune** |
93
+ | **Batch NDJSON** (10K items, multi-core) | 13.4M/sec | 5.1M/sec | **2.6x faster** |
94
+ | **validate(obj)** valid (micro) | 15M ops/sec | 8.5M ops/sec | **1.8x faster** |
95
+ | **Schema compilation** | 125K ops/sec | 831 ops/sec | **151x faster** |
49
96
 
50
- | | ata-validator | ajv |
51
- |---|---|---|
52
- | validate(obj) valid | **1.1x faster** | baseline |
53
- | validate(obj) 100 users | **2.7x faster** | baseline |
54
- | Schema compilation | **151x faster** | baseline |
55
- | Parallel batch (10K) | **5.9x faster** (12.5M items/sec) | 2.1M items/sec |
56
- | Engine | simdjson + V8 codegen + multi-core | JS (single-thread) |
57
- | Spec compliance | 98.5% Draft 2020-12 | ~100% |
58
- | Standard Schema V1 | Yes | No |
97
+ ### Things only ata can do
59
98
 
60
- > ata uses speculative validation: a V8-optimized JS codegen fast path runs first. Valid data (the common case) never crosses the NAPI boundary.
99
+ - **RE2 regex engine** linear-time guaranteed, immune to ReDoS attacks
100
+ - **Multi-core parallel validation** — NDJSON batch at 12.5M items/sec
101
+ - **Standard Schema V1** — native support, ajv doesn't have it
102
+ - **151x faster compilation** — serverless cold starts, dynamic schemas
61
103
 
62
104
  ## License
63
105
 
@@ -0,0 +1,304 @@
1
+ 'use strict'
2
+
3
+ const { execSync } = require('child_process')
4
+ const { writeFileSync, unlinkSync } = require('fs')
5
+ const path = require('path')
6
+
7
+ // ============================================================
8
+ // Scenario 1: Serverless Cold Start
9
+ // Compile 50 schemas + validate 1 request each
10
+ // ============================================================
11
+ async function benchColdStart() {
12
+ console.log('--- Scenario 1: Serverless Cold Start ---')
13
+ console.log(' Compile 50 schemas + validate 1 request each\n')
14
+
15
+ const script = (useAta) => `
16
+ 'use strict'
17
+ ${useAta ? `
18
+ const { Validator } = require('ata-validator')
19
+ ` : `
20
+ const Ajv = require('ajv')
21
+ const addFormats = require('ajv-formats')
22
+ `}
23
+
24
+ const schemas = []
25
+ for (let i = 0; i < 50; i++) {
26
+ schemas.push({
27
+ type: 'object',
28
+ properties: {
29
+ ['field' + i]: { type: 'string', minLength: 1 },
30
+ id: { type: 'integer', minimum: 1 },
31
+ name: { type: 'string' },
32
+ email: { type: 'string', format: 'email' },
33
+ age: { type: 'integer', minimum: 0, maximum: 150 },
34
+ active: { type: 'boolean' },
35
+ },
36
+ required: ['id', 'name', 'email'],
37
+ })
38
+ }
39
+
40
+ // Measure: compile all 50 schemas + validate 1 request each (simulates cold start)
41
+ // Single iteration — that's what cold start means
42
+ const start = performance.now()
43
+ ${useAta ? `
44
+ const validators = schemas.map(s => new Validator(s))
45
+ validators.forEach(v => v.validate({ id: 1, name: 'x', email: 'a@b.com', active: true }))
46
+ ` : `
47
+ schemas.forEach(s => {
48
+ const ajv = new Ajv({ allErrors: true })
49
+ addFormats(ajv)
50
+ const validate = ajv.compile(s)
51
+ validate({ id: 1, name: 'x', email: 'a@b.com', active: true })
52
+ })
53
+ `}
54
+ const elapsed = performance.now() - start
55
+ console.log(JSON.stringify({ elapsed: elapsed.toFixed(1) }))
56
+ `
57
+
58
+ const ataPath = path.join(__dirname, '_cold_ata.js')
59
+ const ajvPath = path.join(__dirname, '_cold_ajv.js')
60
+ writeFileSync(ataPath, script(true))
61
+ writeFileSync(ajvPath, script(false))
62
+
63
+ const ataResult = JSON.parse(execSync(`node ${ataPath}`, { cwd: __dirname }).toString())
64
+ const ajvResult = JSON.parse(execSync(`node ${ajvPath}`, { cwd: __dirname }).toString())
65
+
66
+ unlinkSync(ataPath)
67
+ unlinkSync(ajvPath)
68
+
69
+ console.log(` ata: ${ataResult.elapsed}ms to compile 50 schemas + validate`)
70
+ console.log(` ajv: ${ajvResult.elapsed}ms to compile 50 schemas + validate`)
71
+ console.log(` >>> ata is ${(parseFloat(ajvResult.elapsed) / parseFloat(ataResult.elapsed)).toFixed(1)}x faster\n`)
72
+ }
73
+
74
+ // ============================================================
75
+ // Scenario 2: ReDoS Protection
76
+ // Pattern with catastrophic backtracking potential
77
+ // ============================================================
78
+ async function benchReDoS() {
79
+ console.log('--- Scenario 2: ReDoS Protection ---')
80
+ console.log(' Pattern: ^(a+)+$ with pathological input\n')
81
+
82
+ const script = (useAta) => `
83
+ 'use strict'
84
+ ${useAta ? `
85
+ // Force NAPI path to use RE2 (not JS RegExp)
86
+ process.env.ATA_FORCE_NAPI = '1'
87
+ const { Validator } = require('ata-validator')
88
+ const v = new Validator({ type: 'string', pattern: '^(a+)+$' })
89
+ ` : `
90
+ const Ajv = require('ajv')
91
+ const ajv = new Ajv()
92
+ const validate = ajv.compile({ type: 'string', pattern: '^(a+)+$' })
93
+ `}
94
+
95
+ // Pathological input — causes catastrophic backtracking in JS regex
96
+ const input = 'a'.repeat(25) + 'b'
97
+
98
+ const start = performance.now()
99
+ ${useAta ? `
100
+ const json = JSON.stringify(input)
101
+ v.validateJSON(json)
102
+ ` : `validate(input)`}
103
+ const elapsed = performance.now() - start
104
+ console.log(JSON.stringify({ elapsed: elapsed.toFixed(3) }))
105
+ `
106
+
107
+ const ataPath = path.join(__dirname, '_redos_ata.js')
108
+ const ajvPath = path.join(__dirname, '_redos_ajv.js')
109
+ writeFileSync(ataPath, script(true))
110
+ writeFileSync(ajvPath, script(false))
111
+
112
+ const ataResult = JSON.parse(execSync(`node ${ataPath}`, { cwd: __dirname, timeout: 5000 }).toString())
113
+
114
+ let ajvResult
115
+ try {
116
+ ajvResult = JSON.parse(execSync(`node ${ajvPath}`, { cwd: __dirname, timeout: 5000 }).toString())
117
+ } catch {
118
+ ajvResult = { elapsed: 'TIMEOUT (>5s)' }
119
+ }
120
+
121
+ unlinkSync(ataPath)
122
+ unlinkSync(ajvPath)
123
+
124
+ console.log(` ata (RE2): ${ataResult.elapsed}ms`)
125
+ console.log(` ajv (JS regex): ${ajvResult.elapsed}${typeof ajvResult.elapsed === 'string' && ajvResult.elapsed.includes('TIMEOUT') ? '' : 'ms'}`)
126
+ if (typeof ajvResult.elapsed === 'number' || !ajvResult.elapsed.includes('TIMEOUT')) {
127
+ const ratio = parseFloat(ajvResult.elapsed) / parseFloat(ataResult.elapsed)
128
+ console.log(` >>> ata is ${ratio.toFixed(0)}x faster (immune to ReDoS)\n`)
129
+ } else {
130
+ console.log(` >>> ajv HANGS — catastrophic backtracking. ata is immune.\n`)
131
+ }
132
+ }
133
+
134
+ // ============================================================
135
+ // Scenario 3: Large Payload Validation (HTTP)
136
+ // ============================================================
137
+ async function benchLargePayload() {
138
+ console.log('--- Scenario 3: Large Payload HTTP Validation ---')
139
+ console.log(' 500 users per request, real HTTP\n')
140
+
141
+ const schema = {
142
+ body: {
143
+ type: 'object',
144
+ properties: {
145
+ users: {
146
+ type: 'array',
147
+ items: {
148
+ type: 'object',
149
+ properties: {
150
+ id: { type: 'integer', minimum: 1 },
151
+ name: { type: 'string', minLength: 1 },
152
+ email: { type: 'string', format: 'email' },
153
+ active: { type: 'boolean' },
154
+ role: { enum: ['admin', 'user', 'moderator'] },
155
+ },
156
+ required: ['id', 'name', 'email', 'active', 'role'],
157
+ },
158
+ },
159
+ },
160
+ required: ['users'],
161
+ },
162
+ }
163
+
164
+ const users = []
165
+ for (let i = 0; i < 500; i++) {
166
+ users.push({ id: i + 1, name: `User ${i}`, email: `u${i}@example.com`, active: true, role: 'user' })
167
+ }
168
+ const payload = JSON.stringify({ users })
169
+
170
+ function serverScript(useAta) {
171
+ return `
172
+ 'use strict'
173
+ const fastify = require('fastify')()
174
+ ${useAta ? "fastify.register(require('./index'))" : ''}
175
+ const schema = ${JSON.stringify(schema)}
176
+ fastify.post('/users', { schema }, (req, reply) => {
177
+ reply.send({ ok: true })
178
+ })
179
+ fastify.listen({ port: 0 }).then(() => {
180
+ process.send({ port: fastify.server.address().port })
181
+ })
182
+ `
183
+ }
184
+
185
+ async function run(label, useAta) {
186
+ const scriptPath = path.join(__dirname, '_bench_server.js')
187
+ writeFileSync(scriptPath, serverScript(useAta))
188
+ const { fork } = require('child_process')
189
+ const child = fork(scriptPath, { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] })
190
+ const port = await new Promise((resolve) => { child.on('message', (msg) => resolve(msg.port)) })
191
+ const escaped = payload.replace(/'/g, "'\\''")
192
+ const result = execSync(
193
+ `npx autocannon -c 10 -d 5 -j http://localhost:${port}/users -m POST -H "content-type: application/json" -b '${escaped}'`,
194
+ { cwd: __dirname, timeout: 30000 }
195
+ ).toString()
196
+ child.kill()
197
+ try { unlinkSync(scriptPath) } catch {}
198
+ return JSON.parse(result).requests.average
199
+ }
200
+
201
+ const ata = await run('ata', true)
202
+ const ajv = await run('ajv', false)
203
+
204
+ console.log(` ata: ${ata.toLocaleString()} req/sec`)
205
+ console.log(` ajv: ${ajv.toLocaleString()} req/sec`)
206
+ console.log(` >>> ata ${(ata / ajv).toFixed(2)}x faster (${((ata / ajv - 1) * 100).toFixed(0)}%)\n`)
207
+ }
208
+
209
+ // ============================================================
210
+ // Scenario 4: Batch NDJSON Processing
211
+ // ============================================================
212
+ async function benchBatch() {
213
+ console.log('--- Scenario 4: Batch NDJSON Processing ---')
214
+ console.log(' 10K items, multi-core vs single-thread\n')
215
+
216
+ const script = (useAta) => `
217
+ 'use strict'
218
+ ${useAta ? `
219
+ const { Validator } = require('ata-validator')
220
+ const v = new Validator({
221
+ type: 'object',
222
+ properties: {
223
+ id: { type: 'integer' },
224
+ name: { type: 'string' },
225
+ value: { type: 'number' }
226
+ },
227
+ required: ['id', 'name']
228
+ })
229
+ const lines = []
230
+ for (let i = 0; i < 10000; i++) {
231
+ lines.push(JSON.stringify({ id: i, name: 'item' + i, value: Math.random() }))
232
+ }
233
+ const buf = Buffer.from(lines.join('\\n'))
234
+ // Warmup
235
+ for (let i = 0; i < 10; i++) v.isValidParallel(buf)
236
+
237
+ const N = 100
238
+ const start = performance.now()
239
+ for (let i = 0; i < N; i++) v.isValidParallel(buf)
240
+ const elapsed = performance.now() - start
241
+ const itemsPerSec = (N * 10000) / (elapsed / 1000)
242
+ console.log(JSON.stringify({ items: Math.round(itemsPerSec) }))
243
+ ` : `
244
+ const Ajv = require('ajv')
245
+ const ajv = new Ajv()
246
+ const validate = ajv.compile({
247
+ type: 'object',
248
+ properties: {
249
+ id: { type: 'integer' },
250
+ name: { type: 'string' },
251
+ value: { type: 'number' }
252
+ },
253
+ required: ['id', 'name']
254
+ })
255
+ // Same NDJSON input — parse each line + validate (fair comparison)
256
+ const lines = []
257
+ for (let i = 0; i < 10000; i++) {
258
+ lines.push(JSON.stringify({ id: i, name: 'item' + i, value: Math.random() }))
259
+ }
260
+ const ndjson = lines.join('\\n')
261
+ // Warmup
262
+ for (let i = 0; i < 5; i++) { for (const line of ndjson.split('\\n')) validate(JSON.parse(line)) }
263
+
264
+ const N = 50
265
+ const start = performance.now()
266
+ for (let i = 0; i < N; i++) { for (const line of ndjson.split('\\n')) validate(JSON.parse(line)) }
267
+ const elapsed = performance.now() - start
268
+ const itemsPerSec = (N * 10000) / (elapsed / 1000)
269
+ console.log(JSON.stringify({ items: Math.round(itemsPerSec) }))
270
+ `}
271
+ `
272
+
273
+ const ataPath = path.join(__dirname, '_batch_ata.js')
274
+ const ajvPath = path.join(__dirname, '_batch_ajv.js')
275
+ writeFileSync(ataPath, script(true))
276
+ writeFileSync(ajvPath, script(false))
277
+
278
+ const ataResult = JSON.parse(execSync(`node ${ataPath}`, { cwd: __dirname, timeout: 60000 }).toString())
279
+ const ajvResult = JSON.parse(execSync(`node ${ajvPath}`, { cwd: __dirname, timeout: 60000 }).toString())
280
+
281
+ unlinkSync(ataPath)
282
+ unlinkSync(ajvPath)
283
+
284
+ console.log(` ata (multi-core): ${ataResult.items.toLocaleString()} items/sec`)
285
+ console.log(` ajv (single-thread): ${ajvResult.items.toLocaleString()} items/sec`)
286
+ console.log(` >>> ata is ${(ataResult.items / ajvResult.items).toFixed(1)}x faster\n`)
287
+ }
288
+
289
+ async function main() {
290
+ console.log('\n==============================================')
291
+ console.log(' ata-validator vs ajv — Real-World Scenarios')
292
+ console.log('==============================================\n')
293
+
294
+ await benchColdStart()
295
+ await benchReDoS()
296
+ await benchLargePayload()
297
+ await benchBatch()
298
+
299
+ console.log('==============================================')
300
+ console.log(' Summary: ata wins where it matters most')
301
+ console.log('==============================================\n')
302
+ }
303
+
304
+ main().catch(console.error)
package/bench.js ADDED
@@ -0,0 +1,118 @@
1
+ 'use strict'
2
+
3
+ const { execSync } = require('child_process')
4
+ const { writeFileSync, unlinkSync } = require('fs')
5
+ const path = require('path')
6
+
7
+ const schema = {
8
+ body: {
9
+ type: 'object',
10
+ properties: {
11
+ users: {
12
+ type: 'array',
13
+ items: {
14
+ type: 'object',
15
+ properties: {
16
+ id: { type: 'integer', minimum: 1 },
17
+ name: { type: 'string', minLength: 1 },
18
+ email: { type: 'string', format: 'email' },
19
+ age: { type: 'integer', minimum: 0, maximum: 150 },
20
+ active: { type: 'boolean' },
21
+ role: { enum: ['admin', 'user', 'moderator'] },
22
+ },
23
+ required: ['id', 'name', 'email', 'active', 'role'],
24
+ },
25
+ },
26
+ metadata: {
27
+ type: 'object',
28
+ properties: {
29
+ total: { type: 'integer' },
30
+ page: { type: 'integer', minimum: 1 },
31
+ },
32
+ required: ['total', 'page'],
33
+ },
34
+ },
35
+ required: ['users', 'metadata'],
36
+ },
37
+ }
38
+
39
+ function makePayload(n) {
40
+ const users = []
41
+ for (let i = 0; i < n; i++) {
42
+ users.push({
43
+ id: i + 1,
44
+ name: `User ${i}`,
45
+ email: `user${i}@example.com`,
46
+ age: 25,
47
+ active: true,
48
+ role: 'user',
49
+ })
50
+ }
51
+ return JSON.stringify({ users, metadata: { total: n, page: 1 } })
52
+ }
53
+
54
+ function serverScript(useAta) {
55
+ return `
56
+ 'use strict'
57
+ const fastify = require('fastify')()
58
+ ${useAta ? "fastify.register(require('./index'))" : ''}
59
+ const schema = ${JSON.stringify(schema)}
60
+ fastify.post('/users', { schema }, (req, reply) => {
61
+ reply.send({ ok: true, count: req.body.users.length })
62
+ })
63
+ fastify.listen({ port: 0 }).then(() => {
64
+ process.send({ port: fastify.server.address().port })
65
+ })
66
+ `
67
+ }
68
+
69
+ async function bench(label, useAta, payload) {
70
+ const scriptPath = path.join(__dirname, '_bench_server.js')
71
+ writeFileSync(scriptPath, serverScript(useAta))
72
+
73
+ const { fork } = require('child_process')
74
+ const child = fork(scriptPath, { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] })
75
+ const port = await new Promise((resolve) => {
76
+ child.on('message', (msg) => resolve(msg.port))
77
+ })
78
+
79
+ const escaped = payload.replace(/'/g, "'\\''")
80
+ const result = execSync(
81
+ `npx autocannon -c 10 -d 5 -j http://localhost:${port}/users -m POST -H "content-type: application/json" -b '${escaped}'`,
82
+ { cwd: __dirname, timeout: 30000 }
83
+ ).toString()
84
+
85
+ child.kill()
86
+ try { unlinkSync(scriptPath) } catch {}
87
+
88
+ const parsed = JSON.parse(result)
89
+ return parsed.requests.average
90
+ }
91
+
92
+ async function main() {
93
+ console.log('\n==============================================')
94
+ console.log(' Fastify POST Validation Benchmark')
95
+ console.log(' 10 connections, 5 seconds, real HTTP')
96
+ console.log('==============================================\n')
97
+
98
+ for (const count of [1, 10, 50, 100]) {
99
+ const payload = makePayload(count)
100
+ console.log(`--- ${count} users (${(payload.length / 1024).toFixed(1)} KB) ---`)
101
+
102
+ const ata = await bench('ata', true, payload)
103
+ const ajv = await bench('ajv', false, payload)
104
+
105
+ console.log(` ata: ${ata.toLocaleString().padStart(10)} req/sec`)
106
+ console.log(` ajv: ${ajv.toLocaleString().padStart(10)} req/sec`)
107
+
108
+ const ratio = ata / ajv
109
+ if (ratio >= 1) console.log(` >>> ata ${ratio.toFixed(2)}x faster`)
110
+ else console.log(` >>> ajv ${(1 / ratio).toFixed(2)}x faster`)
111
+ console.log()
112
+ }
113
+ }
114
+
115
+ main().catch((err) => {
116
+ console.error(err)
117
+ process.exit(1)
118
+ })
package/index.js CHANGED
@@ -5,11 +5,15 @@ const { Validator } = require('ata-validator')
5
5
 
6
6
  function fastifyAta(fastify, opts, done) {
7
7
  const cache = new WeakMap()
8
+ const validatorOpts = {
9
+ coerceTypes: opts.coerceTypes || false,
10
+ removeAdditional: opts.removeAdditional || false,
11
+ }
8
12
 
9
13
  fastify.setValidatorCompiler(({ schema }) => {
10
14
  let validator = cache.get(schema)
11
15
  if (!validator) {
12
- validator = new Validator(schema)
16
+ validator = new Validator(schema, validatorOpts)
13
17
  cache.set(schema, validator)
14
18
  }
15
19
  return (data) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify-ata",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Fastify plugin for ata-validator — beats ajv on every valid-path benchmark. 2.7x faster validate(obj), 151x faster compilation, simdjson + multi-core.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -26,13 +26,14 @@
26
26
  },
27
27
  "homepage": "https://github.com/ata-core/fastify-ata#readme",
28
28
  "dependencies": {
29
- "ata-validator": "^0.4.1",
29
+ "ata-validator": "^0.4.3",
30
30
  "fastify-plugin": "^5.1.0"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "fastify": ">=4.0.0"
34
34
  },
35
35
  "devDependencies": {
36
+ "autocannon": "^8.0.0",
36
37
  "fastify": "^5.8.4"
37
38
  }
38
39
  }