Challenge 3: Add Singleton Lifetimes — Possible Solution ==================================================================== type Lifetime = "singleton" | "transient"; interface Registration { factory: Factory; lifetime: Lifetime; } type Factory = () => T; class Container { private registrations = new Map, Registration>(); private singletons = new Map, unknown>(); register(token: Token, factory: Factory, lifetime: Lifetime = "transient"): void { this.registrations.set(token, { factory, lifetime }); } resolve(token: Token): T { const registration = this.registrations.get(token) as Registration | undefined; if (!registration) { throw new Error(`No registration for ${token.name}`); } if (registration.lifetime === "singleton") { if (!this.singletons.has(token)) { this.singletons.set(token, registration.factory()); } return this.singletons.get(token) as T; } return registration.factory(); } } class Token { constructor(public readonly name: string) {} } // --- Verifying singleton behavior --- class ConnectionPool { private static instanceCount = 0; public readonly id: number; constructor() { this.id = ++ConnectionPool.instanceCount; } } const ConnectionPoolToken = new Token("ConnectionPool"); const container = new Container(); container.register(ConnectionPoolToken, () => new ConnectionPool(), "singleton"); const poolA = container.resolve(ConnectionPoolToken); const poolB = container.resolve(ConnectionPoolToken); console.log(poolA === poolB); // true — same instance console.log(poolA.id, poolB.id); // both print the same id, e.g. 1, 1 // Compare against a transient registration: class RequestId { private static counter = 0; public readonly value: number; constructor() { this.value = ++RequestId.counter; } } const RequestIdToken = new Token("RequestId"); container.register(RequestIdToken, () => new RequestId()); // defaults to "transient" const req1 = container.resolve(RequestIdToken); const req2 = container.resolve(RequestIdToken); console.log(req1 === req2); // false — different instances console.log(req1.value, req2.value); // e.g. 1, 2 WHY THIS WORKS -------------- - The registration now stores both the factory and its lifetime, so resolve() can branch on how the instance should be cached (or not). - The `singletons` Map is keyed by token, so a singleton is created lazily on first resolve() and reused for every subsequent resolve() of that token. - Transient registrations skip the cache entirely and just call factory() fresh each time — exactly the original Challenge 2 behavior, now opt-in rather than the only option.