You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This RFC proposes a standardized, modern, and ergonomic system for automatic disposal and lifecycle hooks in NexusDI. The goal is to ensure that resources (e.g., DB connections, file handles, sockets) are reliably cleaned up when a container or scope is destroyed, leveraging TypeScript 5.2+ disposal protocol and supporting both synchronous and (optionally) asynchronous cleanup.
Motivation
Resource safety: Prevent resource leaks by ensuring all disposable services are cleaned up.
Modern DX: Leverage TypeScript’s new using/await using keyword and [Symbol.dispose]()/[Symbol.asyncDispose]() protocol for idiomatic resource management.
Consistency: Provide a unified lifecycle for all services, regardless of lifetime (singleton, scoped, transient).
Scope isolation: Allow safe disposal of child/request containers without affecting parent/global services.
Proposed Features
1. Disposal Protocol Support
Container and services can implement [Symbol.dispose]() (sync) or [Symbol.asyncDispose]() (async).
The container will call these methods on all disposable services it owns when disposed.
2. Using the using/await using Keyword
If the container implements the disposal protocol, you can use:
await using requestContainer=container.createChildContainer();// ... use request-scoped services ...// When the block ends, requestContainer and its services are disposed automatically
This ensures automatic cleanup at the end of the block.
3. Scope of Disposal
Disposing a child/request container only disposes its own services.
Parent container instances (e.g., singletons/global providers) remain alive and untouched.
This enables safe, isolated cleanup for request/operation-scoped resources.
constrequestContainer=container.createChildContainer();// ... register request-scoped services ...awaitrequestContainer.dispose();// Only disposes services in this scope
4. Lifecycle Hooks
Support for additional lifecycle hooks (e.g., onInit, onDispose) for advanced use cases.
Hooks can be called by the container at appropriate times (e.g., after instantiation, before disposal).
5. Error Handling
If a service’s disposal throws, the container should aggregate errors and continue disposing other services.
Optionally, provide a way to listen for or log disposal errors.
Premature Disposal: Risks and Best Practices
What Happens If You Dispose Prematurely?
Singletons: If a singleton service is disposed before the application is truly finished with it, subsequent requests for that service may fail, throw errors, or operate on a "dead" resource (e.g., closed DB connection).
Scoped/Request Services: Disposing a request-scoped container too early (before the request is complete) can lead to errors, lost data, or incomplete operations.
Best Practice: Only dispose a container (or child container) when you are certain that all consumers are done with its services.
How This Helps in Application Shutdown
On application shutdown (e.g., SIGTERM), you can dispose the root container:
This ensures all resources (DB connections, file handles, etc.) are cleaned up gracefully, preventing leaks or corruption.
Per-Request Containers in Node.js: Value Beyond Thread Safety
Although Node.js is single-threaded and the singleton pattern is safe for sharing state between requests, per-request containers offer important benefits beyond thread safety:
1. Request-Scoped Dependencies
Some services should be unique per request (e.g., database transactions, request-specific caches, correlation IDs, authentication context).
Per-request containers allow you to:
Cleanly inject request-specific data/services.
Avoid accidental state sharing between requests.
2. Automatic Resource Cleanup
Ensures that any resources (e.g., file handles, DB sessions, temp files) created for a request are disposed of at the end of that request.
3. Isolation and Testability
Isolate dependencies for each request (no cross-request pollution).
Write tests that simulate a single request’s scope, with its own DI context.
4. Middleware/Framework Integration
Enables features like request-level logging, tracing, and context propagation.
5. No Significant Bloat
Creating a child container per request is lightweight (just a new object graph).
Only request-scoped services are instantiated per request; singletons remain shared and efficient.
When You Might Not Need Per-Request Containers
If your app only uses stateless, singleton services, and you don’t need request-specific resources, you can skip per-request containers for maximum simplicity.
Summary Table
Use Case
Singleton Only
Per-Request Container Needed?
Stateless services
✅
❌
Request-specific context/data
❌
✅
Per-request DB transactions
❌
✅
Request-level logging/tracing
❌
✅
Resource cleanup per request
❌
✅
Example: Per-Request Scope in Express
Pattern:
Create a child container for each request, register/request-scoped services, and dispose it at the end of the request.
importexpressfrom'express';constapp=express();constrootContainer=newNexus();app.use(async(req,res,next)=>{// Create a child container for this requestawait using requestContainer=rootContainer.createChildContainer();// Optionally register request-scoped services// requestContainer.set(...);// Attach to req for downstream handlersreq.container=requestContainer;try{awaitnext();}finally{// requestContainer is disposed automatically at the end of the block}});// In a route handler:app.get('/user/:id',async(req,res)=>{constuserService=req.container.get(UserService);constuser=awaituserService.getUser(req.params.id);res.json(user);});
Benefits:
Each request gets its own isolated scope for services.
All request-scoped resources are cleaned up automatically at the end of the request.
No risk of leaking resources between requests or into the global scope.
Example: Application Shutdown
process.on('SIGTERM',async()=>{awaitcontainer.dispose();// Disposes all services and resourcesprocess.exit(0);});
Example: Premature Disposal Pitfall
constsingletonService=container.get(SingletonService);awaitcontainer.dispose();// SingletonService is now disposed// Later in the app:container.get(SingletonService);// May throw or return a disposed instance!
Best Practice: Only dispose the root container when the application is truly shutting down.
Backward Compatibility
Existing services that implement dispose() or similar can be adapted to the new protocol.
The container should support both sync and async disposal for a smooth migration.
Open Questions
Should the container support both [Symbol.dispose]() and [Symbol.asyncDispose]() or only async?
Should lifecycle hooks be standardized (e.g., onInit, onDispose) or left to userland?
How should disposal errors be surfaced to the user?
Summary
This RFC brings NexusDI in line with modern TypeScript resource management, providing safe, ergonomic, and isolated disposal of resources using the new disposal protocol and using/await using keywords. It ensures that only the relevant scope is cleaned up, preventing accidental disposal of global/singleton services and enabling robust, leak-free applications.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Overview
This RFC proposes a standardized, modern, and ergonomic system for automatic disposal and lifecycle hooks in NexusDI. The goal is to ensure that resources (e.g., DB connections, file handles, sockets) are reliably cleaned up when a container or scope is destroyed, leveraging TypeScript 5.2+ disposal protocol and supporting both synchronous and (optionally) asynchronous cleanup.
Motivation
using/await usingkeyword and[Symbol.dispose]()/[Symbol.asyncDispose]()protocol for idiomatic resource management.Proposed Features
1. Disposal Protocol Support
[Symbol.dispose]()(sync) or[Symbol.asyncDispose]()(async).2. Using the
using/await usingKeyword3. Scope of Disposal
Disposing a child/request container only disposes its own services.
Parent container instances (e.g., singletons/global providers) remain alive and untouched.
This enables safe, isolated cleanup for request/operation-scoped resources.
4. Lifecycle Hooks
onInit,onDispose) for advanced use cases.5. Error Handling
Premature Disposal: Risks and Best Practices
What Happens If You Dispose Prematurely?
How This Helps in Application Shutdown
Per-Request Containers in Node.js: Value Beyond Thread Safety
Although Node.js is single-threaded and the singleton pattern is safe for sharing state between requests, per-request containers offer important benefits beyond thread safety:
1. Request-Scoped Dependencies
2. Automatic Resource Cleanup
3. Isolation and Testability
4. Middleware/Framework Integration
5. No Significant Bloat
When You Might Not Need Per-Request Containers
Summary Table
Example: Per-Request Scope in Express
Pattern:
Create a child container for each request, register/request-scoped services, and dispose it at the end of the request.
Benefits:
Example: Application Shutdown
Example: Premature Disposal Pitfall
Best Practice: Only dispose the root container when the application is truly shutting down.
Backward Compatibility
dispose()or similar can be adapted to the new protocol.Open Questions
[Symbol.dispose]()and[Symbol.asyncDispose]()or only async?onInit,onDispose) or left to userland?Summary
This RFC brings NexusDI in line with modern TypeScript resource management, providing safe, ergonomic, and isolated disposal of resources using the new disposal protocol and
using/await usingkeywords. It ensures that only the relevant scope is cleaned up, preventing accidental disposal of global/singleton services and enabling robust, leak-free applications.Beta Was this translation helpful? Give feedback.
All reactions