expunge
v0.1.1
Published
Self-cleaning temp files and directories — scope-bound via `using`, swept on crash and signal, zero dependencies.
Maintainers
Readme
expunge
Self-cleaning temp files and directories — scope-bound via await using, swept on crash and signal, zero dependencies.
The problem
Tests and CLIs create temp dirs and forget them. A thrown error skips the cleanup line, and CI accumulates gigabytes of /tmp cruft over weeks. Cleanup-on-crash is exactly when hand-rolled finally blocks fail. Existing solutions like tmp and tmp-promise predate TypeScript 5.2's using/Symbol.asyncDispose and have unreliable exit-handler cleanup across signals.
Install
npm install expunge
# or
pnpm add expunge
# or
yarn add expungeUse
import expunge from "expunge";
// Scope-bound cleanup - auto-removed at end of scope
await using dir = await expunge.dir();
await fs.writeFile(dir.path + "/data", "content");
// dir is automatically removed when scope exits
// Manual cleanup with keep:true
const persist = await expunge.file({ postfix: ".png", keep: true });
await processImage(persist.path);
await persist.remove(); // you own cleanup for keep:true
// Sweep everything (except keep:true temps)
await expunge.all();API
interface TempOptions {
prefix?: string; // default "expunge-"
postfix?: string; // e.g. ".png"
dir?: string; // parent dir, default os.tmpdir()
keep?: boolean; // exclude from auto-sweep
}
interface Temp {
readonly path: string;
readonly keep: boolean;
readonly removed: boolean;
remove(): Promise<void>;
removeSync(): void;
[Symbol.asyncDispose]: () => Promise<void>;
}
interface Expunge {
dir(opts?: TempOptions): Promise<Temp>;
file(opts?: TempOptions): Promise<Temp>;
all(): Promise<void>;
readonly size: number;
cleanupHandlers(): void;
}
function createExpunge(): Expunge;
const expunge: Expunge;
export default expunge;Behavior:
dir()/file()create with crypto-random names (base32) to avoid collisions- Dirs: mode 0o700, files: mode 0o600
remove()is idempotent and recursive for dirs- Registry tracks non-keep temps and installs exit/signal handlers lazily
keep:trueexcludes from auto-sweep (caller owns cleanup)- Signal handlers (SIGINT/SIGTERM) call
all()then re-raise
Non-goals: expunge does NOT provide content writing helpers, template/scaffolding, in-memory FS, file watching, or cross-process locking. Compose with other libraries for those needs.
TypeScript
Requires TypeScript 5.2+ for Symbol.asyncDispose support and Node 18+ for node:fs/promises.
import expunge, { createExpunge } from "expunge";
// Shared default instance
await using dir = await expunge.dir();
// Isolated registry (useful for tests)
const registry = createExpunge();
const file = await registry.file({ postfix: ".log" });
await file.remove();Related Packages
Caching & Concurrency:
- @azghr/filterkit — Framework-agnostic, type-safe filtering for TypeScript
- @azghr/singlet — Deduplicate concurrent async calls
- staleness — Stale-while-revalidate caching for async functions
Text Processing:
- @azghr/shorn — Truncate strings by byte budget without breaking graphemes
- seriatim — Sequential processing utilities
HTTP & Network:
- forbear — Read server rate-limit instructions from HTTP responses
- forestall — Delay execution until a condition is met
- obviate — Render operations unnecessary through caching
System & Process:
- quiesce — Ordered, timeboxed graceful shutdown for Node
- sortition — Deterministic percentage rollouts and A/B bucketing
- stanch — Stop flows or operations based on conditions
Utilities:
- occlude — Hide or mask data and functionality
- placemark — Geographic location and mapping utilities
- specie — Currency and financial calculations
License
MIT
