modestbench 0.0.2 → 0.1.0

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 (224) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +21 -19
  3. package/dist/bootstrap.cjs +0 -2
  4. package/dist/bootstrap.cjs.map +1 -1
  5. package/dist/bootstrap.d.cts.map +1 -1
  6. package/dist/bootstrap.d.ts.map +1 -1
  7. package/dist/bootstrap.js +0 -2
  8. package/dist/bootstrap.js.map +1 -1
  9. package/dist/cli/commands/history.cjs +2 -1
  10. package/dist/cli/commands/history.cjs.map +1 -1
  11. package/dist/cli/commands/history.d.cts.map +1 -1
  12. package/dist/cli/commands/history.d.ts.map +1 -1
  13. package/dist/cli/commands/history.js +2 -1
  14. package/dist/cli/commands/history.js.map +1 -1
  15. package/dist/cli/commands/init.cjs +5 -4
  16. package/dist/cli/commands/init.cjs.map +1 -1
  17. package/dist/cli/commands/init.d.cts.map +1 -1
  18. package/dist/cli/commands/init.d.ts.map +1 -1
  19. package/dist/cli/commands/init.js +5 -4
  20. package/dist/cli/commands/init.js.map +1 -1
  21. package/dist/cli/commands/run.cjs +28 -3
  22. package/dist/cli/commands/run.cjs.map +1 -1
  23. package/dist/cli/commands/run.d.cts.map +1 -1
  24. package/dist/cli/commands/run.d.ts.map +1 -1
  25. package/dist/cli/commands/run.js +28 -3
  26. package/dist/cli/commands/run.js.map +1 -1
  27. package/dist/cli/index.cjs +38 -14
  28. package/dist/cli/index.cjs.map +1 -1
  29. package/dist/cli/index.d.cts +1 -2
  30. package/dist/cli/index.d.cts.map +1 -1
  31. package/dist/cli/index.d.ts +1 -2
  32. package/dist/cli/index.d.ts.map +1 -1
  33. package/dist/cli/index.js +33 -9
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/config/manager.cjs +9 -3
  36. package/dist/config/manager.cjs.map +1 -1
  37. package/dist/config/manager.d.cts.map +1 -1
  38. package/dist/config/manager.d.ts.map +1 -1
  39. package/dist/config/manager.js +9 -3
  40. package/dist/config/manager.js.map +1 -1
  41. package/dist/constants.cjs +53 -1
  42. package/dist/constants.cjs.map +1 -1
  43. package/dist/constants.d.cts +36 -0
  44. package/dist/constants.d.cts.map +1 -1
  45. package/dist/constants.d.ts +36 -0
  46. package/dist/constants.d.ts.map +1 -1
  47. package/dist/constants.js +52 -0
  48. package/dist/constants.js.map +1 -1
  49. package/dist/core/engine.cjs +19 -42
  50. package/dist/core/engine.cjs.map +1 -1
  51. package/dist/core/engine.d.cts +1 -3
  52. package/dist/core/engine.d.cts.map +1 -1
  53. package/dist/core/engine.d.ts +1 -3
  54. package/dist/core/engine.d.ts.map +1 -1
  55. package/dist/core/engine.js +19 -42
  56. package/dist/core/engine.js.map +1 -1
  57. package/dist/core/engines/accurate-engine.cjs +2 -1
  58. package/dist/core/engines/accurate-engine.cjs.map +1 -1
  59. package/dist/core/engines/accurate-engine.d.cts.map +1 -1
  60. package/dist/core/engines/accurate-engine.d.ts.map +1 -1
  61. package/dist/core/engines/accurate-engine.js +2 -1
  62. package/dist/core/engines/accurate-engine.js.map +1 -1
  63. package/dist/core/engines/tinybench-engine.cjs +6 -5
  64. package/dist/core/engines/tinybench-engine.cjs.map +1 -1
  65. package/dist/core/engines/tinybench-engine.d.cts.map +1 -1
  66. package/dist/core/engines/tinybench-engine.d.ts.map +1 -1
  67. package/dist/core/engines/tinybench-engine.js +6 -5
  68. package/dist/core/engines/tinybench-engine.js.map +1 -1
  69. package/dist/core/loader.cjs +16 -5
  70. package/dist/core/loader.cjs.map +1 -1
  71. package/dist/core/loader.d.cts.map +1 -1
  72. package/dist/core/loader.d.ts.map +1 -1
  73. package/dist/core/loader.js +16 -5
  74. package/dist/core/loader.js.map +1 -1
  75. package/dist/errors/base.cjs +130 -0
  76. package/dist/errors/base.cjs.map +1 -0
  77. package/dist/errors/base.d.cts +97 -0
  78. package/dist/errors/base.d.cts.map +1 -0
  79. package/dist/errors/base.d.ts +97 -0
  80. package/dist/errors/base.d.ts.map +1 -0
  81. package/dist/errors/base.js +124 -0
  82. package/dist/errors/base.js.map +1 -0
  83. package/dist/errors/cli.cjs +58 -0
  84. package/dist/errors/cli.cjs.map +1 -0
  85. package/dist/errors/cli.d.cts +44 -0
  86. package/dist/errors/cli.d.cts.map +1 -0
  87. package/dist/errors/cli.d.ts +44 -0
  88. package/dist/errors/cli.d.ts.map +1 -0
  89. package/dist/errors/cli.js +52 -0
  90. package/dist/errors/cli.js.map +1 -0
  91. package/dist/errors/configuration.cjs +48 -0
  92. package/dist/errors/configuration.cjs.map +1 -0
  93. package/dist/errors/configuration.d.cts +41 -0
  94. package/dist/errors/configuration.d.cts.map +1 -0
  95. package/dist/errors/configuration.d.ts +41 -0
  96. package/dist/errors/configuration.d.ts.map +1 -0
  97. package/dist/errors/configuration.js +41 -0
  98. package/dist/errors/configuration.js.map +1 -0
  99. package/dist/errors/execution.cjs +65 -0
  100. package/dist/errors/execution.cjs.map +1 -0
  101. package/dist/errors/execution.d.cts +56 -0
  102. package/dist/errors/execution.d.cts.map +1 -0
  103. package/dist/errors/execution.d.ts +56 -0
  104. package/dist/errors/execution.d.ts.map +1 -0
  105. package/dist/errors/execution.js +56 -0
  106. package/dist/errors/execution.js.map +1 -0
  107. package/dist/errors/file.cjs +56 -0
  108. package/dist/errors/file.cjs.map +1 -0
  109. package/dist/errors/file.d.cts +48 -0
  110. package/dist/errors/file.d.cts.map +1 -0
  111. package/dist/errors/file.d.ts +48 -0
  112. package/dist/errors/file.d.ts.map +1 -0
  113. package/dist/errors/file.js +48 -0
  114. package/dist/errors/file.js.map +1 -0
  115. package/dist/errors/index.cjs +59 -0
  116. package/dist/errors/index.cjs.map +1 -0
  117. package/dist/errors/index.d.cts +16 -0
  118. package/dist/errors/index.d.cts.map +1 -0
  119. package/dist/errors/index.d.ts +16 -0
  120. package/dist/errors/index.d.ts.map +1 -0
  121. package/dist/errors/index.js +24 -0
  122. package/dist/errors/index.js.map +1 -0
  123. package/dist/errors/reporter.cjs +38 -0
  124. package/dist/errors/reporter.cjs.map +1 -0
  125. package/dist/errors/reporter.d.cts +32 -0
  126. package/dist/errors/reporter.d.cts.map +1 -0
  127. package/dist/errors/reporter.d.ts +32 -0
  128. package/dist/errors/reporter.d.ts.map +1 -0
  129. package/dist/errors/reporter.js +32 -0
  130. package/dist/errors/reporter.js.map +1 -0
  131. package/dist/errors/storage.cjs +55 -0
  132. package/dist/errors/storage.cjs.map +1 -0
  133. package/dist/errors/storage.d.cts +47 -0
  134. package/dist/errors/storage.d.cts.map +1 -0
  135. package/dist/errors/storage.d.ts +47 -0
  136. package/dist/errors/storage.d.ts.map +1 -0
  137. package/dist/errors/storage.js +47 -0
  138. package/dist/errors/storage.js.map +1 -0
  139. package/dist/errors/validation.cjs +38 -0
  140. package/dist/errors/validation.cjs.map +1 -0
  141. package/dist/errors/validation.d.cts +32 -0
  142. package/dist/errors/validation.d.cts.map +1 -0
  143. package/dist/errors/validation.d.ts +32 -0
  144. package/dist/errors/validation.d.ts.map +1 -0
  145. package/dist/errors/validation.js +32 -0
  146. package/dist/errors/validation.js.map +1 -0
  147. package/dist/index.cjs +3 -4
  148. package/dist/index.cjs.map +1 -1
  149. package/dist/index.d.cts +1 -1
  150. package/dist/index.d.cts.map +1 -1
  151. package/dist/index.d.ts +1 -1
  152. package/dist/index.d.ts.map +1 -1
  153. package/dist/index.js +2 -2
  154. package/dist/index.js.map +1 -1
  155. package/dist/reporters/csv.cjs +3 -2
  156. package/dist/reporters/csv.cjs.map +1 -1
  157. package/dist/reporters/csv.d.cts.map +1 -1
  158. package/dist/reporters/csv.d.ts.map +1 -1
  159. package/dist/reporters/csv.js +3 -2
  160. package/dist/reporters/csv.js.map +1 -1
  161. package/dist/reporters/human.cjs +2 -2
  162. package/dist/reporters/human.js +2 -2
  163. package/dist/reporters/json.cjs +3 -2
  164. package/dist/reporters/json.cjs.map +1 -1
  165. package/dist/reporters/json.d.cts.map +1 -1
  166. package/dist/reporters/json.d.ts.map +1 -1
  167. package/dist/reporters/json.js +3 -2
  168. package/dist/reporters/json.js.map +1 -1
  169. package/dist/reporters/registry.cjs +3 -2
  170. package/dist/reporters/registry.cjs.map +1 -1
  171. package/dist/reporters/registry.d.cts.map +1 -1
  172. package/dist/reporters/registry.d.ts.map +1 -1
  173. package/dist/reporters/registry.js +3 -2
  174. package/dist/reporters/registry.js.map +1 -1
  175. package/dist/reporters/simple.cjs +1 -1
  176. package/dist/reporters/simple.js +1 -1
  177. package/dist/storage/history.cjs +32 -11
  178. package/dist/storage/history.cjs.map +1 -1
  179. package/dist/storage/history.d.cts.map +1 -1
  180. package/dist/storage/history.d.ts.map +1 -1
  181. package/dist/storage/history.js +32 -11
  182. package/dist/storage/history.js.map +1 -1
  183. package/dist/types/interfaces.d.cts +1 -34
  184. package/dist/types/interfaces.d.cts.map +1 -1
  185. package/dist/types/interfaces.d.ts +1 -34
  186. package/dist/types/interfaces.d.ts.map +1 -1
  187. package/package.json +12 -8
  188. package/src/bootstrap.ts +0 -2
  189. package/src/cli/commands/history.ts +3 -1
  190. package/src/cli/commands/init.ts +14 -4
  191. package/src/cli/commands/run.ts +36 -3
  192. package/src/cli/index.ts +41 -15
  193. package/src/config/manager.ts +13 -3
  194. package/src/constants.ts +60 -0
  195. package/src/core/engine.ts +35 -49
  196. package/src/core/engines/accurate-engine.ts +4 -1
  197. package/src/core/engines/tinybench-engine.ts +12 -5
  198. package/src/core/loader.ts +27 -5
  199. package/src/errors/base.ts +152 -0
  200. package/src/errors/cli.ts +59 -0
  201. package/src/errors/configuration.ts +45 -0
  202. package/src/errors/execution.ts +62 -0
  203. package/src/errors/file.ts +53 -0
  204. package/src/errors/index.ts +71 -0
  205. package/src/errors/reporter.ts +35 -0
  206. package/src/errors/storage.ts +52 -0
  207. package/src/errors/validation.ts +35 -0
  208. package/src/index.ts +3 -3
  209. package/src/reporters/csv.ts +4 -2
  210. package/src/reporters/human.ts +2 -2
  211. package/src/reporters/json.ts +4 -2
  212. package/src/reporters/registry.ts +9 -2
  213. package/src/reporters/simple.ts +1 -1
  214. package/src/storage/history.ts +58 -11
  215. package/src/types/interfaces.ts +0 -43
  216. package/dist/core/error-manager.cjs +0 -303
  217. package/dist/core/error-manager.cjs.map +0 -1
  218. package/dist/core/error-manager.d.cts +0 -77
  219. package/dist/core/error-manager.d.cts.map +0 -1
  220. package/dist/core/error-manager.d.ts +0 -77
  221. package/dist/core/error-manager.d.ts.map +0 -1
  222. package/dist/core/error-manager.js +0 -299
  223. package/dist/core/error-manager.js.map +0 -1
  224. package/src/core/error-manager.ts +0 -372
