scope-ts
v0.1.3
Published
Hierarchical scoped resource management for TypeScript
Maintainers
Readme
scope-ts
Small, hierarchical resource cleanup for TypeScript.
scope-ts groups synchronous and asynchronous cleanup functions into named
scopes. Scopes can own child scopes, allowing an entire dependency tree to be
closed with one call.
Installation
npm install scope-tsbun add scope-tsBasic Usage
Register cleanup functions with scope.add(), then await scope.close() when
the resources are no longer needed.
import { Scope } from "scope-ts";
const app = Scope.create("app");
const connection = await openDatabaseConnection();
app.add(async () => {
await connection.close();
});
try {
await runApplication(connection);
} finally {
await app.close();
}Cleanup functions run sequentially in last-in, first-out (LIFO) order.
const scope = Scope.create("request");
scope.add(() => console.log("close database"));
scope.add(() => console.log("flush response"));
await scope.close();
// flush response
// close databaseChild Scopes
Use scope.child() to attach an existing scope. It returns the attached child,
which makes creating nested scopes concise. Child names must be unique within
their parent; adding a duplicate throws an error.
import { Scope } from "scope-ts";
const app = Scope.create("app");
const database = app.child(Scope.create("database"));
database.add(async () => {
await connection.close();
});
await app.close();Children close sequentially in reverse registration order before the parent's cleanup functions run. Each child recursively closes its own children and cleanup functions.
Close Hooks And Cancellation
Pass onClose to run a callback after a scope's children close and before its
cleanup functions run. The callback receives the scope name.
An optional AbortController is aborted as soon as closing begins, allowing
active work to observe cancellation before cleanup starts.
const controller = new AbortController();
const request = Scope.create("request", {
abortController: controller,
onClose: (name) => {
console.log(`${name} children closed`);
},
});
request.add(async () => {
await flushPendingWork();
});
await fetch(url, { signal: controller.signal });
await request.close();Cleanup and close-hook failures are logged, and closing continues through the remaining callbacks.
Scope State
const scope = Scope.create("worker");
scope.isOpen(); // true
const closing = scope.close();
scope.isClosing(); // true while asynchronous cleanup is running
await closing;
scope.isClosed(); // trueCleanup functions and child scopes cannot be added after closing begins.
API
Scope
| API | Description |
| ------------------------------ | ------------------------------------------------------------------ |
| Scope.create(name, options?) | Create a named scope |
| options.onClose | Callback run after children close and before local cleanup |
| options.abortController | Controller aborted when closing begins |
| scope.name | The scope's name |
| scope.add(cleanup) | Register a sync or async cleanup function |
| scope.child(childScope) | Attach and return a uniquely named child; throws on duplicate name |
| scope.close() | Close children, run the close hook, then run cleanup functions |
| scope.isOpen() | Whether the scope accepts new cleanup functions and children |
| scope.isClosing() | Whether closing is currently in progress |
| scope.isClosed() | Whether closing has completed |
Development
bun install
bun test
bun run check
bun run build