diff --git a/README.md b/README.md index af4fcaf..5c8ced3 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,9 @@ Runtime exports: - `Inject` - `Token` +`Container` exposes the runtime API for resolution and low-level registration, +including `of()`, `get()`, `set()`, `has()`, `remove()`, and `reset()`. + Type-only exports: - `Constructable` / `AbstractConstructable` @@ -239,16 +242,64 @@ This is especially useful in tests. Registers or replaces a bound value or provider for a service identifier. +Use `set()` when you want manual registration without decorating a class. +This is the supported low-level API for values, classes, and factories. + Supported forms today: - `container.set(id, value)` - `container.set(id, { useValue: value })` -- `container.set(id, { useClass: Class, scope? })` +- `container.set(id, { useClass: Class, scope?, injections? })` - `container.set(id, { useFactory: (container) => value, scope? })` This is the low-level API for manual value, class, and factory registration. For decorator-driven classes, prefer `@Service()`. +Value example: + +```ts +const REQUEST_ID = 'request-id'; + +Container.of().set(REQUEST_ID, { useValue: crypto.randomUUID() }); + +const requestId = Container.of().get(REQUEST_ID); +``` + +Class example: + +```ts +interface Logger { + log(message: string): void; +} + +class ConsoleLogger implements Logger { + public log(message: string) { + console.log(message); + } +} + +Container.of().set('logger', { useClass: ConsoleLogger, scope: 'singleton' }); +``` + +Factory example: + +```ts +class Connection { + constructor(public readonly requestId: string) {} +} + +Container.of().set('request-id', { useValue: 'request-1' }); +Container.of().set(Connection, { + useFactory: (container) => new Connection(container.get('request-id')), + scope: 'container', +}); +``` + +For named containers, values bound in the default container are available through +fallback. Provider objects are detected structurally, so plain object values that +contain `useValue`, `useClass`, or `useFactory` should be wrapped with +`{ useValue: value }` if you want them treated as values. + ## Internal architecture The implementation is intentionally small and split into a few focused modules: diff --git a/src/container/container.ts b/src/container/container.ts index 756aaa4..8cbfdb5 100644 --- a/src/container/container.ts +++ b/src/container/container.ts @@ -141,7 +141,7 @@ export class Container { * @returns `true` when the current container has a local registration. */ public has(id: ServiceIdentifier): boolean { - return this.metadataMap.has(id); + return this.bindingMap.has(id) || this.metadataMap.has(id); } /** @@ -235,6 +235,10 @@ export class Container { let metadata = this.metadataMap.get(id) as Metadata | undefined; if (!metadata && !this.isDefault()) { + if (ContainerRegistry.defaultContainer.bindingMap.has(id)) { + return ContainerRegistry.defaultContainer.bindingMap.get(id) as T; + } + const defaultMetadata = ContainerRegistry.defaultContainer.metadataMap.get(id) as Metadata | undefined; if (!defaultMetadata) { diff --git a/test/container/container.test.ts b/test/container/container.test.ts index e8f9e81..47e2c43 100644 --- a/test/container/container.test.ts +++ b/test/container/container.test.ts @@ -111,6 +111,18 @@ describe('Container', () => { expect(Container.of().get(BoundService)).toBe(bound); }); + test('falls back to a value bound on the default container', () => { + const requestContainer = Container.of('container-binding-fallback'); + + class BoundService {} + + const bound = new BoundService(); + + Container.of().set(BoundService, bound); + + expect(requestContainer.get(BoundService)).toBe(bound); + }); + test('registers a class provider for a custom identifier', () => { interface Logger { log(message: string): string; @@ -192,6 +204,16 @@ describe('Container', () => { expect(defaultInstance).toBe(requestInstance); expect(created).toBe(1); }); + + test('reports local bindings through has()', () => { + class BoundService {} + + const bound = new BoundService(); + + Container.of().set(BoundService, bound); + + expect(Container.of().has(BoundService)).toBe(true); + }); }); describe('remove', () => {