Compare commits
30 Commits
0.0.11
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
![]() |
eb8b105d1b | ||
2942e15fcf | |||
1d94b33542 | |||
08d58b2d41 | |||
19f7be1e3d | |||
![]() |
a490ea980a | ||
![]() |
d69eacf9be | ||
099c8dbfa3 | |||
6e6a521c1f | |||
ac99f7d306 | |||
58767b85f7 | |||
5e095d7b09 | |||
32126c0784 | |||
5df69c219e | |||
5bc9aef9ad | |||
ae9f25fe94 | |||
4a97a46aed | |||
e0542214c0 | |||
e6e304dfc7 | |||
75333b3310 | |||
ba9ec70c77 | |||
6f20913e4a | |||
c5ea21356c | |||
9a4fdecaac | |||
6f189942f6 | |||
27cdbeb37b | |||
567d1c5bd2 | |||
942e1079f6 | |||
37b2eb4da2 | |||
a8fd55befd |
38
.github/workflows/CreateRelease.yml
vendored
38
.github/workflows/CreateRelease.yml
vendored
@@ -4,6 +4,7 @@ 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.
|
||||||
@@ -22,20 +23,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: '20.8.0'
|
node-version: '20.8.0'
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install dependencies, run tests and build
|
||||||
run: npm install
|
run: npm run prepare:deploy
|
||||||
|
|
||||||
- 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
|
||||||
run: |
|
run: |
|
||||||
VERSION=$(npm run version:show | tail -n 1)
|
VERSION=$(npm run version:show | tail -n 1)
|
||||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
echo "VERSION=v$VERSION" >> $GITHUB_ENV
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Get previous release tag
|
- name: Get previous release tag
|
||||||
@@ -77,8 +72,9 @@ 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 "Release notes:"
|
echo "See [CHANGELOG.md](./CHANGELOG.md) for more details."
|
||||||
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
|
||||||
@@ -90,17 +86,6 @@ 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'
|
||||||
@@ -111,6 +96,15 @@ 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
|
||||||
@@ -118,6 +112,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: true
|
prerelease: ${{ env.PRE_RELEASE }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
2
.github/workflows/ValidateBranchName.yml
vendored
2
.github/workflows/ValidateBranchName.yml
vendored
@@ -3,7 +3,7 @@ name: Validate Branch Name on Pull Request
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- UNDEFINED
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
validate-branch-name-on-pull-request:
|
validate-branch-name-on-pull-request:
|
||||||
|
58
CHANGELOG.md
Normal file
58
CHANGELOG.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add pre release building to release workflow on dev/* branches an version changes.
|
||||||
|
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
|
||||||
|
## [0.0.14]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added **ChangeLog** file and format it according to [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||||
|
- Added reference to **Semantic Versioning** in the changelog file. (History will be updated on time).
|
||||||
|
- Version format is now `v0.0.0` instead of `0.0.0`. Changes to this are also reflected in the workflos.
|
||||||
|
- Add `Identifiers` and `Jest` Sections to the `README.md` file.
|
||||||
|
- feat: Add new Error `InitializationError` to reflect errors during initialization of a dependency.
|
||||||
|
- feat: Add initialization error handling and refactor Inject.
|
||||||
|
- feat: After injecting a dependency, the lazzy loading getter will be replaced with the dependency itself.
|
||||||
|
- feat: remove the use of a private property to store the injected dependencies. Now the dependencies are stored in the property itself.
|
||||||
|
- test: Add tests for the new features.
|
||||||
|
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
- Deprecated the old version format `0.0.0`.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[unreleased]: https://github.com/PxaMMaxP/TSinjex/compare/0.0.14...HEAD
|
||||||
|
[0.0.14]: https://github.com/PxaMMaxP/TSinjex/compare/0.0.13...v0.0.14
|
@@ -6,8 +6,14 @@
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
### Identifiers
|
||||||
|
|
||||||
|
Strings and symbols are possible for the **identifiers**.
|
||||||
|
|
||||||
### Jest
|
### Jest
|
||||||
|
|
||||||
|
For the use of TSinjex with Jest, the corresponding source files can be found under `./src` of the TSinjex node_module folder. To use these files, the `moduleNameMapper` must be configured in the Jest configuration file. The following example shows how to configure the Jest configuration file to use the source files of TSinjex.
|
||||||
|
|
||||||
#### Example jest setup
|
#### Example jest setup
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
50
package-lock.json
generated
50
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ts-injex",
|
"name": "ts-injex",
|
||||||
"version": "0.0.9",
|
"version": "0.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ts-injex",
|
"name": "ts-injex",
|
||||||
"version": "0.0.9",
|
"version": "0.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"@typescript-eslint/parser": "^8.1.0",
|
"@typescript-eslint/parser": "^8.1.0",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"eslint-plugin-deprecation": "^3.0.0",
|
"eslint-plugin-deprecation": "^3.0.0",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-jsdoc": "^50.2.2",
|
"eslint-plugin-jsdoc": "^50.2.2",
|
||||||
"eslint-plugin-override": "https://github.com/PxaMMaxP/eslint-plugin-override",
|
"eslint-plugin-override": "https://github.com/PxaMMaxP/eslint-plugin-override",
|
||||||
"istanbul-badges-readme": "^1.9.0",
|
"istanbul-badges-readme": "^1.9.0",
|
||||||
@@ -1187,6 +1187,12 @@
|
|||||||
"url": "https://opencollective.com/unts"
|
"url": "https://opencollective.com/unts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@rtsao/scc": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@shikijs/core": {
|
"node_modules/@shikijs/core": {
|
||||||
"version": "1.13.0",
|
"version": "1.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.13.0.tgz",
|
||||||
@@ -2995,9 +3001,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-module-utils": {
|
"node_modules/eslint-module-utils": {
|
||||||
"version": "2.8.1",
|
"version": "2.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz",
|
||||||
"integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==",
|
"integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^3.2.7"
|
"debug": "^3.2.7"
|
||||||
@@ -3157,34 +3163,36 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-import": {
|
"node_modules/eslint-plugin-import": {
|
||||||
"version": "2.29.1",
|
"version": "2.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz",
|
||||||
"integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==",
|
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"array-includes": "^3.1.7",
|
"@rtsao/scc": "^1.1.0",
|
||||||
"array.prototype.findlastindex": "^1.2.3",
|
"array-includes": "^3.1.8",
|
||||||
|
"array.prototype.findlastindex": "^1.2.5",
|
||||||
"array.prototype.flat": "^1.3.2",
|
"array.prototype.flat": "^1.3.2",
|
||||||
"array.prototype.flatmap": "^1.3.2",
|
"array.prototype.flatmap": "^1.3.2",
|
||||||
"debug": "^3.2.7",
|
"debug": "^3.2.7",
|
||||||
"doctrine": "^2.1.0",
|
"doctrine": "^2.1.0",
|
||||||
"eslint-import-resolver-node": "^0.3.9",
|
"eslint-import-resolver-node": "^0.3.9",
|
||||||
"eslint-module-utils": "^2.8.0",
|
"eslint-module-utils": "^2.12.0",
|
||||||
"hasown": "^2.0.0",
|
"hasown": "^2.0.2",
|
||||||
"is-core-module": "^2.13.1",
|
"is-core-module": "^2.15.1",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
"minimatch": "^3.1.2",
|
"minimatch": "^3.1.2",
|
||||||
"object.fromentries": "^2.0.7",
|
"object.fromentries": "^2.0.8",
|
||||||
"object.groupby": "^1.0.1",
|
"object.groupby": "^1.0.3",
|
||||||
"object.values": "^1.1.7",
|
"object.values": "^1.2.0",
|
||||||
"semver": "^6.3.1",
|
"semver": "^6.3.1",
|
||||||
|
"string.prototype.trimend": "^1.0.8",
|
||||||
"tsconfig-paths": "^3.15.0"
|
"tsconfig-paths": "^3.15.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8"
|
"eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-import/node_modules/debug": {
|
"node_modules/eslint-plugin-import/node_modules/debug": {
|
||||||
@@ -4153,9 +4161,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-core-module": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.15.0",
|
"version": "2.15.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
||||||
"integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==",
|
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hasown": "^2.0.2"
|
"hasown": "^2.0.2"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ts-injex",
|
"name": "ts-injex",
|
||||||
"version": "0.0.11",
|
"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,7 +22,8 @@
|
|||||||
"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",
|
||||||
@@ -39,7 +40,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^8.1.0",
|
"@typescript-eslint/eslint-plugin": "^8.1.0",
|
||||||
"@typescript-eslint/parser": "^8.1.0",
|
"@typescript-eslint/parser": "^8.1.0",
|
||||||
"eslint-plugin-deprecation": "^3.0.0",
|
"eslint-plugin-deprecation": "^3.0.0",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-jsdoc": "^50.2.2",
|
"eslint-plugin-jsdoc": "^50.2.2",
|
||||||
"eslint-plugin-override": "https://github.com/PxaMMaxP/eslint-plugin-override",
|
"eslint-plugin-override": "https://github.com/PxaMMaxP/eslint-plugin-override",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* 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 'src/interfaces/Exceptions';
|
import { DependencyResolutionError } from 'src/interfaces/Exceptions';
|
||||||
@@ -117,6 +118,130 @@ export function test_InjectDecorator(
|
|||||||
}
|
}
|
||||||
expect(_error).toBeInstanceOf(DependencyResolutionError);
|
expect(_error).toBeInstanceOf(DependencyResolutionError);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should replace the property with the resolved dependency', () => {
|
||||||
|
container.register('MockDependencyIdentifier', {
|
||||||
|
value: 'test-value',
|
||||||
|
});
|
||||||
|
|
||||||
|
class TestClass {
|
||||||
|
@Inject('MockDependencyIdentifier')
|
||||||
|
private readonly _dependency!: any;
|
||||||
|
|
||||||
|
public getDependency() {
|
||||||
|
return this._dependency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isDependencyTypeofFunction() {
|
||||||
|
return typeof this._dependency === 'function';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = new TestClass();
|
||||||
|
|
||||||
|
expect(instance.getDependency().value).toBe('test-value');
|
||||||
|
|
||||||
|
expect(instance.isDependencyTypeofFunction()).toBe(false);
|
||||||
|
expect(instance.getDependency().value).toBe('test-value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use a empty initializer when none is provided but true', () => {
|
||||||
|
container.register(
|
||||||
|
'MockDependencyIdentifier',
|
||||||
|
class X {
|
||||||
|
public value: string = 'test-value';
|
||||||
|
constructor() {}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
class TestClass {
|
||||||
|
@Inject('MockDependencyIdentifier', true)
|
||||||
|
private readonly _dependency!: any;
|
||||||
|
|
||||||
|
public getDependency() {
|
||||||
|
return this._dependency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = new TestClass();
|
||||||
|
expect(instance.getDependency().value).toBe('test-value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when the dependency has no instantiation method', () => {
|
||||||
|
container.register('MockDependencyIdentifier', {
|
||||||
|
value: 'test-value',
|
||||||
|
});
|
||||||
|
|
||||||
|
class TestClass {
|
||||||
|
@Inject('MockDependencyIdentifier', true)
|
||||||
|
private readonly _dependency!: any;
|
||||||
|
|
||||||
|
public getDependency() {
|
||||||
|
return this._dependency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
const instance = new TestClass();
|
||||||
|
instance.getDependency();
|
||||||
|
}).toThrow(new RegExp('No instantiation method found for.*'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw an error when the dependency has no instantiation method if not necessary', () => {
|
||||||
|
container.register('MockDependencyIdentifier', {
|
||||||
|
value: 'test-value',
|
||||||
|
});
|
||||||
|
|
||||||
|
class TestClass {
|
||||||
|
@Inject('MockDependencyIdentifier', true, false)
|
||||||
|
private readonly _dependency!: any;
|
||||||
|
|
||||||
|
public getDependency() {
|
||||||
|
return this._dependency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
const instance = new TestClass();
|
||||||
|
instance.getDependency();
|
||||||
|
}).not.toThrow(new RegExp('No instantiation method found for.*'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when the dependency cannot be resolved', () => {
|
||||||
|
container.register('MockDependencyIdentifier', null);
|
||||||
|
|
||||||
|
class TestClass {
|
||||||
|
@Inject('MockDependencyIdentifier', true)
|
||||||
|
private readonly _dependency!: any;
|
||||||
|
|
||||||
|
public getDependency() {
|
||||||
|
return this._dependency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
const instance = new TestClass();
|
||||||
|
instance.getDependency();
|
||||||
|
}).toThrow(new RegExp('.*could not be resolved.*'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw an error when the dependency cannot be resolved if not necessary', () => {
|
||||||
|
container.register('MockDependencyIdentifier', null);
|
||||||
|
|
||||||
|
class TestClass {
|
||||||
|
@Inject('MockDependencyIdentifier', true, false)
|
||||||
|
private readonly _dependency!: any;
|
||||||
|
|
||||||
|
public getDependency() {
|
||||||
|
return this._dependency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
const instance = new TestClass();
|
||||||
|
instance.getDependency();
|
||||||
|
}).not.toThrow(new RegExp('.*could not be resolved.*'));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* istanbul ignore file */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { ITSinjex, ITSinjex_ } from 'src/interfaces/ITSinjex';
|
import { ITSinjex, ITSinjex_ } from 'src/interfaces/ITSinjex';
|
||||||
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* istanbul ignore file */
|
||||||
import { ITSinjex_, ITSinjex } from '../interfaces/ITSinjex';
|
import { ITSinjex_, ITSinjex } from '../interfaces/ITSinjex';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { Identifier } from 'src/types/Identifier';
|
|
||||||
import type { Inject } from '../decorators/Inject';
|
import type { Inject } from '../decorators/Inject';
|
||||||
import type { Register } from '../decorators/Register';
|
import type { Register } from '../decorators/Register';
|
||||||
import type { RegisterInstance } from '../decorators/RegisterInstance';
|
import type { RegisterInstance } from '../decorators/RegisterInstance';
|
||||||
@@ -8,6 +7,7 @@ import { ImplementsStatic } from '../helper/ImplementsStatic';
|
|||||||
import { DependencyResolutionError } from '../interfaces/Exceptions';
|
import { DependencyResolutionError } from '../interfaces/Exceptions';
|
||||||
import { IDependency } from '../interfaces/IDependency';
|
import { IDependency } from '../interfaces/IDependency';
|
||||||
import { ITSinjex, ITSinjex_ } from '../interfaces/ITSinjex';
|
import { ITSinjex, ITSinjex_ } from '../interfaces/ITSinjex';
|
||||||
|
import { Identifier } from '../types/Identifier';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # TSinjex
|
* # TSinjex
|
||||||
@@ -114,7 +114,7 @@ export class TSinjex implements ITSinjex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dependency.deprecated) {
|
if (dependency.deprecated) {
|
||||||
console.warn(`Dependency ${identifier} is deprecated`);
|
console.warn(`Dependency ${identifier.toString()} is deprecated`);
|
||||||
|
|
||||||
// Remove the deprecation warning; it should only be logged once.
|
// Remove the deprecation warning; it should only be logged once.
|
||||||
dependency.deprecated = false;
|
dependency.deprecated = false;
|
||||||
|
@@ -1,5 +1,11 @@
|
|||||||
import { Identifier } from 'src/types/Identifier';
|
import {
|
||||||
|
DependencyResolutionError,
|
||||||
|
InitializationError,
|
||||||
|
InjectorError,
|
||||||
|
NoInstantiationMethodError,
|
||||||
|
} from 'src/interfaces/Exceptions';
|
||||||
import { TSinjex } from '../classes/TSinjex';
|
import { TSinjex } from '../classes/TSinjex';
|
||||||
|
import { Identifier } from '../types/Identifier';
|
||||||
import { InitDelegate } from '../types/InitDelegate';
|
import { InitDelegate } from '../types/InitDelegate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -8,12 +14,17 @@ import { InitDelegate } from '../types/InitDelegate';
|
|||||||
* @template U The type of the property to be injected.
|
* @template U The type of the property to be injected.
|
||||||
* @param identifier The identifier used to resolve the class in the DI container.
|
* @param identifier The identifier used to resolve the class in the DI container.
|
||||||
* @see {@link Identifier} for more information on identifiers.
|
* @see {@link Identifier} for more information on identifiers.
|
||||||
* @param init An optional initializer 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.
|
||||||
* @see {@link InitDelegate} for more information on initializer functions.
|
* @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 A {@link DependencyResolutionError} if the dependency is not found and necessary.
|
* @throws **Only throws errors if the dependency is necessary.**
|
||||||
|
* @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 NoInstantiationMethodError} if the dependency does not have a constructor.
|
||||||
|
* @throws An {@link InitializationError} if an error occurs during the initialization process.
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
* class MyClass {
|
* class MyClass {
|
||||||
@@ -31,47 +42,110 @@ import { InitDelegate } from '../types/InitDelegate';
|
|||||||
*/
|
*/
|
||||||
export function Inject<T, U>(
|
export function Inject<T, U>(
|
||||||
identifier: Identifier,
|
identifier: Identifier,
|
||||||
init?: InitDelegate<T, U>,
|
init?: InitDelegate<T, U> | true,
|
||||||
necessary = true,
|
necessary = true,
|
||||||
) {
|
) {
|
||||||
return function (target: unknown, propertyKey: string | symbol): void {
|
return function (target: unknown, propertyKey: string | symbol): void {
|
||||||
// Unique symbol to store the private property
|
/**
|
||||||
const privatePropertyKey: unique symbol = Symbol();
|
* Function to evaluate the dependency lazily
|
||||||
// Get the DI container instance
|
* to avoid circular dependencies, not found dependencies, etc.
|
||||||
const diContainer = TSinjex.getInstance();
|
* @returns The resolved dependency or undefined if the dependency is not found.
|
||||||
|
*/
|
||||||
// Function to evaluate the dependency lazily
|
const resolve = (): T | undefined => {
|
||||||
// to avoid circular dependencies, not found dependencies, etc.
|
return TSinjex.getInstance().resolve<T>(identifier, necessary);
|
||||||
const evaluate = (): T | undefined => {
|
|
||||||
return diContainer.resolve<T>(identifier, necessary);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Define the property
|
|
||||||
Object.defineProperty(target, propertyKey, {
|
Object.defineProperty(target, propertyKey, {
|
||||||
get() {
|
get() {
|
||||||
// If the property is not defined, evaluate the dependency
|
let instance: T | U | undefined;
|
||||||
if (!this.hasOwnProperty(privatePropertyKey)) {
|
|
||||||
if (init) {
|
|
||||||
try {
|
|
||||||
this[privatePropertyKey] = init(evaluate() as T);
|
|
||||||
} catch (error) {
|
|
||||||
if (necessary) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this[privatePropertyKey] = evaluate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this[privatePropertyKey];
|
const dependency: T | undefined = tryAndCatch(
|
||||||
|
() => resolve(),
|
||||||
|
necessary,
|
||||||
|
identifier,
|
||||||
|
DependencyResolutionError,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dependency != null) {
|
||||||
|
const initFunction: (() => U) | undefined =
|
||||||
|
typeof init === 'function' && dependency != null
|
||||||
|
? (): U => init(dependency)
|
||||||
|
: init === true && hasConstructor(dependency)
|
||||||
|
? (): U => new dependency() as U
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (init == null) instance = dependency;
|
||||||
|
else if (initFunction != null)
|
||||||
|
instance = tryAndCatch(
|
||||||
|
initFunction,
|
||||||
|
necessary,
|
||||||
|
identifier,
|
||||||
|
InitializationError,
|
||||||
|
);
|
||||||
|
else if (necessary)
|
||||||
|
throw new NoInstantiationMethodError(identifier);
|
||||||
|
} else if (necessary)
|
||||||
|
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;
|
||||||
},
|
},
|
||||||
// Not necessary to set the property
|
/**
|
||||||
// set(value: PropertieType) {
|
* Make the property configurable to allow replacing it
|
||||||
// this[privatePropertyKey] = value;
|
*/
|
||||||
// },
|
configurable: true,
|
||||||
enumerable: true,
|
|
||||||
configurable: false,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to execute a function and catches any errors that occur.
|
||||||
|
* If the function is necessary and an error occurs, it throws the error
|
||||||
|
* with the specified error class and identifier.
|
||||||
|
* @param fn The function to execute.
|
||||||
|
* @param necessary If true, throws an error if an error occurs.
|
||||||
|
* @param identifier The identifier of the dependency.
|
||||||
|
* @param errorClass The error class to throw if an error occurs.
|
||||||
|
* @returns The result of the function or undefined if an error occurs and the function is not necessary.
|
||||||
|
*/
|
||||||
|
function tryAndCatch<ReturnType, ErrorType>(
|
||||||
|
fn: () => ReturnType,
|
||||||
|
necessary: boolean,
|
||||||
|
identifier?: Identifier,
|
||||||
|
errorClass?: ErrorType,
|
||||||
|
): ReturnType | undefined {
|
||||||
|
try {
|
||||||
|
return fn();
|
||||||
|
} catch (error) {
|
||||||
|
if (necessary)
|
||||||
|
throw new (errorClass != null ? errorClass : error)(
|
||||||
|
identifier ?? 'not specified',
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
else return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an object has a constructor.
|
||||||
|
* @param obj The object to check.
|
||||||
|
* @returns True if the object has a constructor, false otherwise.
|
||||||
|
*/
|
||||||
|
function hasConstructor<T>(obj: T): obj is T & { new (): unknown } {
|
||||||
|
const _obj = obj as unknown as { prototype?: { constructor?: unknown } };
|
||||||
|
|
||||||
|
return (
|
||||||
|
_obj?.prototype != null &&
|
||||||
|
typeof _obj.prototype.constructor === 'function'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Identifier } from 'src/types/Identifier';
|
|
||||||
import { TSinjex } from '../classes/TSinjex';
|
import { TSinjex } from '../classes/TSinjex';
|
||||||
|
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.
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Identifier } from 'src/types/Identifier';
|
|
||||||
import { TSinjex } from '../classes/TSinjex';
|
import { TSinjex } from '../classes/TSinjex';
|
||||||
|
import { Identifier } from '../types/Identifier';
|
||||||
import { InitDelegate } from '../types/InitDelegate';
|
import { InitDelegate } from '../types/InitDelegate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Identifier } from 'src/types/Identifier';
|
|
||||||
import { TSinjex } from '../classes/TSinjex';
|
import { TSinjex } from '../classes/TSinjex';
|
||||||
|
import { Identifier } from '../types/Identifier';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a dependency.
|
* Register a dependency.
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Identifier } from 'src/types/Identifier';
|
|
||||||
import { TSinjex } from '../classes/TSinjex';
|
import { TSinjex } from '../classes/TSinjex';
|
||||||
import { DependencyResolutionError } from '../interfaces/Exceptions';
|
import { DependencyResolutionError } from '../interfaces/Exceptions';
|
||||||
|
import { Identifier } from '../types/Identifier';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve a dependency.
|
* Resolve a dependency.
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { Identifier } from 'src/types/Identifier';
|
||||||
import { ITSinjex } from './ITSinjex';
|
import { ITSinjex } from './ITSinjex';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,8 +24,61 @@ export class DependencyResolutionError extends TSinjexError {
|
|||||||
* Creates a new instance of {@link DependencyResolutionError}
|
* Creates a new instance of {@link DependencyResolutionError}
|
||||||
* @param identifier **The identifier of the dependency**
|
* @param identifier **The identifier of the dependency**
|
||||||
*/
|
*/
|
||||||
constructor(identifier: string) {
|
constructor(identifier: Identifier) {
|
||||||
super(`Dependency ${identifier} could not be resolved.`);
|
super(`Dependency ${identifier.toString()} could not be resolved.`);
|
||||||
this.name = 'TSinjexResolutionError';
|
this.name = 'TSinjexResolutionError';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error class for Injector errors in {@link ITSinjex}.
|
||||||
|
* @see {@link ITSinjex.inject}
|
||||||
|
*/
|
||||||
|
export class InjectorError extends TSinjexError {
|
||||||
|
/**
|
||||||
|
* Creates a new instance of {@link InjectorError}
|
||||||
|
* @param identifier **The identifier of the dependency**
|
||||||
|
* @param originalError **The original error that caused the injection error**
|
||||||
|
*/
|
||||||
|
constructor(identifier: Identifier, originalError?: Error) {
|
||||||
|
super(
|
||||||
|
`Error injecting dependency ${identifier.toString()} with error: "${originalError}"`,
|
||||||
|
);
|
||||||
|
this.name = 'TSinjexInjectorError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error class for missing instantiation methods in {@link ITSinjex}.
|
||||||
|
* @see {@link ITSinjex.inject}
|
||||||
|
*/
|
||||||
|
export class NoInstantiationMethodError extends TSinjexError {
|
||||||
|
/**
|
||||||
|
* Creates a new instance of {@link NoInstantiationMethodError}
|
||||||
|
* @param identifier **The identifier of the dependency**
|
||||||
|
*/
|
||||||
|
constructor(identifier: Identifier) {
|
||||||
|
super(
|
||||||
|
`No instantiation method found for dependency ${identifier.toString()}.`,
|
||||||
|
);
|
||||||
|
this.name = 'TSinjexNoInstantiationMethodError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error class for errors during the initialization of a dependency in {@link ITSinjex}.
|
||||||
|
* @see {@link ITSinjex.inject}
|
||||||
|
*/
|
||||||
|
export class InitializationError extends TSinjexError {
|
||||||
|
/**
|
||||||
|
* Creates a new instance of {@link InitializationError}
|
||||||
|
* @param identifier **The identifier of the dependency**
|
||||||
|
* @param originalError **The original error that caused the initialization error**
|
||||||
|
*/
|
||||||
|
constructor(identifier: Identifier, originalError?: Error) {
|
||||||
|
super(
|
||||||
|
`Error initializing dependency ${identifier.toString()} with error: "${originalError}"`,
|
||||||
|
);
|
||||||
|
this.name = 'TSinjexInitializationError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Identifier } from 'src/types/Identifier';
|
|
||||||
import { DependencyResolutionError } from './Exceptions';
|
import { DependencyResolutionError } from './Exceptions';
|
||||||
|
import { Identifier } from '../types/Identifier';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static TSInjex Interface
|
* Static TSInjex Interface
|
||||||
|
@@ -8,4 +8,4 @@
|
|||||||
* I.e. a class `ClassA` that implements the interface `IClassA` and is
|
* I.e. a class `ClassA` that implements the interface `IClassA` and is
|
||||||
* registered as a dependent class is registered under the interface name `IClassA`.
|
* registered as a dependent class is registered under the interface name `IClassA`.
|
||||||
*/
|
*/
|
||||||
export type Identifier = string;
|
export type Identifier = string | symbol;
|
||||||
|
Reference in New Issue
Block a user