Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
79c3ee5
add pluralize util
EskiMojo14 Feb 18, 2026
b5c9d16
Add stack benchmarks to libraries
EskiMojo14 Feb 18, 2026
536922f
create stack script
EskiMojo14 Feb 18, 2026
f86f3a9
create stack display card
EskiMojo14 Feb 18, 2026
4cea455
add stack benchmark for effect beta
EskiMojo14 Feb 23, 2026
ffeae03
fix annotation
EskiMojo14 Feb 23, 2026
4a52bf2
include more files in artifact
EskiMojo14 Feb 25, 2026
8cfa539
add ansi display component
EskiMojo14 Feb 26, 2026
5df7661
add runtime tests for stack tests
EskiMojo14 Feb 26, 2026
225841f
typo
EskiMojo14 Feb 26, 2026
9c99b00
extract forwarding helper to utils
EskiMojo14 Feb 26, 2026
c02bcf2
log coloured code from separate process instead of trying to represen…
EskiMojo14 Feb 26, 2026
5ce4df0
make console output collapsible
EskiMojo14 Feb 26, 2026
2431e49
remove unnecessary class
EskiMojo14 Feb 27, 2026
d132da0
fix details icon
EskiMojo14 Feb 27, 2026
508e0f1
Create stack page
EskiMojo14 Feb 27, 2026
5918feb
rm unused import
EskiMojo14 Feb 27, 2026
3cf0a8b
fix snippet
EskiMojo14 Feb 27, 2026
014d253
create stack table display
EskiMojo14 Feb 27, 2026
cf45d88
fix import order
EskiMojo14 Mar 1, 2026
056f8c0
restructure stack results
EskiMojo14 Mar 1, 2026
71780d8
add details icon to known symbols
EskiMojo14 Mar 1, 2026
2a75d8a
add example stack
EskiMojo14 Mar 1, 2026
b84e551
highlight search frame
EskiMojo14 Mar 1, 2026
6061622
Add console output line count
EskiMojo14 Mar 1, 2026
20972ff
add line numbers to output display
EskiMojo14 Mar 2, 2026
7ef8d4d
clarify frame #
EskiMojo14 Mar 2, 2026
6f4e1dd
Update bench results
github-actions[bot] Mar 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/bench-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ jobs:
path: |
bench/bench.json
bench/download.json
bench/stack.json
schemas/libraries/**/download_compiled/**
2 changes: 1 addition & 1 deletion bench/bench.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion bench/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
"exports": {
".": "./src/index.ts",
"./bench.json": "./bench.json",
"./download.json": "./download.json"
"./download.json": "./download.json",
"./stack.json": "./stack.json"
},
"scripts": {
"bench": "node ./src/scripts/bench/orchestrate.ts",
"download": "node ./src/scripts/download.ts",
"stack": "node ./src/scripts/stack.ts",
"typecheck": "tsgo"
},
"dependencies": {
Expand Down
13 changes: 13 additions & 0 deletions bench/src/results/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,16 @@ export const downloadResultsSchema = v.object({
unminified: v.array(downloadResultSchema),
});
export type DownloadResults = v.InferOutput<typeof downloadResultsSchema>;

const resultTypeSchema = v.picklist(["success", "not an error", "no stack", "not found"]);

export const stackResultSchema = v.object({
type: resultTypeSchema,
libraryName: v.string(),
version: v.string(),
snippet: v.string(),
frame: v.optional(v.number()),
output: v.string(),
lineCount: v.number(),
});
export type StackResult = v.InferOutput<typeof stackResultSchema>;
13 changes: 3 additions & 10 deletions bench/src/scripts/bench/orchestrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { promisify } from "node:util";

import { libraries } from "@schema-benchmarks/schemas/libraries";
import { unsafeEntries } from "@schema-benchmarks/utils";
import { forwardStd } from "@schema-benchmarks/utils/node";
import * as v from "valibot";

import type { BenchResults } from "../../results/types.ts";
Expand All @@ -19,20 +20,12 @@ const execFile = promisify(child_process.execFile);

const allResults: Array<BenchResults> = [];

function forward<T>(promise: child_process.PromiseWithChild<T>) {
promise.child.stdout?.pipe(process.stdout);
promise.child.stderr?.pipe(process.stderr);
return promise;
}

for (const lib of Object.keys(libraries)) {
const libResult = await forward(
const libResult = await forwardStd(
execFile(
process.execPath,
[path.resolve(process.cwd(), "./src/scripts/bench/library.ts"), `--lib=${lib}`],
{
signal: sigintAc.signal,
},
{ signal: sigintAc.signal },
),
);
const results = libResult.stdout.split("\n").slice(-3).filter(Boolean).pop();
Expand Down
117 changes: 117 additions & 0 deletions bench/src/scripts/stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import * as child_process from "node:child_process";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import * as process from "node:process";
import * as url from "node:url";
import { promisify } from "node:util";

import { assertNotReached, errorData } from "@schema-benchmarks/schemas";
import { libraries } from "@schema-benchmarks/schemas/libraries";
import { forwardStd } from "@schema-benchmarks/utils/node";

import type { StackResult } from "../results/types.ts";

const execFile = promisify(child_process.execFile);

// this is probably quite fragile, worth keeping an eye on when we update node

const libDist = import.meta
.resolve("@schema-benchmarks/schemas/libraries")
.replace("/index.js", "");

const search = ` at Object.throw (${libDist}/`;

const cwdRegex = new RegExp(
RegExp.escape(
url.pathToFileURL(path.resolve(process.cwd(), "..")).href.replace(/^file:\/\//, ""),
),
"g",
);

async function getLoggedOutput(lib: string) {
const { stderr } = await forwardStd(
execFile(
process.execPath,
[path.resolve(process.cwd(), "./src/scripts/stack/log.ts"), `--lib=${lib}`],
{ env: { ...process.env, FORCE_COLOR: "1" } },
),
);
const output = stderr.replace(cwdRegex, "");
const lineCount = output.split("\n").length;
return { output, lineCount };
}

const results: Array<StackResult> = [];

function getScriptLineNumber(stack?: string) {
if (!stack) return;
const lines = stack.split("\n");
let i = 0;
while (i < lines.length) {
if (lines[i]?.startsWith(search)) return i === 0 ? undefined : i + 1;
i++;
}
}

for (const [lib, getConfig] of Object.entries(libraries)) {
const {
library: { name: libraryName, version },
createContext,
stack,
} = await getConfig();
if (stack) {
const { snippet } = stack;
try {
const context = await createContext();
await stack.throw(context, errorData);
assertNotReached();
} catch (e) {
const output = await getLoggedOutput(lib);
if (Error.isError(e)) {
if (e.name === "ShouldHaveThrownError") throw e;
const hasFrames = e.stack?.includes(" at ");
if (!hasFrames) {
results.push({
type: "no stack",
libraryName,
version,
snippet,
...output,
});
continue;
}
const frames = e.stack?.slice(e.stack.indexOf(" at "));
const frame = getScriptLineNumber(frames);
results.push({
type: "success",
libraryName,
version,
snippet,
frame,
...output,
});
} else {
results.push({
type: "not an error",
libraryName,
version,
snippet,
...output,
});
}
}
}
global.gc?.();
}

await fs.writeFile(
path.join(process.cwd(), "stack.json"),
JSON.stringify(
results.toSorted((a, b) => {
if (typeof a.frame === "number" && typeof b.frame === "number") return a.frame - b.frame;
if (typeof a.frame === "number") return -1;
if (typeof b.frame === "number") return 1;
return 0;
}),
),
);
32 changes: 32 additions & 0 deletions bench/src/scripts/stack/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { parseArgs } from "node:util";

import { assertNotReached, errorData } from "@schema-benchmarks/schemas";
import { libraries } from "@schema-benchmarks/schemas/libraries";

const {
values: { lib },
} = parseArgs({
options: {
lib: {
type: "string",
short: "l",
},
},
});

if (!lib || !libraries[lib]) {
throw new Error(`Library not found: ${lib}`);
}

const config = await libraries[lib]();

try {
await config.stack?.throw(await config.createContext(), errorData);
assertNotReached();
} catch (e) {
if (Error.isError(e) && e.name === "ShouldHaveThrownError") {
throw e;
}
// log the coloured output
console.error(e);
}
5 changes: 5 additions & 0 deletions bench/stack.d.json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { StackResult } from "./src/results/types.ts";

declare const results: Array<StackResult>;

export default results;
1 change: 1 addition & 0 deletions bench/stack.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"website:storybook": "pnpm run --filter website storybook",
"bench:bench": "pnpm run --filter '*/bench' bench",
"bench:download": "pnpm run --filter '*/bench' download",
"bench:all": "pnpm bench:download && pnpm bench:bench",
"bench:stack": "pnpm run --filter '*/bench' stack",
"bench:all": "pnpm bench:download && pnpm bench:bench && pnpm bench:stack",
"postinstall": "lefthook install"
},
"devDependencies": {
Expand Down
10 changes: 9 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions schemas/libraries/ajv/benchmarks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getVersion } from "@schema-benchmarks/utils/node" with { type: "macro" };
import type Ajv from "ajv";
import { ValidationError } from "ajv";
import type { FormatName } from "ajv-formats";
import addFormats from "ajv-formats";
import ts from "dedent";
Expand Down Expand Up @@ -66,4 +67,15 @@ export default defineBenchmarks({
ipv4: createStringBenchmark("ipv4"),
ipv6: createStringBenchmark("ipv6"),
},
stack: {
throw: ({ validate }, data) => {
validate(data);
throw new ValidationError(validate.errors || []);
},
snippet: ts`
// const validate = ajv.compile(schema);
validate(data);
throw new ValidationError(validate.errors || []);
`,
},
});
9 changes: 8 additions & 1 deletion schemas/libraries/arktype/benchmarks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type } from "arktype";
import ts from "dedent";

