1import { execFileSync } from "node:child_process"
2import { mkdirSync, readFileSync, writeFileSync } from "node:fs"
3import os from "node:os"
4import { dirname, join, resolve } from "node:path"
5import { createRequire } from "node:module"
6import { fileURLToPath } from "node:url"
7
8const implementations = [
9 "effect-interpreted",
10 "valibot",
11 "zod-jitless",
12 "effect-jit",
13 "effect-aot",
14 "zod-compiled"
15] as const
16
17const cases = [
18 "struct-decode",
19 "struct-invalid",
20 "struct-is",
21 "array-decode",
22 "union-decode",
23 "transform-decode",
24 "default-make"
25] as const
26
27const [outputArgument = "tmp/schema-compiler-resources.json", roundsArgument = "5", countsArgument = "100,500"] =
28 process.argv.slice(2)
29const rounds = Number(roundsArgument)
30const counts = countsArgument.split(",").map(Number)
31if (!Number.isSafeInteger(rounds) || rounds <= 0) throw new Error("rounds must be a positive integer")
32if (counts.some((count) => !Number.isSafeInteger(count) || count <= 0)) {
33 throw new Error("counts must be comma-separated positive integers")
34}
35
36const root = process.cwd()
37const output = resolve(outputArgument)
38const worker = fileURLToPath(new URL("./resources.mts", import.meta.url))
39const require = createRequire(import.meta.url)
40const packageVersion = (name: string) => {
41 let directory = dirname(require.resolve(name))
42 while (true) {
43 try {
44 const metadata = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"))
45 if (metadata.name === name) return metadata.version as string
46 } catch {
47 // Continue at the parent directory.
48 }
49 const parent = dirname(directory)
50 if (parent === directory) throw new Error(`package.json not found for ${name}`)
51 directory = parent
52 }
53}
54
55const results: Array<unknown> = []
56for (let round = 0; round < rounds; round++) {
57 const orderedImplementations = round % 2 === 0 ? implementations : implementations.toReversed()
58 const orderedCounts = round % 2 === 0 ? counts : counts.toReversed()
59 for (const count of orderedCounts) {
60 for (const caseName of cases) {
61 for (const implementation of orderedImplementations) {
62 const sample = JSON.parse(execFileSync(
63 process.execPath,
64 ["--expose-gc", worker, "measure", root, implementation, caseName, String(count)],
65 { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }
66 ))
67 results.push({ round, ...sample })
68 }
69 }
70 process.stderr.write(`resource round ${round + 1}/${rounds}, count ${count} complete\n`)
71 }
72}
73
74mkdirSync(dirname(output), { recursive: true })
75writeFileSync(output, JSON.stringify({
76 environment: {
77 arch: process.arch,
78 cpu: os.cpus()[0]?.model ?? "unknown",
79 node: process.version,
80 platform: process.platform,
81 valibot: packageVersion("valibot"),
82 v8: process.versions.v8,
83 zod: packageVersion("zod")
84 },
85 measurement: {
86 cases,
87 counts,
88 implementations,
89 rounds
90 },
91 results
92}, null, 2))
93process.stdout.write(`${output}\n`)