diff --git a/.changeset/cyan-carrots-flash.md b/.changeset/cyan-carrots-flash.md new file mode 100644 index 00000000..149c9213 --- /dev/null +++ b/.changeset/cyan-carrots-flash.md @@ -0,0 +1,5 @@ +--- +"@inversifyjs/core": minor +--- + +Added `optional`. diff --git a/.changeset/five-toes-study.md b/.changeset/five-toes-study.md new file mode 100644 index 00000000..40b43616 --- /dev/null +++ b/.changeset/five-toes-study.md @@ -0,0 +1,5 @@ +--- +"@inversifyjs/core": minor +--- + +Added `multiInject`. diff --git a/.changeset/green-garlics-doubt.md b/.changeset/green-garlics-doubt.md new file mode 100644 index 00000000..dfe83cc0 --- /dev/null +++ b/.changeset/green-garlics-doubt.md @@ -0,0 +1,5 @@ +--- +"@inversifyjs/core": minor +--- + +Added `named`. diff --git a/.changeset/new-yaks-own.md b/.changeset/new-yaks-own.md new file mode 100644 index 00000000..cb9e92c6 --- /dev/null +++ b/.changeset/new-yaks-own.md @@ -0,0 +1,5 @@ +--- +"@inversifyjs/core": minor +--- + +Added `postConstruct`. diff --git a/.changeset/purple-eyes-jam.md b/.changeset/purple-eyes-jam.md new file mode 100644 index 00000000..be4c7fec --- /dev/null +++ b/.changeset/purple-eyes-jam.md @@ -0,0 +1,5 @@ +--- +"@inversifyjs/core": minor +--- + +Added `unmanaged`. diff --git a/.changeset/real-goats-sleep.md b/.changeset/real-goats-sleep.md new file mode 100644 index 00000000..43a66bc2 --- /dev/null +++ b/.changeset/real-goats-sleep.md @@ -0,0 +1,5 @@ +--- +"@inversifyjs/core": minor +--- + +Added `tagged`. diff --git a/.changeset/shaggy-wasps-tickle.md b/.changeset/shaggy-wasps-tickle.md new file mode 100644 index 00000000..d3985b59 --- /dev/null +++ b/.changeset/shaggy-wasps-tickle.md @@ -0,0 +1,5 @@ +--- +"@inversifyjs/core": minor +--- + +Added `injectFromBase`. diff --git a/.changeset/strong-lies-dance.md b/.changeset/strong-lies-dance.md new file mode 100644 index 00000000..f9298e8e --- /dev/null +++ b/.changeset/strong-lies-dance.md @@ -0,0 +1,5 @@ +--- +"@inversifyjs/core": minor +--- + +Added `inject`. diff --git a/.changeset/tall-buckets-wonder.md b/.changeset/tall-buckets-wonder.md new file mode 100644 index 00000000..70b6c39a --- /dev/null +++ b/.changeset/tall-buckets-wonder.md @@ -0,0 +1,5 @@ +--- +"@inversifyjs/core": minor +--- + +Added `preDestroy`. diff --git a/.changeset/young-chefs-draw.md b/.changeset/young-chefs-draw.md new file mode 100644 index 00000000..6ad51d15 --- /dev/null +++ b/.changeset/young-chefs-draw.md @@ -0,0 +1,5 @@ +--- +"@inversifyjs/core": minor +--- + +Added `injectable`. diff --git a/packages/container/libraries/core/package.json b/packages/container/libraries/core/package.json index 5aa9504a..1a151c10 100644 --- a/packages/container/libraries/core/package.json +++ b/packages/container/libraries/core/package.json @@ -6,6 +6,7 @@ "description": "InversifyJs core package", "dependencies": { "@inversifyjs/common": "workspace:*", + "@inversifyjs/prototype-utils": "workspace:*", "@inversifyjs/reflect-metadata-utils": "workspace:*" }, "devDependencies": { diff --git a/packages/container/libraries/core/src/index.ts b/packages/container/libraries/core/src/index.ts index f17c8dba..929e10ec 100644 --- a/packages/container/libraries/core/src/index.ts +++ b/packages/container/libraries/core/src/index.ts @@ -24,6 +24,16 @@ import { } from './binding/services/ActivationsService'; import { BindingService } from './binding/services/BindingService'; import { getClassMetadata } from './metadata/calculations/getClassMetadata'; +import { inject } from './metadata/decorators/inject'; +import { injectable } from './metadata/decorators/injectable'; +import { injectFromBase } from './metadata/decorators/injectFromBase'; +import { multiInject } from './metadata/decorators/multiInject'; +import { named } from './metadata/decorators/named'; +import { optional } from './metadata/decorators/optional'; +import { postConstruct } from './metadata/decorators/postConstruct'; +import { preDestroy } from './metadata/decorators/preDestroy'; +import { tagged } from './metadata/decorators/tagged'; +import { unmanaged } from './metadata/decorators/unmanaged'; import { ClassElementMetadata } from './metadata/models/ClassElementMetadata'; import { ClassElementMetadataKind } from './metadata/models/ClassElementMetadataKind'; import { ClassMetadata } from './metadata/models/ClassMetadata'; @@ -108,6 +118,16 @@ export { bindingTypeValues, ClassElementMetadataKind, getClassMetadata, + multiInject, + inject, + injectable, + injectFromBase, + named, + optional, + postConstruct, plan, + preDestroy, resolve, + tagged, + unmanaged, }; diff --git a/packages/container/libraries/core/src/metadata/decorators/injectFromBase.spec.ts b/packages/container/libraries/core/src/metadata/decorators/injectFromBase.spec.ts new file mode 100644 index 00000000..4abeb34e --- /dev/null +++ b/packages/container/libraries/core/src/metadata/decorators/injectFromBase.spec.ts @@ -0,0 +1,123 @@ +import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals'; + +jest.mock('@inversifyjs/prototype-utils'); + +import { Newable } from '@inversifyjs/common'; +import { getBaseType } from '@inversifyjs/prototype-utils'; + +jest.mock('./injectFrom'); + +import { InversifyCoreError } from '../../error/models/InversifyCoreError'; +import { InversifyCoreErrorKind } from '../../error/models/InversifyCoreErrorKind'; +import { InjectFromBaseOptions } from '../models/InjectFromBaseOptions'; +import { InjectFromOptions } from '../models/InjectFromOptions'; +import { injectFrom } from './injectFrom'; +import { injectFromBase } from './injectFromBase'; + +describe(injectFromBase.name, () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + let targetFixture: Function; + + beforeAll(() => { + targetFixture = class {}; + }); + + describe('when called, and getBaseType returns Newable', () => { + let injectFromBaseOptionsFixture: InjectFromBaseOptions; + + let baseTypefixture: Newable; + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + let injectFromResultMock: jest.Mock<(target: Function) => void>; + + let result: unknown; + + beforeAll(() => { + injectFromBaseOptionsFixture = { + extendConstructorArguments: true, + extendProperties: true, + }; + baseTypefixture = class Base {}; + injectFromResultMock = jest.fn().mockReturnValueOnce(undefined); + + (getBaseType as jest.Mock).mockReturnValueOnce( + baseTypefixture, + ); + + (injectFrom as jest.Mock).mockReturnValueOnce( + injectFromResultMock, + ); + + result = injectFromBase(injectFromBaseOptionsFixture)(targetFixture); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call getBaseType()', () => { + expect(getBaseType).toHaveBeenCalledTimes(1); + expect(getBaseType).toHaveBeenCalledWith(targetFixture); + }); + + it('should call injectFrom()', () => { + const expected: InjectFromOptions = { + ...injectFromBaseOptionsFixture, + type: baseTypefixture, + }; + + expect(injectFrom).toHaveBeenCalledTimes(1); + expect(injectFrom).toHaveBeenCalledWith(expected); + + expect(injectFromResultMock).toHaveBeenCalledTimes(1); + expect(injectFromResultMock).toHaveBeenCalledWith(targetFixture); + }); + + it('should return undefined', () => { + expect(result).toBeUndefined(); + }); + }); + + describe('when called, and getBaseType returns undefined', () => { + let injectFromBaseOptionsFixture: InjectFromBaseOptions; + + let result: unknown; + + beforeAll(() => { + injectFromBaseOptionsFixture = { + extendConstructorArguments: true, + extendProperties: true, + }; + + (getBaseType as jest.Mock).mockReturnValueOnce( + undefined, + ); + + try { + injectFromBase(injectFromBaseOptionsFixture)(targetFixture); + } catch (error: unknown) { + result = error; + } + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call getBaseType()', () => { + expect(getBaseType).toHaveBeenCalledTimes(1); + expect(getBaseType).toHaveBeenCalledWith(targetFixture); + }); + + it('should return undefined', () => { + const expectedErrorProperties: Partial = { + kind: InversifyCoreErrorKind.injectionDecoratorConflict, + message: `Expected base type for type "${targetFixture.name}", none found.`, + }; + + expect(result).toBeInstanceOf(InversifyCoreError); + expect(result).toStrictEqual( + expect.objectContaining(expectedErrorProperties), + ); + }); + }); +}); diff --git a/packages/container/libraries/core/src/metadata/decorators/injectFromBase.ts b/packages/container/libraries/core/src/metadata/decorators/injectFromBase.ts new file mode 100644 index 00000000..b63e4f24 --- /dev/null +++ b/packages/container/libraries/core/src/metadata/decorators/injectFromBase.ts @@ -0,0 +1,26 @@ +import { Newable } from '@inversifyjs/common'; +import { getBaseType } from '@inversifyjs/prototype-utils'; + +import { InversifyCoreError } from '../../error/models/InversifyCoreError'; +import { InversifyCoreErrorKind } from '../../error/models/InversifyCoreErrorKind'; +import { InjectFromBaseOptions } from '../models/InjectFromBaseOptions'; +import { injectFrom } from './injectFrom'; + +export function injectFromBase(options: InjectFromBaseOptions): ClassDecorator { + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + return (target: Function): void => { + const baseType: Newable | undefined = getBaseType(target as Newable); + + if (baseType === undefined) { + throw new InversifyCoreError( + InversifyCoreErrorKind.injectionDecoratorConflict, + `Expected base type for type "${target.name}", none found.`, + ); + } + + injectFrom({ + ...options, + type: baseType, + })(target); + }; +} diff --git a/packages/container/libraries/core/src/metadata/models/InjectFromBaseOptions.ts b/packages/container/libraries/core/src/metadata/models/InjectFromBaseOptions.ts new file mode 100644 index 00000000..85d3412e --- /dev/null +++ b/packages/container/libraries/core/src/metadata/models/InjectFromBaseOptions.ts @@ -0,0 +1,4 @@ +export interface InjectFromBaseOptions { + extendConstructorArguments?: boolean | undefined; + extendProperties?: boolean | undefined; +} diff --git a/packages/container/libraries/core/src/metadata/models/InjectFromOptions.ts b/packages/container/libraries/core/src/metadata/models/InjectFromOptions.ts index 01cb306b..e70be9b5 100644 --- a/packages/container/libraries/core/src/metadata/models/InjectFromOptions.ts +++ b/packages/container/libraries/core/src/metadata/models/InjectFromOptions.ts @@ -1,7 +1,7 @@ import { Newable } from '@inversifyjs/common'; export interface InjectFromOptions { - extendConstructorArguments?: boolean; - extendProperties?: boolean; + extendConstructorArguments?: boolean | undefined; + extendProperties?: boolean | undefined; type: Newable; } diff --git a/packages/container/libraries/core/src/prototype/calculations/getBaseType.spec.ts b/packages/container/libraries/core/src/prototype/calculations/getBaseType.spec.ts deleted file mode 100644 index 3f4679fb..00000000 --- a/packages/container/libraries/core/src/prototype/calculations/getBaseType.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { beforeAll, describe, expect, it } from '@jest/globals'; - -import { Newable } from '@inversifyjs/common'; - -import { getBaseType } from './getBaseType'; - -describe(getBaseType.name, () => { - describe('having a type with base type', () => { - let baseTypeFixture: Newable; - let typeFixture: Newable; - - beforeAll(() => { - class BaseType {} - - baseTypeFixture = BaseType; - typeFixture = class extends BaseType {}; - }); - - describe('when called', () => { - let result: unknown; - - beforeAll(() => { - result = getBaseType(typeFixture); - }); - - it('should return base type', () => { - expect(result).toBe(baseTypeFixture); - }); - }); - }); - - describe('having a type with no base type', () => { - let typeFixture: Newable; - - beforeAll(() => { - typeFixture = Object; - }); - - describe('when called', () => { - let result: unknown; - - beforeAll(() => { - result = getBaseType(typeFixture); - }); - - it('should return undefined', () => { - expect(result).toBeUndefined(); - }); - }); - }); -}); diff --git a/packages/container/libraries/core/src/prototype/calculations/getBaseType.ts b/packages/container/libraries/core/src/prototype/calculations/getBaseType.ts deleted file mode 100644 index e01a1058..00000000 --- a/packages/container/libraries/core/src/prototype/calculations/getBaseType.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Newable } from '@inversifyjs/common'; - -import { Prototype } from '../models/Prototype'; - -export function getBaseType( - type: Newable, -): Newable | undefined { - const prototype: Prototype | null = Object.getPrototypeOf( - type.prototype, - ) as Prototype | null; - - const baseType: Newable | undefined = prototype?.constructor; - - return baseType; -} diff --git a/packages/container/libraries/core/src/prototype/models/Prototype.ts b/packages/container/libraries/core/src/prototype/models/Prototype.ts deleted file mode 100644 index ecca2fd9..00000000 --- a/packages/container/libraries/core/src/prototype/models/Prototype.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Newable } from '@inversifyjs/common'; - -export interface Prototype { - constructor: Newable; -}