pi-mono/packages/schema/test/bench/setup.ts
Mario Zechner fab2c5353e feat(schema): add pi-schema, JSON Schema builders and validation without eval
@earendil-works/pi-schema compiles JSON Schema (drafts 2020-12 and 7) once into
plain closures: no eval or new Function, so it runs under a strict CSP. It
passes 2,222 of the 2,223 core tests of the official JSON-Schema-Test-Suite,
checks Chord-style argument schemas within 1.2x of TypeBox's JIT, and keeps
3,200 compiled schemas in 7.9 MB (TypeBox: 54.7 MB).

- compile(schema, options) returns a Validator: check() is the boolean check
  (a type guard for builder schemas), errors() reports each failure with a
  JSON Pointer path, missing and unknown fields at the field.
- Builders (str, int, obj, opt, union, ...) return plain JSON Schema typed with
  the values it accepts (Static<typeof schema>).
- Formats are an optional entry point (./formats), adapted from TypeBox (MIT).
- Tests run the vendored suite (drafts 2020-12 and 7, with optional and format
  tests); npm run bench compares conformance, speed, setup cost, memory and
  bundle size with TypeBox, @cfworker/json-schema and omptype.
2026-10-31 21:39:34 +00:00

51 lines
2.1 KiB
TypeScript

// Setup cost: compiling a schema the library has not seen, and the time of its first checks. Uses distinct
// schemas from the corpus after warming each library's own code, so it measures a new schema, not a new process.
// Run: node test/bench/setup.ts [--json <file>]
import { writeFileSync } from "node:fs";
import { candidates } from "./candidates.ts";
import { corpus } from "./corpus.ts";
const COUNT = Number(process.env.COUNT ?? 1000);
/** Mean µs per schema: compile, then each of the first three checks and the mean of checks 4-10. */
export type SetupResults = Record<string, { compileUs: number; checkUs: [number, number, number, number] }>;
const results: SetupResults = {};
for (const candidate of candidates) {
for (const { schema, value } of corpus(200, 7)) {
const { check } = candidate.prepare(schema, { draft: "2020-12" });
for (let i = 0; i < 10; i++) check(value);
}
const items = corpus(COUNT, 99);
let compile = 0;
const checks = [0, 0, 0, 0];
for (const { schema, value } of items) {
let start = process.hrtime.bigint();
const { check } = candidate.prepare(schema, { draft: "2020-12" });
compile += Number(process.hrtime.bigint() - start);
for (let k = 0; k < 3; k++) {
start = process.hrtime.bigint();
check(value);
checks[k] += Number(process.hrtime.bigint() - start);
}
start = process.hrtime.bigint();
for (let k = 3; k < 10; k++) check(value);
checks[3] += Number(process.hrtime.bigint() - start) / 7;
}
const us = (ns: number) => ns / COUNT / 1000;
results[candidate.name] = {
compileUs: us(compile),
checkUs: [us(checks[0]), us(checks[1]), us(checks[2]), us(checks[3])],
};
}
console.log("| candidate | compile | check 1 | check 2 | check 3 | checks 4-10 |");
console.log("|---|---:|---:|---:|---:|---:|");
for (const [name, r] of Object.entries(results)) {
console.log(
`| ${name} | ${r.compileUs.toFixed(1)} µs | ${r.checkUs.map((x) => `${x.toFixed(2)} µs`).join(" | ")} |`,
);
}
const out = process.argv.indexOf("--json");
if (out >= 0) writeFileSync(process.argv[out + 1], JSON.stringify(results, null, "\t"));