Observing Events
Problem
You need visibility into what the container is doing at runtime — which providers are registered, when they are resolved, and when the container is torn down. This is useful for logging, performance metrics, and debugging unexpected resolution behaviour.
Solution
Use container.on(listener) to subscribe to lifecycle events. The listener receives events synchronously as they happen. Call the returned unsubscribe function to stop observing.
Logging all container activity
ts
import { createContainer, token } from '@vielzeug/conduit';
const Logger = token<{ log(msg: string): void }>('Logger');
const Config = token<{ apiUrl: string }>('Config');
const container = createContainer({ name: 'app' });
const unsubscribe = container.on((event) => {
switch (event.type) {
case 'register':
console.log(`[di:register] ${event.description} (${event.kind})`);
break;
case 'resolve':
console.log(`[di:resolve] ${event.description}`);
break;
case 'dispose':
console.log('[di:dispose] container disposed');
break;
}
});
container.value(Logger, console);
// → [di:register] Logger (value)
container.factory(Config, async () => ({ apiUrl: 'https://api.example.com' }));
// → [di:register] Config (factory)
await container.resolve(Config);
// → [di:resolve] Config
await container.dispose();
// → [di:dispose] container disposed
unsubscribe(); // stop observingCollecting metrics
ts
import { createContainer, token } from '@vielzeug/conduit';
const resolveCounts = new Map<string, number>();
const container = createContainer();
container.on((event) => {
if (event.type === 'resolve') {
resolveCounts.set(event.description, (resolveCounts.get(event.description) ?? 0) + 1);
}
});
const Config = token<object>('Config');
container.value(Config, {});
await container.resolve(Config);
await container.resolve(Config);
console.log(resolveCounts.get('Config')); // 2Observing child container events from the root
Events propagate up the container hierarchy. A listener on the root container observes events from all child and scope containers.
ts
import { createContainer, token } from '@vielzeug/conduit';
const Session = token<{ id: string }>('Session');
const root = createContainer({ name: 'root' });
const events: string[] = [];
root.on((e) => {
if (e.type === 'resolve') events.push(e.description);
});
root.factory(Session, () => ({ id: crypto.randomUUID() }), { lifetime: 'scoped' });
const child = root.createChild({ name: 'request-1' });
await child.resolve(Session);
console.log(events); // ['Session'] — observed from the root listenerPitfalls
- Errors thrown inside an
on()listener are silently swallowed — they do not propagate to the caller and do not interrupt container operation. Keep listeners side-effect-free and defensive. on()returns an unsubscribe function. Store it and call it when the observer is no longer needed to avoid memory leaks, especially in tests or short-lived scopes.- Listeners are called synchronously inside the resolution / registration path. Avoid slow or blocking operations inside listeners.