modestbench 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE.md +55 -0
  3. package/README.md +699 -0
  4. package/dist/bootstrap.cjs +37 -0
  5. package/dist/bootstrap.cjs.map +1 -0
  6. package/dist/bootstrap.d.cts +17 -0
  7. package/dist/bootstrap.d.cts.map +1 -0
  8. package/dist/bootstrap.d.ts +17 -0
  9. package/dist/bootstrap.d.ts.map +1 -0
  10. package/dist/bootstrap.js +33 -0
  11. package/dist/bootstrap.js.map +1 -0
  12. package/dist/cli/commands/history.cjs +459 -0
  13. package/dist/cli/commands/history.cjs.map +1 -0
  14. package/dist/cli/commands/history.d.cts +34 -0
  15. package/dist/cli/commands/history.d.cts.map +1 -0
  16. package/dist/cli/commands/history.d.ts +34 -0
  17. package/dist/cli/commands/history.d.ts.map +1 -0
  18. package/dist/cli/commands/history.js +422 -0
  19. package/dist/cli/commands/history.js.map +1 -0
  20. package/dist/cli/commands/init.cjs +566 -0
  21. package/dist/cli/commands/init.cjs.map +1 -0
  22. package/dist/cli/commands/init.d.cts +26 -0
  23. package/dist/cli/commands/init.d.cts.map +1 -0
  24. package/dist/cli/commands/init.d.ts +26 -0
  25. package/dist/cli/commands/init.d.ts.map +1 -0
  26. package/dist/cli/commands/init.js +562 -0
  27. package/dist/cli/commands/init.js.map +1 -0
  28. package/dist/cli/commands/run.cjs +285 -0
  29. package/dist/cli/commands/run.cjs.map +1 -0
  30. package/dist/cli/commands/run.d.cts +37 -0
  31. package/dist/cli/commands/run.d.cts.map +1 -0
  32. package/dist/cli/commands/run.d.ts +37 -0
  33. package/dist/cli/commands/run.d.ts.map +1 -0
  34. package/dist/cli/commands/run.js +248 -0
  35. package/dist/cli/commands/run.js.map +1 -0
  36. package/dist/cli/index.cjs +523 -0
  37. package/dist/cli/index.cjs.map +1 -0
  38. package/dist/cli/index.d.cts +58 -0
  39. package/dist/cli/index.d.cts.map +1 -0
  40. package/dist/cli/index.d.ts +58 -0
  41. package/dist/cli/index.d.ts.map +1 -0
  42. package/dist/cli/index.js +515 -0
  43. package/dist/cli/index.js.map +1 -0
  44. package/dist/config/manager.cjs +370 -0
  45. package/dist/config/manager.cjs.map +1 -0
  46. package/dist/config/manager.d.cts +46 -0
  47. package/dist/config/manager.d.cts.map +1 -0
  48. package/dist/config/manager.d.ts +46 -0
  49. package/dist/config/manager.d.ts.map +1 -0
  50. package/dist/config/manager.js +333 -0
  51. package/dist/config/manager.js.map +1 -0
  52. package/dist/config/schema.cjs +182 -0
  53. package/dist/config/schema.cjs.map +1 -0
  54. package/dist/config/schema.d.cts +51 -0
  55. package/dist/config/schema.d.cts.map +1 -0
  56. package/dist/config/schema.d.ts +51 -0
  57. package/dist/config/schema.d.ts.map +1 -0
  58. package/dist/config/schema.js +145 -0
  59. package/dist/config/schema.js.map +1 -0
  60. package/dist/constants.cjs +22 -0
  61. package/dist/constants.cjs.map +1 -0
  62. package/dist/constants.d.cts +10 -0
  63. package/dist/constants.d.cts.map +1 -0
  64. package/dist/constants.d.ts +10 -0
  65. package/dist/constants.d.ts.map +1 -0
  66. package/dist/constants.js +19 -0
  67. package/dist/constants.js.map +1 -0
  68. package/dist/core/benchmark-schema.cjs +135 -0
  69. package/dist/core/benchmark-schema.cjs.map +1 -0
  70. package/dist/core/benchmark-schema.d.cts +139 -0
  71. package/dist/core/benchmark-schema.d.cts.map +1 -0
  72. package/dist/core/benchmark-schema.d.ts +139 -0
  73. package/dist/core/benchmark-schema.d.ts.map +1 -0
  74. package/dist/core/benchmark-schema.js +132 -0
  75. package/dist/core/benchmark-schema.js.map +1 -0
  76. package/dist/core/engine.cjs +669 -0
  77. package/dist/core/engine.cjs.map +1 -0
  78. package/dist/core/engine.d.cts +128 -0
  79. package/dist/core/engine.d.cts.map +1 -0
  80. package/dist/core/engine.d.ts +128 -0
  81. package/dist/core/engine.d.ts.map +1 -0
  82. package/dist/core/engine.js +632 -0
  83. package/dist/core/engine.js.map +1 -0
  84. package/dist/core/engines/accurate-engine.cjs +292 -0
  85. package/dist/core/engines/accurate-engine.cjs.map +1 -0
  86. package/dist/core/engines/accurate-engine.d.cts +63 -0
  87. package/dist/core/engines/accurate-engine.d.cts.map +1 -0
  88. package/dist/core/engines/accurate-engine.d.ts +63 -0
  89. package/dist/core/engines/accurate-engine.d.ts.map +1 -0
  90. package/dist/core/engines/accurate-engine.js +288 -0
  91. package/dist/core/engines/accurate-engine.js.map +1 -0
  92. package/dist/core/engines/index.cjs +21 -0
  93. package/dist/core/engines/index.cjs.map +1 -0
  94. package/dist/core/engines/index.d.cts +16 -0
  95. package/dist/core/engines/index.d.cts.map +1 -0
  96. package/dist/core/engines/index.d.ts +16 -0
  97. package/dist/core/engines/index.d.ts.map +1 -0
  98. package/dist/core/engines/index.js +16 -0
  99. package/dist/core/engines/index.js.map +1 -0
  100. package/dist/core/engines/tinybench-engine.cjs +286 -0
  101. package/dist/core/engines/tinybench-engine.cjs.map +1 -0
  102. package/dist/core/engines/tinybench-engine.d.cts +18 -0
  103. package/dist/core/engines/tinybench-engine.d.cts.map +1 -0
  104. package/dist/core/engines/tinybench-engine.d.ts +18 -0
  105. package/dist/core/engines/tinybench-engine.d.ts.map +1 -0
  106. package/dist/core/engines/tinybench-engine.js +282 -0
  107. package/dist/core/engines/tinybench-engine.js.map +1 -0
  108. package/dist/core/error-manager.cjs +303 -0
  109. package/dist/core/error-manager.cjs.map +1 -0
  110. package/dist/core/error-manager.d.cts +77 -0
  111. package/dist/core/error-manager.d.cts.map +1 -0
  112. package/dist/core/error-manager.d.ts +77 -0
  113. package/dist/core/error-manager.d.ts.map +1 -0
  114. package/dist/core/error-manager.js +299 -0
  115. package/dist/core/error-manager.js.map +1 -0
  116. package/dist/core/loader.cjs +287 -0
  117. package/dist/core/loader.cjs.map +1 -0
  118. package/dist/core/loader.d.cts +55 -0
  119. package/dist/core/loader.d.cts.map +1 -0
  120. package/dist/core/loader.d.ts +55 -0
  121. package/dist/core/loader.d.ts.map +1 -0
  122. package/dist/core/loader.js +250 -0
  123. package/dist/core/loader.js.map +1 -0
  124. package/dist/core/stats-utils.cjs +99 -0
  125. package/dist/core/stats-utils.cjs.map +1 -0
  126. package/dist/core/stats-utils.d.cts +50 -0
  127. package/dist/core/stats-utils.d.cts.map +1 -0
  128. package/dist/core/stats-utils.d.ts +50 -0
  129. package/dist/core/stats-utils.d.ts.map +1 -0
  130. package/dist/core/stats-utils.js +94 -0
  131. package/dist/core/stats-utils.js.map +1 -0
  132. package/dist/index.cjs +64 -0
  133. package/dist/index.cjs.map +1 -0
  134. package/dist/index.d.cts +22 -0
  135. package/dist/index.d.cts.map +1 -0
  136. package/dist/index.d.ts +22 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +30 -0
  139. package/dist/index.js.map +1 -0
  140. package/dist/progress/manager.cjs +325 -0
  141. package/dist/progress/manager.cjs.map +1 -0
  142. package/dist/progress/manager.d.cts +125 -0
  143. package/dist/progress/manager.d.cts.map +1 -0
  144. package/dist/progress/manager.d.ts +125 -0
  145. package/dist/progress/manager.d.ts.map +1 -0
  146. package/dist/progress/manager.js +321 -0
  147. package/dist/progress/manager.js.map +1 -0
  148. package/dist/reporters/csv.cjs +250 -0
  149. package/dist/reporters/csv.cjs.map +1 -0
  150. package/dist/reporters/csv.d.cts +92 -0
  151. package/dist/reporters/csv.d.cts.map +1 -0
  152. package/dist/reporters/csv.d.ts +92 -0
  153. package/dist/reporters/csv.d.ts.map +1 -0
  154. package/dist/reporters/csv.js +246 -0
  155. package/dist/reporters/csv.js.map +1 -0
  156. package/dist/reporters/human.cjs +516 -0
  157. package/dist/reporters/human.cjs.map +1 -0
  158. package/dist/reporters/human.d.cts +86 -0
  159. package/dist/reporters/human.d.cts.map +1 -0
  160. package/dist/reporters/human.d.ts +86 -0
  161. package/dist/reporters/human.d.ts.map +1 -0
  162. package/dist/reporters/human.js +509 -0
  163. package/dist/reporters/human.js.map +1 -0
  164. package/dist/reporters/index.cjs +17 -0
  165. package/dist/reporters/index.cjs.map +1 -0
  166. package/dist/reporters/index.d.cts +10 -0
  167. package/dist/reporters/index.d.cts.map +1 -0
  168. package/dist/reporters/index.d.ts +10 -0
  169. package/dist/reporters/index.d.ts.map +1 -0
  170. package/dist/reporters/index.js +10 -0
  171. package/dist/reporters/index.js.map +1 -0
  172. package/dist/reporters/json.cjs +215 -0
  173. package/dist/reporters/json.cjs.map +1 -0
  174. package/dist/reporters/json.d.cts +79 -0
  175. package/dist/reporters/json.d.cts.map +1 -0
  176. package/dist/reporters/json.d.ts +79 -0
  177. package/dist/reporters/json.d.ts.map +1 -0
  178. package/dist/reporters/json.js +211 -0
  179. package/dist/reporters/json.js.map +1 -0
  180. package/dist/reporters/registry.cjs +255 -0
  181. package/dist/reporters/registry.cjs.map +1 -0
  182. package/dist/reporters/registry.d.cts +155 -0
  183. package/dist/reporters/registry.d.cts.map +1 -0
  184. package/dist/reporters/registry.d.ts +155 -0
  185. package/dist/reporters/registry.d.ts.map +1 -0
  186. package/dist/reporters/registry.js +249 -0
  187. package/dist/reporters/registry.js.map +1 -0
  188. package/dist/reporters/simple.cjs +328 -0
  189. package/dist/reporters/simple.cjs.map +1 -0
  190. package/dist/reporters/simple.d.cts +51 -0
  191. package/dist/reporters/simple.d.cts.map +1 -0
  192. package/dist/reporters/simple.d.ts +51 -0
  193. package/dist/reporters/simple.d.ts.map +1 -0
  194. package/dist/reporters/simple.js +321 -0
  195. package/dist/reporters/simple.js.map +1 -0
  196. package/dist/schema/modestbench-config.schema.json +162 -0
  197. package/dist/storage/history.cjs +456 -0
  198. package/dist/storage/history.cjs.map +1 -0
  199. package/dist/storage/history.d.cts +99 -0
  200. package/dist/storage/history.d.cts.map +1 -0
  201. package/dist/storage/history.d.ts +99 -0
  202. package/dist/storage/history.d.ts.map +1 -0
  203. package/dist/storage/history.js +452 -0
  204. package/dist/storage/history.js.map +1 -0
  205. package/dist/types/cli.cjs +21 -0
  206. package/dist/types/cli.cjs.map +1 -0
  207. package/dist/types/cli.d.cts +296 -0
  208. package/dist/types/cli.d.cts.map +1 -0
  209. package/dist/types/cli.d.ts +296 -0
  210. package/dist/types/cli.d.ts.map +1 -0
  211. package/dist/types/cli.js +18 -0
  212. package/dist/types/cli.js.map +1 -0
  213. package/dist/types/core.cjs +14 -0
  214. package/dist/types/core.cjs.map +1 -0
  215. package/dist/types/core.d.cts +380 -0
  216. package/dist/types/core.d.cts.map +1 -0
  217. package/dist/types/core.d.ts +380 -0
  218. package/dist/types/core.d.ts.map +1 -0
  219. package/dist/types/core.js +13 -0
  220. package/dist/types/core.js.map +1 -0
  221. package/dist/types/index.cjs +27 -0
  222. package/dist/types/index.cjs.map +1 -0
  223. package/dist/types/index.d.cts +11 -0
  224. package/dist/types/index.d.cts.map +1 -0
  225. package/dist/types/index.d.ts +11 -0
  226. package/dist/types/index.d.ts.map +1 -0
  227. package/dist/types/index.js +11 -0
  228. package/dist/types/index.js.map +1 -0
  229. package/dist/types/interfaces.cjs +10 -0
  230. package/dist/types/interfaces.cjs.map +1 -0
  231. package/dist/types/interfaces.d.cts +381 -0
  232. package/dist/types/interfaces.d.cts.map +1 -0
  233. package/dist/types/interfaces.d.ts +381 -0
  234. package/dist/types/interfaces.d.ts.map +1 -0
  235. package/dist/types/interfaces.js +9 -0
  236. package/dist/types/interfaces.js.map +1 -0
  237. package/dist/types/utility.cjs +92 -0
  238. package/dist/types/utility.cjs.map +1 -0
  239. package/dist/types/utility.d.cts +330 -0
  240. package/dist/types/utility.d.cts.map +1 -0
  241. package/dist/types/utility.d.ts +330 -0
  242. package/dist/types/utility.d.ts.map +1 -0
  243. package/dist/types/utility.js +78 -0
  244. package/dist/types/utility.js.map +1 -0
  245. package/package.json +211 -0
  246. package/src/bootstrap.ts +35 -0
  247. package/src/cli/commands/history.ts +569 -0
  248. package/src/cli/commands/init.ts +658 -0
  249. package/src/cli/commands/run.ts +346 -0
  250. package/src/cli/index.ts +642 -0
  251. package/src/config/manager.ts +387 -0
  252. package/src/config/schema.ts +188 -0
  253. package/src/constants.ts +21 -0
  254. package/src/core/benchmark-schema.ts +185 -0
  255. package/src/core/engine.ts +888 -0
  256. package/src/core/engines/accurate-engine.ts +408 -0
  257. package/src/core/engines/index.ts +16 -0
  258. package/src/core/engines/tinybench-engine.ts +335 -0
  259. package/src/core/error-manager.ts +372 -0
  260. package/src/core/loader.ts +324 -0
  261. package/src/core/stats-utils.ts +135 -0
  262. package/src/index.ts +46 -0
  263. package/src/progress/manager.ts +415 -0
  264. package/src/reporters/csv.ts +368 -0
  265. package/src/reporters/human.ts +707 -0
  266. package/src/reporters/index.ts +10 -0
  267. package/src/reporters/json.ts +302 -0
  268. package/src/reporters/registry.ts +349 -0
  269. package/src/reporters/simple.ts +459 -0
  270. package/src/storage/history.ts +600 -0
  271. package/src/types/cli.ts +312 -0
  272. package/src/types/core.ts +414 -0
  273. package/src/types/index.ts +18 -0
  274. package/src/types/interfaces.ts +451 -0
  275. package/src/types/utility.ts +446 -0
