yieldless/resource
Native async disposal with acquire and release functions.
yieldless/resource turns a pair of acquire/release functions into an object that participates in native await using cleanup.
Exports
type ResourceAcquire<T> = () => PromiseLike<T> | Ttype ResourceRelease<T> = (resource: T) => PromiseLike<void> | voidinterface AsyncResource<T> extends AsyncDisposable { readonly value: T }acquireResource(acquire, release): Promise<AsyncResource<T>>withResource(acquire, release, use): Promise<Return>— functional scope; works withoutawait usingsupport
Example
import { acquireResource } from "yieldless/resource";
{
await using db = await acquireResource(connect, disconnect);
await db.value.query("select 1");
}When the runtime or TypeScript configuration does not support explicit resource management, or the resource's lifetime matches one callback, use withResource():
import { withResource } from "yieldless/resource";
const rows = await withResource(
connect,
disconnect,
(db) => db.query("select 1"),
);Where it fits
- Database or queue connections scoped to a request or job
- Temporary filesystem handles
- External clients that need explicit teardown
Important detail
The resource wrapper exposes the underlying value as .value. That keeps the disposable handle explicit and avoids pretending that current await using syntax can destructure directly into a tuple.
Good
Use acquireResource() when cleanup must happen even if the body throws.
{
await using temp = await acquireResource(
() => createTempDirectory(),
(directory) => rmSafe(directory, { recursive: true, force: true }),
);
await writeBuildArtifacts(temp.value);
}Keep the resource lifetime as small as the work that needs it.
Avoid
Do not acquire a resource and hope every return path remembers cleanup.
const client = await connect();
const result = await runJob(client);
await disconnect(client);
return result;Use await using when the TypeScript target and runtime support it. Otherwise use withResource() so release still runs on every path.