Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 1 addition & 5 deletions packages/errors/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,12 @@
"devDependencies": {
"@types/lodash": "4.17.24",
"@types/mocha": "10.0.10",
"@types/uuid": "11.0.0",
"c8": "11.0.0",
"esbuild": "0.27.3",
"lodash": "4.17.23",
"mocha": "11.7.5",
"ts-node": "10.9.2",
"typescript": "5.9.3"
},
"dependencies": {
"@stdlib/utils-copy": "0.2.3",
"uuid": "13.0.0"
}
"dependencies": {}
}
4 changes: 2 additions & 2 deletions packages/errors/src/GhostError.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {v1 as uuidv1} from 'uuid';
import {randomUUID} from 'crypto';
import {wrapStack} from './wrap-stack';

export interface GhostErrorOptions {
Expand Down Expand Up @@ -42,7 +42,7 @@ export class GhostError extends Error {
this.errorType = 'InternalServerError';
this.level = 'normal';
this.message = 'The server has encountered an error.';
this.id = uuidv1();
this.id = randomUUID();

/**
* custom overrides
Expand Down
31 changes: 25 additions & 6 deletions packages/errors/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,33 @@
import deepCopy from '@stdlib/utils-copy';
import {GhostError} from './GhostError';
import * as errors from './errors';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyObject = Record<string, any>

// structuredClone doesn't preserve custom properties on Error subclasses
// (see https://github.com/ungap/structured-clone/issues/12)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function deepCloneValue(value: any): any {
if (value === null || typeof value !== 'object') {
return value;
}
if (value instanceof Error) {
const clone = Object.create(Object.getPrototypeOf(value));
for (const key of Object.getOwnPropertyNames(value)) {
clone[key] = deepCloneValue((value as AnyObject)[key]);
}
return clone;
}
if (Array.isArray(value)) {
return value.map(deepCloneValue);
}
const clone: AnyObject = {};
for (const key of Object.keys(value)) {
clone[key] = deepCloneValue(value[key]);
}
return clone;
}

const errorsWithBase: Record<string, typeof GhostError> = {...errors, GhostError};

const _private = {
Expand Down Expand Up @@ -205,11 +228,7 @@ export function prepareStackForUser(error: Error): Error {
stackbits.splice(1, 0, `${error.context}`);
}

// @NOTE: would be a good idea to swap out the cloning implementation with native
// `structuredClone` one once we use Node v17 or higher. Before making an
// upgrade make sure structuredClone does a full copy of all properties
// present on a custom error (see issue: https://github.com/ungap/structured-clone/issues/12)
const errorClone = deepCopy(error);
const errorClone = deepCloneValue(error);
errorClone.stack = stackbits.join('\n');
return errorClone;
};
Expand Down
14 changes: 14 additions & 0 deletions packages/errors/test/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ describe('Error Utils', function () {
assert.notEqual(processedError.message, originalError.message);
});

it('deep clones array values in errorDetails', function () {
const items = [{id: 1}, {id: 2}];
const ghostError = new errors.ValidationError({
message: 'mistakes were made',
errorDetails: items
});

const processedError = utils.prepareStackForUser(ghostError);

assert.deepEqual(processedError.errorDetails, items);
items[0].id = 999;
assert.equal(processedError.errorDetails[0].id, 1);
});

it('Preserves the stack trace', function () {
const errorCreatingFunction = () => {
return new Error('Original error');
Expand Down