@@ -10,6 +10,12 @@ import { resolve } from 'node:path';
10
10
  import type { BenchmarkRun } from '../../types/index.js';
11
11
  import type { CliContext } from '../index.js';
12
12
 
13
+ import { ErrorCodes } from '../../constants.js';
14
+ import {
15
+ InvalidArgumentError,
16
+ type ModestBenchError,
17
+ UnknownReporterError,
18
+ } from '../../errors/index.js';
13
19
  import { ExitCodes } from '../../types/cli.js';
14
20
 
15
21
  /**
@@ -89,6 +95,18 @@ export const handleRunCommand = async (
89
95
  console.error(`Found ${discoveredFiles.length} benchmark file(s)`);
90
96
  }
91
97
 
98
+ // Check if no files found and throw to trigger help display
99
+ if (discoveredFiles.length === 0) {
100
+ let msg = `No benchmark files found matching pattern "${config.pattern}"`;
101
+ if (config.exclude?.length) {
102
+ msg += ` (excluding: ${config.exclude.join(', ')})`;
103
+ }
104
+ // Throw error to trigger yargs fail handler which shows help
105
+ const error = new Error(msg);
106
+ error.name = 'FileDiscoveryError';
107
+ throw error;
108
+ }
109
+
92
110
  // Step 4: Validation phase
93
111
  if (showCliMessages) {
94
112
  console.error('Validating benchmark files...');
@@ -143,6 +161,11 @@ export const handleRunCommand = async (
143
161
 
144
162
  return handleResults(executionResult, options, shouldBeQuiet);
145
163
  } catch (error) {
164
+ // Re-throw FileDiscoveryError so yargs fail handler can show help
165
+ if ((error as ModestBenchError).code === ErrorCodes.FILE_DISCOVERY_FAILED) {
166
+ throw error;
167
+ }
168
+
146
169
  if (!shouldBeQuiet) {
147
170
  console.error(
148
171
  `Error: ${error instanceof Error ? error.message : String(error)}`,
@@ -253,8 +276,13 @@ const loadConfiguration = async (context: CliContext, options: RunOptions) => {
253
276
 
254
277
  return config;
255
278
  } catch (error) {
256
- throw new Error(
279
+ // Re-throw our custom errors
280
+ if ((error as ModestBenchError).code === ErrorCodes.CONFIG_LOAD_FAILED) {
281
+ throw error;
282
+ }
283
+ throw new InvalidArgumentError(
257
284
  `Configuration error: ${error instanceof Error ? error.message : String(error)}`,
285
+ { cause: error },
258
286
  );
259
287
  }
260
288
  };
@@ -324,7 +352,7 @@ const setupReporters = async (
324
352
  reporter = context.reporterRegistry.get(reporterName);
325
353
  if (!reporter) {
326
354
  const availableReporters = ['human', 'json', 'csv', 'simple'];
327
- throw new Error(
355
+ throw new UnknownReporterError(
328
356
  `Unknown reporter: ${reporterName}. Available: ${availableReporters.join(', ')}`,
329
357
  );
330
358
  }
@@ -339,8 +367,13 @@ const setupReporters = async (
339
367
 
340
368
  return reporters;
341
369
  } catch (error) {
342
- throw new Error(
370
+ // Re-throw our custom errors
371
+ if ((error as ModestBenchError).code === ErrorCodes.REPORTER_UNKNOWN) {
372
+ throw error;
373
+ }
374
+ throw new InvalidArgumentError(
343
375
  `Reporter setup error: ${error instanceof Error ? error.message : String(error)}`,
376
+ { cause: error },
344
377
  );
345
378
  }
346
379
  };
package/src/cli/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+
2
3
  /**
3
4
  * ModestBench CLI Entry Point
4
5
  *
@@ -16,7 +17,6 @@ import { hideBin } from 'yargs/helpers';
16
17
  import type {
17
18
  BenchmarkEngine,
18
19
  ConfigurationManager,
19
- ErrorManager,
20
20
  HistoryStorage,
21
21
  ProgressManager,
22
22
  ReporterRegistry,
@@ -24,6 +24,7 @@ import type {
24
24
 
25
25
  import { bootstrap } from '../bootstrap.js';
26
26
  import { AccurateEngine, TinybenchEngine } from '../core/engines/index.js';
27
+ import { isModestBenchError, UnknownError } from '../errors/index.js';
27
28
  import {
28
29
  CsvReporter,
29
30
  HumanReporter,
@@ -42,7 +43,6 @@ export interface CliContext {
42
43
  readonly abortController: AbortController;
43
44
  readonly configManager: ConfigurationManager;
44
45
  readonly engine: BenchmarkEngine;
45
- readonly errorManager: ErrorManager;
46
46
  readonly historyStorage: HistoryStorage;
47
47
  readonly options: GlobalOptions;
48
48
  readonly progressManager: ProgressManager;
@@ -151,13 +151,13 @@ export const main = async (
151
151
  .completion()
152
152
  .wrap(Math.min(120, cli.terminalWidth()))
153
153
  .command(
154
- 'run [pattern..]',
154
+ ['$0 [pattern..]', 'run [pattern..]'],
155
155
  'Run benchmark files',
156
156
  (yargs) => {
157
157
  return yargs
158
158
  .positional('pattern', {
159
159
  array: true,
160
- default: [],
160
+ default: ['./bench/**/*.bench.{js,mjs,cjs,ts}'],
161
161
  describe:
