npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@unlib-js/depi

v1.2.1

Published

Async disposal with dependencies

Readme

Depi: Async Disposal with Dependencies

A library for managing asynchronous disposal of objects with dependencies, with built-in support for InversifyJS.

Features

  • Asynchronous disposal of objects in dependency order.
  • Circular dependency detection (circular dependency is strongly discouraged).
  • Use existing interfaces Disposable and AsyncDisposable.
  • Built-in support of extracting dependencies from the @inject annotation of InversifyJS.
  • Also works without InversifyJS.

Motivation

In many cases, we need to dispose of objects in a specific order, especially when dealing with asynchronous operations. For example, say UserService depends on DatabaseService, UserService is only usable when DatabaseService is ready. Therefore, UserService should be disposed before DatabaseService. Otherwise, the state "UserService is still marked as available, but DatabaseService is destroyed" is possible, allowing other part of the program to use UserService when its dependency DatabaseService is no longer available.

Manually managing disposal order can be error-prone and difficult to maintain. While "disposing objects in the reverse order of their creation" sounds like exactly the job of dependency injection library, none of them in JS world provides this feature -- at least not the ones I know of. In the Java empire, this is part of the dependency injection features of Spring Framework.

Related discussions:

  • https://github.com/inversify/InversifyJS/issues/1606
  • https://github.com/nestjs/nest/issues/4599#issuecomment-2257682983

Installation

Setup GitHub Packages Registry

Firstly follow the GitHub Packages Registry documentation to set up authentication to the GitHub Package Registry.

Then, add the following line to your .npmrc file under your project root:

@unlib-js:registry=https://npm.pkg.github.com

Add Depi to Your Project

pNpm

pnpm add @unlib-js/depi

Yarn

yarn add @unlib-js/depi

npm

npm install @unlib-js/depi

Usage

Basic Usage

import { setTimeout } from 'node:timers/promises'
import { destroy } from '@unlib-js/depi'
import DependsOn from '@unlib-js/depi/decorators/DependsOn'
import Dependency from '@unlib-js/depi/decorators/Dependency'

class RemoteConfigService implements AsyncDisposable {
  public async [Symbol.asyncDispose]() {
    console.log('Destroyed DatabaseService')
  }
}
const remoteConfigService = new RemoteConfigService()

class DatabaseService implements AsyncDisposable {
  @Dependency()
  private readonly remoteConfigService = remoteConfigService

  public async [Symbol.asyncDispose]() {
    console.log('Destroyed DatabaseService')
  }
}
const databaseService = new DatabaseService()

@DependsOn(['databaseService'])
class UserService implements AsyncDisposable {
  private readonly databaseService = databaseService
  public async [Symbol.asyncDispose]() {
    await setTimeout(1000) // Simulate some async work
    console.log('Destroyed UserService')
  }
}
const userService = new UserService()
// Now `userService` depends on its property `databaseService`

await destroy({
  instances: [databaseSerivce, userService],
  onCircularDependencyDetected(stack, graph) {
    console.warn('Circular dependency detected:', stack)
  }
})

// Output:
// Destroyed UserService
// Destroyed DatabaseService
// Destroyed RemoteConfigService

With InversifyJS

import { setTimeout } from 'node:timers/promises'
import { Container, injectable, inject } from 'inversify'
import { destroy } from '@unlib-js/depi'
import DependsOn from '@unlib-js/depi/decorators/DependsOn'
import getDeps from '@unlib-js/depi/helpers/inversify/getDeps'

@injectable()
class RemoteConfigService implements AsyncDisposable {
  public async [Symbol.asyncDispose]() {
    console.log('Destroyed RemoteConfigService')
  }
}

@DependsOn(getDeps(DatabaseService))
@injectable()
class DatabaseService implements AsyncDisposable {
  public constructor(
    @inject(RemoteConfigService)
    private readonly remoteConfigService: RemoteConfigService,
  ) {}
  public async [Symbol.asyncDispose]() {
    await setTimeout(500) // Simulate some async work
    console.log('Destroyed DatabaseService')
  }
}

@DependsOn(getDeps(UserService))
@injectable()
class UserService {
  @inject(RemoteConfigService)
  private readonly remoteConfigService!: RemoteConfigService
  public constructor(
    @inject(DatabaseService)
    databaseService: DatabaseService,
  ) {
    // ...
  }

  public async [Symbol.asyncDispose]() {
    await setTimeout(1000) // Simulate some async work
    console.log('Destroyed UserService')
  }
}

const container = new Container()
container.bind(RemoteConfigService).toSelf().inSingletonScope()
container.bind(DatabaseService).toSelf().inSingletonScope()
container.bind(UserService).toSelf().inSingletonScope()

const userService = await container.getAsync(UserService)

// ...

// During application shutdown:
await destroy({
  instances: [
    container.get(RemoteConfigService),
    container.get(DatabaseService),
    container.get(UserService),
  ],
  onCircularDependencyDetected(stack, graph) {
    console.warn('Circular dependency detected:', stack)
  }
})

// Output:
// Destroyed UserService
// Destroyed DatabaseService
// Destroyed RemoteConfigService

Run Examples

pnpm example basic
pnpm example inversify

For more examples, please refer to the tests, e.g., this test.

Development

Build

pnpm i && pnpm build

Build Docs

pnpm typedoc

Test

pnpm test

API

See here.

How it Works

  1. Build a reversed dependency graph from the provided instances.
  2. Traverse the graph in leaf-first order. For each node, enqueue the following async disposal task:
  3. Wait for all dependants of the node, i.e., child nodes, to complete their disposal.
  4. Dispose the node itself.
  5. Wait for all queued tasks to complete.

Cicular Dependency Behavior

Circular dependencies are generally discouraged. However, if there is a loop, the library invokes the onCircularDependencyDetected callback with the stack of detected circular dependency (a loop path), and ignores the edge that caused the loop as if it were not there.

License

TODO