Reporter Plugin API Reference
This is the complete API reference for building third-party reporter plugins. For a practical guide with examples, see Build a Custom Reporter.
Plugin Export Patterns
Section titled “Plugin Export Patterns”A reporter plugin module must have a default export that matches one of these patterns:
// Pattern 1: Plain Reporter objectexport default reporter satisfies Reporter;
// Pattern 2: Factory function (sync or async)export default createReporter satisfies ReporterFactory;
// Pattern 3: Class constructorexport default MyReporter satisfies ReporterClass;Reporter
Section titled “Reporter”The core interface that all reporters must implement.
interface Reporter { // Required methods onStart(run: BenchmarkRun): Promise<void> | void; onEnd(run: BenchmarkRun): Promise<void> | void; onError(error: Error): Promise<void> | void; onTaskResult(result: TaskResult): Promise<void> | void;
// Optional methods onFileStart?(file: string): Promise<void> | void; onFileEnd?(result: FileResult): Promise<void> | void; onSuiteStart?(suite: string): Promise<void> | void; onSuiteEnd?(result: SuiteResult): Promise<void> | void; onSuiteInit?( suite: string, taskNames: readonly string[], ): Promise<void> | void; onTaskStart?(task: string): Promise<void> | void; onProgress?(state: ProgressState): Promise<void> | void; onBudgetResult?(summary: BudgetSummary): Promise<void> | void;}ReporterFactory
Section titled “ReporterFactory”A function that creates a Reporter instance. Receives options and context. Use the generic type parameter to define your options shape, if needed.
type ReporterFactory< TOptions extends Record<string, unknown> = Record<string, unknown>,> = ( options: TOptions, context: ReporterContext,) => Reporter | Promise<Reporter>;Example with typed options:
interface SlackReporterOptions { webhookUrl: string; channel?: string;}
const createReporter: ReporterFactory<SlackReporterOptions> = ( options, context,) => { // options.webhookUrl is typed as string // options.channel is typed as string | undefined};ReporterClass
Section titled “ReporterClass”A class constructor for reporters. Use the generic type parameter to define your options shape.
interface ReporterClass< TOptions extends Record<string, unknown> = Record<string, unknown>,> { new (options?: TOptions, context?: ReporterContext): Reporter;}ReporterContext
Section titled “ReporterContext”Context object provided to factory functions and class constructors.
interface ReporterContext { /** ModestBench version string (e.g., "0.6.0") */ readonly version: string;
/** Plugin API version for compatibility checks (currently 1) */ readonly pluginApiVersion: number;
/** Logger for reporter output */ readonly logger: Logger;
/** Formatting utility functions */ readonly utils: ReporterUtils;}Logger
Section titled “Logger”Logging interface for reporter output. Use this instead of console methods to ensure output respects the user’s verbosity settings.
interface Logger { debug(message: string, ...args: unknown[]): void; info(message: string, ...args: unknown[]): void; warn(message: string, ...args: unknown[]): void; error(message: string, ...args: unknown[]): void; trace(message: string, ...args: unknown[]): void;}ReporterUtils
Section titled “ReporterUtils”Utility functions for consistent formatting of benchmark data.
interface ReporterUtils { /** * Format bytes in human-readable format * * @param bytes - Number of bytes * @returns Formatted string (e.g., "1.5 GB", "256 MB", "1.0 KB") */ formatBytes(bytes: number): string;
/** * Format duration in human-readable format * * @param nanoseconds - Duration in nanoseconds * @returns Formatted string (e.g., "1.23ms", "456.78μs", "1.00s") */ formatDuration(nanoseconds: number): string;
/** * Format operations per second with SI prefix * * @param opsPerSecond - Operations per second * @returns Formatted string (e.g., "1.2M ops/sec", "456K ops/sec") */ formatOpsPerSecond(opsPerSecond: number): string;
/** * Format percentage value * * @param value - Percentage value * @returns Formatted string (e.g., "12.34%") */ formatPercentage(value: number): string;}ReporterPlugin
Section titled “ReporterPlugin”Union type representing all valid reporter plugin exports.
type ReporterPlugin = Reporter | ReporterClass | ReporterFactory;Method Parameters
Section titled “Method Parameters”BenchmarkRun
Section titled “BenchmarkRun”Complete benchmark run information passed to onStart and onEnd.
interface BenchmarkRun { /** Unique run identifier */ id: string;
/** Run start timestamp (ISO 8601) */ startTime: string;
/** Run end timestamp (ISO 8601), undefined until run completes */ endTime?: string;
/** Total duration in nanoseconds */ duration: number;
/** Run status */ status: 'running' | 'completed' | 'failed' | 'interrupted';
/** Environment information */ environment: { nodeVersion: string; platform: string; arch: string; cpu: { model: string; cores: number; speed: number }; memory: { total: number; totalGB: number }; };
/** Configuration used for this run */ config: ModestBenchConfig;
/** All benchmark files */ files: BenchmarkFile[];
/** Summary statistics */ summary: { totalFiles: number; totalSuites: number; totalTasks: number; passedTasks: number; failedTasks: number; skippedTasks: number; };}TaskResult
Section titled “TaskResult”Individual benchmark task result passed to onTaskResult.
interface TaskResult { /** Task name */ name: string;
/** Task status */ status: 'passed' | 'failed' | 'skipped';
/** Operations per second (throughput) */ opsPerSecond: number;
/** Mean execution time in nanoseconds */ mean: number;
/** Minimum execution time in nanoseconds */ min: number;
/** Maximum execution time in nanoseconds */ max: number;
/** Standard deviation in nanoseconds */ stdDev: number;
/** Variance */ variance: number;
/** 95th percentile in nanoseconds */ p95: number;
/** 99th percentile in nanoseconds */ p99: number;
/** Margin of error percentage */ marginOfError: number;
/** Number of iterations completed */ iterations: number;
/** Tags assigned to this task */ tags: string[];
/** Error message if status is 'failed' */ error?: string;}FileResult
Section titled “FileResult”File completion result passed to onFileEnd.
interface FileResult { /** File path */ file: string;
/** File status */ status: 'passed' | 'failed' | 'skipped';
/** Total tasks in file */ totalTasks: number;
/** Passed tasks */ passedTasks: number;
/** Failed tasks */ failedTasks: number;
/** Skipped tasks */ skippedTasks: number;
/** File execution duration in nanoseconds */ duration: number;}SuiteResult
Section titled “SuiteResult”Suite completion result passed to onSuiteEnd.
interface SuiteResult { /** Suite name */ name: string;
/** Suite status */ status: 'passed' | 'failed' | 'skipped';
/** Total tasks in suite */ totalTasks: number;
/** Passed tasks */ passedTasks: number;
/** Failed tasks */ failedTasks: number;
/** Skipped tasks */ skippedTasks: number;
/** Suite execution duration in nanoseconds */ duration: number;}ProgressState
Section titled “ProgressState”Progress update passed to onProgress.
interface ProgressState { /** Current task name */ task: string;
/** Current iteration number */ iteration: number;
/** Total iterations planned */ totalIterations: number;
/** Progress percentage (0-100) */ percentage: number;
/** Elapsed time in nanoseconds */ elapsed: number;}BudgetSummary
Section titled “BudgetSummary”Budget evaluation result passed to onBudgetResult.
interface BudgetSummary { /** Overall budget status */ status: 'passed' | 'failed';
/** Number of budgets checked */ totalBudgets: number;
/** Number of budgets that passed */ passedBudgets: number;
/** Number of budgets that failed */ failedBudgets: number;
/** Individual budget results */ results: BudgetResult[];}
interface BudgetResult { /** Task identifier */ taskId: string;
/** Budget type */ type: 'absolute' | 'relative';
/** Whether budget passed */ passed: boolean;
/** Actual measured value */ actual: number;
/** Budget threshold value */ threshold: number;
/** Human-readable message */ message: string;}Error Classes
Section titled “Error Classes”ReporterLoadError
Section titled “ReporterLoadError”Thrown when a reporter module cannot be loaded.
class ReporterLoadError extends ModestBenchError { readonly code = 'ERR_MB_REPORTER_LOAD_FAILED';
/** The specifier (file path or package name) that failed to load */ readonly specifier: string;}Common causes:
- File not found
- Syntax error in module
- Invalid module format
- Constructor threw an error
- Factory function threw an error
ReporterValidationError
Section titled “ReporterValidationError”Thrown when a loaded module doesn’t implement the Reporter interface.
class ReporterValidationError extends ModestBenchError { readonly code = 'ERR_MB_REPORTER_INVALID';
/** The specifier of the invalid reporter */ readonly specifier: string;
/** Methods that are missing from the reporter */ readonly missingMethods: string[];}Utility Function Details
Section titled “Utility Function Details”formatDuration
Section titled “formatDuration”Formats nanoseconds to human-readable duration.
| Input Range | Output Format | Example |
|---|---|---|
| < 1,000 ns | X.XXns | 500.00ns |
| < 1,000,000 ns | X.XXμs | 123.45μs |
| < 1,000,000,000 ns | X.XXms | 1.23ms |
| >= 1,000,000,000 ns | X.XXs | 1.50s |
formatOpsPerSecond
Section titled “formatOpsPerSecond”Formats operations per second with SI prefix.
| Input Range | Output Format | Example |
|---|---|---|
| < 1,000 | X.XX ops/sec | 500.00 ops/sec |
| < 1,000,000 | X.XXK ops/sec | 123.45K ops/sec |
| < 1,000,000,000 | X.XXM ops/sec | 1.23M ops/sec |
| >= 1,000,000,000 | X.XXB ops/sec | 1.50B ops/sec |
formatBytes
Section titled “formatBytes”Formats bytes to human-readable size.
| Input Range | Output Format | Example |
|---|---|---|
| < 1,024 | X.X B | 512.0 B |
| < 1,048,576 | X.X KB | 1.5 KB |
| < 1,073,741,824 | X.X MB | 256.0 MB |
| < 1,099,511,627,776 | X.X GB | 1.5 GB |
| >= 1,099,511,627,776 | X.X TB | 2.5 TB |
formatPercentage
Section titled “formatPercentage”Formats a number as a percentage with 2 decimal places.
| Input | Output |
|---|---|
0 | 0.00% |
12.345 | 12.35% |
100 | 100.00% |
-5.5 | -5.50% |
Lifecycle Sequence
Section titled “Lifecycle Sequence”Methods are called in this order during a benchmark run:
onStart(run)├── onFileStart(file)│ ├── onSuiteInit(suite, taskNames)│ ├── onSuiteStart(suite)│ │ ├── onTaskStart(task)│ │ │ └── onProgress(state) [multiple times]│ │ ├── onTaskResult(result)│ │ └── [repeat for each task]│ ├── onSuiteEnd(result)│ └── [repeat for each suite]├── onFileEnd(result)├── [repeat for each file]├── onBudgetResult(summary) [if budgets configured]└── onEnd(run)
onError(error) [can occur at any point]Configuration
Section titled “Configuration”Configure reporters in modestbench.config.json:
{ "reporterConfig": { "./my-reporter.ts": { "verbose": true, "outputFormat": "markdown" }, "@flibbertigibbet/modestbench-reporter-wazit": { "path": "/foo/bar/", "site": "example.com" } }, "reporters": [ "human", "./my-reporter.ts", "@flibbertigibbet/modestbench-reporter-wazit" ]}The options parameter in factory functions and constructors receives the corresponding reporterConfig entry.
Specifier Resolution
Section titled “Specifier Resolution”Reporter specifiers are resolved as follows:
- Built-in names (
human,json,csv,simple,nyan) → Load built-in reporter - Starts with
.or/→ Resolve as file path relative to the current working directory - Absolute path → Use directly
- Otherwise → Use Node.js’ module resolution algorithm
Package Conventions
Section titled “Package Conventions”When publishing a reporter package to npm:
- Use
modestbench-reporter-*for unscoped packages - Use
@scope/modestbench-reporter-*for scoped packages - Add the
modestbench-pluginkeyword - Add modestbench as a peer dependency
Example:
{ "name": "modestbench-reporter-wazit", "keywords": ["modestbench-plugin"], "peerDependencies": { "modestbench": ">=0.6.0" }}If you are a smartypants and want to actually test your reporter, you’ll probably want to add a devDependencies entry for modestbench, too.
See Also
Section titled “See Also”- Build a Custom Reporter - Practical guide with examples
- Output Formats - Built-in reporter documentation