import type { StringBenchmarkConfig } from "#src";
import { defineBenchmarks } from "#src";
import { assertNotReached, defineBenchmarks } from "#src";

import { getArkTypeSchema } from ".";

Expand Down Expand Up @@ -61,4 +61,11 @@ export default defineBenchmarks({
ipv4: createStringBenchmark("ip.v4"),
ipv6: createStringBenchmark("ip.v6"),
},
stack: {
throw: ({ schema }, data) => {
schema.assert(data);
assertNotReached();
},
snippet: ts`schema.assert(data)`,
},
});
17 changes: 15 additions & 2 deletions schemas/libraries/effect/benchmarks.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { getVersion } from "@schema-benchmarks/utils/node" with { type: "macro" };
import ts from "dedent";
import { Either } from "effect";
import { Effect, Either } from "effect";
import * as Schema from "effect/Schema";

import { defineBenchmarks } from "#src";
import { assertNotReached, defineBenchmarks } from "#src";

import { getEffectSchema } from ".";

Expand Down Expand Up @@ -99,4 +99,17 @@ export default defineBenchmarks({
`,
},
},
stack: {
throw: ({ decodeAll }, data) => {
Effect.runSync(decodeAll(data));
assertNotReached();
},
snippet: ts`
// const decodeAll = Schema.decodeUnknownEither(
// schema,
// { errors: "all" }
// );
Effect.runSync(decodeAll(data));
`,
},
});
11 changes: 10 additions & 1 deletion schemas/libraries/effect___beta/benchmarks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ts from "dedent";
import { isSome } from "effect___beta/Option";
import * as Schema from "effect___beta/Schema";

import { defineBenchmarks } from "#src";
import { assertNotReached, defineBenchmarks } from "#src";

import { getEffectSchema } from ".";

Expand Down Expand Up @@ -94,4 +94,13 @@ export default defineBenchmarks({
`,
},
},
stack: {
throw: ({ schema }, data) => {
Schema.decodeUnknownSync(schema)(data, { errors: "first" });
assertNotReached();
},
snippet: ts`
Schema.decodeUnknownSync(schema)(data, { errors: "first" })
`,
},
});
6 changes: 6 additions & 0 deletions schemas/libraries/joi/benchmarks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,10 @@ export default defineBenchmarks({
ipv4: createStringBenchmark("ip", ts`ip({ version: "ipv4" })`, { version: "ipv4" }),
ipv6: createStringBenchmark("ip", ts`ip({ version: "ipv6" })`, { version: "ipv6" }),
},
stack: {
throw: ({ schema }, data) => {
throw schema.validate(data).error;
},
snippet: ts`throw schema.validate(data).error`,
},
});
Loading