Created
June 5, 2025 10:58
-
-
Save tengla/0c94c554294894f76e420d969cb9e6c5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Type } from '@sinclair/typebox'; | |
import Ajv from 'ajv'; | |
import addFormats from 'ajv-formats'; | |
import { z } from 'zod'; | |
const ajv = new Ajv({ allErrors: true, strict: false }); | |
addFormats(ajv); | |
// TypeBox/AJV Schemas | |
const personSchemaTypeBox = Type.Object({ | |
name: Type.String(), | |
age: Type.Optional(Type.Integer({ minimum: 0 })), | |
email: Type.Optional(Type.String({ format: 'email' })), | |
tags: Type.Optional(Type.Array(Type.String())), | |
}); | |
const complexSchemaTypeBox = Type.Object({ | |
user: Type.Object({ | |
id: Type.Integer({ minimum: 1 }), | |
profile: Type.Object({ | |
firstName: Type.String({ minLength: 1 }), | |
lastName: Type.String({ minLength: 1 }), | |
bio: Type.Optional(Type.String({ maxLength: 500 })), | |
socials: Type.Optional(Type.Array( | |
Type.Object({ | |
platform: Type.Union([Type.Literal('twitter'), Type.Literal('github'), Type.Literal('linkedin')]), | |
url: Type.String({ format: 'uri' }), | |
}) | |
)), | |
}), | |
preferences: Type.Object({ | |
theme: Type.Union([Type.Literal('light'), Type.Literal('dark')]), | |
notifications: Type.Boolean(), | |
language: Type.String({ pattern: '^[a-z]{2}$' }), | |
}), | |
}), | |
metadata: Type.Object({ | |
createdAt: Type.String({ format: 'date-time' }), | |
updatedAt: Type.String({ format: 'date-time' }), | |
version: Type.Integer({ minimum: 1 }), | |
}), | |
}); | |
// Zod Schemas | |
const personSchemaZod = z.object({ | |
name: z.string(), | |
age: z.number().int().min(0).optional(), | |
email: z.string().email().optional(), | |
tags: z.array(z.string()).optional(), | |
}); | |
const complexSchemaZod = z.object({ | |
user: z.object({ | |
id: z.number().int().min(1), | |
profile: z.object({ | |
firstName: z.string().min(1), | |
lastName: z.string().min(1), | |
bio: z.string().max(500).optional(), | |
socials: z.array( | |
z.object({ | |
platform: z.enum(['twitter', 'github', 'linkedin']), | |
url: z.string().url(), | |
}) | |
).optional(), | |
}), | |
preferences: z.object({ | |
theme: z.enum(['light', 'dark']), | |
notifications: z.boolean(), | |
language: z.string().regex(/^[a-z]{2}$/), | |
}), | |
}), | |
metadata: z.object({ | |
createdAt: z.string().datetime(), | |
updatedAt: z.string().datetime(), | |
version: z.number().int().min(1), | |
}), | |
}); | |
// Compile AJV validators | |
const validatePersonAjv = ajv.compile(personSchemaTypeBox); | |
const validateComplexAjv = ajv.compile(complexSchemaTypeBox); | |
// Test Data | |
const validPersonData = { | |
name: 'John Doe', | |
age: 30, | |
email: '[email protected]', | |
tags: ['developer', 'typescript'], | |
}; | |
const invalidPersonData = { | |
name: '', | |
age: -5, | |
email: 'invalid-email', | |
tags: 'not-an-array', | |
}; | |
const validComplexData = { | |
user: { | |
id: 123, | |
profile: { | |
firstName: 'Jane', | |
lastName: 'Smith', | |
bio: 'Software engineer passionate about clean code', | |
socials: [ | |
{ platform: 'github', url: 'https://github.com/janesmith' }, | |
{ platform: 'twitter', url: 'https://twitter.com/janesmith' }, | |
], | |
}, | |
preferences: { | |
theme: 'dark', | |
notifications: true, | |
language: 'en', | |
}, | |
}, | |
metadata: { | |
createdAt: '2023-01-01T00:00:00Z', | |
updatedAt: '2023-06-01T12:00:00Z', | |
version: 1, | |
}, | |
}; | |
const invalidComplexData = { | |
user: { | |
id: 0, | |
profile: { | |
firstName: '', | |
lastName: 'Smith', | |
bio: 'x'.repeat(501), | |
socials: [ | |
{ platform: 'invalid', url: 'not-a-url' }, | |
], | |
}, | |
preferences: { | |
theme: 'purple', | |
notifications: 'yes', | |
language: 'english', | |
}, | |
}, | |
metadata: { | |
createdAt: 'invalid-date', | |
updatedAt: '2023-06-01', | |
version: 0, | |
}, | |
}; | |
// Benchmark utilities | |
function measureTime(fn: () => void, iterations: number = 100000): number { | |
const start = performance.now(); | |
for (let i = 0; i < iterations; i++) { | |
fn(); | |
} | |
const end = performance.now(); | |
return end - start; | |
} | |
function benchmark(name: string, ajvFn: () => boolean, zodFn: () => boolean, iterations: number = 100000) { | |
console.log(`\nπ₯ ${name} Benchmark (${iterations.toLocaleString()} iterations)`); | |
console.log('β'.repeat(60)); | |
// Warm up | |
for (let i = 0; i < 1000; i++) { | |
ajvFn(); | |
zodFn(); | |
} | |
const ajvTime = measureTime(ajvFn, iterations); | |
const zodTime = measureTime(zodFn, iterations); | |
const ajvOpsPerSec = Math.floor(iterations / (ajvTime / 1000)); | |
const zodOpsPerSec = Math.floor(iterations / (zodTime / 1000)); | |
console.log(`AJV: ${ajvTime.toFixed(2)}ms | ${ajvOpsPerSec.toLocaleString()} ops/sec`); | |
console.log(`Zod: ${zodTime.toFixed(2)}ms | ${zodOpsPerSec.toLocaleString()} ops/sec`); | |
const winner = ajvTime < zodTime ? 'AJV' : 'Zod'; | |
const speedup = Math.max(ajvTime, zodTime) / Math.min(ajvTime, zodTime); | |
console.log(`π Winner: ${winner} (${speedup.toFixed(2)}x faster)`); | |
} | |
// Run benchmarks | |
console.log('π« AJV vs Zod Shootout - Lock and Load! π«'); | |
console.log('=' .repeat(60)); | |
// Simple schema - Valid data | |
benchmark( | |
'Simple Schema - Valid Data', | |
() => validatePersonAjv(validPersonData), | |
() => personSchemaZod.safeParse(validPersonData).success, | |
); | |
// Simple schema - Invalid data | |
benchmark( | |
'Simple Schema - Invalid Data', | |
() => validatePersonAjv(invalidPersonData), | |
() => personSchemaZod.safeParse(invalidPersonData).success, | |
); | |
// Complex schema - Valid data | |
benchmark( | |
'Complex Schema - Valid Data', | |
() => validateComplexAjv(validComplexData), | |
() => complexSchemaZod.safeParse(validComplexData).success, | |
); | |
// Complex schema - Invalid data | |
benchmark( | |
'Complex Schema - Invalid Data', | |
() => validateComplexAjv(invalidComplexData), | |
() => complexSchemaZod.safeParse(invalidComplexData).success, | |
); | |
// Schema compilation benchmark | |
console.log('\nπ§ Schema Compilation Benchmark'); | |
console.log('β'.repeat(60)); | |
const compilationIterations = 1000; | |
const ajvCompilationTime = measureTime(() => { | |
ajv.compile(personSchemaTypeBox); | |
}, compilationIterations); | |
const zodCompilationTime = measureTime(() => { | |
z.object({ | |
name: z.string(), | |
age: z.number().int().min(0).optional(), | |
email: z.string().email().optional(), | |
tags: z.array(z.string()).optional(), | |
}); | |
}, compilationIterations); | |
console.log(`AJV Compilation: ${ajvCompilationTime.toFixed(2)}ms (${compilationIterations} schemas)`); | |
console.log(`Zod Creation: ${zodCompilationTime.toFixed(2)}ms (${compilationIterations} schemas)`); | |
const compileWinner = ajvCompilationTime < zodCompilationTime ? 'AJV' : 'Zod'; | |
const compileSpeedup = Math.max(ajvCompilationTime, zodCompilationTime) / Math.min(ajvCompilationTime, zodCompilationTime); | |
console.log(`π Winner: ${compileWinner} (${compileSpeedup.toFixed(2)}x faster)`); | |
console.log('\nπ― Battle Complete! Check the results above to see who wins each round.'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment