Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 28 additions & 15 deletions src/WorkerError.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
const stack = (err, worker, workerId) => {
const originError = (err.stack || '')
// Utility function to safely build a merged stack trace
const buildStackTrace = (err, workerStack = '', workerId = 'unknown') => {
const originStackLines = (err?.stack || '')
.split('\n')
.filter((line) => line.trim().startsWith('at'));

const workerError = worker
const workerStackLines = (workerStack || '')
.split('\n')
.filter((line) => line.trim().startsWith('at'));

const diff = workerError
.slice(0, workerError.length - originError.length)
// Compute the difference between the worker's stack and the error's stack
const extraWorkerLines = workerStackLines.slice(
0,
Math.max(0, workerStackLines.length - originStackLines.length)
);

// Build a readable, merged stack trace
const mergedStack = [
`Thread Loader (Worker ${workerId})`,
err?.message || 'Unknown error',
extraWorkerLines.join('\n'),
...originStackLines
]
.filter(Boolean)
.join('\n');

originError.unshift(diff);
originError.unshift(err.message);
originError.unshift(`Thread Loader (Worker ${workerId})`);

return originError.join('\n');
return mergedStack;
};

class WorkerError extends Error {
constructor(err, workerId) {
super(err);
this.name = err.name;
this.message = err.message;
// Pass a safe message to the base Error class
super(err?.message || 'Unknown worker error');

this.name = err?.name || 'WorkerError';
this.originalError = err;

Error.captureStackTrace(this, this.constructor);
// Capture the current stack before modifying it
Error.captureStackTrace?.(this, WorkerError);

this.stack = stack(err, this.stack, workerId);
// Replace the default stack with a combined one
this.stack = buildStackTrace(err, this.stack, workerId);
}
}

Expand Down
52 changes: 52 additions & 0 deletions test/WorkerError.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import WorkerError from '../src/WorkerError.js';

describe('WorkerError', () => {
it('should create a WorkerError with merged stack trace', () => {
const originalError = new Error('Something went wrong');
originalError.stack = `Error: Something went wrong
at doSomething (worker.js:10:5)
at runWorker (thread.js:20:10)`;

const workerError = new WorkerError(originalError, 3);

expect(workerError).toBeInstanceOf(Error);
expect(workerError.name).toBe('Error');
expect(workerError.message).toBe('Something went wrong');
expect(workerError.stack).toContain('Thread Loader (Worker 3)');
expect(workerError.stack).toContain('doSomething');
});

it('should handle missing err.stack gracefully', () => {
const err = { message: 'No stack here' };
const workerError = new WorkerError(err, 1);

expect(workerError.stack).toContain('Thread Loader (Worker 1)');
expect(workerError.message).toBe('No stack here');
});

it('should handle undefined workerId safely', () => {
const err = new Error('Worker ID missing');
const workerError = new WorkerError(err);

expect(workerError.stack).toContain('Thread Loader (Worker unknown)');
expect(workerError.message).toBe('Worker ID missing');
});

it('should preserve originalError reference', () => {
const err = new Error('Check original');
const workerError = new WorkerError(err, 2);

expect(workerError.originalError).toBe(err);
});

it('should include both worker and origin stack traces when available', () => {
const err = new Error('Test merge');
err.stack = `Error: Test merge
at originFunction (origin.js:5:3)`;

const workerError = new WorkerError(err, 4);

expect(workerError.stack).toMatch(/Thread Loader \(Worker 4\)/);
expect(workerError.stack).toMatch(/originFunction/);
});
});