modestbench 0.0.3 → 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 (223) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/bootstrap.cjs +0 -2
  3. package/dist/bootstrap.cjs.map +1 -1
  4. package/dist/bootstrap.d.cts.map +1 -1
  5. package/dist/bootstrap.d.ts.map +1 -1
  6. package/dist/bootstrap.js +0 -2
  7. package/dist/bootstrap.js.map +1 -1
  8. package/dist/cli/commands/history.cjs +2 -1
  9. package/dist/cli/commands/history.cjs.map +1 -1
  10. package/dist/cli/commands/history.d.cts.map +1 -1
  11. package/dist/cli/commands/history.d.ts.map +1 -1
  12. package/dist/cli/commands/history.js +2 -1
  13. package/dist/cli/commands/history.js.map +1 -1
  14. package/dist/cli/commands/init.cjs +5 -4
  15. package/dist/cli/commands/init.cjs.map +1 -1
  16. package/dist/cli/commands/init.d.cts.map +1 -1
  17. package/dist/cli/commands/init.d.ts.map +1 -1
  18. package/dist/cli/commands/init.js +5 -4
  19. package/dist/cli/commands/init.js.map +1 -1
  20. package/dist/cli/commands/run.cjs +14 -4
  21. package/dist/cli/commands/run.cjs.map +1 -1
  22. package/dist/cli/commands/run.d.cts.map +1 -1
  23. package/dist/cli/commands/run.d.ts.map +1 -1
  24. package/dist/cli/commands/run.js +14 -4
  25. package/dist/cli/commands/run.js.map +1 -1
  26. package/dist/cli/index.cjs +29 -12
  27. package/dist/cli/index.cjs.map +1 -1
  28. package/dist/cli/index.d.cts +1 -2
  29. package/dist/cli/index.d.cts.map +1 -1
  30. package/dist/cli/index.d.ts +1 -2
  31. package/dist/cli/index.d.ts.map +1 -1
  32. package/dist/cli/index.js +24 -7
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/config/manager.cjs +8 -2
  35. package/dist/config/manager.cjs.map +1 -1
  36. package/dist/config/manager.d.cts.map +1 -1
  37. package/dist/config/manager.d.ts.map +1 -1
  38. package/dist/config/manager.js +8 -2
  39. package/dist/config/manager.js.map +1 -1
  40. package/dist/constants.cjs +53 -1
  41. package/dist/constants.cjs.map +1 -1
  42. package/dist/constants.d.cts +36 -0
  43. package/dist/constants.d.cts.map +1 -1
  44. package/dist/constants.d.ts +36 -0
  45. package/dist/constants.d.ts.map +1 -1
  46. package/dist/constants.js +52 -0
  47. package/dist/constants.js.map +1 -1
  48. package/dist/core/engine.cjs +15 -42
  49. package/dist/core/engine.cjs.map +1 -1
  50. package/dist/core/engine.d.cts +1 -3
  51. package/dist/core/engine.d.cts.map +1 -1
  52. package/dist/core/engine.d.ts +1 -3
  53. package/dist/core/engine.d.ts.map +1 -1
  54. package/dist/core/engine.js +15 -42
  55. package/dist/core/engine.js.map +1 -1
  56. package/dist/core/engines/accurate-engine.cjs +2 -1
  57. package/dist/core/engines/accurate-engine.cjs.map +1 -1
  58. package/dist/core/engines/accurate-engine.d.cts.map +1 -1
  59. package/dist/core/engines/accurate-engine.d.ts.map +1 -1
  60. package/dist/core/engines/accurate-engine.js +2 -1
  61. package/dist/core/engines/accurate-engine.js.map +1 -1
  62. package/dist/core/engines/tinybench-engine.cjs +6 -5
  63. package/dist/core/engines/tinybench-engine.cjs.map +1 -1
  64. package/dist/core/engines/tinybench-engine.d.cts.map +1 -1
  65. package/dist/core/engines/tinybench-engine.d.ts.map +1 -1
  66. package/dist/core/engines/tinybench-engine.js +6 -5
  67. package/dist/core/engines/tinybench-engine.js.map +1 -1
  68. package/dist/core/loader.cjs +16 -5
  69. package/dist/core/loader.cjs.map +1 -1
  70. package/dist/core/loader.d.cts.map +1 -1
  71. package/dist/core/loader.d.ts.map +1 -1
  72. package/dist/core/loader.js +16 -5
  73. package/dist/core/loader.js.map +1 -1
  74. package/dist/errors/base.cjs +130 -0
  75. package/dist/errors/base.cjs.map +1 -0
  76. package/dist/errors/base.d.cts +97 -0
  77. package/dist/errors/base.d.cts.map +1 -0
  78. package/dist/errors/base.d.ts +97 -0
  79. package/dist/errors/base.d.ts.map +1 -0
  80. package/dist/errors/base.js +124 -0
  81. package/dist/errors/base.js.map +1 -0
  82. package/dist/errors/cli.cjs +58 -0
  83. package/dist/errors/cli.cjs.map +1 -0
  84. package/dist/errors/cli.d.cts +44 -0
  85. package/dist/errors/cli.d.cts.map +1 -0
  86. package/dist/errors/cli.d.ts +44 -0
  87. package/dist/errors/cli.d.ts.map +1 -0
  88. package/dist/errors/cli.js +52 -0
  89. package/dist/errors/cli.js.map +1 -0
  90. package/dist/errors/configuration.cjs +48 -0
  91. package/dist/errors/configuration.cjs.map +1 -0
  92. package/dist/errors/configuration.d.cts +41 -0
  93. package/dist/errors/configuration.d.cts.map +1 -0
  94. package/dist/errors/configuration.d.ts +41 -0
  95. package/dist/errors/configuration.d.ts.map +1 -0
  96. package/dist/errors/configuration.js +41 -0
  97. package/dist/errors/configuration.js.map +1 -0
  98. package/dist/errors/execution.cjs +65 -0
  99. package/dist/errors/execution.cjs.map +1 -0
  100. package/dist/errors/execution.d.cts +56 -0
  101. package/dist/errors/execution.d.cts.map +1 -0
  102. package/dist/errors/execution.d.ts +56 -0
  103. package/dist/errors/execution.d.ts.map +1 -0
  104. package/dist/errors/execution.js +56 -0
  105. package/dist/errors/execution.js.map +1 -0
  106. package/dist/errors/file.cjs +56 -0
  107. package/dist/errors/file.cjs.map +1 -0
  108. package/dist/errors/file.d.cts +48 -0
  109. package/dist/errors/file.d.cts.map +1 -0
  110. package/dist/errors/file.d.ts +48 -0
  111. package/dist/errors/file.d.ts.map +1 -0
  112. package/dist/errors/file.js +48 -0
  113. package/dist/errors/file.js.map +1 -0
  114. package/dist/errors/index.cjs +59 -0
  115. package/dist/errors/index.cjs.map +1 -0
  116. package/dist/errors/index.d.cts +16 -0
  117. package/dist/errors/index.d.cts.map +1 -0
  118. package/dist/errors/index.d.ts +16 -0
  119. package/dist/errors/index.d.ts.map +1 -0
  120. package/dist/errors/index.js +24 -0
  121. package/dist/errors/index.js.map +1 -0
  122. package/dist/errors/reporter.cjs +38 -0
  123. package/dist/errors/reporter.cjs.map +1 -0
  124. package/dist/errors/reporter.d.cts +32 -0
  125. package/dist/errors/reporter.d.cts.map +1 -0
  126. package/dist/errors/reporter.d.ts +32 -0
  127. package/dist/errors/reporter.d.ts.map +1 -0
  128. package/dist/errors/reporter.js +32 -0
  129. package/dist/errors/reporter.js.map +1 -0
  130. package/dist/errors/storage.cjs +55 -0
  131. package/dist/errors/storage.cjs.map +1 -0
  132. package/dist/errors/storage.d.cts +47 -0
  133. package/dist/errors/storage.d.cts.map +1 -0
  134. package/dist/errors/storage.d.ts +47 -0
  135. package/dist/errors/storage.d.ts.map +1 -0
  136. package/dist/errors/storage.js +47 -0
  137. package/dist/errors/storage.js.map +1 -0
  138. package/dist/errors/validation.cjs +38 -0
  139. package/dist/errors/validation.cjs.map +1 -0
  140. package/dist/errors/validation.d.cts +32 -0
  141. package/dist/errors/validation.d.cts.map +1 -0
  142. package/dist/errors/validation.d.ts +32 -0
  143. package/dist/errors/validation.d.ts.map +1 -0
  144. package/dist/errors/validation.js +32 -0
  145. package/dist/errors/validation.js.map +1 -0
  146. package/dist/index.cjs +3 -4
  147. package/dist/index.cjs.map +1 -1
  148. package/dist/index.d.cts +1 -1
  149. package/dist/index.d.cts.map +1 -1
  150. package/dist/index.d.ts +1 -1
  151. package/dist/index.d.ts.map +1 -1
  152. package/dist/index.js +2 -2
  153. package/dist/index.js.map +1 -1
  154. package/dist/reporters/csv.cjs +3 -2
  155. package/dist/reporters/csv.cjs.map +1 -1
  156. package/dist/reporters/csv.d.cts.map +1 -1
  157. package/dist/reporters/csv.d.ts.map +1 -1
  158. package/dist/reporters/csv.js +3 -2
  159. package/dist/reporters/csv.js.map +1 -1
  160. package/dist/reporters/human.cjs +1 -1
  161. package/dist/reporters/human.js +1 -1
  162. package/dist/reporters/json.cjs +3 -2
  163. package/dist/reporters/json.cjs.map +1 -1
  164. package/dist/reporters/json.d.cts.map +1 -1
  165. package/dist/reporters/json.d.ts.map +1 -1
  166. package/dist/reporters/json.js +3 -2
  167. package/dist/reporters/json.js.map +1 -1
  168. package/dist/reporters/registry.cjs +3 -2
  169. package/dist/reporters/registry.cjs.map +1 -1
  170. package/dist/reporters/registry.d.cts.map +1 -1
  171. package/dist/reporters/registry.d.ts.map +1 -1
  172. package/dist/reporters/registry.js +3 -2
  173. package/dist/reporters/registry.js.map +1 -1
  174. package/dist/reporters/simple.cjs +1 -1
  175. package/dist/reporters/simple.js +1 -1
  176. package/dist/storage/history.cjs +32 -11
  177. package/dist/storage/history.cjs.map +1 -1
  178. package/dist/storage/history.d.cts.map +1 -1
  179. package/dist/storage/history.d.ts.map +1 -1
  180. package/dist/storage/history.js +32 -11
  181. package/dist/storage/history.js.map +1 -1
  182. package/dist/types/interfaces.d.cts +1 -34
  183. package/dist/types/interfaces.d.cts.map +1 -1
  184. package/dist/types/interfaces.d.ts +1 -34
  185. package/dist/types/interfaces.d.ts.map +1 -1
  186. package/package.json +7 -6
  187. package/src/bootstrap.ts +0 -2
  188. package/src/cli/commands/history.ts +3 -1
  189. package/src/cli/commands/init.ts +14 -4
  190. package/src/cli/commands/run.ts +20 -4
  191. package/src/cli/index.ts +32 -13
  192. package/src/config/manager.ts +12 -2
  193. package/src/constants.ts +60 -0
  194. package/src/core/engine.ts +31 -47
  195. package/src/core/engines/accurate-engine.ts +4 -1
  196. package/src/core/engines/tinybench-engine.ts +12 -5
  197. package/src/core/loader.ts +27 -5
  198. package/src/errors/base.ts +152 -0
  199. package/src/errors/cli.ts +59 -0
  200. package/src/errors/configuration.ts +45 -0
  201. package/src/errors/execution.ts +62 -0
  202. package/src/errors/file.ts +53 -0
  203. package/src/errors/index.ts +71 -0
  204. package/src/errors/reporter.ts +35 -0
  205. package/src/errors/storage.ts +52 -0
  206. package/src/errors/validation.ts +35 -0
  207. package/src/index.ts +3 -3
  208. package/src/reporters/csv.ts +4 -2
  209. package/src/reporters/human.ts +1 -1
  210. package/src/reporters/json.ts +4 -2
  211. package/src/reporters/registry.ts +9 -2
  212. package/src/reporters/simple.ts +1 -1
  213. package/src/storage/history.ts +58 -11
  214. package/src/types/interfaces.ts +0 -43
  215. package/dist/core/error-manager.cjs +0 -303
  216. package/dist/core/error-manager.cjs.map +0 -1
  217. package/dist/core/error-manager.d.cts +0 -77
  218. package/dist/core/error-manager.d.cts.map +0 -1
  219. package/dist/core/error-manager.d.ts +0 -77
  220. package/dist/core/error-manager.d.ts.map +0 -1
  221. package/dist/core/error-manager.js +0 -299
  222. package/dist/core/error-manager.js.map +0 -1
  223. package/src/core/error-manager.ts +0 -372