162
162
  'File paths, directory paths, or glob patterns for benchmark files',
163
163
  type: 'string',
@@ -491,8 +491,15 @@ export const main = async (
491
491
  if (process.env.DEBUG) {
492
492
  console.error(err.stack);
493
493
  }
494
+ // Show help for file discovery errors (similar to usage errors)
495
+ if (err.name === 'FileDiscoveryError') {
496
+ console.error();
497
+ yargsInstance.showHelp();
498
+ process.exit(ExitCodes.DISCOVERY_ERROR);
499
+ }
494
500
  process.exit(ExitCodes.RUNTIME_ERROR);
495
501
  } else {
502
+ // Show help for usage errors (unknown arguments, etc.)
496
503
  console.error(msg);
497
504
  console.error();
498
505
  yargsInstance.showHelp();
@@ -564,7 +571,6 @@ const createCliContext = async (
564
571
  abortController,
565
572
  configManager: engine.configManager,
566
573
  engine,
567
- errorManager: engine.errorManager,
568
574
  historyStorage: engine.historyStorage,
569
575
  options,
570
576
  progressManager: engine.progressManager,
@@ -589,9 +595,7 @@ const setupSignalHandlers = (abortController: AbortController): void => {
589
595
  if (abortRequested) {
590
596
  // Second signal, force exit
591
597
  console.log(`\nReceived ${signal} again, forcing exit...`);
592
- process.exit(
593
- 128 + (signal === 'SIGINT' ? 2 : signal === 'SIGQUIT' ? 3 : 15),
594
- );
598
+ process.exit(computeExitCode(signal));
595
599
  }
596
600
 
597
601
  console.log(`\nReceived ${signal}, aborting benchmarks...`);
@@ -601,9 +605,7 @@ const setupSignalHandlers = (abortController: AbortController): void => {
601
605
  // Give a short grace period for cleanup, then exit
602
606
  setTimeout(() => {
603
607
  console.log('\nBenchmark aborted.');
604
- process.exit(
605
- 128 + (signal === 'SIGINT' ? 2 : signal === 'SIGQUIT' ? 3 : 15),
606
- );
608
+ process.exit(computeExitCode(signal));
607
609
  }, 100);
608
610
  };
609
611
 
@@ -611,13 +613,27 @@ const setupSignalHandlers = (abortController: AbortController): void => {
611
613
  process.on('SIGQUIT', () => handleSignal('SIGQUIT'));
612
614
  process.on('SIGTERM', () => handleSignal('SIGTERM'));
613
615
 
614
- process.on('uncaughtException', (error) => {
615
- console.error('Uncaught exception:', error);
616
+ process.once('uncaughtException', (error) => {
617
+ // Wrap non-ModestBench errors with UnknownError
618
+ const wrappedError: Error = isModestBenchError(error)
619
+ ? error
620
+ : new UnknownError(
621
+ error instanceof Error ? error.message : String(error),
622
+ { cause: error },
623
+ );
624
+ console.error(wrappedError.toString());
616
625
  process.exit(ExitCodes.RUNTIME_ERROR);
617
626
  });
618
627
 
619
- process.on('unhandledRejection', (reason, promise) => {
620
- console.error('Unhandled rejection at:', promise, 'reason:', reason);
628
+ process.once('unhandledRejection', (reason) => {
629
+ // Wrap non-ModestBench errors with UnknownError
630
+ const wrappedError: Error = isModestBenchError(reason)
631
+ ? (reason as Error)
632
+ : new UnknownError(
633
+ reason instanceof Error ? reason.message : String(reason),
634
+ { cause: reason },
635
+ );
636
+ console.error(wrappedError.toString());
621
637
  process.exit(ExitCodes.RUNTIME_ERROR);
622
638
  });
623
639
  };
@@ -640,3 +656,13 @@ try {
640
656
  cli();
641
657
  }
642
658
  }
659
+
660
+ /**
661
+ * Compute the exit code based on the signal
662
+ *
663
+ * @param signal - The signal that caused the exit
664
+ * @returns The exit code
665
+ */
666
+ const computeExitCode = (signal: string): number => {
667
+ return 128 + (signal === 'SIGINT' ? 2 : signal === 'SIGQUIT' ? 3 : 15);
668
+ };
@@ -9,6 +9,7 @@
9
9
  import { cosmiconfig } from 'cosmiconfig';
10
10
  import { resolve } from 'node:path';
11
11
 
12
+ import type { ModestBenchError } from '../errors/base.js';
12
13
  import type {
13
14
  ConfigurationManager,
14
15
  ModestBenchConfig,
@@ -17,6 +18,8 @@ import type {
17
18
  ValidationWarning,
18
19
  } from '../types/index.js';
19
20
 
21
+ import { ErrorCodes } from '../constants.js';
22
+ import { ConfigLoadError, ConfigValidationError } from '../errors/index.js';
20
23
  import { safeParseConfig } from './schema.js';
21
24
 
22
25
  /**
@@ -62,7 +65,7 @@ const DEFAULT_CONFIG: ModestBenchConfig = {
62
65
  time: 1000, // 1 second minimum for tinybench to gather samples
63
66
  timeout: 30000, // 30 seconds
64
67
  verbose: false, // No verbose output by default
65
- warmup: 0, // No warmup by default for test speed
68
+ warmup: 30, // Light warmup by default - enough for basic JIT optimization
66
69
  };
67
70
 
68
71
  /**
@@ -175,15 +178,22 @@ export class ModestBenchConfigurationManager implements ConfigurationManager {
175
178
  // 3. Validate final configuration
176
179
  const validation = this.validate(finalConfig);
177
180
  if (!validation.valid) {
178
- throw new Error(
181
+ throw new ConfigValidationError(
179
182
  `Configuration validation failed: ${validation.errors.map((e) => e.message).join(', ')}`,
180
183
  );
181
184
  }
182
185
 
183
186
  return finalConfig;
184
187
  } catch (error) {
185
- throw new Error(
188
+ // Re-throw our custom errors
189
+ if (
190
+ (error as ModestBenchError).code === ErrorCodes.CONFIG_VALIDATION_FAILED
191
+ ) {
192
+ throw error;
193
+ }
194
+ throw new ConfigLoadError(
186
195
  `Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`,
196
+ { cause: error },
187
197
  );
188
198
  }
189
199
  }
package/src/constants.ts CHANGED
@@ -19,3 +19,63 @@ export const BENCHMARK_FILE_PATTERN = `.bench.{${Array.from(
19
19
  )
20
20
  .map((ext) => ext.slice(1))
21
21
  .join(',')}}`;
22
+
23
+ /**
24
+ * Error codes for all ModestBench errors
25
+ *
26
+ * Use these constants to check error types instead of instanceof checks.
27
+ */
28
+ export const ErrorCodes = {
29
+ //#region cli-errors
30
+ CLI_INVALID_ARGUMENT: 'ERR_MB_CLI_INVALID_ARGUMENT',
31
+ CLI_INVALID_DATE_FORMAT: 'ERR_MB_CLI_INVALID_DATE_FORMAT',
32
+ //#endregion
33
+
34
+ //#region config-errors
35
+ CONFIG_LOAD_FAILED: 'ERR_MB_CONFIG_LOAD_FAILED',
36
+ CONFIG_NOT_FOUND: 'ERR_MB_CONFIG_NOT_FOUND',
37
+ CONFIG_UNSUPPORTED_FORMAT: 'ERR_MB_CONFIG_UNSUPPORTED_FORMAT',
38
+ CONFIG_VALIDATION_FAILED: 'ERR_MB_CONFIG_VALIDATION_FAILED',
39
+ //#endregion
40
+
41
+ //#region execution-errors
42
+ EXECUTION_BENCHMARK_FAILED: 'ERR_MB_EXECUTION_BENCHMARK_FAILED',
43
+ EXECUTION_SETUP_FAILED: 'ERR_MB_EXECUTION_SETUP_FAILED',
44
+ EXECUTION_TASK_FAILED: 'ERR_MB_EXECUTION_TASK_FAILED',
45
+ EXECUTION_TEARDOWN_FAILED: 'ERR_MB_EXECUTION_TEARDOWN_FAILED',
46
+ EXECUTION_TIMEOUT: 'ERR_MB_EXECUTION_TIMEOUT',
47
+ EXECUTION_TOO_FAST: 'ERR_MB_EXECUTION_TOO_FAST',
48
+ //#endregion
49
+
50
+ //#region file-errors
51
+ FILE_DISCOVERY_FAILED: 'ERR_MB_FILE_DISCOVERY_FAILED',
52
+ FILE_LOAD_FAILED: 'ERR_MB_FILE_LOAD_FAILED',
53
+ FILE_NOT_FOUND: 'ERR_MB_FILE_NOT_FOUND',
54
+ FILE_PERMISSION_DENIED: 'ERR_MB_FILE_PERMISSION_DENIED',
55
+ FILE_UNSUPPORTED_EXTENSION: 'ERR_MB_FILE_UNSUPPORTED_EXTENSION',
56
+ //#endregion
57
+
58
+ //#region reporter-errors
59
+ REPORTER_ALREADY_REGISTERED: 'ERR_MB_REPORTER_ALREADY_REGISTERED',
60
+ REPORTER_OUTPUT_FAILED: 'ERR_MB_REPORTER_OUTPUT_FAILED',
61
+ REPORTER_UNKNOWN: 'ERR_MB_REPORTER_UNKNOWN',
62
+ //#endregion
63
+
64
+ //#region storage-errors
65
+ STORAGE_CORRUPTION: 'ERR_MB_STORAGE_CORRUPTION',
66
+ STORAGE_EXPORT_UNSUPPORTED: 'ERR_MB_STORAGE_EXPORT_UNSUPPORTED',
67
+ STORAGE_FAILED: 'ERR_MB_STORAGE_FAILED',
68
+ STORAGE_INDEX_CORRUPTION: 'ERR_MB_STORAGE_INDEX_CORRUPTION',
69
+ STORAGE_INSUFFICIENT_SPACE: 'ERR_MB_STORAGE_INSUFFICIENT_SPACE',
70
+ //#endregion
71
+
72
+ //#region misc-errors
73
+ UNKNOWN: 'ERR_MB_UNKNOWN',
74
+ //#endregion
75
+
76
+ //#region validation-errors
77
+ VALIDATION_SCHEMA_FAILED: 'ERR_MB_VALIDATION_SCHEMA_FAILED',
78
+ VALIDATION_STRUCTURE_INVALID: 'ERR_MB_VALIDATION_STRUCTURE_INVALID',
79
+ VALIDATION_TYPE_FAILED: 'ERR_MB_VALIDATION_TYPE_FAILED',
80
+ //#endregion
81
+ } as const;
@@ -15,8 +15,6 @@ import type {
15
15
  CiInfo,
16
16
  ConfigurationManager,
17
17
  EnvironmentInfo,
18
- ErrorManager,
19
- ExecutionPhase,
20
18
  FileLoader,
21
19
  FileResult,
22
20
  GitInfo,
@@ -33,12 +31,19 @@ import type {
33
31
  ValidationWarning,
34
32
  } from '../types/index.js';
35
33
 
34
+ import {
35
+ BenchmarkExecutionError,
36
+ FileDiscoveryError,
37
+ SchemaValidationError,
38
+ SetupError,
39
+ StructureValidationError,
40
+ } from '../errors/index.js';
41
+
36
42
  /**
37
43
  * Dependencies required by the BenchmarkEngine
38
44
  */
39
45
  interface EngineDependencies {
40
46
  readonly configManager: ConfigurationManager;
41
- readonly errorManager: ErrorManager;
42
47
  readonly fileLoader: FileLoader;
43
48
  readonly historyStorage: HistoryStorage;
44
49
  readonly progressManager: ProgressManager;
@@ -55,8 +60,6 @@ interface EngineDependencies {
55
60
  export abstract class ModestBenchEngine implements BenchmarkEngine {
56
61
  public readonly configManager: ConfigurationManager;
57
62
 
58
- public readonly errorManager: ErrorManager;
59
-
60
63
  public readonly fileLoader: FileLoader;
61
64
 
62
65
  public readonly historyStorage: HistoryStorage;
@@ -71,7 +74,6 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
71
74
  this.reporterRegistry = dependencies.reporterRegistry;
72
75
  this.historyStorage = dependencies.historyStorage;
73
76
  this.progressManager = dependencies.progressManager;
74
- this.errorManager = dependencies.errorManager;
75
77
  }
76
78
 
77
79
  /**
@@ -86,13 +88,10 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
86
88
  } catch (error) {
87
89
  const discoveryError =
88
90
  error instanceof Error ? error : new Error(String(error));
89
- this.errorManager.handleError(discoveryError, {
90
- metadata: { exclude, pattern },
91
- phase: 'discovery',
92
- timestamp: new Date(),
93
- });
94
-
95
- throw new Error(`File discovery failed: ${discoveryError.message}`);
91
+ throw new FileDiscoveryError(
92
+ `File discovery failed: ${discoveryError.message}`,
93
+ { cause: discoveryError },
94
+ );
96
95
  }
97
96
  }
98
97
 
@@ -105,11 +104,9 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
105
104
  signal?: AbortSignal,
106
105
  ): Promise<BenchmarkRun> {
107
106
  const startTime = new Date();
108
- let currentPhase: ExecutionPhase = 'discovery';
109
107
 
110
108
  try {
111
109
  // 1. Merge configuration with defaults
112
- currentPhase = 'discovery';
113
110
  const mergedConfig = await this.configManager.load(
114
111
  undefined, // No specific config path for now
115
112
  config as Record<string, unknown>,
@@ -121,32 +118,22 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
121
118
  (await this.discover(mergedConfig.pattern, mergedConfig.exclude));
122
119
 
123
120
  if (files.length === 0) {
124
- const error = new Error(
125
- 'No benchmark files found matching the pattern',
126
- );
127
- this.errorManager.handleError(error, {
128
- phase: currentPhase,
129
- timestamp: new Date(),
130
- });
131
- throw error;
121
+ let msg = `No benchmark files found matching the pattern "${mergedConfig.pattern}`;
122
+ if (mergedConfig.exclude?.length) {
123
+ msg += ` and excluding "${mergedConfig.exclude.join(', ')}"`;
124
+ }
125
+ throw new FileDiscoveryError(msg);
132
126
  }
133
127
 
134
128
  // 3. Validate files
135
- currentPhase = 'validation';
136
129
  const validationResult = await this.validate(files);
137
130
  if (!validationResult.valid) {
138
- const error = new Error(
131
+ throw new SchemaValidationError(
139
132
  `Validation failed: ${validationResult.errors.map((e) => e.message).join(', ')}`,
140
133
  );
141
- this.errorManager.handleError(error, {
142
- phase: currentPhase,
143
- timestamp: new Date(),
144
- });
145
- throw error;
146
134
  }
147
135
 
148
136
  // 4. Initialize progress tracking
149
- currentPhase = 'setup';
150
137
  const runId = this.generateRunId();
151
138
 
152
139
  // Pre-calculate total tasks for progress tracking
@@ -234,7 +221,6 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
234
221
  await this.callReporters(reporters, 'onStart', initialRun);
235
222
 
236
223
  // 6. Execute benchmark files
237
- currentPhase = 'execution';
238
224
  const fileResults: FileResult[] = [];
239
225
 
240
226
  for (const filePath of files) {
@@ -261,11 +247,6 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
261
247
  } catch (error) {
262
248
  const fileError =
263
249
  error instanceof Error ? error : new Error(String(error));
264
- this.errorManager.handleError(fileError, {
265
- file: filePath,
266
- phase: currentPhase,
267
- timestamp: new Date(),
268
- });
269
250
 
270
251
  // Call reporter onError
271
252
  await this.callReporters(reporters, 'onError', fileError);
@@ -348,15 +329,23 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
348
329
  // 9. Return completed run
349
330
  return finalRun;
350
331
  } catch (error) {
332
+ // Re-throw our custom errors
333
+ if (
334
+ error instanceof FileDiscoveryError ||
335
+ error instanceof SchemaValidationError ||
336
+ error instanceof BenchmarkExecutionError
337
+ ) {
338
+ throw error;
339
+ }
340
+
351
341
  const executionError =
352
342
  error instanceof Error ? error : new Error(String(error));
353
- const handledError = this.errorManager.handleError(executionError, {
354
- phase: currentPhase,
355
- timestamp: new Date(),
356
- });
357
343
 
358
344
  // Re-throw the original error with more context
359
- throw new Error(`Benchmark execution failed: ${handledError.message}`);
345
+ throw new BenchmarkExecutionError(
346
+ `Benchmark execution failed: ${executionError.message}`,
347
+ { cause: executionError },
348
+ );
360
349
  }
361
350
  }
362
351
 
@@ -421,11 +410,6 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
421
410
  } catch (error) {
422
411
  const validationError =
423
412
  error instanceof Error ? error : new Error(String(error));
424
- this.errorManager.handleError(validationError, {
425
- file,
426
- phase: 'validation',
427
- timestamp: new Date(),
428
- });
429
413
 
430
414
  errors.push({
431
415
  code: 'FILE_VALIDATION_ERROR',
@@ -514,7 +498,7 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
514
498
  const benchmarkDef = benchmarkFile.exports as BenchmarkDefinition;
515
499
 
516
500
  if (!benchmarkDef || typeof benchmarkDef !== 'object') {
517
- throw new Error(
501
+ throw new StructureValidationError(
518
502
  'Benchmark file must export a default object with suites',
519
503
  );
520
504
  }
@@ -632,7 +616,9 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
632
616
  error instanceof Error
633
617
  ? error
634
618
  : new Error(`Setup failed: ${String(error)}`);
635
- throw new Error(`Suite setup failed: ${setupError.message}`);
619
+ throw new SetupError(`Suite setup failed: ${setupError.message}`, {
620
+ cause: setupError,
621
+ });
636
622
  }
637
623
  }
638
624
 
@@ -26,6 +26,7 @@ import type {
26
26
  TaskResult,
27
27
  } from '../../types/index.js';
28
28
 
29
+ import { StructureValidationError } from '../../errors/index.js';
29
30
  import { ModestBenchEngine } from '../engine.js';
30
31
  import { calculateStatistics, removeOutliersIQR } from '../stats-utils.js';
31
32
 
@@ -59,7 +60,9 @@ export class AccurateEngine extends ModestBenchEngine {
59
60
  ): Promise<TaskResult> {
60
61
  try {
61
62
  if (!taskData.fn || typeof taskData.fn !== 'function') {
62
- throw new Error('Benchmark task must have a "fn" function property');
63
+ throw new StructureValidationError(
64
+ 'Benchmark task must have a "fn" function property',
65
+ );
63
66
  }
64
67
 
65
68
  // Check for V8 native syntax support
@@ -14,6 +14,11 @@ import type {
14
14
  TaskResult,
15
15
  } from '../../types/index.js';
16
16
 
17
+ import {
18
+ BenchmarkExecutionError,
19
+ OperationTooFastError,
20
+ StructureValidationError,
21
+ } from '../../errors/index.js';
17
22
  import { ModestBenchEngine } from '../engine.js';
18
23
  import { calculateStatistics, removeOutliersIQR } from '../stats-utils.js';
19
24
 
@@ -33,7 +38,9 @@ export class TinybenchEngine extends ModestBenchEngine {
33
38
  ): Promise<TaskResult> {
34
39
  try {
35
40
  if (!taskData.fn || typeof taskData.fn !== 'function') {
36
- throw new Error('Benchmark task must have a "fn" function property');
41
+ throw new StructureValidationError(
42
+ 'Benchmark task must have a "fn" function property',
43
+ );
37
44
  }
38
45
 
39
46
  // Determine effective time and iterations based on limitBy mode
@@ -137,13 +144,13 @@ export class TinybenchEngine extends ModestBenchEngine {
137
144
  await minimalBench.run();
138
145
  } catch {
139
146
  // If still failing, the operation is too fast even for tinybench
140
- throw new Error(
147
+ throw new OperationTooFastError(
141
148
  `Benchmark operation is too fast to measure reliably (execution time < 1ns)`,
142
149
  );
143
150
  }
144
151
  const minimalResults = minimalBench.results[0];
145
152
  if (!minimalResults || minimalResults.error) {
146
- throw new Error(
153
+ throw new OperationTooFastError(
147
154
  `Benchmark too fast to measure reliably: ${minimalResults?.error?.message || 'unknown error'}`,
148
155
  );
149
156
  }
@@ -180,7 +187,7 @@ export class TinybenchEngine extends ModestBenchEngine {
180
187
  // Get results
181
188
  const results = bench.results[0];
182
189
  if (!results) {
183
- throw new Error('No benchmark results returned');
190
+ throw new BenchmarkExecutionError('No benchmark results returned');
184
191
  }
185
192
 
186
193
  // Check if the task was aborted
@@ -251,7 +258,7 @@ export class TinybenchEngine extends ModestBenchEngine {
251
258
 
252
259
  if (!minimalResults || minimalResults.error) {
253
260
  // If retry also fails, just accept it failed
254
- throw new Error(
261
+ throw new OperationTooFastError(
255
262
  `Benchmark operation is too fast to measure reliably`,
256
263
  );
257
264
  }
@@ -22,6 +22,11 @@ import {
22
22
  BENCHMARK_FILE_EXTENSIONS,
23
23
  BENCHMARK_FILE_PATTERN,
24
24
  } from '../constants.js';
25
+ import {
26
+ FileDiscoveryError,
27
+ FileLoadError,
28
+ StructureValidationError,
29
+ } from '../errors/index.js';
25
30
  import { benchmarkFileSchema } from './benchmark-schema.js';
26
31
 
27
32
  /**
@@ -106,8 +111,9 @@ export class BenchmarkFileLoader implements FileLoader {
106
111
 
107
112
  return supportedFiles.sort();
108
113
  } catch (error) {
109
- throw new Error(
114
+ throw new FileDiscoveryError(
110
115
  `File discovery failed: ${error instanceof Error ? error.message : String(error)}`,
116
+ { cause: error },
111
117
  );
112
118
  }
113
119
  }
@@ -120,7 +126,7 @@ export class BenchmarkFileLoader implements FileLoader {
120
126
  // Basic file checks (existence, extension)
121
127
  const basicValidation = await this.validate(filePath);
122
128
  if (!basicValidation.valid) {
123
- throw new Error(
129
+ throw new FileLoadError(
124
130
  `Invalid benchmark file: ${basicValidation.errors.map((e) => e.message).join(', ')}`,
125
131
  );
126
132
  }
@@ -160,7 +166,7 @@ export class BenchmarkFileLoader implements FileLoader {
160
166
  // Validate the loaded exports structure with Zod
161
167
  const structureValidation = this.validateExports(filePath, exports);
162
168
  if (!structureValidation.valid || !structureValidation.data) {
163
- throw new Error(
169
+ throw new StructureValidationError(
164
170
  `Invalid benchmark structure: ${structureValidation.errors.map((e) => e.message).join(', ')}`,
165
171
  );
166
172
  }
@@ -185,8 +191,16 @@ export class BenchmarkFileLoader implements FileLoader {
185
191
  },
186
192
  };
187
193
  } catch (error) {
188
- throw new Error(
194
+ // Re-throw our custom errors
195
+ if (
196
+ error instanceof FileLoadError ||
197
+ error instanceof StructureValidationError
198
+ ) {
199
+ throw error;
200
+ }
201
+ throw new FileLoadError(
189
202
  `Failed to load file ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
203
+ { cause: error },
190
204
  );
191
205
  }
192
206
  }
@@ -199,8 +213,16 @@ export class BenchmarkFileLoader implements FileLoader {
199
213
  const loadPromises = filePaths.map((filePath) => this.load(filePath));
200
214
  return await Promise.all(loadPromises);
201
215
  } catch (error) {
202
- throw new Error(
216
+ // Re-throw our custom errors (from individual load calls)
217
+ if (
218
+ error instanceof FileLoadError ||
219
+ error instanceof StructureValidationError
220
+ ) {
221
+ throw error;
222
+ }
223
+ throw new FileLoadError(
203
224
  `Failed to load files: ${error instanceof Error ? error.message : String(error)}`,
225
+ { cause: error },
204
226
  );
205
227
  }
206
228
  }