
Product
Announcing Socket Certified Patches: One-Click Fixes for Vulnerable Dependencies
A safer, faster way to eliminate vulnerabilities without updating dependencies
@universal-packages/base-runner
Advanced tools
Base class for runner oriented functionalities. It handles the lifecycle of a runner so you just worry about the implementation of what is running.
The BaseRunner provides a state machine with events for the different phases: prepare → run → release → finally, with support for stopping, skipping, timeouts, and error handling.
npm install @universal-packages/base-runner
classThe BaseRunner class is designed to be extended. It provides a complete lifecycle management system for any kind of runner (workers, tasks, processes, etc.).
import { BaseRunner } from '@universal-packages/base-runner'
class MyWorker extends BaseRunner {
protected async internalPrepare(): Promise<void> {
// Setup your worker
console.log('Setting up worker...')
}
protected async internalRun(): Promise<string | Error | void> {
// Do the actual work
console.log('Working...')
await new Promise((resolve) => setTimeout(resolve, 1000))
// Return for success, or a string for failure reason, or an error for failure
return
}
protected async internalRelease(): Promise<void> {
// Cleanup after work
console.log('Cleaning up...')
}
protected async internalStop(): Promise<void> {
// Handle stopping the work
console.log('Stopping work...')
}
protected async internalFinally(): Promise<void> {
// Always cleanup, regardless of outcome
console.log('Cleaning up always...')
}
}
const worker = new MyWorker({ timeout: 5000 })
worker.on('succeeded', (event) => {
console.log('Worker completed successfully!')
})
worker.on('failed', (event) => {
console.log('Worker failed:', event.payload.reason)
})
worker.on('timed-out', (event) => {
console.log('Worker timed out at:', event.payload.timedOutAt)
})
await worker.run()
// Check final status
console.log('Final status:', worker.status) // 'succeeded', 'failed', 'timed-out', etc.
constructornew BaseRunner(options?: BaseRunnerOptions)
Creates a new BaseRunner instance.
All options from @universal-packages/event-emitter are supported.
timeout Number optional
The timeout in milliseconds. If the runner takes longer than this time, it will be stopped.
Timeout only applies to the running state. Preparation and releasing states are not affected.runMode single | multi optional default: single
The run mode. When set to 'single', the runner can only be run once. When set to 'multi', the runner can be run multiple times and will reset to idle state after completion.prepareOnMultiMode always | never | on-first-run optional default: on-first-run
Whether to prepare the runner on multi mode.releaseOnMultiMode always | never optional default: never
Whether to release the runner on multi mode.The BaseRunner follows a specific state machine:
Idle - Initial statePreparing - Running internalPrepare()Running - Running internalRun()Releasing - Running internalRelease()Stopping - Trying to stop by running internalStop()Succeeded, Failed, Error, Stopped, TimedOut, or Skipped
internalFinally() runs after reaching any final state, before emitting the final eventget status(): Status
Returns the current status of the runner.
const runner = new MyWorker()
console.log(runner.status) // Status.Idle
await runner.run()
console.log(runner.status) // Status.Succeeded, Status.Failed, etc.
get error(): Error | null
Returns the error that occurred during execution, or null if no error occurred.
runner.on('error', () => {
console.log('Error occurred:', runner.error?.message)
})
get failureReason(): string | null
Returns the failure reason or null if the runner didn't fail.
runner.on('failed', () => {
console.log('Failed because:', runner.failureReason)
})
get skipReason(): string | null
Returns the reason the runner was skipped, or null if it wasn't skipped.
runner.skip('Not needed today')
console.log(runner.skipReason) // "Not needed today"
get startedAt(): Date | null
Returns the date when the runner started execution, or null if it hasn't started or was skipped.
runner.on('running', () => {
console.log('Started at:', runner.startedAt)
})
get finishedAt(): Date | null
Returns the date when the runner finished execution, or null if it hasn't finished yet.
runner.on('succeeded', () => {
console.log('Finished at:', runner.finishedAt)
console.log('Duration:', runner.finishedAt.getTime() - runner.startedAt.getTime())
})
get measurement(): Measurement | null
Returns the time measurement of the entire execution, or null if the runner hasn't finished yet. This includes preparation, running, and release phases.
runner.on('succeeded', () => {
console.log('Total execution time:', runner.measurement?.toString())
})
The BaseRunner provides boolean getter methods for easy status checking:
get isIdle(): boolean
Returns true if the runner is in the Idle state.
get isPreparing(): boolean
Returns true if the runner is in the Preparing state.
get isRunning(): boolean
Returns true if the runner is in the Running state.
get isStopping(): boolean
Returns true if the runner is in the Stopping state.
get isReleasing(): boolean
Returns true if the runner is in the Releasing state.
get isStopped(): boolean
Returns true if the runner is in the Stopped state.
get isFailed(): boolean
Returns true if the runner is in the Failed state.
get isError(): boolean
Returns true if the runner is in the Error state.
get isSucceeded(): boolean
Returns true if the runner is in the Succeeded state.
get isTimedOut(): boolean
Returns true if the runner is in the TimedOut state.
get isSkipped(): boolean
Returns true if the runner is in the Skipped state.
get isActive(): boolean
Returns true if the runner is currently active (preparing, running, or stopping).
get isFinished(): boolean
Returns true if the runner has reached a terminal state (succeeded, failed, stopped, timed out, skipped, or error).
async run(): Promise<void>
Starts the lifecycle of the runner
const runner = new MyWorker()
await runner.run()
async stop(reason?: string): Promise<void>
Attempts to stop the runner. The behavior depends on the current state.
const runner = new MyWorker()
const runPromise = runner.run()
// Stop after 2 seconds
setTimeout(() => runner.stop('User requested'), 2000)
await runPromise
skip(reason?: string): void
Skips the runner execution. Can only be called when the runner is in Idle state.
const runner = new MyWorker()
runner.skip('Not needed')
// Runner will be in 'Skipped' state
fail(reason: string | Error): void
Marks the runner as failed without going through the normal lifecycle. Can only be called when the runner is in Idle state. This is useful for pre-execution validation failures.
const runner = new MyWorker()
// Check some precondition
if (!isConfigurationValid()) {
runner.fail('Invalid configuration')
// Runner will be in 'Failed' state immediately
return
}
// Or fail with an Error object
try {
validateInput()
} catch (error) {
runner.fail(error)
return
}
await runner.run()
async waitForStatusLevel(status: Status): Promise<void>
Waits for the runner to reach a certain status level. Useful for waiting for completion regardless of the final state.
const runner = new MyWorker()
const runPromise = runner.run()
// Wait for any final status (succeeded, failed, stopped, etc.)
await runner.waitForStatusLevel(Status.Succeeded)
protected async internalPrepare(): Promise<void>
Override this method to implement your preparation logic. This runs before the main work.
protected async internalPrepare(): Promise<void> {
this.database = await connectToDatabase()
this.logger = new Logger('my-worker')
}
protected async internalRun(): Promise<string | undefined>
Required override. Implement your main work logic here.
undefined or don't return anything for successprotected async internalRun(): Promise<string | undefined> {
try {
await this.processData()
return undefined // Success
} catch (error) {
return `Processing failed: ${error.message}` // Failure
}
}
protected async internalRelease(): Promise<void>
Override this method to implement cleanup logic. This always runs after the work, regardless of success or failure.
protected async internalRelease(): Promise<void> {
await this.database?.close()
this.logger?.close()
}
protected async internalStop(): Promise<void>
Override this method to handle stop requests. This should interrupt the current work.
protected async internalStop(): Promise<void> {
this.shouldStop = true
await this.currentTask?.cancel()
}
protected async internalFinally(): Promise<void>
Override this method to implement cleanup logic that should always run after the runner reaches any finished state, regardless of success, failure, error, timeout, stop, or skip. This is similar to a finally block in try-catch-finally.
This method is called after the final status is set but before the final event is emitted, giving you access to the final state while ensuring cleanup always happens.
Important Notes:
Succeeded, Failed, Error, Stopped, TimedOut, and Skippederror event but won't prevent the final status event from being emittedprotected async internalFinally(): Promise<void> {
// Always runs regardless of outcome
await this.closeConnections()
await this.cleanupTempFiles()
// You have access to final state
this.logger?.info(`Runner finished with status: ${this.status}`)
if (this.error) {
this.logger?.error('Runner had error:', this.error)
}
if (this.measurement) {
this.logger?.info(`Total execution time: ${this.measurement.toString()}`)
}
}
Use Cases:
The BaseRunner emits events for each phase of the lifecycle:
preparing - Preparation phase startedprepared - Preparation phase completedrunning - Running phase startedreleasing - Release phase startedreleased - Release phase completedstopping - Stopping phase startedsucceeded - Runner completed successfullyfailed - Runner failed (returned failure reason or was marked as failed using fail())stopped - Runner was stoppedtimed-out - Runner exceeded timeoutskipped - Runner was skippederror - An error occurred during executionwarning - Warning event (e.g., invalid operations)All events include timing information and relevant payloads:
worker.on('succeeded', (event) => {
console.log('Duration:', event.measurement.toString())
console.log('Started at:', event.payload.startedAt)
console.log('Finished at:', event.payload.finishedAt)
})
worker.on('failed', (event) => {
console.log('Failure reason:', event.payload.reason)
console.log('Duration:', event.measurement.toString())
})
worker.on('stopped', (event) => {
console.log('Stop reason:', event.payload.reason)
console.log('Stopped at:', event.payload.stoppedAt)
})
worker.on('timed-out', (event) => {
console.log('Runner timed out at:', event.payload.timedOutAt)
// Note: Final status will be TimedOut, not Stopped
})
import { BaseRunner, Status } from '@universal-packages/base-runner'
import { readFile, writeFile } from 'fs/promises'
class FileProcessor extends BaseRunner {
private inputFile: string
private outputFile: string
private data: string[]
private processedData: string[] = []
private shouldStop: boolean = false
constructor(inputFile: string, outputFile: string, options?: BaseRunnerOptions) {
super(options)
this.inputFile = inputFile
this.outputFile = outputFile
}
protected async internalPrepare(): Promise<void> {
// Validate files exist, setup temp directories, etc.
this.data = (await readFile(this.inputFile, 'utf-8')).split('\n')
}
protected async internalRun(): Promise<string | undefined> {
try {
// Process the data
let processedData: string[] = []
for (const line of this.data) {
if (this.shouldStop) break
processedData.push(line.toUpperCase())
}
this.processedData = processedData
} catch (error) {
return `Processing failed: ${error.message}`
}
}
protected async internalRelease(): Promise<void> {
// Write output file if we have processed data
if (this.processedData) {
await writeFile(this.outputFile, this.processedData)
}
}
protected async internalStop(): Promise<void> {
// Cancel any ongoing operations
console.log('Stopping file processing...')
this.shouldStop = true
}
protected async internalFinally(): Promise<void> {
// Always cleanup, regardless of success or failure
console.log(`File processing completed with status: ${this.status}`)
if (this.measurement) {
console.log(`Total processing time: ${this.measurement.toString()}`)
}
// Any cleanup that should always happen
this.processedData = []
}
}
// Usage
const processor = new FileProcessor('input.txt', 'output.txt', { timeout: 10000 })
processor.on('succeeded', () => console.log('File processed successfully!'))
processor.on('failed', (event) => console.log('Processing failed:', event.payload.reason))
processor.on('timed-out', () => console.log('Processing timed out'))
await processor.run()
This library is developed in TypeScript and shipped fully typed.
The development of this library happens in the open on GitHub, and we are grateful to the community for contributing bugfixes and improvements. Read below to learn how you can take part in improving this library.
FAQs
Base class for runners.
We found that @universal-packages/base-runner demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
A safer, faster way to eliminate vulnerabilities without updating dependencies

Product
Reachability analysis for Ruby is now in beta, helping teams identify which vulnerabilities are truly exploitable in their applications.

Research
/Security News
Malicious npm packages use Adspect cloaking and fake CAPTCHAs to fingerprint visitors and redirect victims to crypto-themed scam sites.