Providers
A provider is a recipe: a token to register under, and a way to produce the value. container.register accepts:
| Form | Spelling | Produced |
|---|---|---|
| Class | ApiClient | constructed on first resolve |
| Class with options | { useClass: ApiClient, ... } | constructed on first resolve |
| Class, explicit token | { provide, useClass } | constructed on first resolve |
| Value | { provide, useValue } | never - you hand the value over |
| Factory | { provide, useFactory } | the factory runs on first resolve |
| Alias | { provide, useExisting } | never - points at another token |
container.register([
ApiClient,
{ useClass: OrdersCache, scope: "transient" },
{ provide: OrdersApi, useClass: RestOrdersApi },
{ provide: API_URL, useValue: "https://api.acme.dev" },
{ provide: CLOCK, useFactory: () => () => new Date() },
{ provide: OrdersReader, useExisting: OrdersApi },
])There is no lazy here. The kernel constructs on first resolve anyway; eager construction is the module layer's init pass.
Options
provide- the token. Required on every object form except{ useClass }, which registers under the class itself.scope- on class and factory forms. A value is always a singleton; an alias has nothing to scope.multi- contribute to a collection under the token instead of claiming it.metadata- a frozen readonly bag carried to the entry's snapshot and events. The container never reads it.
| Scope | Instances |
|---|---|
singleton (default) | one per container that declares it; cached after first resolve |
transient | a fresh one per resolve |
request | one per resolution graph - shared by everything reached from a single resolve, gone when it returns |
Tokens
A token is a class, a string, or a symbol. A class is its own token - that is the shorthand. For everything else, mint one:
import { makeTokenizer } from "@remodulo/container"
const token = makeTokenizer("@acme/orders")
export const API_URL = token<string>("api-url")Every token is interned as Symbol.for("<namespace>:<name>") - the same name through the same namespace is always the same token, in any copy of the code sharing a process. Use your package name as the namespace.
Collections
A token is either single or multi, across the whole container chain. A single registration claims the token - a second claim throws. multi: true contributes - mixing the two throws at registration. resolve on a multi token throws; resolveAll on a single one throws.
A multi class provider needs an explicit provide.
Aliases
useExisting is a second name for an existing binding - it builds nothing, both names hand back the same instance.
- An alias may be a member of a collection; it may never target one.
- A dangling alias - target registered nowhere - throws from
resolve, naming the target.resolveOptionalandresolveOrtreat it as a miss. - Each hop resolves from the container that declared the alias, so shadowing reads as usual.