A set of common utilities for Node.js that I use in my projects.
Important
As of version 2.0.0, a majority of the utilities in this package have been extracted out into the new @depthbomb/common package. This package will continue to receive Node.js-only utilities.
Cross-platform config, cache, data, state, log, runtime, and temporary directories for Node applications.
import { ensureApplicationDirectories } from '@depthbomb/node-common/appdirs';
const directories = await ensureApplicationDirectories('my-tool');
await directories.config.joinpath('settings.json').writeJson({ enabled: true });Durable atomic file replacement, fingerprints, advisory compare-and-swap, and locked JSON updates.
import { updateJsonAtomic, writeFileAtomic } from '@depthbomb/node-common/atomic';
await writeFileAtomic('state.bin', Buffer.from([1, 2, 3]));
await updateJsonAtomic<{ count: number }>('counter.json', (current) => ({
count: (current?.count ?? 0) + 1,
}));Cancellation primitives for long-running async work, with AbortSignal interop.
import {
CancellationToken,
CancellationTokenSource,
CancellationTokenUtils,
TimeoutError,
} from '@depthbomb/node-common/cancellation';
const source = new CancellationTokenSource();
const controller = source.toAbortController();
const token = CancellationTokenUtils.any(
source.token,
CancellationToken.fromAbortSignal(controller.signal)
);
try {
const result = await CancellationTokenUtils.withTimeout(
token.wrap(() => fetch('https://example.com').then(r => r.text())),
500,
token,
{ timeoutError: true }
);
console.log(result);
} catch (error) {
if (error instanceof TimeoutError) {
console.error('Timed out');
}
}Advisory file locking for coordinating exclusive access to shared resources.
import { Lockfile } from '@depthbomb/node-common/lockfile';
const lock = await Lockfile.acquire('/tmp/my-resource.lock', {
retries: 10,
retryDelayMs: 50,
staleMs: 60_000,
});
try {
// exclusive work
} finally {
await lock.release();
}Application shutdown coordination with OS signal handling, cancellation, LIFO cleanup, and bounded shutdown time.
import { ApplicationLifecycle } from '@depthbomb/node-common/lifecycle';
const lifecycle = new ApplicationLifecycle();
lifecycle.onShutdown(async () => {
await server.close();
});
await lifecycle.run(async (token) => {
await runService(token);
});Path is a Node-first path and filesystem helper with async/sync methods for common file and directory workflows.
import { Path } from '@depthbomb/node-common/pathlib';
const root = Path.cwd().joinpath('tmp-demo');
await root.mkdir();
const file = root.joinpath('notes.txt');
await file.writeText('hello');
await file.appendText('\nworld');
for await (const line of file.readLines()) {
console.log(line);
}
const txtFiles = await root.globList('*.txt');
console.log(txtFiles.map((entry) => entry.name));
for await (const [current, dirs, files] of root.walk()) {
console.log(current.toString(), dirs.length, files.length);
}
const uri = file.toUri();
const fromUri = Path.fromUri(uri);
console.log(fromUri.equals(file)); // trueRuntime/platform detection helpers for Node/Bun environments.
import { getRuntimeInfo, assertRuntime } from '@depthbomb/node-common/platform';
assertRuntime(['node', 'bun']);
const info = getRuntimeInfo();
console.log(info.runtime); // node | bun | unknown
console.log(info.version); // runtime version when available
console.log(info.platform); // win32 | linux | darwin | ...
console.log(info.arch); // x64 | arm64 | ...Race-free TCP port and local socket reservations, plus cancellation-aware port readiness checks.
import { reserveTcpPort, waitForPort } from '@depthbomb/node-common/ports';
await using reservation = await reserveTcpPort();
console.log(`Reserved ${reservation.host}:${reservation.port}`);
await waitForPort(reservation.port);Process helpers for spawning commands, capturing output, executable lookup, and cancellation-aware execution.
import {
captureProcess,
execProcess,
whichSync,
} from '@depthbomb/node-common/process';
import { CancellationTokenSource } from '@depthbomb/node-common/cancellation';
const nodePath = whichSync('node');
console.log(nodePath);
const output = await captureProcess(process.execPath, ['-e', 'console.log("hello")']);
console.log(output.stdout.trim()); // hello
const source = new CancellationTokenSource();
const pending = execProcess(
process.execPath,
['-e', 'setTimeout(() => console.log("done"), 5000)'],
{ token: source.token }
);
source.cancel('stop');
await pending;Long-running and piped processes can use bounded capture, live line iteration, timeouts, and explicit tree termination:
import { spawnManaged } from '@depthbomb/node-common/process';
const managed = spawnManaged(process.execPath, ['worker.js'], {
maxOutputBytes: 10 * 1024 * 1024,
timeoutMs: 30_000,
});
for await (const line of managed.stdoutLines()) {
console.log(line);
}
const result = await managed.result;Bounded, cancellation-aware helpers for collecting streams, iterating lines, and running pipelines.
import { createReadStream } from 'node:fs';
import { collectStream, iterateLines } from '@depthbomb/node-common/streams';
const content = await collectStream(createReadStream('notes.txt'), {
maxBytes: 10 * 1024 * 1024,
});
for await (const line of iterateLines(createReadStream('notes.txt'))) {
console.log(line);
}Helpers for creating temporary directories/files with explicit cleanup or scoped automatic cleanup.
import { createTempDir, createTempFile } from '@depthbomb/node-common/temp';
const tempDir = await createTempDir({ prefix: 'my-app-' });
await tempDir.path.joinpath('data.txt').writeText('value');
await tempDir.cleanup();
const tempFile = await createTempFile({ suffix: '.json' });
await tempFile.use(async (file) => {
await file.writeText('{"ok":true}');
// file is removed automatically when this callback finishes
});Normalized, bounded filesystem change events exposed as a cancellation-aware async iterator.
import { watchPath } from '@depthbomb/node-common/watch';
for await (const change of watchPath('.', { recursive: true, debounceMs: 50 })) {
console.log(change.type, change.path.toString());
}