# Resource management

For automatic cleanup of resources

Resources like database connections, file handles, and locks need cleanup. Traditional approaches are error-prone:

```ts
// 🚨 Manual cleanup is easy to forget
const conn = openConnection();
doWork(conn);
conn.close(); // What if doWork throws?
```

```ts
// 🚨 try/finally is verbose and doesn't compose
const conn = openConnection();
try {
  doWork(conn);
} finally {
  conn.close();
}
```

The [`using`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using) declaration is a new JavaScript feature that automatically disposes resources when they go out of scope:

```ts
const process = () => {
  using conn = openConnection();
  doWork(conn);
}; // conn is automatically disposed here
```

This works even if `doWork` throws—disposal is guaranteed.

## Disposable resources

A resource is disposable if it has a `[Symbol.dispose]` method:

```ts
interface Disposable {
  [Symbol.dispose](): void;
}
```

For async cleanup, use `[Symbol.asyncDispose]` with `await using`:

```ts
interface AsyncDisposable {
  [Symbol.asyncDispose](): PromiseLike<void>;
}
```

## Block scopes

Use block scopes to control exactly when resources are disposed:

```ts
const createLock = (name: string): Disposable => ({
  [Symbol.dispose]: () => {
    console.log(`unlock:${name}`);
  },
});

const process = () => {
  console.log("start");

  {
    using lock = createLock("a");
    console.log("critical-section-a");
  } // lock "a" released here

  console.log("between");

  {
    using lock = createLock("b");
    console.log("critical-section-b");
  } // lock "b" released here

  console.log("end");
};

// Output:
// "start"
// "critical-section-a"
// "unlock:a"
// "between"
// "critical-section-b"
// "unlock:b"
// "end"
```

## Combining with Result

`Result` and `Disposable` are orthogonal:

- **Result** answers: "Did the operation succeed?"
- **Disposable** answers: "When do we clean up resources?"

Early returns from `Result` checks don't bypass `using`—disposal is guaranteed on any exit path (see below).

## DisposableStack

When acquiring multiple resources, use [`DisposableStack`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DisposableStack) to ensure all are cleaned up:

```ts
const processResources = (): Result<string, CreateResourceError> => {
  using disposer = new DisposableStack();

  const db = createResource("db");
  if (!db.ok) return db; // disposer disposes nothing yet

  disposer.use(db.value);

  const file = createResource("file");
  if (!file.ok) return file; // disposer disposes db

  disposer.use(file.value);

  return ok("processed");
}; // disposer disposes file, then db (reverse order)
```

The pattern is simple:

1. Create a `DisposableStack` with `using`
2. Try to create a resource (returns `Result`)
3. If failed, return early—the disposer disposes what's been acquired
4. If succeeded, add to the disposer with `disposer.use()`
5. Repeat for additional resources

For async resources, use [`AsyncDisposableStack`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncDisposableStack) with `await using`.

API overview:

