Skip to content

Commit ad9a387

Browse files
Shuunenjordan-boyer
authored andcommitted
add biome for formatting/linting and shuutils for improved logging
1 parent fe12d1b commit ad9a387

File tree

9 files changed

+166
-138
lines changed

9 files changed

+166
-138
lines changed

biome.json

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3+
"files": {
4+
"ignore": [
5+
"node_modules/**",
6+
"dist/**",
7+
"coverage/**"
8+
],
9+
"ignoreUnknown": false
10+
},
11+
"formatter": {
12+
"enabled": true,
13+
"indentStyle": "space",
14+
"indentWidth": 2,
15+
"lineEnding": "lf",
16+
"lineWidth": 300
17+
},
18+
"javascript": {
19+
"formatter": {
20+
"arrowParentheses": "asNeeded",
21+
"bracketSpacing": true,
22+
"quoteStyle": "single",
23+
"semicolons": "asNeeded"
24+
}
25+
},
26+
"linter": {
27+
"enabled": true,
28+
"rules": {
29+
"recommended": true
30+
}
31+
},
32+
"organizeImports": {
33+
"enabled": true
34+
},
35+
"overrides": [
36+
{
37+
"include": [
38+
"**/suretype.ts"
39+
],
40+
"linter": {
41+
"rules": {
42+
"style": {
43+
"noParameterAssign": "off"
44+
}
45+
}
46+
}
47+
}
48+
],
49+
"vcs": {
50+
"clientKind": "git",
51+
"enabled": false,
52+
"useIgnoreFile": false
53+
}
54+
}

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
},
2121
"description": "Comparison of different TypeScript validation libraries",
2222
"devDependencies": {
23+
"@biomejs/biome": "1.9.4",
2324
"@tsconfig/strictest": "^2.0",
2425
"@types/bun": "latest",
2526
"repo-check": "^1.42",
@@ -49,8 +50,8 @@
4950
"bench:node": "hyperfine --runs 10 'node dist/arktype.js' 'node dist/valibot.js' 'node dist/zod-mini.js' 'node dist/zod3.js' 'node dist/zod4.js'",
5051
"bench:node:strip": "hyperfine --runs 10 'node --experimental-strip-types src/arktype.ts' 'node --experimental-strip-types src/valibot.ts' 'node --experimental-strip-types src/zod-mini.ts' 'node --experimental-strip-types src/zod3.ts' 'node --experimental-strip-types src/zod4.ts'",
5152
"build": "bun scripts/build.ts && echo build success",
52-
"check": "pnpm i && bun check:tsc && bun run build && bun check:once && echo check success",
53-
"check:once": "bun src/arktype.ts && bun src/valibot.ts && bun src/zod-mini.ts && bun src/zod3.ts && bun src/zod4.ts && echo check:once success",
53+
"check": "pnpm i && bun check:tsc && bun check:biome && bun run build && bun check:once && echo check success",
54+
"check:biome": "biome check --error-on-warnings --write --unsafe src && echo check:biome success",
5455
"check:repo": "repo-check && echo check:repo success",
5556
"check:tsc": "tsc --noEmit && echo check:tsc success"
5657
},

scripts/build.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
1-
import { Glob } from "bun"
1+
import { Glob, type BuildConfig } from "bun"
2+
import { green, kebabToPascalCase, red, yellow } from 'shuutils'
23

34
const glob = new Glob("*.ts")
45
const ignore = ['utils.ts']
56
const outdir = './dist'
67
const nameRegex = /dist[\/\\](?<name>[\w-]+)/
78

