@hardlydifficult/teardown
v1.0.2
Published
Idempotent resource teardown with signal trapping. Register cleanup functions once at resource creation time — all exit paths call a single `run()`.
Readme
@hardlydifficult/teardown
Idempotent resource teardown with signal trapping. Register cleanup functions once at resource creation time — all exit paths call a single run().
Installation
npm install @hardlydifficult/teardownQuick Start
import { createTeardown } from "@hardlydifficult/teardown";
const teardown = createTeardown();
teardown.add(() => server.stop());
teardown.add(() => db.close());
teardown.trapSignals();
// Any manual exit path:
await teardown.run();Creating a Teardown Registry
Use createTeardown() to create a new teardown registry that manages cleanup functions.
import { createTeardown } from "@hardlydifficult/teardown";
const teardown = createTeardown();Registering Cleanup Functions
Call add() to register a cleanup function. Functions run in LIFO order (last added runs first). The add() method returns an unregister function for selective cleanup.
const teardown = createTeardown();
// Register sync cleanup
teardown.add(() => {
console.log("Closing server");
server.stop();
});
// Register async cleanup
teardown.add(async () => {
console.log("Closing database");
await db.close();
});
// Unregister a specific function
const unregister = teardown.add(() => {
console.log("This won't run");
});
unregister();
await teardown.run();
// Output:
// Closing database
// Closing serverRunning Teardown
Call run() to execute all registered cleanup functions in LIFO order. The method is idempotent — subsequent calls are no-ops.
const teardown = createTeardown();
teardown.add(() => console.log("cleanup"));
await teardown.run();
// Output: cleanup
await teardown.run();
// No output (idempotent)Signal Trapping
Call trapSignals() to automatically run teardown when the process receives SIGTERM or SIGINT signals. Returns an untrap function to remove signal handlers.
const teardown = createTeardown();
teardown.add(() => server.stop());
// Wire SIGTERM/SIGINT to run() then process.exit(0)
const untrap = teardown.trapSignals();
// Later, if needed:
untrap();Error Resilience
Each teardown function is wrapped in try/catch. Errors don't block remaining teardowns — all functions run regardless of failures.
const teardown = createTeardown();
teardown.add(() => {
throw new Error("First cleanup fails");
});
teardown.add(() => {
console.log("Second cleanup still runs");
});
await teardown.run();
// Output: Second cleanup still runsBehavior Reference
| Behavior | Details |
|----------|---------|
| LIFO order | Teardowns run in reverse registration order (last added runs first) |
| Idempotent | run() executes once; subsequent calls are no-ops |
| Error resilient | Each function is wrapped in try/catch; failures don't block remaining teardowns |
| Safe unregister | add() returns an unregister function; safe to call multiple times |
| Post-run add | add() after run() is a silent no-op |
| Duplicate safe | Same function added twice runs twice; unregister only removes its own registration |
