Tokens
TinyDI identifies services with tokens, not with strings or classes. A token is created once, and used both to register a service and to resolve it.
Creating a token
interface IMailService {
send(to: string, body: string): Promise<void>;
}
const MailServiceToken = createToken<IMailService>('MailService');
createToken<T>(description) returns a Token<T>. The description string is a human-readable label used only in error messages — it does not need to be unique.
Why a symbol, not a string
Every token wraps a unique JavaScript symbol as its real identity. Two tokens created with the same description are still two distinct registrations:
const a = createToken<string>('Name');
const b = createToken<string>('Name');
a.symbol !== b.symbol; // true — distinct tokens, no collision
This rules out an entire category of bugs that string-keyed containers have to work around: two unrelated modules accidentally choosing the same string identifier for different services.
Type inference without generics
The service type T is carried by the token itself, through an optional __type?: T property that exists only at the type level — it is never assigned or read at runtime. Because of it, container.resolve(token) infers the exact return type automatically:
// No explicit generic needed — inferred as IMailService.
const mailService = container.resolve(MailServiceToken);
Type your token with an interface
Always parameterize createToken with an interface (IMailService), not a concrete class. Code that depends on MailServiceToken should never need to import the concrete implementation behind it.