8-
function showFileSize (path: string) {
9+
function showFileSize(path: string) {
910
const name = nameRegex.exec(path)?.groups?.name
1011
if (!name) throw new Error(`Failed to extract name from ${path}`)
1112
const stats = Bun.file(path)
1213
const kb = Math.round(stats.size / 1024)
13-
console.log(name.padStart(10), kb, 'KB')
14+
const color = kb < 30 ? green : kb < 100 ? yellow : red
15+
console.log(color(kebabToPascalCase(name).padEnd(8)), 'minified build size :', color(`${kb}`), 'KB')
1416
}
1517

16-
function build (file: string) {
18+
function build(file: string) {
1719
const entrypoints = [`./src/${file}`]
18-
Bun.build({ entrypoints, outdir, minify: true }).then((data) => {
20+
const options = {
21+
entrypoints,
22+
external: ['shuutils'],
23+
outdir,
24+
minify: true
25+
} satisfies BuildConfig
26+
Bun.build(options).then((data) => {
1927
if (data.outputs[0]?.path) showFileSize(data.outputs[0].path)
2028
}).catch((err) => { console.error('Build failed:', err) })
2129
}

src/arktype.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,38 @@
1-
import { type } from "arktype"
2-
import { checkUserA, checkUserB, checkUserC, nbIterations } from './utils.ts'
1+
import { type } from 'arktype'
2+
import { checkUserA, checkUserB, checkUserC, logExecTime, nbIterations } from './utils.ts'
33

44
const startTime = performance.now()
55

66
for (let i = 0; i < nbIterations; i++) {
7-
87
const userSchema = type({
98
name: type.string,
109
age: type.number.default(42),
11-
phone: type.string.or(type.number).pipe(phone => typeof phone === 'number' ? phone.toString() : phone).default("123-456-7890"),
10+
phone: type.string
11+
.or(type.number)
12+
.pipe(phone => (typeof phone === 'number' ? phone.toString() : phone))
13+
.default('123-456-7890'),
1214
})
1315

1416
type User = typeof userSchema.inferOut
1517

1618
type UserInput = typeof userSchema.inferIn
1719

18-
const userA = userSchema({ name: "Jordan" })
20+
const userA = userSchema({ name: 'Jordan' })
1921
// @ts-expect-error Argument of type '{ name: string; age: number; } | ArkErrors' is not assignable to parameter of type 'User'.
2022
checkUserA(userA)
2123

22-
function createUser (input: UserInput) {
24+
function createUser(input: UserInput) {
2325
const user = userSchema(input)
2426
if (user instanceof type.errors) return userA as User // :'''''(
2527
return user
2628
}
2729

28-
const userB = createUser({ name: "Romain", age: 35, phone: 1234567890 })
30+
const userB = createUser({ name: 'Romain', age: 35, phone: 1234567890 })
2931
checkUserB(userB)
3032

3133
// @ts-expect-error age should be a number
32-
const userC = createUser({ name: "Romain", age: "35" })
34+
const userC = createUser({ name: 'Romain', age: '35' })
3335
checkUserC(userC)
34-
3536
}
3637

37-
console.log(`ArkType exec time for ${nbIterations} iterations :`, performance.now() - startTime, 'ms')
38+
logExecTime('ArkType', startTime)

src/utils.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,33 @@
1+
import { green, red, yellow } from 'shuutils'
2+
13
type User = {
2-
name: string;
3-
age?: number;
4-
phone: string;
5-
};
4+
name: string
5+
age?: number
6+
phone: string
7+
}
68

79
export function checkUserA(userA: User) {
8-
console.assert(userA.age === 42, "userA.age should be 42");
10+
console.assert(userA.name === 'Jordan', `userA.name should be "Jordan" but got "${userA.name}"`)
11+
console.assert(userA.age === 42, `userA.age should be 42 but got "${userA.age}"`)
12+
console.assert(userA.phone === '123-456-7890', `userA.phone should be "123-456-7890" but got "${userA.phone}"`)
913
}
1014

1115
export function checkUserB(userB: User) {
12-
console.assert(userB.age === 35, "userB.age should be 35");
13-
console.assert(
14-
userB.phone === "1234567890",
15-
"userB.phone should be 1234567890"
16-
);
16+
console.assert(userB.name === 'Romain', `userB.name should be "Romain" but got "${userB.name}"`)
17+
console.assert(userB.age === 35, `userB.age should be 35 but got "${userB.age}"`)
18+
console.assert(userB.phone === '1234567890', `userB.phone should be "1234567890" but got "${userB.phone}"`)
1719
}
1820

1921
export function checkUserC(userC: User) {
20-
console.assert(userC.name === "Jordan", "userC.name should be Jordan");
22+
console.assert(userC.name === 'Jordan', `userC.name should be "Jordan" but got "${userC.name}"`)
23+
console.assert(userC.age === 42, `userC.age should be 42 but got "${userC.age}"`)
24+
console.assert(userC.phone === '123-456-7890', `userC.phone should be "123-456-7890" but got "${userC.phone}"`)
2125
}
2226

23-
export const nbIterations = 1000;
27+
export const nbIterations = 1000
28+
29+
export function logExecTime(name: string, startTime: number) {
30+
const ms = Math.round(performance.now() - startTime)
31+
const color = ms < 100 ? green : ms < 200 ? yellow : red
32+
console.log(`${color(name.padEnd(8))} lib exec time :`, color(`${ms}`.padStart(4)), 'ms')
33+
}

src/valibot.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
import { number, union, object, optional, parse, safeParse, string, type InferInput, type InferOutput, pipe, transform } from 'valibot'
2-
import { checkUserA, checkUserB, checkUserC, nbIterations } from './utils.ts'
1+
import { type InferInput, type InferOutput, number, object, optional, parse, pipe, safeParse, string, transform, union } from 'valibot'
2+
import { checkUserA, checkUserB, checkUserC, logExecTime, nbIterations } from './utils.ts'
33

44
const startTime = performance.now()
55

66
for (let i = 0; i < nbIterations; i++) {
7-
87
const userSchema = object({
98
name: string(),
109
age: optional(number(), 42),
1110
phone: pipe(
12-
optional(union([string(), number()]), "123-456-7890"),
13-
transform((phone) => typeof phone === 'number' ? phone.toString() : phone)
11+
optional(union([string(), number()]), '123-456-7890'),
12+
transform(phone => (typeof phone === 'number' ? phone.toString() : phone)),
1413
),
1514
})
1615

@@ -19,22 +18,21 @@ for (let i = 0; i < nbIterations; i++) {
1918

2019
type UserInput = InferInput<typeof userSchema>
2120

22-
const userA = parse(userSchema, { name: "Jordan" })
21+
const userA = parse(userSchema, { name: 'Jordan' })
2322
checkUserA(userA)
2423

25-
function createUser (input: UserInput) {
24+
function createUser(input: UserInput) {
2625
const result = safeParse(userSchema, input)
2726
if (!result.success) return userA
2827
return result.output
2928
}
3029

31-
const userB = createUser({ name: "Romain", age: 35, phone: 1234567890 })
30+
const userB = createUser({ name: 'Romain', age: 35, phone: 1234567890 })
3231
checkUserB(userB)
3332

3433
// @ts-expect-error age should be a number
35-
const userC = createUser({ name: "Romain", age: "35" })
34+
const userC = createUser({ name: 'Romain', age: '35' })
3635
checkUserC(userC)
37-
3836
}
3937

40-
console.log(`Valibot exec time for ${nbIterations} iterations :`, performance.now() - startTime, 'ms')
38+
logExecTime('Valibot', startTime)

src/zod-mini.ts

Lines changed: 18 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,36 @@
1-
import {
2-
string,
3-
number,
4-
object,
5-
type infer as InferOutput,
6-
type input as InferInput,
7-
refine,
8-
optional,
9-
_default,
10-
} from "@zod/mini";
11-
import { checkUserA, checkUserB, checkUserC, nbIterations } from "./utils.ts";
12-
13-
const startTime = performance.now();
1+
import { type input as InferInput, type infer as InferOutput, _default, number, object, optional, refine, string } from '@zod/mini'
2+
import { checkUserA, checkUserB, checkUserC, logExecTime, nbIterations } from './utils.ts'
3+
4+
const startTime = performance.now()
145

156
for (let i = 0; i < nbIterations; i++) {
167
const userSchema = object({
178
name: string(),
189
age: _default(optional(number()), 42),
19-
phone: _default(
20-
optional(
21-
string().check(
22-
refine((phone) => (typeof phone === "number" ? String(phone) : phone))
23-
)
24-
),
25-
"123-456-7890"
26-
),
27-
});
10+
phone: _default(optional(string().check(refine(phone => (typeof phone === 'number' ? String(phone) : phone)))), '123-456-7890'),
11+
})
2812

2913
// @ts-expect-error not exported, it's ok :p
30-
type User = InferOutput<typeof userSchema>;
14+
type User = InferOutput<typeof userSchema>
3115

32-
type UserInput = InferInput<typeof userSchema>;
16+
type UserInput = InferInput<typeof userSchema>
3317

34-
const userA = userSchema.parse({ name: "Jordan" });
35-
checkUserA(userA);
18+
const userA = userSchema.parse({ name: 'Jordan' })
19+
checkUserA(userA)
3620

3721
function createUser(input: UserInput) {
38-
const result = userSchema.safeParse(input);
39-
if (!result.success) return userA;
40-
return result.data;
22+
const result = userSchema.safeParse(input)
23+
if (!result.success) return userA
24+
return result.data
4125
}
4226
// (╯°□°)╯︵ ┻━┻
4327
// Zod mini doesn't support "or" so a string is given for phone instead of a number like in other scripts
44-
const userB = createUser({ name: "Romain", age: 35, phone: "1234567890" });
45-
checkUserB(userB);
28+
const userB = createUser({ name: 'Romain', age: 35, phone: '1234567890' })
29+
checkUserB(userB)
4630

4731
// @ts-expect-error age should be a number
48-
const userC = createUser({ name: "Romain", age: "35" });
49-
checkUserC(userC);
32+
const userC = createUser({ name: 'Romain', age: '35' })
33+
checkUserC(userC)
5034
}
5135

52-
console.log(
53-
`Zod mini exec time for ${nbIterations} iterations :`,
54-
performance.now() - startTime,
55-
"ms"
56-
);
36+
logExecTime('Zod mini', startTime)

src/zod3.ts

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,38 @@
1-
import {
2-
string,
3-
number,
4-
object,
5-
type infer as InferOutput,
6-
type input as InferInput,
7-
} from "zod3";
8-
import { checkUserA, checkUserB, checkUserC, nbIterations } from "./utils.ts";
1+
import { type input as InferInput, type infer as InferOutput, number, object, string } from 'zod3'
2+
import { checkUserA, checkUserB, checkUserC, logExecTime, nbIterations } from './utils.ts'
93

10-
const startTime = performance.now();
4+
const startTime = performance.now()
115

126
for (let i = 0; i < nbIterations; i++) {
137
const userSchema = object({
148
name: string(),
159
age: number().default(42),
1610
phone: string()
1711
.or(number())
18-
.default("123-456-7890")
19-
.transform((phone) =>
20-
typeof phone === "number" ? phone.toString() : phone
21-
),
22-
});
12+
.default('123-456-7890')
13+
.transform(phone => (typeof phone === 'number' ? phone.toString() : phone)),
14+
})
2315

2416
// @ts-expect-error not exported, it's ok :p
25-
type User = InferOutput<typeof userSchema>;
17+
type User = InferOutput<typeof userSchema>
2618

27-
type UserInput = InferInput<typeof userSchema>;
19+
type UserInput = InferInput<typeof userSchema>
2820

29-
const userA = userSchema.parse({ name: "Jordan" });
30-
checkUserA(userA);
21+
const userA = userSchema.parse({ name: 'Jordan' })
22+
checkUserA(userA)
3123

3224
function createUser(input: UserInput) {
33-
const result = userSchema.safeParse(input);
34-
if (!result.success) return userA;
35-
return result.data;
25+
const result = userSchema.safeParse(input)
26+
if (!result.success) return userA
27+
return result.data
3628
}
3729

38-
const userB = createUser({ name: "Romain", age: 35, phone: 1234567890 });
39-
checkUserB(userB);
30+
const userB = createUser({ name: 'Romain', age: 35, phone: 1234567890 })
31+
checkUserB(userB)
4032

4133
// @ts-expect-error age should be a number
42-
const userC = createUser({ name: "Romain", age: "35" });
43-
checkUserC(userC);
34+
const userC = createUser({ name: 'Romain', age: '35' })
35+
checkUserC(userC)
4436
}
4537

46-
console.log(
47-
`Zod v3 exec time for ${nbIterations} iterations :`,
48-
performance.now() - startTime,
49-
"ms"
50-
);
38+
logExecTime('Zod v3', startTime)

0 commit comments

Comments
 (0)