
Product
Introducing Webhook Events for Alert Changes
Add real-time Socket webhook events to your workflows to automatically receive software supply chain alert changes in real time.
@unlib-js/depi
Advanced tools
A library for managing asynchronous disposal of objects with dependencies, with built-in support for InversifyJS.
Disposable and AsyncDisposable.@inject annotation of InversifyJS.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:
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
pnpm add @unlib-js/depi
yarn add @unlib-js/depi
npm install @unlib-js/depi
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
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
pnpm example basic
pnpm example inversify
For more examples, please refer to the tests, e.g., this test.
pnpm i && pnpm build
pnpm typedoc
pnpm test
See here.
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.
TODO
FAQs
Async disposal with dependencies
We found that @unlib-js/depi demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Add real-time Socket webhook events to your workflows to automatically receive software supply chain alert changes in real time.

Security News
ENISA has become a CVE Program Root, giving the EU a central authority for coordinating vulnerability reporting, disclosure, and cross-border response.

Product
Socket now scans OpenVSX extensions, giving teams early detection of risky behaviors, hidden capabilities, and supply chain threats in developer tools.