@@ -0,0 +1,324 @@
1
+ /**
2
+ * ModestBench File Loader
3
+ *
4
+ * Handles discovery, loading, and validation of benchmark files. Supports glob
5
+ * pattern matching and file structure validation.
6
+ */
7
+
8
+ import { glob } from 'glob';
9
+ import { access, readFile, stat } from 'node:fs/promises';
10
+ import { extname } from 'node:path';
11
+
12
+ import type {
13
+ BenchmarkDefinition,
14
+ BenchmarkFile,
15
+ FileLoader,
16
+ ValidationError,
17
+ ValidationResult,
18
+ ValidationWarning,
19
+ } from '../types/index.js';
20
+
21
+ import {
22
+ BENCHMARK_FILE_EXTENSIONS,
23
+ BENCHMARK_FILE_PATTERN,
24
+ } from '../constants.js';
25
+ import { benchmarkFileSchema } from './benchmark-schema.js';
26
+
27
+ /**
28
+ * File change notification for watch functionality
29
+ */
30
+ interface FileChange {
31
+ readonly filePath: string;
32
+ readonly timestamp: Date;
33
+ readonly type: 'added' | 'deleted' | 'modified';
34
+ }
35
+
36
+ /**
37
+ * File watcher interface
38
+ */
39
+ interface FileWatcher {
40
+ close(): void;
41
+ }
42
+
43
+ /**
44
+ * Implementation of FileLoader for benchmark files
45
+ */
46
+ export class BenchmarkFileLoader implements FileLoader {
47
+ private readonly supportedExtensions = BENCHMARK_FILE_EXTENSIONS;
48
+
49
+ /**
50
+ * Discover benchmark files using glob patterns or explicit file paths
51
+ */
52
+ async discover(
53
+ pattern: string | string[],
54
+ exclude: string[] = [],
55
+ ): Promise<string[]> {
56
+ try {
57
+ let patterns = Array.isArray(pattern) ? pattern : [pattern];
58
+
59
+ // Handle empty patterns - use sensible defaults
60
+ if (patterns.length === 0) {
61
+ patterns = [
62
+ `*${BENCHMARK_FILE_PATTERN}`, // top-level current directory
63
+ `bench/*${BENCHMARK_FILE_PATTERN}`, // top-level bench/ directory
64
+ ];
65
+ }
66
+
67
+ // Expand directory paths to recursive glob patterns
68
+ const expandedPatterns: string[] = [];
69
+ for (const p of patterns) {
70
+ try {
71
+ const stats = await stat(p);
72
+ if (stats.isDirectory()) {
73
+ // Directory: search recursively
74
+ expandedPatterns.push(`${p}/**/*${BENCHMARK_FILE_PATTERN}`);
75
+ } else {
76
+ // File or doesn't exist: use as-is (glob will handle it)
77
+ expandedPatterns.push(p);
78
+ }
79
+ } catch {
80
+ // Path doesn't exist, treat as glob pattern
81
+ expandedPatterns.push(p);
82
+ }
83
+ }
84
+
85
+ const allFiles = new Set<string>();
86
+
87
+ // Process each pattern
88
+ for (const p of expandedPatterns) {
89
+ const files = await glob(p, {
90
+ absolute: true,
91
+ ignore: exclude,
92
+ nodir: true,
93
+ });
94
+
95
+ // Add discovered files to the set (automatic deduplication)
96
+ for (const file of files) {
97
+ allFiles.add(file);
98
+ }
99
+ }
100
+
101
+ // Filter to supported file extensions
102
+ const supportedFiles = Array.from(allFiles).filter((file: string) => {
103
+ const ext = extname(file);
104
+ return this.supportedExtensions.has(ext);
105
+ });
106
+
107
+ return supportedFiles.sort();
108
+ } catch (error) {
109
+ throw new Error(
110
+ `File discovery failed: ${error instanceof Error ? error.message : String(error)}`,
111
+ );
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Load a single benchmark file
117
+ */
118
+ async load(filePath: string): Promise<BenchmarkFile> {
119
+ try {
120
+ // Basic file checks (existence, extension)
121
+ const basicValidation = await this.validate(filePath);
122
+ if (!basicValidation.valid) {
123
+ throw new Error(
124
+ `Invalid benchmark file: ${basicValidation.errors.map((e) => e.message).join(', ')}`,
125
+ );
126
+ }
127
+
128
+ // Read file content
129
+ const content = await readFile(filePath, 'utf-8');
130
+
131
+ // Get file stats for metadata
132
+ const stats = await stat(filePath);
133
+
134
+ // Load the module using dynamic import
135
+ const ext = extname(filePath);
136
+ let module: { [key: string]: unknown; default?: unknown };
137
+
138
+ if (ext === '.ts' || ext === '.mts' || ext === '.cts') {
139
+ // For TypeScript files, use cosmiconfig-typescript-loader
140
+ const { TypeScriptLoader: createTypeScriptLoader } = await import(
141
+ 'cosmiconfig-typescript-loader'
142
+ );
143
+ const loader = createTypeScriptLoader();
144
+ module = (await loader(filePath, content)) as {
145
+ [key: string]: unknown;
146
+ default?: unknown;
147
+ };
148
+ } else {
149
+ // Use native dynamic import for JavaScript files with cache busting
150
+ // Add timestamp to prevent module caching issues across multiple loads
151
+ const timestamp = Date.now();
152
+ module = (await import(`${filePath}?t=${timestamp}`)) as {
153
+ [key: string]: unknown;
154
+ default?: unknown;
155
+ };
156
+ }
157
+
158
+ const exports = module.default || module;
159
+
160
+ // Validate the loaded exports structure with Zod
161
+ const structureValidation = this.validateExports(filePath, exports);
162
+ if (!structureValidation.valid || !structureValidation.data) {
163
+ throw new Error(
164
+ `Invalid benchmark structure: ${structureValidation.errors.map((e) => e.message).join(', ')}`,
165
+ );
166
+ }
167
+
168
+ // Use the transformed/normalized data from Zod
169
+ // (this ensures shorthand functions are properly wrapped)
170
+ const normalizedExports = structureValidation.data;
171
+
172
+ // Analyze exports for metadata (simplified - structure already validated)
173
+ const hasDefaultExport = module.default !== undefined;
174
+ const exportNames = Object.keys(module);
175
+
176
+ return {
177
+ content,
178
+ exports: normalizedExports,
179
+ filePath,
180
+ metadata: {
181
+ exportNames,
182
+ hasDefaultExport,
183
+ mtime: stats.mtime,
184
+ size: stats.size,
185
+ },
186
+ };
187
+ } catch (error) {
188
+ throw new Error(
189
+ `Failed to load file ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
190
+ );
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Load multiple files in parallel
196
+ */
197
+ async loadAll(filePaths: string[]): Promise<BenchmarkFile[]> {
198
+ try {
199
+ const loadPromises = filePaths.map((filePath) => this.load(filePath));
200
+ return await Promise.all(loadPromises);
201
+ } catch (error) {
202
+ throw new Error(
203
+ `Failed to load files: ${error instanceof Error ? error.message : String(error)}`,
204
+ );
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Validate benchmark file (basic checks only - file existence and extension)
210
+ * Structure validation happens after loading in the load() method
211
+ */
212
+ async validate(filePath: string): Promise<ValidationResult> {
213
+ const errors: ValidationError[] = [];
214
+ const warnings: ValidationWarning[] = [];
215
+
216
+ try {
217
+ // Check file existence
218
+ try {
219
+ await access(filePath);
220
+ } catch {
221
+ errors.push({
222
+ code: 'FILE_NOT_FOUND',
223
+ file: filePath,
224
+ message: 'File does not exist',
225
+ severity: 'error',
226
+ });
227
+ return { errors, files: [], valid: false, warnings };
228
+ }
229
+
230
+ // Check file extension
231
+ const ext = extname(filePath);
232
+ if (!this.supportedExtensions.has(ext)) {
233
+ errors.push({
234
+ code: 'UNSUPPORTED_EXTENSION',
235
+ file: filePath,
236
+ message: `Unsupported file extension: ${ext}. Supported extensions: ${Array.from(this.supportedExtensions).join(', ')}`,
237
+ severity: 'error',
238
+ });
239
+ }
240
+
241
+ return {
242
+ errors,
243
+ files: [filePath],
244
+ valid: errors.length === 0,
245
+ warnings,
246
+ };
247
+ } catch (error) {
248
+ errors.push({
249
+ code: 'VALIDATION_ERROR',
250
+ file: filePath,
251
+ message: `Validation failed: ${error instanceof Error ? error.message : String(error)}`,
252
+ severity: 'error',
253
+ });
254
+
255
+ return {
256
+ errors,
257
+ files: [filePath],
258
+ valid: false,
259
+ warnings,
260
+ };
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Watch for file changes (placeholder implementation)
266
+ */
267
+ watch(
268
+ _pattern: string,
269
+ _callback: (changes: FileChange[]) => void,
270
+ ): FileWatcher {
271
+ // TODO: Implement file watching with chokidar or similar
272
+ // For now, return a no-op watcher
273
+ return {
274
+ close() {
275
+ // No-op
276
+ },
277
+ };
278
+ }
279
+
280
+ /**
281
+ * Validate the structure of loaded exports using Zod schema Returns the
282
+ * transformed/normalized data if validation succeeds
283
+ */
284
+ private validateExports(
285
+ filePath: string,
286
+ exports: unknown,
287
+ ): {
288
+ data?: BenchmarkDefinition;
289
+ errors: ValidationError[];
290
+ valid: boolean;
291
+ warnings: ValidationWarning[];
292
+ } {
293
+ const errors: ValidationError[] = [];
294
+ const warnings: ValidationWarning[] = [];
295
+
296
+ try {
297
+ const result = benchmarkFileSchema.safeParse(exports);
298
+
299
+ if (!result.success) {
300
+ for (const issue of result.error.issues) {
301
+ const path = issue.path.length > 0 ? `${issue.path.join('.')}: ` : '';
302
+ errors.push({
303
+ code: 'INVALID_STRUCTURE',
304
+ file: filePath,
305
+ message: `${path}${issue.message}`,
306
+ severity: 'error',
307
+ });
308
+ }
309
+ return { errors, valid: false, warnings };
310
+ }
311
+
312
+ // Return the transformed data (with shorthand functions normalized)
313
+ return { data: result.data, errors, valid: true, warnings };
314
+ } catch (error) {
315
+ errors.push({
316
+ code: 'VALIDATION_ERROR',
317
+ file: filePath,
318
+ message: `Structure validation failed: ${error instanceof Error ? error.message : String(error)}`,
319
+ severity: 'error',
320
+ });
321
+ return { errors, valid: false, warnings };
322
+ }
323
+ }
324
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Statistical utility functions for benchmark result analysis
3
+ *
4
+ * Provides IQR-based outlier removal and comprehensive statistics calculation
5
+ * adapted from bench-node's StatisticalHistogram.
6
+ */
7
+
8
+ /**
9
+ * Statistics computed from sample data
10
+ */
11
+ export interface SampleStatistics {
12
+ /** Coefficient of variation (stdDev/mean × 100) */
13
+ readonly cv: number;
14
+ /** Margin of error at 95% confidence */
15
+ readonly marginOfError: number;
16
+ /** Maximum value */
17
+ readonly max: number;
18
+ /** Mean (average) value */
19
+ readonly mean: number;
20
+ /** Minimum value */
21
+ readonly min: number;
22
+ /** 95th percentile */
23
+ readonly p95: number;
24
+ /** 99th percentile */
25
+ readonly p99: number;
26
+ /** Standard deviation */
27
+ readonly stdDev: number;
28
+ /** Variance */
29
+ readonly variance: number;
30
+ }
31
+
32
+ /**
33
+ * Calculate comprehensive statistics from samples
34
+ *
35
+ * Adapted from bench-node's StatisticalHistogram. Assumes samples are already
36
+ * sorted (e.g., from removeOutliersIQR output).
37
+ *
38
+ * @param samples - Sample values (should be sorted for accurate percentiles)
39
+ * @returns Computed statistics
40
+ */
41
+ export const calculateStatistics = (samples: number[]): SampleStatistics => {
42
+ if (samples.length === 0) {
43
+ return {
44
+ cv: 0,
45
+ marginOfError: 0,
46
+ max: 0,
47
+ mean: 0,
48
+ min: 0,
49
+ p95: 0,
50
+ p99: 0,
51
+ stdDev: 0,
52
+ variance: 0,
53
+ };
54
+ }
55
+
56
+ // Min/Max (samples are already sorted from removeOutliersIQR)
57
+ const min = samples[0]!;
58
+ const max = samples[samples.length - 1]!;
59
+
60
+ // Mean
61
+ const mean =
62
+ samples.length === 1
63
+ ? samples[0]!
64
+ : samples.reduce(
65
+ (acc, v) => Math.min(Number.MAX_SAFE_INTEGER, acc + v),
66
+ 0,
67
+ ) / samples.length;
68
+
69
+ // Standard deviation and variance
70
+ let stdDev = 0;
71
+ let variance = 0;
72
+ if (samples.length >= 2) {
73
+ variance =
74
+ samples.reduce((acc, v) => acc + Math.pow(v - mean, 2), 0) /
75
+ (samples.length - 1);
76
+ stdDev = Math.sqrt(variance);
77
+ }
78
+
79
+ // Coefficient of Variation
80
+ const cv = mean === 0 || samples.length < 2 ? 0 : (stdDev / mean) * 100;
81
+
82
+ // Margin of Error (95% confidence)
83
+ const Z = 1.96;
84
+ const marginOfError =
85
+ samples.length === 0 ? 0 : (Z * stdDev) / Math.sqrt(samples.length);
86
+
87
+ // Percentiles (using standard formula: floor((n-1) * p))
88
+ const p95 = samples[Math.floor((samples.length - 1) * 0.95)] ?? 0;
89
+ const p99 = samples[Math.floor((samples.length - 1) * 0.99)] ?? 0;
90
+
91
+ return { cv, marginOfError, max, mean, min, p95, p99, stdDev, variance };
92
+ };
93
+
94
+ /**
95
+ * Remove outliers using Interquartile Range (IQR) method
96
+ *
97
+ * Adapted from bench-node's StatisticalHistogram.removeOutliers. Filters values
98
+ * outside [Q1 - 1.5×IQR, Q3 + 1.5×IQR] range.
99
+ *
100
+ * @param samples - Raw sample values (will be sorted internally)
101
+ * @returns Filtered samples with outliers removed
102
+ */
103
+ export const removeOutliersIQR = (samples: number[]): number[] => {
104
+ if (samples.length < 4) {
105
+ return samples;
106
+ }
107
+
108
+ const sorted = samples.slice().sort((a, b) => a - b);
109
+
110
+ // Calculate Q1 and Q3
111
+ let q1: number;
112
+ let q3: number;
113
+ const size = sorted.length;
114
+
115
+ if (((size - 1) / 4) % 1 === 0 || (size / 4) % 1 === 0) {
116
+ q1 =
117
+ (1 / 2) *
118
+ (sorted[Math.floor(size / 4) - 1]! + sorted[Math.floor(size / 4)]!);
119
+ q3 =
120
+ (1 / 2) *
121
+ (sorted[Math.ceil((size * 3) / 4) - 1]! +
122
+ sorted[Math.ceil((size * 3) / 4)]!);
123
+ } else {
124
+ q1 = sorted[Math.floor(size / 4)]!;
125
+ q3 = sorted[Math.floor((size * 3) / 4)]!;
126
+ }
127
+
128
+ // Calculate IQR and bounds
129
+ const iqr = q3 - q1;
130
+ const minValue = q1 - iqr * 1.5;
131
+ const maxValue = q3 + iqr * 1.5;
132
+
133
+ // Filter outliers
134
+ return sorted.filter((value) => value <= maxValue && value >= minValue);
135
+ };
package/src/index.ts ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * ModestBench Public API
3
+ *
4
+ * Main entry point for programmatic usage of ModestBench. This module exports
5
+ * all core classes, utilities, and types needed to use ModestBench as a
6
+ * library.
7
+ */
8
+
9
+ export { bootstrap as modestbench } from './bootstrap.js';
10
+ // Configuration management
11
+ export { ModestBenchConfigurationManager } from './config/manager.js';
12
+
13
+ // Core engine and loader
14
+ export { ModestBenchEngine } from './core/engine.js';
15
+ export { AccurateEngine, TinybenchEngine } from './core/engines/index.js';
16
+
17
+ // Error handling
18
+ export { ModestBenchErrorManager } from './core/error-manager.js';
19
+
20
+ export { BenchmarkFileLoader } from './core/loader.js';
21
+
22
+ // Statistical utilities
23
+ export {
24
+ calculateStatistics,
25
+ removeOutliersIQR,
26
+ type SampleStatistics,
27
+ } from './core/stats-utils.js';
28
+
29
+ // Progress tracking
30
+ export { ModestBenchProgressManager } from './progress/manager.js';
31
+ // Reporters
32
+ export { CsvReporter } from './reporters/csv.js';
33
+ export { HumanReporter } from './reporters/human.js';
34
+ export { JsonReporter } from './reporters/json.js';
35
+
36
+ export {
37
+ BaseReporter,
38
+ CompositeReporter,
39
+ ModestBenchReporterRegistry,
40
+ } from './reporters/registry.js';
41
+
42
+ // Storage
43
+ export { FileHistoryStorage } from './storage/history.js';
44
+
45
+ // Export all types
46
+ export * from './types/index.js';