@energica-city/shared-amplify-utils
Version:
Shared utilities for AWS Amplify projects
296 lines • 10 kB
JavaScript
import { logger } from '../log';
/**
* Helper to process string messages with optional context.
*/
function processStringMessage(message, originalErrorOrContext) {
if (originalErrorOrContext === undefined) {
return {
finalMessage: message,
originalError: undefined,
errorContext: {},
};
}
if (originalErrorOrContext instanceof Error) {
return {
finalMessage: `${message}: ${originalErrorOrContext.message}`,
originalError: originalErrorOrContext,
errorContext: {
originalError: {
name: originalErrorOrContext.name,
message: originalErrorOrContext.message,
stack: originalErrorOrContext.stack,
},
},
};
}
if (typeof originalErrorOrContext === 'object' &&
originalErrorOrContext !== null) {
return {
finalMessage: message,
originalError: originalErrorOrContext,
errorContext: originalErrorOrContext,
};
}
return {
finalMessage: `${message}: ${String(originalErrorOrContext)}`,
originalError: originalErrorOrContext,
errorContext: { originalError: originalErrorOrContext },
};
}
/**
* Helper to process Error objects.
*/
function processErrorObject(error) {
return {
finalMessage: error.message,
originalError: error,
errorContext: {
originalError: {
name: error.name,
message: error.message,
stack: error.stack,
},
},
};
}
/**
* Helper to process arrays of errors.
*/
function processErrorArray(errors) {
const errorMessages = errors.map(err => {
if (err instanceof Error)
return err.message;
if (typeof err === 'object' && err !== null && 'message' in err) {
return String(err.message);
}
return String(err);
});
const finalMessage = errorMessages.length > 1
? `Multiple errors occurred: ${errorMessages.join('; ')}`
: errorMessages[0] || 'Unknown error occurred';
return {
finalMessage,
originalError: errors,
errorContext: { errors },
};
}
/**
* Helper to process unknown values.
*/
function processUnknownValue(unknownError) {
const errorObj = unknownError;
if (typeof errorObj === 'object' &&
errorObj !== null &&
'message' in errorObj) {
return {
finalMessage: String(errorObj.message),
originalError: unknownError,
errorContext: { originalError: unknownError },
};
}
return {
finalMessage: `Unexpected error: ${String(unknownError)}`,
originalError: unknownError,
errorContext: { originalError: unknownError },
};
}
/**
* Helper to create and configure the error object using ES2022 standards.
*/
function createErrorObject(finalMessage, originalError, errorContext) {
let error;
// Use modern Error constructor with cause (ES2022 standard) when originalError exists
if (originalError !== undefined) {
try {
// Modern approach - supported in Node.js 16.9+ and modern browsers
error = new Error(finalMessage, { cause: originalError });
}
catch {
// Fallback for older environments that don't support cause parameter
error = new Error(finalMessage);
Object.defineProperty(error, 'cause', {
value: originalError,
writable: true,
enumerable: false,
configurable: true,
});
}
// Backward compatibility: also set originalError property
if (!('originalError' in error)) {
Object.defineProperty(error, 'originalError', {
value: originalError,
writable: true,
enumerable: false,
configurable: true,
});
}
}
else {
error = new Error(finalMessage);
}
// Enhanced stack trace chaining for better debugging
if (originalError instanceof Error && originalError.stack) {
error.stack = `${error.stack}\n\nCaused by: ${originalError.stack}`;
}
// Always add a marker to identify errors from our library
Object.defineProperty(error, '__fromErrorLibrary', {
value: true,
writable: false,
enumerable: false,
configurable: false,
});
// Assign context properties as non-enumerable properties
if (errorContext && typeof errorContext === 'object') {
for (const [key, value] of Object.entries(errorContext)) {
// Skip system properties that are already handled
if (!['originalError', 'cause', 'stack', '__fromErrorLibrary'].includes(key)) {
Object.defineProperty(error, key, {
value,
writable: true,
enumerable: false,
configurable: true,
});
}
}
}
return error;
}
/**
* Enhanced error throwing function that ensures consistent error handling and logging
* across the application. It automatically logs errors with structured data and creates
* properly formatted Error objects with additional context.
*
* @param messageOrError - The primary error message (string) or Error object to throw
* @param originalErrorOrContext - Optional: original error object or additional context
* @throws {Error} Always throws a properly formatted Error with enhanced context
*
* @example
* ```typescript
* // Simple message
* throwError('Database connection failed');
*
* // Message with context
* throwError('User not found', { userId: '123', operation: 'getUser' });
*
* // Message with original error
* throwError('Failed to process request', originalError);
*
* // Error object (will extract message)
* throwError(new Error('Something went wrong'));
*
* // Array of errors (will combine messages)
* throwError([error1, error2, 'Additional info']);
* ```
*/
export function throwError(messageOrError, originalErrorOrContext) {
let result;
if (typeof messageOrError === 'string') {
result = processStringMessage(messageOrError, originalErrorOrContext);
}
else if (messageOrError instanceof Error) {
result = processErrorObject(messageOrError);
}
else if (Array.isArray(messageOrError)) {
result = processErrorArray(messageOrError);
}
else {
result = processUnknownValue(messageOrError);
}
const { finalMessage, originalError, errorContext } = result;
// Enhanced logging with structured data (CloudWatch handles timestamps)
logger.error(finalMessage, {
...errorContext,
errorType: typeof originalError,
stack: new Error().stack, // Capture current stack trace for debugging
});
// Create and throw the error
const error = createErrorObject(finalMessage, originalError, errorContext);
throw error;
}
/**
* Helper to extract message from object with specific property names
*/
function extractMessageFromObject(obj, propertyNames) {
for (const prop of propertyNames) {
if (prop in obj &&
typeof obj[prop] === 'string') {
const value = obj[prop];
return value !== undefined ? String(value) : null;
}
}
return null;
}
/**
* Utility function to safely extract an error message from an unknown error type.
* It handles various types of error objects, strings, and other values to produce a consistent
* string representation of the error, which is useful for displaying error messages to users.
*
* @param error - The error from which to extract the message. It can be of any type.
* @returns A string representing the error message.
* @example
* try {
* // some operation
* } catch (error) {
* const message = extractErrorMessage(error);
* console.log('Error occurred:', message);
* }
*/
export function extractErrorMessage(error) {
if (error instanceof Error) {
return error.message;
}
if (typeof error === 'string') {
return error;
}
if (Array.isArray(error)) {
if (error.length === 0) {
return '';
}
const messages = [];
function flattenAndExtract(item) {
if (Array.isArray(item)) {
item.forEach(flattenAndExtract);
}
else {
const message = extractErrorMessage(item);
if (message) {
messages.push(message);
}
}
}
error.forEach(flattenAndExtract);
return messages.join('; ');
}
if (typeof error === 'object' && error !== null) {
const message = extractMessageFromObject(error, [
'message',
'error',
'details',
'description',
]);
if (message) {
return message;
}
}
return String(error);
}
/**
* Utility function to create error context objects with proper filtering.
* This ensures consistent context structure while removing undefined values
* that could clutter logs or cause serialization issues.
*
* @param context - Raw context object that may contain undefined values
* @returns Filtered context object with undefined values removed
* @example
* const context = createErrorContext({
* userId: user?.id, // might be undefined
* operation: 'deleteUser', // always defined
* requestId: req.id // always defined
* });
* // Result: { operation: 'deleteUser', requestId: 'abc123' }
* // (userId omitted because it was undefined)
*/
export function createErrorContext(context) {
return Object.fromEntries(Object.entries(context).filter(([, value]) => value !== undefined));
}
//# sourceMappingURL=error.js.map