diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b3613d0..473e8c20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated `interfaces.NextArgs` with optional `isOptional` param. +- Updated `container` with `tryGet`. +- Updated `container` with `tryGetAsync`. +- Updated `container` with `tryGetTagged`. +- Updated `container` with `tryGetTaggedAsync`. +- Updated `container` with `tryGetNamed`. +- Updated `container` with `tryGetNamedAsync`. +- Updated `container` with `tryGetAll`. +- Updated `container` with `tryGetAllAsync`. +- Updated `container` with `tryGetAllTagged`. +- Updated `container` with `tryGetAllTaggedAsync`. +- Updated `container` with `tryGetAllNamed`. +- Updated `container` with `tryGetAllNamedAsync`. ### Fixed diff --git a/src/container/container.ts b/src/container/container.ts index a45a06b4..689bdcc4 100644 --- a/src/container/container.ts +++ b/src/container/container.ts @@ -390,7 +390,11 @@ class Container implements interfaces.Container { // The runtime identifier must be associated with only one binding // use getAll when the runtime identifier is associated with multiple bindings public get(serviceIdentifier: interfaces.ServiceIdentifier): T { - const getArgs: GetArgs = this._getNotAllArgs(serviceIdentifier, false); + const getArgs: GetArgs = this._getNotAllArgs( + serviceIdentifier, + false, + false, + ); return this._getButThrowIfAsync(getArgs) as T; } @@ -398,7 +402,11 @@ class Container implements interfaces.Container { public async getAsync( serviceIdentifier: interfaces.ServiceIdentifier, ): Promise { - const getArgs: GetArgs = this._getNotAllArgs(serviceIdentifier, false); + const getArgs: GetArgs = this._getNotAllArgs( + serviceIdentifier, + false, + false, + ); return this._get(getArgs) as Promise | T; } @@ -411,6 +419,7 @@ class Container implements interfaces.Container { const getArgs: GetArgs = this._getNotAllArgs( serviceIdentifier, false, + false, key, value, ); @@ -426,6 +435,7 @@ class Container implements interfaces.Container { const getArgs: GetArgs = this._getNotAllArgs( serviceIdentifier, false, + false, key, value, ); @@ -457,7 +467,11 @@ class Container implements interfaces.Container { serviceIdentifier: interfaces.ServiceIdentifier, options?: interfaces.GetAllOptions, ): T[] { - const getArgs: GetArgs = this._getAllArgs(serviceIdentifier, options); + const getArgs: GetArgs = this._getAllArgs( + serviceIdentifier, + options, + false, + ); return this._getButThrowIfAsync(getArgs) as T[]; } @@ -466,7 +480,11 @@ class Container implements interfaces.Container { serviceIdentifier: interfaces.ServiceIdentifier, options?: interfaces.GetAllOptions, ): Promise { - const getArgs: GetArgs = this._getAllArgs(serviceIdentifier, options); + const getArgs: GetArgs = this._getAllArgs( + serviceIdentifier, + options, + false, + ); return this._getAll(getArgs); } @@ -479,6 +497,7 @@ class Container implements interfaces.Container { const getArgs: GetArgs = this._getNotAllArgs( serviceIdentifier, true, + false, key, value, ); @@ -494,6 +513,7 @@ class Container implements interfaces.Container { const getArgs: GetArgs = this._getNotAllArgs( serviceIdentifier, true, + false, key, value, ); @@ -535,6 +555,164 @@ class Container implements interfaces.Container { return resolved; } + public tryGet( + serviceIdentifier: interfaces.ServiceIdentifier, + ): T | undefined { + const getArgs: GetArgs = this._getNotAllArgs( + serviceIdentifier, + false, + true, + ); + + return this._getButThrowIfAsync(getArgs) as T | undefined; + } + + public async tryGetAsync( + serviceIdentifier: interfaces.ServiceIdentifier, + ): Promise { + const getArgs: GetArgs = this._getNotAllArgs( + serviceIdentifier, + false, + true, + ); + + return this._get(getArgs) as Promise | T | undefined; + } + + public tryGetTagged( + serviceIdentifier: interfaces.ServiceIdentifier, + key: string | number | symbol, + value: unknown, + ): T | undefined { + const getArgs: GetArgs = this._getNotAllArgs( + serviceIdentifier, + false, + true, + key, + value, + ); + + return this._getButThrowIfAsync(getArgs) as T | undefined; + } + + public async tryGetTaggedAsync( + serviceIdentifier: interfaces.ServiceIdentifier, + key: string | number | symbol, + value: unknown, + ): Promise { + const getArgs: GetArgs = this._getNotAllArgs( + serviceIdentifier, + false, + true, + key, + value, + ); + + return this._get(getArgs) as Promise | T | undefined; + } + + public tryGetNamed( + serviceIdentifier: interfaces.ServiceIdentifier, + named: string | number | symbol, + ): T | undefined { + return this.tryGetTagged( + serviceIdentifier, + METADATA_KEY.NAMED_TAG, + named, + ); + } + + public async tryGetNamedAsync( + serviceIdentifier: interfaces.ServiceIdentifier, + named: string | number | symbol, + ): Promise { + return this.tryGetTaggedAsync( + serviceIdentifier, + METADATA_KEY.NAMED_TAG, + named, + ); + } + + public tryGetAll( + serviceIdentifier: interfaces.ServiceIdentifier, + options?: interfaces.GetAllOptions, + ): T[] { + const getArgs: GetArgs = this._getAllArgs( + serviceIdentifier, + options, + true, + ); + + return this._getButThrowIfAsync(getArgs) as T[]; + } + + public async tryGetAllAsync( + serviceIdentifier: interfaces.ServiceIdentifier, + options?: interfaces.GetAllOptions, + ): Promise { + const getArgs: GetArgs = this._getAllArgs( + serviceIdentifier, + options, + true, + ); + + return this._getAll(getArgs); + } + + public tryGetAllTagged( + serviceIdentifier: interfaces.ServiceIdentifier, + key: string | number | symbol, + value: unknown, + ): T[] { + const getArgs: GetArgs = this._getNotAllArgs( + serviceIdentifier, + true, + true, + key, + value, + ); + + return this._getButThrowIfAsync(getArgs) as T[]; + } + + public async tryGetAllTaggedAsync( + serviceIdentifier: interfaces.ServiceIdentifier, + key: string | number | symbol, + value: unknown, + ): Promise { + const getArgs: GetArgs = this._getNotAllArgs( + serviceIdentifier, + true, + true, + key, + value, + ); + + return this._getAll(getArgs); + } + + public tryGetAllNamed( + serviceIdentifier: interfaces.ServiceIdentifier, + named: string | number | symbol, + ): T[] { + return this.tryGetAllTagged( + serviceIdentifier, + METADATA_KEY.NAMED_TAG, + named, + ); + } + + public async tryGetAllNamedAsync( + serviceIdentifier: interfaces.ServiceIdentifier, + named: string | number | symbol, + ): Promise { + return this.tryGetAllTaggedAsync( + serviceIdentifier, + METADATA_KEY.NAMED_TAG, + named, + ); + } + private _preDestroy( constructor: NewableFunction | undefined, instance: unknown, @@ -828,23 +1006,25 @@ class Container implements interfaces.Container { return this._planAndResolve()(planAndResolveArgs); } - private _getButThrowIfAsync(getArgs: GetArgs): T | T[] { + private _getButThrowIfAsync(getArgs: GetArgs): undefined | T | T[] { const result: interfaces.ContainerResolution = this._get(getArgs); if (isPromiseOrContainsPromise(result)) { throw new Error(ERROR_MSGS.LAZY_IN_SYNC(getArgs.serviceIdentifier)); } - return result as T | T[]; + return result as undefined | T | T[]; } private _getAllArgs( serviceIdentifier: interfaces.ServiceIdentifier, options: interfaces.GetAllOptions | undefined, + isOptional: boolean, ): GetArgs { const getAllArgs: GetArgs = { avoidConstraints: !(options?.enforceBindingConstraints ?? false), isMultiInject: true, + isOptional, serviceIdentifier, }; @@ -854,12 +1034,14 @@ class Container implements interfaces.Container { private _getNotAllArgs( serviceIdentifier: interfaces.ServiceIdentifier, isMultiInject: boolean, + isOptional: boolean, key?: string | number | symbol | undefined, value?: unknown, ): GetArgs { const getNotAllArgs: GetArgs = { avoidConstraints: false, isMultiInject, + isOptional, key, serviceIdentifier, value, diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts index 7f640c1c..2f2c27a1 100644 --- a/src/interfaces/interfaces.ts +++ b/src/interfaces/interfaces.ts @@ -10,7 +10,11 @@ import { // eslint-disable-next-line @typescript-eslint/no-namespace namespace interfaces { export type DynamicValue = (context: interfaces.Context) => T | Promise; - export type ContainerResolution = T | Promise | (T | Promise)[]; + export type ContainerResolution = + | undefined + | T + | Promise + | (T | Promise)[]; type AsyncCallback = TCallback extends ( ...args: infer TArgs @@ -301,6 +305,56 @@ namespace interfaces { onDeactivation: BindingDeactivation, ): void; resolve(constructorFunction: interfaces.Newable): T; + tryGet( + serviceIdentifier: interfaces.ServiceIdentifier, + ): T | undefined; + tryGetAsync( + serviceIdentifier: interfaces.ServiceIdentifier, + ): Promise; + tryGetTagged( + serviceIdentifier: interfaces.ServiceIdentifier, + key: string | number | symbol, + value: unknown, + ): T | undefined; + tryGetTaggedAsync( + serviceIdentifier: interfaces.ServiceIdentifier, + key: string | number | symbol, + value: unknown, + ): Promise; + tryGetNamed( + serviceIdentifier: interfaces.ServiceIdentifier, + named: string | number | symbol, + ): T | undefined; + tryGetNamedAsync( + serviceIdentifier: interfaces.ServiceIdentifier, + named: string | number | symbol, + ): Promise; + tryGetAll( + serviceIdentifier: interfaces.ServiceIdentifier, + options?: interfaces.GetAllOptions, + ): T[]; + tryGetAllAsync( + serviceIdentifier: interfaces.ServiceIdentifier, + options?: interfaces.GetAllOptions, + ): Promise; + tryGetAllTagged( + serviceIdentifier: interfaces.ServiceIdentifier, + key: string | number | symbol, + value: unknown, + ): T[]; + tryGetAllTaggedAsync( + serviceIdentifier: interfaces.ServiceIdentifier, + key: string | number | symbol, + value: unknown, + ): Promise; + tryGetAllNamed( + serviceIdentifier: interfaces.ServiceIdentifier, + named: string | number | symbol, + ): T[]; + tryGetAllNamedAsync( + serviceIdentifier: interfaces.ServiceIdentifier, + named: string | number | symbol, + ): Promise; load(...modules: ContainerModule[]): void; loadAsync(...modules: AsyncContainerModule[]): Promise; unload(...modules: ContainerModuleBase[]): void; diff --git a/src/test/container/container.test.ts b/src/test/container/container.test.ts index 4e7f109d..2a9e95d9 100644 --- a/src/test/container/container.test.ts +++ b/src/test/container/container.test.ts @@ -580,6 +580,37 @@ describe('Container', () => { expect(es[1]?.goodbye).to.equal('adios'); }); + it('Should be able to resolve optional injection', async () => { + const container: Container = new Container(); + + const serviceIdentifier: string = 'service-id'; + + expect(container.tryGet(serviceIdentifier)).to.eq(undefined); + expect(container.tryGetAll(serviceIdentifier)).to.deep.eq([]); + expect(await container.tryGetAllAsync(serviceIdentifier)).to.deep.eq([]); + expect(container.tryGetAllNamed(serviceIdentifier, 'name')).to.deep.eq([]); + expect( + await container.tryGetAllNamedAsync(serviceIdentifier, 'name'), + ).to.deep.eq([]); + expect( + container.tryGetAllTagged(serviceIdentifier, 'tag', 'value'), + ).to.deep.eq([]); + expect( + await container.tryGetAllTaggedAsync(serviceIdentifier, 'tag', 'value'), + ).to.deep.eq([]); + expect(await container.tryGetAsync(serviceIdentifier)).to.eq(undefined); + expect(container.tryGetNamed(serviceIdentifier, 'name')).to.eq(undefined); + expect(await container.tryGetNamedAsync(serviceIdentifier, 'name')).to.eq( + undefined, + ); + expect(container.tryGetTagged(serviceIdentifier, 'tag', 'value')).to.eq( + undefined, + ); + expect( + await container.tryGetTaggedAsync(serviceIdentifier, 'tag', 'value'), + ).to.eq(undefined); + }); + it('Should be able configure the default scope at a global level', () => { interface Warrior { health: number; diff --git a/wiki/container_api.md b/wiki/container_api.md index 251861e8..105d985b 100644 --- a/wiki/container_api.md +++ b/wiki/container_api.md @@ -127,6 +127,7 @@ An advanced feature that can be used for cross cutting concerns. See [middleware ## container.createChild(containerOptions?: interfaces.ContainerOptions): Container; Create a [container hierarchy ](https://github.com/inversify/InversifyJS/blob/master/wiki/hierarchical_di.md). If you do not provide options the child receives the options of the parent. + ## container.get\(serviceIdentifier: interfaces.ServiceIdentifier\): T Resolves a dependency by its runtime identifier. The runtime identifier must be associated with only one binding and the binding must be synchronously resolved, otherwise an error is thrown: @@ -585,6 +586,55 @@ Restore container state to last snapshot. ## container.snapshot(): void Save the state of the container to be later restored with the restore method. + +## container.tryGet\(serviceIdentifier: interfaces.ServiceIdentifier\): T | undefined + +Same as `container.get`, but returns `undefined` in the event no bindings are bound to `serviceIdentifier`. + +## container.tryGetAsync\(serviceIdentifier: interfaces.ServiceIdentifier\): Promise + +Same as `container.getAsync`, but returns `Promise` in the event no bindings are bound to `serviceIdentifier`. + +## container.tryGetNamed\(serviceIdentifier: interfaces.ServiceIdentifier\, named: string | number | symbol): T | undefined + +Same as `container.getNamed`, but returns `undefined` in the event no bindings are bound to `serviceIdentifier`. + +## container.tryGetNamedAsync\(serviceIdentifier: interfaces.ServiceIdentifier\, named: string | number | symbol): Promise\ + +Same as `container.getNamedAsync`, but returns `Promise` in the event no bindings are bound to `serviceIdentifier`. + +## container.tryGetTagged\(serviceIdentifier: interfaces.ServiceIdentifier\, key: string | number | symbol, value: unknown): T | undefined + +Same as `container.getTagged`, but returns `undefined` in the event no bindings are bound to `serviceIdentifier`. + +## container.tryGetTaggedAsync\(serviceIdentifier: interfaces.ServiceIdentifier\, key: string | number | symbol, value: unknown): Promise\ + +Same as `container.getTaggedAsync`, but returns `Promise` in the event no bindings are bound to `serviceIdentifier`. + +## container.tryGetAll\(serviceIdentifier: interfaces.ServiceIdentifier\, options?: interfaces.GetAllOptions): T[] + +Same as `container.getAll`, but returns `[]` in the event no bindings are bound to `serviceIdentifier`. + +## container.tryGetAllAsync\(serviceIdentifier: interfaces.ServiceIdentifier\, options?: interfaces.GetAllOptions): Promise\ + +Same as `container.getAllAsync`, but returns `Promise<[]>` in the event no bindings are bound to `serviceIdentifier`. + +## container.tryGetAllNamed\(serviceIdentifier: interfaces.ServiceIdentifier\, named: string | number | symbol): T[] + +Same as `container.getAllNamed`, but returns `[]` in the event no bindings are bound to `serviceIdentifier`. + +## container.tryGetAllNamedAsync\(serviceIdentifier: interfaces.ServiceIdentifier\, named: string | number | symbol): Promise\ + +Same as `container.getAllNamedAsync`, but returns `Promise<[]>` in the event no bindings are bound to `serviceIdentifier`. + +## container.tryGetAllTagged\(serviceIdentifier: interfaces.ServiceIdentifier\, key: string | number | symbol, value: unknown): T[] + +Same as `container.getAllTagged`, but returns `[]` in the event no bindings are bound to `serviceIdentifier`. + +## container.tryGetAllTaggedAsync\(serviceIdentifier: interfaces.ServiceIdentifier\, key: string | number | symbol, value: unknown): Promise\ + +Same as `container.getAllTaggedAsync`, but returns `Promise<[]>` in the event no bindings are bound to `serviceIdentifier`. + ## container.unbind(serviceIdentifier: interfaces.ServiceIdentifier\): void Remove all bindings binded in this container to the service identifier. This will result in the [deactivation process](https://github.com/inversify/InversifyJS/blob/master/wiki/deactivation_handler.md).