@@ -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
  /**
@@ -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>,
@@ -125,30 +122,18 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
125
122
  if (mergedConfig.exclude?.length) {
126
123
  msg += ` and excluding "${mergedConfig.exclude.join(', ')}"`;
127
124
  }
128
- const error = new Error(msg);
129
- this.errorManager.handleError(error, {
130
- phase: currentPhase,
131
- timestamp: new Date(),
132
- });
133
- throw error;
125
+ throw new FileDiscoveryError(msg);
134
126
  }
135
127
 
136
128
  // 3. Validate files
137
- currentPhase = 'validation';
138
129
  const validationResult = await this.validate(files);
139
130
  if (!validationResult.valid) {
140
- const error = new Error(
131
+ throw new SchemaValidationError(
141
132
  `Validation failed: ${validationResult.errors.map((e) => e.message).join(', ')}`,
142
133
  );
143
- this.errorManager.handleError(error, {
144
- phase: currentPhase,
145
- timestamp: new Date(),
146
- });
147
- throw error;
148
134
  }
149
135
 
150
136
  // 4. Initialize progress tracking
151
- currentPhase = 'setup';
152
137
  const runId = this.generateRunId();
153
138
 
154
139
  // Pre-calculate total tasks for progress tracking
@@ -236,7 +221,6 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
236
221
  await this.callReporters(reporters, 'onStart', initialRun);
237
222
 
238
223
  // 6. Execute benchmark files
239
- currentPhase = 'execution';
240
224
  const fileResults: FileResult[] = [];
241
225
 
242
226
  for (const filePath of files) {
@@ -263,11 +247,6 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
263
247
  } catch (error) {
264
248
  const fileError =
265
249
  error instanceof Error ? error : new Error(String(error));
266
- this.errorManager.handleError(fileError, {
267
- file: filePath,
268
- phase: currentPhase,
269
- timestamp: new Date(),
270
- });
271
250
 
272
251
  // Call reporter onError
273
252
  await this.callReporters(reporters, 'onError', fileError);
@@ -350,15 +329,23 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
350
329
  // 9. Return completed run
351
330
  return finalRun;
352
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
+
353
341
  const executionError =
354
342
  error instanceof Error ? error : new Error(String(error));
355
- const handledError = this.errorManager.handleError(executionError, {
356
- phase: currentPhase,
357
- timestamp: new Date(),
358
- });
359
343
 
360
344
  // Re-throw the original error with more context
361
- throw new Error(`Benchmark execution failed: ${handledError.message}`);
345
+ throw new BenchmarkExecutionError(
346
+ `Benchmark execution failed: ${executionError.message}`,
347
+ { cause: executionError },
348
+ );
362
349
  }
363
350
  }
364
351
 
@@ -423,11 +410,6 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
423
410
  } catch (error) {
424
411
  const validationError =
425
412
  error instanceof Error ? error : new Error(String(error));
426
- this.errorManager.handleError(validationError, {
427
- file,
428
- phase: 'validation',
429
- timestamp: new Date(),
430
- });
431
413
 
432
414
  errors.push({
433
415
  code: 'FILE_VALIDATION_ERROR',
@@ -516,7 +498,7 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
516
498
  const benchmarkDef = benchmarkFile.exports as BenchmarkDefinition;
517
499
 
518
500
  if (!benchmarkDef || typeof benchmarkDef !== 'object') {
519
- throw new Error(
501
+ throw new StructureValidationError(
520
502
  'Benchmark file must export a default object with suites',
521
503
  );
522
504
  }
@@ -634,7 +616,9 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
634
616
  error instanceof Error
635
617
  ? error
636
618
  : new Error(`Setup failed: ${String(error)}`);
637
- throw new Error(`Suite setup failed: ${setupError.message}`);
619
+ throw new SetupError(`Suite setup failed: ${setupError.message}`, {
620
+ cause: setupError,
621
+ });
638
622
  }
639
623
  }
640
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
  }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * ModestBench Custom Error System
3
+ *
4
+ * Base error classes providing structured error handling with error codes,
5
+ * documentation URLs, and consistent error display.
6
+ */
7
+
8
+ /**
9
+ * Base URL for error documentation
10
+ */
11
+ const ERROR_DOC_BASE_URL =
12
+ 'https://boneskull.github.io/modestbench/reference/errors';
13
+
14
+ /**
15
+ * Abstract base class for ModestBench aggregate errors
16
+ *
17
+ * Extends AggregateError to support multiple errors with ModestBench error
18
+ * system features.
19
+ */
20
+ export abstract class ModestBenchAggregateError extends AggregateError {
21
+ /**
22
+ * Unique error code for this error type Must be in format:
23
+ * ERR_MB_CATEGORY_DESCRIPTION
24
+ */
25
+ abstract readonly code: string;
26
+
27
+ /**
28
+ * Error name (matches class name)
29
+ */
30
+ public override readonly name: string;
31
+
32
+ /**
33
+ * Create a new ModestBench aggregate error
34
+ *
35
+ * @param errors - Array of errors that occurred
36
+ * @param message - Human-readable error message
37
+ * @param options - Optional Error options (e.g., cause)
38
+ */
39
+ constructor(errors: unknown[], message: string, options?: ErrorOptions) {
40
+ super(errors, message, options);
41
+ this.name = this.constructor.name;
42
+ }
43
+
44
+ /**
45
+ * Get the documentation URL for this error
46
+ *
47
+ * Returns a URL to the error reference page with an anchor to the specific
48
+ * error.
49
+ *
50
+ * @returns Documentation URL
51
+ */
52
+ getDocUrl(): string {
53
+ // Use the error class name as the anchor (e.g., ConfigValidationError -> #configvalidationerror)
54
+ const anchor = this.name.toLowerCase();
55
+ return `${ERROR_DOC_BASE_URL}#${anchor}`;
56
+ }
57
+
58
+ /**
59
+ * Convert error to string with code, documentation URL, and nested errors
60
+ *
61
+ * @returns Formatted error string
62
+ */
63
+ override toString(): string {
64
+ let result = `${this.name} [${this.code}]: ${this.message}\n`;
65
+ result += `See: ${this.getDocUrl()}\n`;
66
+
67
+ if (this.errors.length > 0) {
68
+ result += `\nContains ${this.errors.length} error(s):\n`;
69
+ this.errors.forEach((err, index) => {
70
+ const errMsg = err instanceof Error ? err.message : String(err);
71
+ result += ` ${index + 1}. ${errMsg}\n`;
72
+ });
73
+ }
74
+
75
+ return result;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Abstract base class for all ModestBench errors
81
+ *
82
+ * Provides:
83
+ *
84
+ * - Unique error codes with ERR_MB_ prefix
85
+ * - Documentation URL generation
86
+ * - Consistent error display format
87
+ * - TypeScript type safety
88
+ */
89
+ export abstract class ModestBenchError extends Error {
90
+ /**
91
+ * Unique error code for this error type Must be in format:
92
+ * ERR_MB_CATEGORY_DESCRIPTION
93
+ */
94
+ abstract readonly code: string;
95
+
96
+ /**
97
+ * Error name (matches class name)
98
+ */
99
+ public override readonly name: string;
100
+
101
+ /**
102
+ * Create a new ModestBench error
103
+ *
104
+ * @param message - Human-readable error message
105
+ * @param options - Optional Error options (e.g., cause)
106
+ */
107
+ constructor(message: string, options?: ErrorOptions) {
108
+ super(message, options);
109
+ this.name = this.constructor.name;
110
+ }
111
+
112
+ /**
113
+ * Get the documentation URL for this error
114
+ *
115
+ * Returns a URL to the error reference page with an anchor to the specific
116
+ * error.
117
+ *
118
+ * @returns Documentation URL
119
+ */
120
+ getDocUrl(): string {
121
+ // Use the error class name as the anchor (e.g., ConfigValidationError -> #configvalidationerror)
122
+ const anchor = this.name.toLowerCase();
123
+ return `${ERROR_DOC_BASE_URL}#${anchor}`;
124
+ }
125
+
126
+ /**
127
+ * Convert error to string with code and documentation URL
128
+ *
129
+ * @returns Formatted error string
130
+ */
131
+ override toString(): string {
132
+ return `${this.name} [${this.code}]: ${this.message}\nSee: ${this.getDocUrl()}`;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Type guard to check if an error is a ModestBench error
138
+ *
139
+ * @param error - The error to check
140
+ * @returns True if the error is a ModestBench error
141
+ */
142
+ export const isModestBenchError = (
143
+ error: unknown,
144
+ ): error is ModestBenchError => {
145
+ return (
146
+ typeof error === 'object' &&
147
+ error !== null &&
148
+ 'code' in error &&
149
+ typeof (error as { code: unknown }).code === 'string' &&
150
+ (error as { code: string }).code.startsWith('ERR_MB_')
151
+ );
152
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * CLI-related errors
3
+ *
4
+ * Errors that occur during command-line interface operations.
5
+ */
6
+
7
+ import { ModestBenchError } from './base.js';
8
+
9
+ /**
10
+ * Invalid CLI argument
11
+ *
12
+ * Thrown when a CLI argument is invalid or cannot be parsed.
13
+ */
14
+ export class InvalidArgumentError extends ModestBenchError {
15
+ readonly code = 'ERR_MB_CLI_INVALID_ARGUMENT';
16
+ }
17
+
18
+ /**
19
+ * Invalid date format
20
+ *
21
+ * Thrown when a date string cannot be parsed into a valid date.
22
+ */
23
+ export class InvalidDateFormatError extends ModestBenchError {
24
+ readonly code = 'ERR_MB_CLI_INVALID_DATE_FORMAT';
25
+ }
26
+
27
+ /**
28
+ * Unknown error
29
+ *
30
+ * Thrown at the CLI boundary to wrap unexpected errors that are not ModestBench
31
+ * errors. This ensures all errors have proper structure and documentation
32
+ * links.
33
+ */
34
+ export class UnknownError extends ModestBenchError {
35
+ readonly code = 'ERR_MB_UNKNOWN';
36
+
37
+ /**
38
+ * Create a new UnknownError wrapping an unexpected error
39
+ *
40
+ * @param message - The error message (typically from the original error)
41
+ * @param options - Error options with the original error as the cause
42
+ */
43
+ constructor(message: string, options: ErrorOptions) {
44
+ super(message, options);
45
+ }
46
+
47
+ /**
48
+ * Override toString to show full details of the wrapped error
49
+ */
50
+ override toString(): string {
51
+ let result = super.toString();
52
+
53
+ if (this.cause instanceof Error && this.cause.stack) {
54
+ result += '\n\nOriginal error:\n' + this.cause.stack;
55
+ }
56
+
57
+ return result;
58
+ }
59
+ }