-
Notifications
You must be signed in to change notification settings - Fork 2
Doctor Vial
Class decorator that registers a class with the Doctor injector under a chosen lifecycle.
@Vial(mode: 'SINGLETON' | 'SCOPED' | 'TRANSIENT')
class MyService { ... }
// Long form, with optional factory:
@Vial({ mode: 'SCOPED', factory: () => new Db(env.URL) })
class Db { constructor(public url: string) {} }The decorator calls
Doctor.prescribe(MyService, modeOrOptions) at decoration
(module-load) time.
The decorator is a TC39 standard class decorator — no
experimentalDecorators, no metadata emission. It registers and
nothing else (Doctor's decorators record; they never supply values).
-
SINGLETON— one instance for the entire process. Constructed lazily on first resolution and cached on successful construction. -
SCOPED— one instance per named scope. Requires a scope at resolution time (Doctor.dispense(Type, scopeName), an explicitinject('Type', scopeName), or the ambient scope of aDoctor.resolve(Class, scope)operation). -
TRANSIENT— fresh instance every resolution.
When the class needs constructor arguments, register a factory
that returns the constructed instance. Doctor calls the factory
every time it would otherwise have called new Klass().
@Vial({
mode: 'SINGLETON',
factory: () => new Config(loadFromEnv()),
})
class Config {
constructor(public readonly opts: ConfigOpts) {}
}Anything the factory constructs wires itself the ordinary way — the
inject() field initializers run while new runs, inheriting the
driving operation's scope as their ambient fallback — so the factory
never has to perform injection itself.
@Vial('SINGLETON')
class Logger {
log(msg: string) { console.log(msg); }
}
@Vial('SCOPED')
class Database {
query<T>(sql: string): T[] { ... }
}
@Vial('TRANSIENT')
class RequestId {
public id = crypto.randomUUID();
}-
DuplicateVialError— when the same class is registered twice.
The order-service example shows each lifecycle plus the factory hook:
-
Logger.ts—@Vial('SINGLETON')with aninject()field dependency -
Connection.ts—@Vial('SCOPED') -
OrderRepository.ts—@Vial('TRANSIENT') -
wiring.ts—Doctor.prescribe(Class, { mode, factory })for a class that needs constructor arguments
@Vial always calls Doctor.prescribe on the global registry, never on
a container you happen to be inside — see containers
for what that means once child containers are involved.