Compare commits
2 Commits
dev/v1.0.0
...
feature/re
Author | SHA1 | Date | |
---|---|---|---|
7feb0fc694 | |||
ea2383234f |
2
.github/config.conf
vendored
Normal file
2
.github/config.conf
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[branch_validation]
|
||||||
|
valid_prefixes = feature/,fix/,refactoring/,testing/,dependabot/,gh-pages
|
36
.github/workflows/CreateRelease.yml
vendored
36
.github/workflows/CreateRelease.yml
vendored
@@ -4,7 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- 'dev/*'
|
|
||||||
paths:
|
paths:
|
||||||
- 'package.json'
|
- 'package.json'
|
||||||
workflow_dispatch: # Allows manual execution of the workflow.
|
workflow_dispatch: # Allows manual execution of the workflow.
|
||||||
@@ -23,8 +22,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: '20.8.0'
|
node-version: '20.8.0'
|
||||||
|
|
||||||
- name: Install dependencies, run tests and build
|
- name: Install Dependencies
|
||||||
run: npm run prepare:deploy
|
run: npm install
|
||||||
|
|
||||||
|
- name: Run Tests
|
||||||
|
run: npm run test:verbose
|
||||||
|
|
||||||
|
- name: Build the Project
|
||||||
|
run: npm run build:tsc
|
||||||
|
|
||||||
- name: Get the version
|
- name: Get the version
|
||||||
id: get_version
|
id: get_version
|
||||||
@@ -72,9 +77,8 @@ jobs:
|
|||||||
echo "Generating release notes from ${{ env.PREVIOUS_TAG }} to HEAD..."
|
echo "Generating release notes from ${{ env.PREVIOUS_TAG }} to HEAD..."
|
||||||
repo_url=$(git config --get remote.origin.url)
|
repo_url=$(git config --get remote.origin.url)
|
||||||
notes=$(git log ${{ env.PREVIOUS_TAG }}..HEAD --pretty=format:"- [\`%h\`]($repo_url/commit/%H): %s%n")
|
notes=$(git log ${{ env.PREVIOUS_TAG }}..HEAD --pretty=format:"- [\`%h\`]($repo_url/commit/%H): %s%n")
|
||||||
echo "See [CHANGELOG.md](./CHANGELOG.md) for more details."
|
echo "Release notes:"
|
||||||
echo "$notes"
|
echo "$notes"
|
||||||
echo ""
|
|
||||||
echo "### Changes in this release" > release_notes.md
|
echo "### Changes in this release" > release_notes.md
|
||||||
echo "$notes" >> release_notes.md
|
echo "$notes" >> release_notes.md
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -86,6 +90,17 @@ jobs:
|
|||||||
git config --local user.email "actions@github.com"
|
git config --local user.email "actions@github.com"
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
|
# - name: Create temporary branch
|
||||||
|
# id: create_temp_branch
|
||||||
|
# if: steps.check_version.outputs.skip_release == 'false'
|
||||||
|
# run: |
|
||||||
|
# git checkout --orphan release/v${{ env.VERSION }}
|
||||||
|
# git reset
|
||||||
|
# rm -f .gitignore
|
||||||
|
# git add README.md package.json LICENSE dist/ src/ tsconfig.json
|
||||||
|
# git commit -m "Prepare files for release ${{ env.VERSION }}"
|
||||||
|
# shell: bash
|
||||||
|
|
||||||
- name: Create and push tag
|
- name: Create and push tag
|
||||||
id: create_tag
|
id: create_tag
|
||||||
if: steps.check_version.outputs.skip_release == 'false'
|
if: steps.check_version.outputs.skip_release == 'false'
|
||||||
@@ -96,15 +111,6 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Set Release Prerelease Flag
|
|
||||||
id: set_prerelease_flag
|
|
||||||
run: |
|
|
||||||
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
|
|
||||||
echo "PRE_RELEASE=false" >> $GITHUB_ENV
|
|
||||||
elif [[ "${{ github.ref }}" == refs/heads/dev/* ]]; then
|
|
||||||
echo "PRE_RELEASE=true" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
if: steps.check_version.outputs.skip_release == 'false'
|
if: steps.check_version.outputs.skip_release == 'false'
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
@@ -112,6 +118,6 @@ jobs:
|
|||||||
tag_name: ${{ env.VERSION }}
|
tag_name: ${{ env.VERSION }}
|
||||||
name: Release ${{ env.VERSION }}
|
name: Release ${{ env.VERSION }}
|
||||||
body_path: release_notes.md
|
body_path: release_notes.md
|
||||||
prerelease: ${{ env.PRE_RELEASE }}
|
prerelease: true
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
25
.github/workflows/ValidateBranchName.yml
vendored
25
.github/workflows/ValidateBranchName.yml
vendored
@@ -3,7 +3,8 @@ name: Validate Branch Name on Pull Request
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- UNDEFINED
|
- main
|
||||||
|
workflow_dispatch: # Allows manual execution of the workflow.
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
validate-branch-name-on-pull-request:
|
validate-branch-name-on-pull-request:
|
||||||
@@ -13,11 +14,27 @@ jobs:
|
|||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Read Config File
|
||||||
|
id: config
|
||||||
|
run: |
|
||||||
|
VALID_PREFIXES=$(grep 'valid_prefixes' .github/config.conf | cut -d '=' -f2 | tr -d ' ')
|
||||||
|
echo "VALID_PREFIXES=$VALID_PREFIXES" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Validate Branch Name on Pull Request
|
- name: Validate Branch Name on Pull Request
|
||||||
run: |
|
run: |
|
||||||
BRANCH_NAME=${GITHUB_HEAD_REF}
|
BRANCH_NAME=${{ github.head_ref }}
|
||||||
if [[ ! "$BRANCH_NAME" =~ ^(feature\/|fix\/|refactoring\/|testing\/|dependabot\/|gh-pages) ]]; then
|
VALID_PREFIXES_ARRAY=(${VALID_PREFIXES//,/ })
|
||||||
|
VALID=false
|
||||||
|
|
||||||
|
for PREFIX in "${VALID_PREFIXES_ARRAY[@]}"; do
|
||||||
|
if [[ "$BRANCH_NAME" =~ ^$PREFIX ]]; then
|
||||||
|
VALID=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$VALID" = false ]; then
|
||||||
echo "Invalid branch name: $BRANCH_NAME"
|
echo "Invalid branch name: $BRANCH_NAME"
|
||||||
echo "Branch name must start with 'feature/', 'fix/', 'refactoring/', 'testing/', dependabot/" or "gh-pages"
|
echo "Branch name must start with one of the following prefixes: $VALID_PREFIXES"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
@@ -9,21 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- feat: Add new `IdentifierRequiredError` class for missing identifiers.
|
|
||||||
- feat: Add the option to use the decorators without passing the identifier: In this case, the identifier will be the class name (register) or the property name (inject).
|
|
||||||
- Add pre release building to release workflow on dev/* branches an version changes.
|
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- feat!: Disable `experimentalDecorators` and `emitDecoratorMetadata` in the `tsconfig.json` file to reflect the change to the stable decorators api.
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- feat!: Update `Register`, `RegisterInstance` and `Inject` decorators to reflect the change to the stable decorators api.
|
|
||||||
- feat!: Update `Inject` Decorator typing to reflect the correct property type.
|
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
|
9
package-lock.json
generated
9
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ts-injex",
|
"name": "ts-injex",
|
||||||
"version": "0.1.0-alpha",
|
"version": "0.0.9",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ts-injex",
|
"name": "ts-injex",
|
||||||
"version": "0.1.0-alpha",
|
"version": "0.0.9",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
@@ -5958,11 +5958,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
|
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
|
||||||
},
|
},
|
||||||
"node_modules/reflect-metadata": {
|
|
||||||
"version": "0.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
|
||||||
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="
|
|
||||||
},
|
|
||||||
"node_modules/regexp.prototype.flags": {
|
"node_modules/regexp.prototype.flags": {
|
||||||
"version": "1.5.2",
|
"version": "1.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ts-injex",
|
"name": "ts-injex",
|
||||||
"version": "0.1.0-alpha",
|
"version": "0.1.0",
|
||||||
"description": "Simple boilerplate code free dependency injection system for TypeScript.",
|
"description": "Simple boilerplate code free dependency injection system for TypeScript.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
@@ -22,8 +22,7 @@
|
|||||||
"docs:fix:coverage": "node scripts/fix-coverage-paths.cjs",
|
"docs:fix:coverage": "node scripts/fix-coverage-paths.cjs",
|
||||||
"docs:generate:badge": "node scripts/generate-badge.cjs",
|
"docs:generate:badge": "node scripts/generate-badge.cjs",
|
||||||
"docs:fix:escape": "node scripts/replace-doc-escaping.cjs",
|
"docs:fix:escape": "node scripts/replace-doc-escaping.cjs",
|
||||||
"version:show": "node -e \"console.log(require('./package.json').version)\"",
|
"version:show": "node -e \"console.log(require('./package.json').version)\""
|
||||||
"prepare:deploy": "npm install && npm run test:verbose && npm run build:tsc"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { Inject } from 'src/decorators/Inject';
|
import { Inject } from 'src/decorators/Inject';
|
||||||
import { DependencyResolutionError } from '../interfaces/Exceptions';
|
import { DependencyResolutionError } from 'src/interfaces/Exceptions';
|
||||||
|
import { ForceConstructor } from 'src/types/GenericContructor';
|
||||||
import { ITSinjex_, ITSinjex } from '../interfaces/ITSinjex';
|
import { ITSinjex_, ITSinjex } from '../interfaces/ITSinjex';
|
||||||
import { ForceConstructor } from '../types/GenericContructor';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the Inject decorator.
|
* Test the Inject decorator.
|
||||||
@@ -67,29 +67,6 @@ export function test_InjectDecorator(
|
|||||||
expect(instance.getDependency().value).toBe('test-value-init');
|
expect(instance.getDependency().value).toBe('test-value-init');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should inject dependency and run initializer without identifier', () => {
|
|
||||||
container.register('MockDependencyIdentifier', {
|
|
||||||
value: 'test-value',
|
|
||||||
});
|
|
||||||
|
|
||||||
class TestClass {
|
|
||||||
@Inject(undefined, (x: string) => {
|
|
||||||
(x as unknown as { value: string }).value =
|
|
||||||
'test-value-init';
|
|
||||||
|
|
||||||
return x;
|
|
||||||
})
|
|
||||||
MockDependencyIdentifier!: any;
|
|
||||||
|
|
||||||
public getDependency() {
|
|
||||||
return this.MockDependencyIdentifier;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const instance = new TestClass();
|
|
||||||
expect(instance.getDependency().value).toBe('test-value-init');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error when necessary is true and the initializer throws an error', () => {
|
it('should throw an error when necessary is true and the initializer throws an error', () => {
|
||||||
let _error: Error | undefined = undefined;
|
let _error: Error | undefined = undefined;
|
||||||
|
|
||||||
@@ -101,7 +78,7 @@ export function test_InjectDecorator(
|
|||||||
class TestClass {
|
class TestClass {
|
||||||
@Inject(
|
@Inject(
|
||||||
'InitThrowDependencie',
|
'InitThrowDependencie',
|
||||||
(): any => {
|
() => {
|
||||||
throw new Error('Initializer error');
|
throw new Error('Initializer error');
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
@@ -298,19 +275,6 @@ export function test_RegisterDecorator(
|
|||||||
TestClass,
|
TestClass,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should register a dependency without an identifier', () => {
|
|
||||||
@register()
|
|
||||||
class TestClass {
|
|
||||||
private readonly _dependency!: any;
|
|
||||||
|
|
||||||
public getDependency() {
|
|
||||||
return this._dependency;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(container.resolve('TestClass')).toBe(TestClass);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,23 +311,6 @@ export function test_RegisterInstanceDecorator(
|
|||||||
).toBe('instance');
|
).toBe('instance');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should register an instance of a dependency with an identifier', () => {
|
|
||||||
@registerInstance()
|
|
||||||
class TestClass {
|
|
||||||
private readonly _dependency!: any;
|
|
||||||
|
|
||||||
public getDependency() {
|
|
||||||
return this._dependency;
|
|
||||||
}
|
|
||||||
|
|
||||||
public mark: string = 'instance';
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(container.resolve<TestClass>('TestClass').mark).toBe(
|
|
||||||
'instance',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should register an instance of a dependency an run the init function', () => {
|
it('should register an instance of a dependency an run the init function', () => {
|
||||||
@registerInstance(
|
@registerInstance(
|
||||||
'InstanceIdentifier',
|
'InstanceIdentifier',
|
||||||
|
@@ -1,27 +1,26 @@
|
|||||||
import { TSinjex } from '../classes/TSinjex';
|
|
||||||
import {
|
import {
|
||||||
DependencyResolutionError,
|
DependencyResolutionError,
|
||||||
IdentifierRequiredError,
|
|
||||||
InitializationError,
|
InitializationError,
|
||||||
InjectorError,
|
InjectorError,
|
||||||
NoInstantiationMethodError,
|
NoInstantiationMethodError,
|
||||||
} from '../interfaces/Exceptions';
|
} from 'src/interfaces/Exceptions';
|
||||||
|
import { TSinjex } from '../classes/TSinjex';
|
||||||
import { Identifier } from '../types/Identifier';
|
import { Identifier } from '../types/Identifier';
|
||||||
import { InitDelegate } from '../types/InitDelegate';
|
import { InitDelegate } from '../types/InitDelegate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A decorator to inject a dependency from a DI (Dependency Injection) container into a class property.
|
* A decorator to inject a dependency from a DI (Dependency Injection) container into a class property.
|
||||||
* @template TargetType The type of the class to inject the dependency into.
|
* @template T The type of the dependency to be injected.
|
||||||
* @template DependencyType The type of the dependency to be injected.
|
* @template U The type of the property to be injected.
|
||||||
* @template PropertyType The type of the property to be injected.
|
* @param identifier The identifier used to resolve the class in the DI container.
|
||||||
* @param identifier The {@link Identifier|identifier} used to resolve the dependencie in the DI container or the property name if not provided.
|
* @see {@link Identifier} for more information on identifiers.
|
||||||
* @param init An optional initializer {@link InitDelegate|function} to transform the dependency before injection
|
* @param init Optional an initializer function to transform the dependency before injection
|
||||||
* or true to instantiate the dependency if it has a constructor.
|
* or true to instantiate the dependency if it has a constructor.
|
||||||
|
* @see {@link InitDelegate} for more information on initializer functions.
|
||||||
* @param necessary If true, throws an error if the dependency is not found.
|
* @param necessary If true, throws an error if the dependency is not found.
|
||||||
* @returns The resolved dependency or undefined if the dependency is not necessary
|
* @returns The resolved dependency or undefined if the dependency is not necessary
|
||||||
* and not found, or throws an error if the dependency is necessary and not found.
|
* and not found, or throws an error if the dependency is necessary and not found.
|
||||||
* @throws **Only throws errors if the dependency is necessary.**
|
* @throws **Only throws errors if the dependency is necessary.**
|
||||||
* @throws An {@link IdentifierRequiredError} if the identifier is not provided and the class name is not available.
|
|
||||||
* @throws A {@link DependencyResolutionError} if the dependency is not found.
|
* @throws A {@link DependencyResolutionError} if the dependency is not found.
|
||||||
* @throws A {@link InjectorError} if an error occurs during the injection process.
|
* @throws A {@link InjectorError} if an error occurs during the injection process.
|
||||||
* @throws A {@link NoInstantiationMethodError} if the dependency does not have a constructor.
|
* @throws A {@link NoInstantiationMethodError} if the dependency does not have a constructor.
|
||||||
@@ -41,83 +40,71 @@ import { InitDelegate } from '../types/InitDelegate';
|
|||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function Inject<TargetType, DependencyType, PropertyType>(
|
export function Inject<T, U>(
|
||||||
identifier?: Identifier,
|
identifier: Identifier,
|
||||||
init?: InitDelegate<DependencyType, PropertyType> | true,
|
init?: InitDelegate<T, U> | true,
|
||||||
necessary = true,
|
necessary = true,
|
||||||
) {
|
) {
|
||||||
return function (
|
return function (target: unknown, propertyKey: string | symbol): void {
|
||||||
constructor: undefined,
|
|
||||||
context: ClassFieldDecoratorContext<TargetType>,
|
|
||||||
): (
|
|
||||||
this: TargetType,
|
|
||||||
initialValue: PropertyType | undefined,
|
|
||||||
) => PropertyType {
|
|
||||||
const _identifier = identifier ?? context.name;
|
|
||||||
|
|
||||||
if (_identifier == null && necessary === true)
|
|
||||||
throw new IdentifierRequiredError();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to evaluate the dependency lazily
|
* Function to evaluate the dependency lazily
|
||||||
* to avoid circular dependencies, not found dependencies, etc.
|
* to avoid circular dependencies, not found dependencies, etc.
|
||||||
* @returns The resolved dependency or undefined if the dependency is not found.
|
* @returns The resolved dependency or undefined if the dependency is not found.
|
||||||
*/
|
*/
|
||||||
const resolve = (): DependencyType | undefined => {
|
const resolve = (): T | undefined => {
|
||||||
return TSinjex.getInstance().resolve<DependencyType>(
|
return TSinjex.getInstance().resolve<T>(identifier, necessary);
|
||||||
_identifier,
|
|
||||||
necessary,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return function (
|
Object.defineProperty(target, propertyKey, {
|
||||||
this: TargetType,
|
get() {
|
||||||
initialValue: PropertyType | undefined,
|
let instance: T | U | undefined;
|
||||||
): PropertyType {
|
|
||||||
let instance: DependencyType | PropertyType | undefined;
|
|
||||||
|
|
||||||
const dependency: DependencyType | undefined = tryAndCatch(
|
const dependency: T | undefined = tryAndCatch(
|
||||||
() => resolve(),
|
() => resolve(),
|
||||||
necessary,
|
necessary,
|
||||||
_identifier,
|
identifier,
|
||||||
DependencyResolutionError,
|
DependencyResolutionError,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (dependency != null) {
|
if (dependency != null) {
|
||||||
const initFunction: (() => PropertyType) | undefined =
|
const initFunction: (() => U) | undefined =
|
||||||
typeof init === 'function' && dependency != null
|
typeof init === 'function' && dependency != null
|
||||||
? (): PropertyType => init(dependency)
|
? (): U => init(dependency)
|
||||||
: init === true && hasConstructor(dependency)
|
: init === true && hasConstructor(dependency)
|
||||||
? (): PropertyType => new dependency() as PropertyType
|
? (): U => new dependency() as U
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
if (init == null) instance = dependency;
|
if (init == null) instance = dependency;
|
||||||
else if (initFunction != null)
|
else if (initFunction != null)
|
||||||
instance = tryAndCatch(
|
instance = tryAndCatch(
|
||||||
initFunction,
|
initFunction,
|
||||||
necessary,
|
necessary,
|
||||||
_identifier,
|
identifier,
|
||||||
InitializationError,
|
InitializationError,
|
||||||
);
|
);
|
||||||
else if (necessary)
|
else if (necessary)
|
||||||
throw new NoInstantiationMethodError(_identifier);
|
throw new NoInstantiationMethodError(identifier);
|
||||||
} else if (necessary)
|
} else if (necessary)
|
||||||
throw new DependencyResolutionError(_identifier);
|
throw new DependencyResolutionError(identifier);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace itself with the resolved dependency
|
||||||
|
* for performance reasons.
|
||||||
|
*/
|
||||||
|
Object.defineProperty(this, propertyKey, {
|
||||||
|
value: instance,
|
||||||
|
writable: false,
|
||||||
|
enumerable: false,
|
||||||
|
configurable: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Replace itself with the resolved dependency
|
* Make the property configurable to allow replacing it
|
||||||
* for performance reasons.
|
|
||||||
*/
|
*/
|
||||||
Object.defineProperty(this, context.name, {
|
configurable: true,
|
||||||
value: instance,
|
});
|
||||||
writable: false,
|
|
||||||
enumerable: false,
|
|
||||||
configurable: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
return instance as any;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
import { TSinjex } from '../classes/TSinjex';
|
import { TSinjex } from '../classes/TSinjex';
|
||||||
import { IdentifierRequiredError } from '../interfaces/Exceptions';
|
|
||||||
import { Identifier } from '../types/Identifier';
|
import { Identifier } from '../types/Identifier';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A decorator to register a class in the **TSinjex** DI (Dependency Injection) container.
|
* A decorator to register a class in the **TSinjex** DI (Dependency Injection) container.
|
||||||
* @template TargetType The type of the class to be registered.
|
* @template TargetType The type of the class to be registered.
|
||||||
* @param identifier The {@link Identifier|identifier} used to register the class in the DI container or the class name if not provided.
|
* @param identifier The identifier used to register the class in the DI container.
|
||||||
* @param deprecated If true, the dependency is deprecated and a warning is logged only once upon the first resolution of the dependency.
|
* @see {@link Identifier} for more information on identifiers.
|
||||||
|
* @param deprecated If true, the dependency is deprecated and a warning
|
||||||
|
* is logged only once upon the first resolution of the dependency.
|
||||||
* @returns The decorator function to be applied on the class.
|
* @returns The decorator function to be applied on the class.
|
||||||
* @throws An {@link IdentifierRequiredError} if the identifier is not provided and the class name is not available.
|
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
* \@Register('MyClassIdentifier')
|
* \@Register('MyClassIdentifier')
|
||||||
@@ -19,16 +19,12 @@ import { Identifier } from '../types/Identifier';
|
|||||||
*/
|
*/
|
||||||
export function Register<
|
export function Register<
|
||||||
TargetType extends new (...args: unknown[]) => InstanceType<TargetType>,
|
TargetType extends new (...args: unknown[]) => InstanceType<TargetType>,
|
||||||
>(identifier?: Identifier, deprecated?: boolean) {
|
>(identifier: Identifier, deprecated?: boolean) {
|
||||||
return function (
|
return function (constructor: TargetType, ...args: unknown[]): void {
|
||||||
constructor: TargetType,
|
// Get the instance of the DI container
|
||||||
context: ClassDecoratorContext<TargetType>,
|
|
||||||
) {
|
|
||||||
const _identifier = identifier ?? context.name;
|
|
||||||
|
|
||||||
if (_identifier == null) throw new IdentifierRequiredError();
|
|
||||||
|
|
||||||
const diContainer = TSinjex.getInstance();
|
const diContainer = TSinjex.getInstance();
|
||||||
diContainer.register(_identifier, constructor, deprecated);
|
|
||||||
|
// Register the class in the DI container
|
||||||
|
diContainer.register(identifier, constructor, deprecated);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,16 +1,16 @@
|
|||||||
import { TSinjex } from '../classes/TSinjex';
|
import { TSinjex } from '../classes/TSinjex';
|
||||||
import { IdentifierRequiredError } from '../interfaces/Exceptions';
|
|
||||||
import { Identifier } from '../types/Identifier';
|
import { Identifier } from '../types/Identifier';
|
||||||
import { InitDelegate } from '../types/InitDelegate';
|
import { InitDelegate } from '../types/InitDelegate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A decorator to register an instance of a class in the DI (Dependency Injection) container.
|
* A decorator to register an instance of a class in the DI (Dependency Injection) container.
|
||||||
* @template TargetType The type of the class whose instance is to be registered.
|
* @template TargetType The type of the class whose instance is to be registered.
|
||||||
* @param identifier The {@link Identifier|identifier} used to register the class in the DI container or the class name if not provided.
|
* @param identifier The identifier used to register the instance in the DI container.
|
||||||
* @param init An optional initializer {@link InitDelegate|function} which get the constructor of the class
|
* @see {@link Identifier} for more information on identifiers.
|
||||||
|
* @param init An optional initializer function which get the constructor of the class
|
||||||
* as input and returns an instance of the class.
|
* as input and returns an instance of the class.
|
||||||
|
* @see {@link InitDelegate} for more information on initializer functions.
|
||||||
* @returns The decorator function to be applied on the class.
|
* @returns The decorator function to be applied on the class.
|
||||||
* @throws An {@link IdentifierRequiredError} if the identifier is not provided and the class name is not available.
|
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
* \@RegisterInstance('MyClassInstanceIdentifier', (constructor) => new constructor())
|
* \@RegisterInstance('MyClassInstanceIdentifier', (constructor) => new constructor())
|
||||||
@@ -22,51 +22,43 @@ import { InitDelegate } from '../types/InitDelegate';
|
|||||||
export function RegisterInstance<
|
export function RegisterInstance<
|
||||||
TargetType extends new (..._args: unknown[]) => InstanceType<TargetType>,
|
TargetType extends new (..._args: unknown[]) => InstanceType<TargetType>,
|
||||||
>(
|
>(
|
||||||
identifier?: Identifier,
|
identifier: Identifier,
|
||||||
init?: InitDelegate<
|
init?: InitDelegate<
|
||||||
TargetType & { new (..._args: unknown[]): InstanceType<TargetType> },
|
TargetType & { new (..._args: unknown[]): InstanceType<TargetType> },
|
||||||
InstanceType<TargetType>
|
InstanceType<TargetType>
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
return function (
|
return function (constructor: TargetType, ...args: unknown[]): void {
|
||||||
constructor: TargetType,
|
// Get the instance of the DI container
|
||||||
context: ClassDecoratorContext<TargetType>,
|
|
||||||
): void {
|
|
||||||
const _identifier = identifier ?? context.name;
|
|
||||||
|
|
||||||
if (_identifier == null) throw new IdentifierRequiredError();
|
|
||||||
|
|
||||||
const diContainer = TSinjex.getInstance();
|
const diContainer = TSinjex.getInstance();
|
||||||
let instance: InstanceType<TargetType>;
|
let instance: InstanceType<TargetType>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the instance of the class
|
|
||||||
* and replace the lazy proxy with the instance
|
|
||||||
* for performance optimization.
|
|
||||||
*/
|
|
||||||
const getAndRegisterInstance = (): void => {
|
|
||||||
if (instance == null) {
|
|
||||||
if (init) {
|
|
||||||
instance = init(constructor);
|
|
||||||
} else {
|
|
||||||
instance = new constructor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
diContainer.register(_identifier, instance);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a proxy to instantiate the class when needed (Lazy Initialization)
|
// Create a proxy to instantiate the class when needed (Lazy Initialization)
|
||||||
const lazyProxy: unknown = new Proxy(
|
let lazyProxy: unknown = new Proxy(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
get(_target, prop, _receiver) {
|
get(target, prop, receiver) {
|
||||||
getAndRegisterInstance();
|
if (instance == null) {
|
||||||
|
if (init) {
|
||||||
|
instance = init(constructor);
|
||||||
|
} else {
|
||||||
|
instance = new constructor(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lazyProxy = instance;
|
||||||
|
|
||||||
// Return the requested property of the instance
|
// Return the requested property of the instance
|
||||||
return instance[prop as keyof InstanceType<TargetType>];
|
return instance[prop as keyof InstanceType<TargetType>];
|
||||||
},
|
},
|
||||||
set(_target, prop, value, _receiver) {
|
set(target, prop, value, receiver) {
|
||||||
getAndRegisterInstance();
|
if (instance == null) {
|
||||||
|
if (init) {
|
||||||
|
instance = init(constructor);
|
||||||
|
} else {
|
||||||
|
instance = new constructor(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lazyProxy = instance;
|
||||||
|
|
||||||
// Set the requested property of the instance
|
// Set the requested property of the instance
|
||||||
return (instance[prop as keyof InstanceType<TargetType>] =
|
return (instance[prop as keyof InstanceType<TargetType>] =
|
||||||
@@ -75,6 +67,7 @@ export function RegisterInstance<
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
diContainer.register(_identifier, lazyProxy);
|
// Register the lazy proxy in the DI container
|
||||||
|
diContainer.register(identifier, lazyProxy);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -15,19 +15,6 @@ export class TSinjexError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Error class for missing identifiers in {@link ITSinjex} methods.
|
|
||||||
*/
|
|
||||||
export class IdentifierRequiredError extends TSinjexError {
|
|
||||||
/**
|
|
||||||
* Creates a new instance of {@link IdentifierRequiredError}
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
super('Identifier is required.');
|
|
||||||
this.name = 'TSinjexIdentifierRequiredError';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error class for dependency resolution errors in {@link ITSinjex}.
|
* Error class for dependency resolution errors in {@link ITSinjex}.
|
||||||
* @see {@link ITSinjex.resolve}
|
* @see {@link ITSinjex.resolve}
|
||||||
|
@@ -18,6 +18,8 @@
|
|||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"strictPropertyInitialization": true,
|
"strictPropertyInitialization": true,
|
||||||
"lib": [
|
"lib": [
|
||||||
|
Reference in New Issue
Block a user