2.0 rewrites the resolution engine of @nodelibraries/ioc (last 1.x release: 1.3.12). The shape of the API is the same:
ServiceCollection, ServiceProvider, the three lifetimes, keyed services, tryAdd*, addValue, scopes, lifecycle
hooks and the dependency-tree helpers. Most apps upgrade by bumping the version and fixing what the compiler and the new
registration-time checks point out.
Resolves token, throwing ServiceNotFoundError if it is not registered.
getRequiredService(
constIUserService:InjectionToken<IUserService>
IUserService);
constusers:IUserService
Symbol and string tokens with explicit type arguments still work, so you can migrate tokens gradually. Explicit
addScoped<IUserService>(...) compiles too, but skips the dependency type check: drop the type argument to get it.
Upgrade.npm install @nodelibraries/ioc@^2. The import path does not change.
Replace removeAll(token) with remove(token). Note that remove now also drops the token’s keyed
registrations.
Run the app or the test suite once. Registrations that pass fewer dependency tokens than the constructor has
parameters now throw a RegistrationError while registering, not later when resolving. Add the missing tokens.
Check functions passed as implementations. If you passed an ES5-style constructor function (or a class compiled to
ES5) as the implementation, it is now called as a factory. Use class syntax or wrap it: () => new Legacy().
Check keyed services. Code that registered addKeyedSingleton(Cache, RedisCache, 'redis') and then called
getService(Cache) used to get the keyed instance. Now it gets undefined (or a ServiceNotFoundError from
getRequiredService). Register a plain default too, or resolve with the key. Keyed classes with constructor
parameters can now take a dependency array instead of a factory.
Check replace. It used to turn every replaced service into a singleton. It now keeps the old lifetime, which is
probably what you wanted; if you relied on the old behaviour, remove then addSingleton.
Drop await from isService. It is synchronous now. (await on a boolean still works, so this is cleanup.)
Update error handling. Replace message matching (/No provider found/) with instanceof ServiceNotFoundError,
instanceof ScopeError and so on. See Errors.
Review disposal.dispose() now rejects when an onDestroy throws (after disposing everything else) instead of
logging. Instances with [Symbol.dispose] / [Symbol.asyncDispose] are now disposed too. Values registered with
addValue are never disposed.
Optional: move to typed tokens. Replace Symbol('X') + getRequiredService<X>(token) with createToken<X>('X')
and remove the type arguments, so dependencies are type-checked.
Concurrent resolutions (Promise.all) of a singleton or scoped service share one instance; transients stay distinct.
Circular dependencies resolve through a proxy that keeps identity
(a.b.a === a) and works with #private fields; using a partner inside a constructor throws a
CircularDependencyError with the path. Cycles through factories and between concurrent resolutions are detected too.
Factories receive a provider that carries the resolution chain, so scope validation and cycle detection work through
them. See Factories.
onInit runs for class registrations only; factories initialize their own instance.
buildServiceProvider snapshots the registrations: changing the collection later doesn’t affect built providers.