JavaScript
The package ships ESM and CommonJS builds, so it works from plain JavaScript on Node 20 and later. The API is identical; you lose the compile-time checks, so the runtime checks matter more.
Import
Section titled “Import”import { ServiceCollection, createToken } from '@ylcnfrht/ioc';const { ServiceCollection, createToken } = require('@ylcnfrht/ioc');A complete example
Section titled “A complete example”import { ServiceCollection, createToken } from '@ylcnfrht/ioc';
const Logger = createToken('Logger');
class ConsoleLogger { log(msg) { console.log(`[app] ${msg}`); }}
class UserRepository { constructor(logger) { this.logger = logger; } findAll() { this.logger.log('loading users'); return ['Ada', 'Grace']; }}
class UserService { constructor(repo, logger) { this.repo = repo; this.logger = logger; } list() { return this.repo.findAll(); }}
const services = new ServiceCollection();services.addSingleton(Logger, ConsoleLogger);services.addScoped(UserRepository, [Logger]);services.addScoped(UserService, [UserRepository, Logger]); // same order as the constructor
const provider = services.buildServiceProvider({ validateScopes: true, validateOnBuild: true });
const scope = provider.createScope();const users = await scope.getRequiredService(UserService);console.log(users.list());await scope.dispose();What still gets checked
Section titled “What still gets checked”Without TypeScript, nothing checks that each token matches its constructor parameter. The runtime still checks:
- The count. A constructor with more parameters than dependency tokens throws a
RegistrationErrorwhen you register it, naming the class and the counts. (It usesFunction.length, so parameters with default values and rest parameters don’t count.) - Missing registrations.
validateOnBuild: truelists every dependency that has no registration when the provider is built; without it, resolving throws aServiceNotFoundErrorwith the path. - Scopes.
validateScopes: truethrows aScopeErrorfor scoped services resolved from the root or captured by a singleton.
Turn both options on during development. In JavaScript they are your type checker.
Classes must use class syntax
Section titled “Classes must use class syntax”The container tells an implementation from a factory by syntax: only class declarations and expressions are
implementations, called with new. Any other function is a factory, called as factory(provider, key).
function LegacyService() { this.ready = true;}
// Treated as a factory: called without `new`, which fails.services.addSingleton('legacy', LegacyService);
// Fine: wrap it.services.addSingleton('legacy', () => new LegacyService());Registering a constructor function as its own token (services.addSingleton(LegacyService)) does call it with new,
because there is no second argument to interpret. Prefer class anyway, so both forms behave the same.
Bound classes are not classes either: Class.bind(null, …) returns a plain function, so the container calls it as a
factory and it throws Class constructor … cannot be invoked without 'new'. Pass the class itself and list its
dependencies, or write an arrow factory.
Tokens in JavaScript
Section titled “Tokens in JavaScript”createToken('Logger') works without a type argument; it gives a unique token object with a readable name in errors.
Strings and symbols work too:
const Logger = Symbol('Logger');services.addSingleton(Logger, ConsoleLogger);services.addSingleton('config', () => ({ port: 3000 }));