Skip to content

Commit d93f111

Browse files
authored
feat: export bench-node ts types (#77)
1 parent 29aa66c commit d93f111

File tree

4 files changed

+275
-1
lines changed

4 files changed

+275
-1
lines changed

biome.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
"enabled": true,
88
"rules": {
99
"recommended": true,
10+
"suspicious": {
11+
"noExplicitAny": "off"
12+
},
1013
"style": {
1114
"noParameterAssign": "off",
1215
"noUselessElse": "off"

index.d.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Type definitions for bench-node
2+
3+
/// <reference types="node" />
4+
import type { Histogram } from "node:perf_hooks";
5+
6+
export declare namespace BenchNode {
7+
interface PluginHookVarNames {
8+
awaitOrEmpty: string;
9+
bench: any; // Can be string during validation, object with 'fn' property during actual run
10+
context: any;
11+
timer: any;
12+
}
13+
14+
interface BenchmarkResult {
15+
name: string;
16+
opsSec?: number; // Only in 'ops' mode
17+
opsSecPerRun?: number[]; // Useful when repeatSuite > 1
18+
totalTime?: number; // Total execution time in seconds (Only in 'time' mode)
19+
iterations: number;
20+
histogram: Histogram;
21+
plugins?: Record<string, any>; // Object with plugin results
22+
}
23+
24+
type ReporterFunction = (results: BenchmarkResult[]) => void;
25+
26+
interface SuiteOptions {
27+
reporter?: ReporterFunction | false | null;
28+
benchmarkMode?: "ops" | "time";
29+
useWorkers?: boolean;
30+
plugins?: Plugin[];
31+
}
32+
33+
interface BenchmarkOptions {
34+
minTime?: number; // Minimum duration in seconds
35+
maxTime?: number; // Maximum duration in seconds
36+
repeatSuite?: number; // Number of times to repeat benchmark
37+
minSamples?: number; // Minimum number of samples per round
38+
}
39+
40+
type BenchmarkFunction = (timer?: {
41+
start: () => void;
42+
count: number;
43+
}) => void | Promise<void>;
44+
45+
interface Plugin {
46+
isSupported?(): boolean;
47+
beforeClockTemplate?(varNames: PluginHookVarNames): string[];
48+
afterClockTemplate?(varNames: PluginHookVarNames): string[];
49+
onCompleteBenchmark?(result: BenchmarkResult): void;
50+
toString?(): string;
51+
}
52+
53+
class Suite {
54+
constructor(options?: SuiteOptions);
55+
add(name: string, fn: BenchmarkFunction): this;
56+
add(name: string, options: BenchmarkOptions, fn: BenchmarkFunction): this;
57+
run(): Promise<BenchmarkResult[]>;
58+
}
59+
60+
class V8NeverOptimizePlugin implements Plugin {
61+
isSupported(): boolean;
62+
beforeClockTemplate(varNames: PluginHookVarNames): string[];
63+
toString(): string;
64+
}
65+
66+
class V8GetOptimizationStatus implements Plugin {
67+
isSupported(): boolean;
68+
beforeClockTemplate(varNames: PluginHookVarNames): string[];
69+
afterClockTemplate(varNames: PluginHookVarNames): string[];
70+
onCompleteBenchmark(result: BenchmarkResult): void;
71+
toString(): string;
72+
}
73+
74+
class V8OptimizeOnNextCallPlugin implements Plugin {
75+
isSupported(): boolean;
76+
beforeClockTemplate(varNames: PluginHookVarNames): string[];
77+
toString(): string;
78+
}
79+
}
80+
81+
export declare const textReport: BenchNode.ReporterFunction;
82+
export declare const chartReport: BenchNode.ReporterFunction;
83+
export declare const htmlReport: BenchNode.ReporterFunction;
84+
export declare const jsonReport: BenchNode.ReporterFunction;
85+
export declare const csvReport: BenchNode.ReporterFunction;
86+
87+
export declare class Suite extends BenchNode.Suite {}
88+
export declare class V8NeverOptimizePlugin extends BenchNode.V8NeverOptimizePlugin {}
89+
export declare class V8GetOptimizationStatus extends BenchNode.V8GetOptimizationStatus {}
90+
export declare class V8OptimizeOnNextCallPlugin extends BenchNode.V8OptimizeOnNextCallPlugin {}

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
"version": "0.7.0",
44
"description": "",
55
"main": "lib/index.js",
6+
"types": "index.d.ts",
67
"scripts": {
78
"test": "node --test --allow-natives-syntax",
9+
"test:types": "tsd -f test/types.test-d.ts",
810
"lint": "biome lint .",
911
"lint:ci": "biome ci .",
1012
"lint:fix": "biome lint --write .",
@@ -35,6 +37,8 @@
3537
"piscina": "^4.8.0"
3638
},
3739
"devDependencies": {
38-
"@biomejs/biome": "1.9.4"
40+
"@biomejs/biome": "1.9.4",
41+
"@types/node": "^20",
42+
"tsd": "^0.31.0"
3943
}
4044
}

test/types.test-d.ts

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import type { Histogram } from "node:perf_hooks";
2+
import { expectAssignable, expectNotAssignable, expectType } from "tsd";
3+
import {
4+
type BenchNode,
5+
Suite,
6+
V8GetOptimizationStatus,
7+
V8NeverOptimizePlugin,
8+
V8OptimizeOnNextCallPlugin,
9+
chartReport,
10+
csvReport,
11+
htmlReport,
12+
jsonReport,
13+
textReport,
14+
} from "../index";
15+
16+
expectType<BenchNode.Suite>(new Suite());
17+
expectType<BenchNode.Suite>(
18+
new Suite({
19+
reporter: textReport,
20+
benchmarkMode: "ops",
21+
useWorkers: true,
22+
plugins: [new V8NeverOptimizePlugin()],
23+
}),
24+
);
25+
expectType<BenchNode.Suite>(new Suite({ reporter: false }));
26+
expectType<BenchNode.Suite>(new Suite({ reporter: null }));
27+
28+
expectAssignable<BenchNode.SuiteOptions>({});
29+
expectAssignable<BenchNode.SuiteOptions>({ reporter: chartReport });
30+
expectAssignable<BenchNode.SuiteOptions>({ benchmarkMode: "time" });
31+
expectAssignable<BenchNode.SuiteOptions>({ useWorkers: false });
32+
expectAssignable<BenchNode.SuiteOptions>({
33+
plugins: [new V8GetOptimizationStatus()],
34+
});
35+
expectNotAssignable<BenchNode.SuiteOptions>({ unknownOption: "test" });
36+
expectNotAssignable<BenchNode.SuiteOptions>({ reporter: "not-a-function" });
37+
38+
expectAssignable<BenchNode.BenchmarkOptions>({});
39+
expectAssignable<BenchNode.BenchmarkOptions>({ minTime: 0.1 });
40+
expectAssignable<BenchNode.BenchmarkOptions>({ maxTime: 1 });
41+
expectAssignable<BenchNode.BenchmarkOptions>({ repeatSuite: 2 });
42+
expectAssignable<BenchNode.BenchmarkOptions>({ minSamples: 5 });
43+
expectNotAssignable<BenchNode.BenchmarkOptions>({ minTime: "not-a-number" });
44+
45+
// Test Suite.add method
46+
const suite = new Suite();
47+
expectType<BenchNode.Suite>(
48+
suite.add("sync benchmark", () => {
49+
const arr = [];
50+
for (let i = 0; i < 1000; i++) {
51+
arr.push(i);
52+
}
53+
}),
54+
);
55+
expectType<BenchNode.Suite>(
56+
suite.add("async benchmark", async () => {
57+
await new Promise((resolve) => setTimeout(resolve, 10));
58+
}),
59+
);
60+
expectType<BenchNode.Suite>(
61+
suite.add(
62+
"benchmark with options",
63+
{ minTime: 0.1, maxTime: 1, repeatSuite: 2, minSamples: 5 },
64+
() => {
65+
/* ... */
66+
},
67+
),
68+
);
69+
70+
const managedBenchFn: BenchNode.BenchmarkFunction = (timer) => {
71+
if (timer) {
72+
expectType<() => void>(timer.start);
73+
expectType<number>(timer.count);
74+
timer.start();
75+
for (let i = 0; i < timer.count; i++) {
76+
// some operation
77+
}
78+
}
79+
};
80+
expectType<BenchNode.Suite>(suite.add("managed benchmark", managedBenchFn));
81+
82+
// Test Suite.run method
83+
expectType<Promise<BenchNode.BenchmarkResult[]>>(suite.run());
84+
85+
suite.run().then((results) => {
86+
expectType<BenchNode.BenchmarkResult[]>(results);
87+
if (results.length > 0) {
88+
const result = results[0];
89+
expectType<string>(result.name);
90+
expectType<number | undefined>(result.opsSec);
91+
expectType<number[] | undefined>(result.opsSecPerRun);
92+
expectType<number | undefined>(result.totalTime);
93+
expectType<number>(result.iterations);
94+
expectType<Histogram>(result.histogram);
95+
expectType<Record<string, any> | undefined>(result.plugins);
96+
97+
if (result.plugins?.V8GetOptimizationStatus) {
98+
expectType<any>(
99+
result.plugins.V8GetOptimizationStatus.optimizationStatuses,
100+
);
101+
}
102+
}
103+
});
104+
105+
// Test ReporterFunction
106+
const sampleResults: BenchNode.BenchmarkResult[] = [
107+
{
108+
name: "sample",
109+
iterations: 100,
110+
histogram: {} as Histogram, // Cast for simplicity in type test
111+
opsSec: 10000,
112+
opsSecPerRun: [10000],
113+
totalTime: 0.1,
114+
plugins: { MyPlugin: { data: "value" } },
115+
},
116+
];
117+
expectType<void>(textReport(sampleResults));
118+
expectType<void>(chartReport(sampleResults));
119+
expectType<void>(htmlReport(sampleResults));
120+
expectType<void>(jsonReport(sampleResults));
121+
expectType<void>(csvReport(sampleResults));
122+
123+
// Test Plugins
124+
const plugin1 = new V8NeverOptimizePlugin();
125+
expectAssignable<BenchNode.Plugin>(plugin1);
126+
if (plugin1.isSupported?.()) {
127+
expectType<boolean>(plugin1.isSupported());
128+
const varNames: BenchNode.PluginHookVarNames = {
129+
awaitOrEmpty: "",
130+
bench: "fn",
131+
context: {},
132+
timer: {},
133+
};
134+
expectType<string[]>(plugin1.beforeClockTemplate(varNames));
135+
expectType<string>(plugin1.toString());
136+
}
137+
138+
const plugin2 = new V8GetOptimizationStatus();
139+
expectAssignable<BenchNode.Plugin>(plugin2);
140+
if (plugin2.isSupported?.()) {
141+
expectType<boolean>(plugin2.isSupported());
142+
const varNames: BenchNode.PluginHookVarNames = {
143+
awaitOrEmpty: "",
144+
bench: "fn",
145+
context: {},
146+
timer: {},
147+
};
148+
expectType<string[]>(plugin2.beforeClockTemplate(varNames));
149+
expectType<string[]>(plugin2.afterClockTemplate(varNames));
150+
if (plugin2.onCompleteBenchmark) {
151+
expectType<void>(plugin2.onCompleteBenchmark(sampleResults[0]));
152+
}
153+
expectType<string>(plugin2.toString());
154+
}
155+
156+
const plugin3 = new V8OptimizeOnNextCallPlugin();
157+
expectAssignable<BenchNode.Plugin>(plugin3);
158+
if (plugin3.isSupported?.()) {
159+
expectType<boolean>(plugin3.isSupported());
160+
const varNames: BenchNode.PluginHookVarNames = {
161+
awaitOrEmpty: "",
162+
bench: "fn",
163+
context: {},
164+
timer: {},
165+
};
166+
expectType<string[]>(plugin3.beforeClockTemplate(varNames));
167+
expectType<string>(plugin3.toString());
168+
}
169+
170+
expectAssignable<BenchNode.Suite>(new Suite());
171+
expectAssignable<BenchNode.V8NeverOptimizePlugin>(new V8NeverOptimizePlugin());
172+
expectAssignable<BenchNode.V8GetOptimizationStatus>(
173+
new V8GetOptimizationStatus(),
174+
);
175+
expectAssignable<BenchNode.V8OptimizeOnNextCallPlugin>(
176+
new V8OptimizeOnNextCallPlugin(),
177+
);

0 commit comments

Comments
 (0)