Compositional Resource Management Pattern for Dependent Lifetimes in TypeScript

How to model dependent resource lifetimes compositionally (not imperatively) to avoid callback hell while ensuring proper cleanup?
I'm processing a chain of resources where each step depends on the previous output (e.g., src → processedMat → paddedMat →), and each resource requires cleanup. Using nested acquireUseRelease blocks quickly becomes unwieldy. What pattern or abstraction can I use to manage these dependent resources linearly (without deep nesting) while ensuring:

1. Automatic cleanup of intermediate resources
2. Composability for operations like cv.cvtColor or padImage that produce new resources (if they allocate internally).

Example of the problem:
// Current nested structure (simplified)
const paddedMat = yield* E.acquireUseRelease(
E.succeed(cv.matFromImageData(
    new ImageData(
    new Uint8ClampedArray(obj),
    imageBitmap.width,
    imageBitmap.height,
    ),
)),
(src) =>
    E.acquireUseRelease(E.succeed(new cv.Mat()), (processedMat) =>
    E.succeed(processedMat).pipe(
        E.tap((processedMat) =>
        // Convert color space from RGBA to RGB
        cv.cvtColor(src, processedMat, cv.COLOR_RGBA2RGB)
        ),
        E.andThen(padImage),
    ), (processedMat) => E.sync(processedMat.delete)),
(src) =>
    E.sync(src.delete),
);
// How to manage `paddedMat` cleanup without nesting further?


Desired Pseudo-Code
// How to model this with a clean, flat structure?
const paddedMat = yield* withResource(src, (src) => 
  withResource(processedMat, (processedMat) => 
    cv.cvtColor(src, processedMat, ...),
    padImage(processedMat) // Returns paddedMat
  )
);
// All resources (src, processedMat, paddedMat) are automatically tracked and released.
Was this page helpful?