- `disposer.use(resource)` — adds a disposable resource
- `disposer.defer(fn)` — adds a cleanup function (like Go's `defer`)
- `disposer.adopt(value, cleanup)` — wraps a non-disposable value with cleanup
- `disposer.move()` — transfers ownership to caller

## Ownership transfer

When a factory function creates resources for use elsewhere, use `move()` to transfer ownership:

```ts
interface OpenFiles extends Disposable {
  readonly handles: ReadonlyArray<FileHandle>;
}

const openFiles = (
  paths: ReadonlyArray<string>,
): Result<OpenFiles, OpenFileError> => {
  using disposer = new DisposableStack();

  const handles: Array<FileHandle> = [];
  for (const path of paths) {
    const file = open(path);
    if (!file.ok) return file; // Error: disposer cleans up opened files

    disposer.use(file.value);
    handles.push(file.value);
  }

  // Success: transfer ownership to caller
  const disposables = disposer.move();
  return ok({
    handles,
    [Symbol.dispose]: () => disposables.dispose(),
  });
};

const processFiles = (): Result<void, MyError> => {
  const result = openFiles(["a.txt", "b.txt", "c.txt"]);
  if (!result.ok) return result;

  using files = result.value;

  // ... use files.handles ...

  return ok();
}; // files cleaned up here
```

Without `move()`, the disposer would dispose files when `openFiles` returns, even on success.

This naming mirrors the two roles:

- `disposer` while you are still registering resources
- `disposables` after ownership has been moved to the returned object

## Lifetime guards

Not every `DisposableStack` usage owns an external resource such as a file handle or connection.

Some helpers use a stack only to model lifetime and make `assertNotDisposed` available for synchronous methods that must fail fast after disposal.

This mirrors what C# does by default with [`ObjectDisposedException`](https://learn.microsoft.com/en-us/dotnet/api/system.objectdisposedexception?view=net-10.0): using a disposed object is a programmer error and should fail fast. In Evolu we enforce that correctness constraint by convention with explicit `assertNotDisposed` guards.

The same rule applies to `AsyncDisposable` helpers that also expose synchronous methods: guard those synchronous methods with `assertNotDisposed` on the moved `AsyncDisposableStack`.

Async methods are different. For reusable async resources, create one internal `Run` with `run.create()` and use that `Run` for the resource's async operations. Disposing the resource then disposes that internal `Run`, which aborts in-flight child tasks, waits for them to settle, and rejects later calls through it.

If aborting one of those async operations would indicate a lifecycle bug rather than ordinary control flow, assert that explicitly with [`assertNotAborted`](https://evolu.dev/docs/api-reference/common/Assert/functions/assertNotAborted).

```ts
interface RefCount extends Disposable {
  readonly increment: () => number;
  readonly getCount: () => number;
}

const createRefCount = (): RefCount => {
  using disposer = new DisposableStack();
  let count = 0;
  const disposables = disposer.move();

  return {
    increment: () => {
      assertNotDisposed(disposables);
      count += 1;
      return count;
    },

    getCount: () => {
      assertNotDisposed(disposables);
      return count;
    },

    [Symbol.dispose]: () => disposables.dispose(),
  };
};
```

This pattern is useful when the helper owns mutable state rather than an external resource, but use-after-dispose is still a programmer error.

## Anti-patterns

Avoid writing disposal logic by hand when a stack can own it for you.

### Manual disposed flags

```ts
// Avoid
interface Counter extends Disposable {
  readonly next: () => number;
}

const createCounter = (): Counter => {
  let isDisposed = false;
  let value = 0;

  return {
    next: () => {
      if (isDisposed) throw new Error("disposed");
      value += 1;
      return value;
    },
    [Symbol.dispose]: () => {
      if (isDisposed) return;
      isDisposed = true;
    },
  };
};
```

Prefer a stack plus `assertNotDisposed`.

### Manual disposal arrays or loops

```ts
// Avoid
for (const resource of resources) {
  resource[Symbol.dispose]();
}
```

If one dispose throws, later resources are skipped. A `DisposableStack` preserves reverse-order disposal and continues cleanup even when an earlier dispose fails.

```ts
// Use
using disposer = new DisposableStack();
for (const resource of resources) {
  disposer.use(resource);
}
```

## Ready to use

[TypeScript 5.2+](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management) implements the `using` keyword, and Evolu polyfills runtime resource management for environments that still need it (Safari and React Native), see [polyfills setup](https://evolu.dev/docs/library#polyfills).

## Learn more

- [MDN: Resource management](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Resource_management)
- [MDN: using statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using)
- [MDN: DisposableStack](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DisposableStack)
- [MDN: AsyncDisposableStack](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncDisposableStack)
- [`Result.test.ts`](https://github.com/evoluhq/evolu/blob/main/test/unit/vitest/common/Result.test.ts) for comprehensive usage patterns
- [Resources](https://evolu.dev/docs/api-reference/common/Resources) for reference-counted shared resources with delayed disposal