nestjs-aborter
Version:
Automatic request cancellation and timeout handling for NestJS applications
76 lines (75 loc) • 2.55 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AbortError = void 0;
exports.withAbort = withAbort;
/**
* Custom error thrown when an operation is aborted.
*/
class AbortError extends Error {
constructor(reason) {
super(reason || 'Operation aborted');
this.name = 'AbortError';
Error.captureStackTrace?.(this, AbortError);
}
}
exports.AbortError = AbortError;
/**
* Wraps a promise with abort signal and optional timeout support.
*
* @param promise - The promise to wrap
* @param signal - Optional AbortSignal to cancel the operation
* @param options - Optional timeout configuration
* @returns Promise that rejects with AbortError if aborted or timed out
* @throws Error if operation timeout exceeds request timeout
*/
function withAbort(promise, signal, options) {
// Early returns for simple cases
if (!signal && !options?.timeout)
return promise;
if (signal?.aborted) {
return Promise.reject(new AbortError(signal.reason ? String(signal.reason) : undefined));
}
validateTimeouts(signal, options);
const promises = [promise];
if (signal) {
promises.push(createAbortPromise(signal));
}
if (options?.timeout) {
promises.push(createTimeoutPromise(options));
}
return Promise.race(promises);
}
/**
* Validates that operation timeout doesn't exceed request timeout
*/
function validateTimeouts(signal, options) {
if (!options?.timeout || !signal?._requestTimeout) {
return;
}
const { timeout } = options;
const requestTimeout = signal._requestTimeout;
if (timeout > requestTimeout) {
throw new Error(`Operation timeout (${timeout}ms) cannot exceed request timeout (${requestTimeout}ms). ` +
`Use @RequestTimeout(${timeout}) decorator to increase the request timeout.`);
}
}
/**
* Creates a promise that rejects when the signal is aborted
*/
function createAbortPromise(signal) {
return new Promise((_, reject) => {
signal.addEventListener('abort', () => reject(new AbortError(signal.reason ? String(signal.reason) : undefined)), { once: true });
});
}
/**
* Creates a promise that rejects after a timeout
*/
function createTimeoutPromise(options) {
return new Promise((_, reject) => {
setTimeout(() => {
const message = options.timeoutMessage ||
`Operation timed out after ${options.timeout}ms`;
reject(new AbortError(message));
}, options.timeout);
});
}