@darkobits/adeiu
Version:
Yet another POSIX signal handler.
103 lines (100 loc) • 3.46 kB
JavaScript
import { constants } from "node:os";
const DEFAULT_SIGNALS = [
"SIGINT",
"SIGQUIT",
"SIGTERM",
// Signal sent by nodemon to child processes when it needs to restart them.
"SIGUSR2"
];
const signalHandlers = /* @__PURE__ */ new Map();
const signalsHandled = {};
async function rejectAfter(timeout, reason) {
return new Promise((resolve, reject) => {
setTimeout(() => reject(reason), timeout);
});
}
function signalToExitCode(signal) {
const signalNum = constants.signals[signal];
return signalNum ? 128 + signalNum : void 0;
}
function getHandlersForSignal(signal) {
if (!signalHandlers.has(signal))
signalHandlers.set(signal, /* @__PURE__ */ new Map());
return signalHandlers.get(signal);
}
function logErrors(signal, errors) {
const supportsColor = Boolean(process.stdout.isTTY && !process.env.NO_COLOR || process.env.FORCE_COLOR);
const red = (text) => supportsColor ? `\x1B[31m${text}\x1B[0m` : text;
process.stderr.write(red(`Encountered the following errors while responding to ${signal}:
`));
errors.forEach(([error, handler]) => {
var _a;
const handlerName = handler.name || "anonymous";
const stackLines = ((_a = error.stack) == null ? void 0 : _a.split("\n")) ?? [error.message];
process.stderr.write(`${stackLines.map((line, lineNumber) => {
return lineNumber === 0 ? red(`• ${line} (via handler: ${handlerName})`) : line;
}).join("\n")}
`);
});
}
async function handleSignal(signal) {
if (signalsHandled[signal])
return;
signalsHandled[signal] = true;
const handlersForSignal = getHandlersForSignal(signal);
if (handlersForSignal.size === 0)
throw new Error(`Unexpected error: Expected at least 1 handler for signal ${signal}, got 0.`);
const errors = [];
await Promise.allSettled([...handlersForSignal.entries()].map(async ([handler, options]) => {
try {
if (options.timeout) {
await Promise.race([
handler(signal),
rejectAfter(options.timeout, new Error(`Operation timed-out after ${options.timeout}ms.`))
]);
} else {
await handler(signal);
}
} catch (err) {
errors.push([err, handler]);
}
}));
if (errors.length > 0) {
logErrors(signal, errors);
process.exit(signalToExitCode(signal) ?? 1);
} else {
process.kill(process.pid, signal);
}
}
function adeiu(handler, options = {}) {
const { signals = DEFAULT_SIGNALS, timeout } = options;
signals.forEach((signal) => {
if (typeof signal !== "string")
throw new TypeError(`Expected signal to be of type "string", got "${typeof signal}".`);
if (!signal.startsWith("SIG"))
throw new Error(`Invalid signal: ${signal}`);
});
if (typeof timeout !== "number" && timeout !== void 0)
throw new TypeError(`Expected type of "timeout" to be "number" or "undefined", got "${typeof timeout}".`);
signals.forEach((signal) => {
const handlersForSignal = getHandlersForSignal(signal);
if (handlersForSignal.size === 0) {
process.prependListener(signal, handleSignal);
}
handlersForSignal.set(handler, options);
});
return () => {
for (const signal of signals) {
const handlersForSignal = getHandlersForSignal(signal);
handlersForSignal.delete(handler);
if (handlersForSignal.size === 0) {
process.removeListener(signal, handleSignal);
}
}
};
}
export {
DEFAULT_SIGNALS,
adeiu as default
};
//# sourceMappingURL=adeiu.js.map