Compare commits
52 Commits
Author | SHA1 | Date | |
---|---|---|---|
a70869a941 | |||
7a6253a386 | |||
31de73db00 | |||
688a9d4ee4 | |||
39dbd6d816 | |||
0718ff9d68 | |||
115b3181e0 | |||
c7fa78270c | |||
3401656219 | |||
7e7fd996b3 | |||
a6fabc329b | |||
81fb7f0071 | |||
2e89cdd619 | |||
b157f48261 | |||
bbf68bff34 | |||
b994a074c0 | |||
1da8efff94 | |||
c6e9fbd2a3 | |||
9bd899581f | |||
b43e4ee12f | |||
ef4a2295bf | |||
f4f8c7b78e | |||
beaad4f65e | |||
![]() |
95ef003e99 | ||
9660d77e0c | |||
dce76a5fb3 | |||
23e6248299 | |||
d8fb82943f | |||
2942e15fcf | |||
1d94b33542 | |||
08d58b2d41 | |||
19f7be1e3d | |||
![]() |
a490ea980a | ||
![]() |
d69eacf9be | ||
099c8dbfa3 | |||
6e6a521c1f | |||
ac99f7d306 | |||
58767b85f7 | |||
5e095d7b09 | |||
32126c0784 | |||
5df69c219e | |||
5bc9aef9ad | |||
ae9f25fe94 | |||
4a97a46aed | |||
e0542214c0 | |||
e6e304dfc7 | |||
75333b3310 | |||
ba9ec70c77 | |||
6f20913e4a | |||
c5ea21356c | |||
9a4fdecaac | |||
6f189942f6 |
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:
|
||||||
|
156
CHANGELOG.md
Normal file
156
CHANGELOG.md
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
## [1.2.0]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- docs: added complete Typedoc-style documentation for all `inject()` overloads
|
||||||
|
Includes parameter descriptions, usage examples, and detailed error annotations for each variant.
|
||||||
|
|
||||||
|
## [1.1.0]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- feat: exported `inject()` function from the public API via `index.ts`
|
||||||
|
The function is now available as part of the main module exports.
|
||||||
|
|
||||||
|
## [1.0.0]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- feat: Enable native ESM support using `"type": "module"` and `moduleResolution: "NodeNext"` in the compiler settings.
|
||||||
|
- feat: All internal imports now explicitly include `.js` extensions for full Node.js ESM compatibility.
|
||||||
|
- feat: Updated `tsconfig.json` to reflect changes for ESM builds (`module: "NodeNext"`, `target: "ES2020"`, etc.).
|
||||||
|
- feat(cli): add `--without-extension` (`-x`) flag to optionally omit file extensions in generated import paths
|
||||||
|
- feat: introduce `inject()` function as a programmatic alternative to the `@Inject` decorator
|
||||||
|
Supports optional initializers and constructor instantiation for resolved dependencies.
|
||||||
|
Designed for cases where decorators are not suitable or dynamic resolution is needed.
|
||||||
|
- test: added comprehensive test suite for `inject()` function, covering resolution, initialization, error cases and instantiation behavior
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- All source files using relative or internal imports were updated to use `.js` extensions to support Node.js ESM runtime resolution.
|
||||||
|
- test: update Jest config for ts-jest ESM compatibility and .js import support
|
||||||
|
- renamed internal parameter `necessary` → `isNecessary` for naming clarity
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed implicit support for CommonJS-style imports without file extensions.
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
- Support for CommonJS consumers using `require()` is no longer available. Use `import` with an ESM-compatible environment instead.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
### ⚠️ Breaking Changes
|
||||||
|
|
||||||
|
- **BREAKING CHANGE**: This version migrates the entire codebase to native ES modules.
|
||||||
|
- Consumers must use Node.js in ESM mode or compatible bundlers.
|
||||||
|
- Import paths now include `.js` extensions.
|
||||||
|
- Using `require()` (CommonJS) to load this library will no longer work.
|
||||||
|
- All consuming projects must either:
|
||||||
|
- Use `"type": "module"` in their `package.json`, or
|
||||||
|
- Use an ESM-aware bundler (e.g. Webpack, Vite, etc.)
|
||||||
|
|
||||||
|
## [0.4.0]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- feat: Export ImplementsStatic helper function
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
## [0.3.0]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- refactor: consolidate registration decorators
|
||||||
|
Introduced Register decorator to handle class and instance registration in the DI container.
|
||||||
|
Deprecated RegisterInstance in favor of Register, which now internally handles instance registration.
|
||||||
|
Added support for marking dependencies as deprecated with a warning logged upon first resolution.
|
||||||
|
Updated documentation with examples and notes on deprecation.
|
||||||
|
- tests: add mode parameter to RegisterInstanceDecorator
|
||||||
|
Introduced a mode parameter to the test_RegisterInstanceDecorator function allowing 'instance' or 'standalone' modes.
|
||||||
|
Updated test cases to utilize the new mode parameter when registering an instance.
|
||||||
|
Disabled specific ESLint rule in Decorators.test.ts for deprecation warnings.
|
||||||
|
Added an additional test call to test_RegisterInstanceDecorator with 'instance' mode.
|
||||||
|
- refactor: add region tags for overloads in Register.ts
|
||||||
|
|
||||||
|
## [0.2.0]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add pre release building to release workflow on dev/\* branches an version changes.
|
||||||
|
- feat: Introduced a new CLI command `tsinjex-generate` to automate the generation of import statements for registered dependencies.
|
||||||
|
The command scans `.ts` files for `@Register` and `@RegisterInstance` decorators and generates an `auto-imports.ts` file.
|
||||||
|
This ensures that all registered dependencies are automatically included without requiring manual imports.
|
||||||
|
The CLI can be executed via `npx tsinjex-generate` or added as a script in `package.json` for easier integration.
|
||||||
|
|
||||||
|
### 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/20Max01/TSinjex/compare/v1.0.0...HEAD
|
||||||
|
[1.1.0]: https://github.com/20Max01/TSinjex/compare/v1.1.0...v1.2.0
|
||||||
|
[1.1.0]: https://github.com/20Max01/TSinjex/compare/v1.0.0...v1.1.0
|
||||||
|
[1.0.0]: https://github.com/20Max01/TSinjex/compare/v0.4.0...v1.0.0
|
||||||
|
[0.4.0]: https://github.com/20Max01/TSinjex/compare/v0.3.0...v0.4.0
|
||||||
|
[0.3.0]: https://github.com/20Max01/TSinjex/compare/v0.2.0...v0.3.0
|
||||||
|
[0.2.0]: https://github.com/20Max01/TSinjex/compare/v0.0.14...v0.2.0
|
||||||
|
[0.0.14]: https://github.com/20Max01/TSinjex/compare/v0.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
|
||||||
|
122
bin/generate-imports.cjs
Normal file
122
bin/generate-imports.cjs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const yargs = require('yargs');
|
||||||
|
|
||||||
|
// CLI argument parsing
|
||||||
|
const argv = yargs
|
||||||
|
.option('src', {
|
||||||
|
alias: 's',
|
||||||
|
type: 'string',
|
||||||
|
description: 'Directory to search for files',
|
||||||
|
default: 'src',
|
||||||
|
})
|
||||||
|
.option('output', {
|
||||||
|
alias: 'o',
|
||||||
|
type: 'string',
|
||||||
|
description: 'Path to the output file',
|
||||||
|
default: 'src/auto-imports.ts',
|
||||||
|
})
|
||||||
|
.option('pattern', {
|
||||||
|
alias: 'p',
|
||||||
|
type: 'string',
|
||||||
|
description: 'File pattern to search for (e.g., .ts, .js)',
|
||||||
|
default: '.ts',
|
||||||
|
})
|
||||||
|
.option('without-extension', {
|
||||||
|
alias: 'x',
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Omit file extension in import paths',
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
.help()
|
||||||
|
.argv;
|
||||||
|
|
||||||
|
// Fixed RegEx patterns for decorator detection
|
||||||
|
const SEARCH_PATTERNS = [
|
||||||
|
/^@Register(?:<(.+)?>)?\(\s*["']{1}(.+)?["']{1}\s*,?\s*(true|false)?\s*\)/m,
|
||||||
|
/^@RegisterInstance(?:<(.+)?>)?\(\s*["']{1}(.+)?["']{1}\s*,?\s*(.+)?\s*\)/m,
|
||||||
|
];
|
||||||
|
|
||||||
|
const FILE_PATTERN = argv.pattern.startsWith('.') ? argv.pattern : `.${argv.pattern}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively collects all files with a specific extension.
|
||||||
|
* @param {string} dirPath - Root directory
|
||||||
|
* @returns {string[]} List of file paths
|
||||||
|
*/
|
||||||
|
function getAllFiles(dirPath) {
|
||||||
|
let files = fs.readdirSync(dirPath);
|
||||||
|
let arrayOfFiles = [];
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
const fullPath = path.join(dirPath, file);
|
||||||
|
if (fs.statSync(fullPath).isDirectory()) {
|
||||||
|
arrayOfFiles = arrayOfFiles.concat(getAllFiles(fullPath));
|
||||||
|
} else if (file.endsWith(FILE_PATTERN)) {
|
||||||
|
arrayOfFiles.push(fullPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return arrayOfFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks files for decorator usage.
|
||||||
|
* @param {string[]} files
|
||||||
|
* @returns {string[]} Filtered files
|
||||||
|
*/
|
||||||
|
function findFilesWithPattern(files) {
|
||||||
|
return files.filter((file) => {
|
||||||
|
const content = fs.readFileSync(file, 'utf8');
|
||||||
|
return SEARCH_PATTERNS.some((pattern) => pattern.test(content));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates ES-style import statements from file paths.
|
||||||
|
* @param {string[]} files
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function generateImports(files) {
|
||||||
|
return files.map((file) => {
|
||||||
|
const relative = './' + path.relative(argv.src, file).replace(/\\/g, '/');
|
||||||
|
const noExt = relative.replace(FILE_PATTERN, '');
|
||||||
|
const finalPath = argv['without-extension'] ? noExt : `${noExt}${FILE_PATTERN}`;
|
||||||
|
return `import '${finalPath}';`;
|
||||||
|
}).join('\n') + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Script entry point.
|
||||||
|
*/
|
||||||
|
function main() {
|
||||||
|
try {
|
||||||
|
const srcDir = path.resolve(process.cwd(), argv.src);
|
||||||
|
const outputFile = path.resolve(process.cwd(), argv.output);
|
||||||
|
|
||||||
|
if (!fs.existsSync(srcDir)) {
|
||||||
|
console.error(`❌ Error: The directory '${srcDir}' does not exist.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const allFiles = getAllFiles(srcDir);
|
||||||
|
const filesWithPattern = findFilesWithPattern(allFiles);
|
||||||
|
|
||||||
|
if (filesWithPattern.length === 0) {
|
||||||
|
console.log(`ℹ️ No ${FILE_PATTERN} files found matching the specified decorator patterns.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const importContent = generateImports(filesWithPattern);
|
||||||
|
fs.writeFileSync(outputFile, importContent);
|
||||||
|
|
||||||
|
console.log(`✅ Imports successfully generated: ${outputFile}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ An error occurred:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
@@ -2,11 +2,17 @@ module.exports = {
|
|||||||
setupFilesAfterEnv: ['./scripts/jest.setup.js'],
|
setupFilesAfterEnv: ['./scripts/jest.setup.js'],
|
||||||
preset: 'ts-jest',
|
preset: 'ts-jest',
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
|
extensionsToTreatAsEsm: ['.ts'],
|
||||||
|
transform: {
|
||||||
|
'^.+\\.ts$': ['ts-jest', { useESM: true }],
|
||||||
|
},
|
||||||
testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(test).ts'],
|
testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(test).ts'],
|
||||||
testPathIgnorePatterns: ['\\.spec\\.ts$', '\\.performance\\.test\\.ts$'],
|
testPathIgnorePatterns: ['\\.spec\\.ts$', '\\.performance\\.test\\.ts$'],
|
||||||
moduleDirectories: ['node_modules', 'src'],
|
moduleDirectories: ['node_modules', 'src'],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
|
'^src/(.*)\\.js$': '<rootDir>/src/$1',
|
||||||
'^src/(.*)$': '<rootDir>/src/$1',
|
'^src/(.*)$': '<rootDir>/src/$1',
|
||||||
|
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||||
},
|
},
|
||||||
collectCoverage: true,
|
collectCoverage: true,
|
||||||
coverageDirectory: '.locale/coverage',
|
coverageDirectory: '.locale/coverage',
|
||||||
|
@@ -1,23 +1,32 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
setupFilesAfterEnv: ['./scripts/jest.setup.js'],
|
setupFilesAfterEnv: ['./scripts/jest.setup.js'],
|
||||||
preset: 'ts-jest',
|
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.ts$': ['ts-jest', { useESM: true }],
|
||||||
|
},
|
||||||
|
extensionsToTreatAsEsm: ['.ts'],
|
||||||
testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(test).ts'],
|
testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(test).ts'],
|
||||||
testPathIgnorePatterns: ['\\.spec\\.ts$', '\\.performance\\.test\\.ts$'],
|
testPathIgnorePatterns: ['\\.spec\\.ts$', '\\.performance\\.test\\.ts$'],
|
||||||
moduleDirectories: ['node_modules', 'src'],
|
moduleDirectories: ['node_modules', 'src'],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
|
'^src/(.*)\\.js$': '<rootDir>/src/$1',
|
||||||
'^src/(.*)$': '<rootDir>/src/$1',
|
'^src/(.*)$': '<rootDir>/src/$1',
|
||||||
|
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||||
},
|
},
|
||||||
collectCoverage: true,
|
collectCoverage: true,
|
||||||
coverageDirectory: '.locale/coverage',
|
coverageDirectory: '.locale/coverage',
|
||||||
coverageReporters: ['text', ['lcov', { projectRoot: '..' }], 'json-summary'],
|
coverageReporters: [
|
||||||
|
'text',
|
||||||
|
['lcov', { projectRoot: '..' }],
|
||||||
|
'json-summary',
|
||||||
|
],
|
||||||
collectCoverageFrom: [
|
collectCoverageFrom: [
|
||||||
'src/**/*.{ts,tsx}',
|
'src/**/*.{ts,tsx}',
|
||||||
'!src/**/*.d.ts',
|
'!src/**/*.d.ts',
|
||||||
'!src/**/*.performance.test.ts',
|
'!src/**/*.performance.test.ts',
|
||||||
'!src/**/*.spec.ts',
|
'!src/**/*.spec.ts',
|
||||||
'!src/**/*.test.ts',
|
'!src/**/*.test.ts',
|
||||||
'!src/auto-imports.ts'
|
'!src/auto-imports.ts',
|
||||||
],
|
],
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
|
7
package-lock.json
generated
7
package-lock.json
generated
@@ -1,17 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "ts-injex",
|
"name": "ts-injex",
|
||||||
"version": "0.0.9",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ts-injex",
|
"name": "ts-injex",
|
||||||
"version": "0.0.9",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"jest-environment-jsdom": "^29.7.0"
|
"jest-environment-jsdom": "^29.7.0"
|
||||||
},
|
},
|
||||||
|
"bin": {
|
||||||
|
"tsinjex-generate": "bin/generate-imports.cjs"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/eslint-plugin": "^2.6.2",
|
"@stylistic/eslint-plugin": "^2.6.2",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
|
@@ -1,10 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "ts-injex",
|
"name": "ts-injex",
|
||||||
"version": "0.0.13",
|
"version": "1.2.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",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
|
"bin": {
|
||||||
|
"tsinjex-generate": "./bin/generate-imports.cjs"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "npm run build",
|
"prepare": "npm run build",
|
||||||
"build": "npm run build:tsc",
|
"build": "npm run build:tsc",
|
||||||
@@ -22,7 +25,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",
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
|
/* 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 '../decorators/Inject.js';
|
||||||
import { DependencyResolutionError } from 'src/interfaces/Exceptions';
|
import { DependencyResolutionError } from '../interfaces/Exceptions.js';
|
||||||
import { ForceConstructor } from 'src/types/GenericContructor';
|
import { ITSinjex_, ITSinjex } from '../interfaces/ITSinjex.js';
|
||||||
import { ITSinjex_, ITSinjex } from '../interfaces/ITSinjex';
|
import { ForceConstructor } from '../types/GenericContructor.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the Inject decorator.
|
* Test the Inject decorator.
|
||||||
@@ -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.*'));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +282,7 @@ export function test_RegisterInstanceDecorator(
|
|||||||
Container: ITSinjex_,
|
Container: ITSinjex_,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
registerInstance: Function,
|
registerInstance: Function,
|
||||||
|
mode: 'instance' | 'standalone' = 'standalone',
|
||||||
): void {
|
): void {
|
||||||
describe('RegisterInstance Decorator Tests', () => {
|
describe('RegisterInstance Decorator Tests', () => {
|
||||||
let container: ITSinjex;
|
let container: ITSinjex;
|
||||||
@@ -170,7 +296,10 @@ export function test_RegisterInstanceDecorator(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should register an instance of a dependency', () => {
|
it('should register an instance of a dependency', () => {
|
||||||
@registerInstance('InstanceIdentifier')
|
@registerInstance(
|
||||||
|
'InstanceIdentifier',
|
||||||
|
mode === 'instance' ? 'instance' : undefined,
|
||||||
|
)
|
||||||
class TestClass {
|
class TestClass {
|
||||||
private readonly _dependency!: any;
|
private readonly _dependency!: any;
|
||||||
|
|
||||||
@@ -212,7 +341,10 @@ export function test_RegisterInstanceDecorator(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should register an instance of a dependency and get it on set', () => {
|
it('should register an instance of a dependency and get it on set', () => {
|
||||||
@registerInstance('InstanceIdentifier')
|
@registerInstance(
|
||||||
|
'InstanceIdentifier',
|
||||||
|
mode === 'instance' ? 'instance' : undefined,
|
||||||
|
)
|
||||||
class TestClass {
|
class TestClass {
|
||||||
private readonly _dependency!: any;
|
private readonly _dependency!: any;
|
||||||
|
|
||||||
|
@@ -1,15 +1,18 @@
|
|||||||
import { TSinjex } from 'src/classes/TSinjex';
|
/* eslint-disable deprecation/deprecation */
|
||||||
import { Inject } from 'src/decorators/Inject';
|
import { TSinjex } from 'src/classes/TSinjex.js';
|
||||||
import { Register } from 'src/decorators/Register';
|
import { Inject } from 'src/decorators/Inject.js';
|
||||||
import { RegisterInstance } from 'src/decorators/RegisterInstance';
|
import { Register } from 'src/decorators/Register.js';
|
||||||
|
import { RegisterInstance } from 'src/decorators/RegisterInstance.js';
|
||||||
import {
|
import {
|
||||||
test_InjectDecorator,
|
test_InjectDecorator,
|
||||||
test_RegisterDecorator,
|
test_RegisterDecorator,
|
||||||
test_RegisterInstanceDecorator,
|
test_RegisterInstanceDecorator,
|
||||||
} from './Decorators.spec';
|
} from './Decorators.spec.js';
|
||||||
|
|
||||||
test_InjectDecorator(TSinjex, Inject);
|
test_InjectDecorator(TSinjex, Inject);
|
||||||
|
|
||||||
test_RegisterDecorator(TSinjex, Register);
|
test_RegisterDecorator(TSinjex, Register);
|
||||||
|
|
||||||
test_RegisterInstanceDecorator(TSinjex, RegisterInstance);
|
test_RegisterInstanceDecorator(TSinjex, RegisterInstance);
|
||||||
|
|
||||||
|
test_RegisterInstanceDecorator(TSinjex, Register, 'instance');
|
||||||
|
@@ -1,5 +1,11 @@
|
|||||||
|
/* 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 {
|
||||||
|
DependencyResolutionError,
|
||||||
|
InitializationError,
|
||||||
|
NoInstantiationMethodError,
|
||||||
|
} from '../interfaces/Exceptions.js';
|
||||||
|
import { ITSinjex, ITSinjex_ } from '../interfaces/ITSinjex.js';
|
||||||
|
|
||||||
export function test_RegisterFunction(
|
export function test_RegisterFunction(
|
||||||
Container: ITSinjex_,
|
Container: ITSinjex_,
|
||||||
@@ -68,3 +74,111 @@ export function test_ResolveFunction(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the inject function.
|
||||||
|
* @param Container The DI container implementation to test against.
|
||||||
|
* @param inject The inject function to test.
|
||||||
|
*/
|
||||||
|
export function test_injectFunction(
|
||||||
|
Container: ITSinjex_,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
|
inject: Function,
|
||||||
|
): void {
|
||||||
|
describe('inject Function Tests', () => {
|
||||||
|
let container: ITSinjex;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Reset singleton
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(Container as any)['_instance'] = undefined;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(Container as any)['_dependencies'] = undefined;
|
||||||
|
container = Container.getInstance();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve and return the dependency as is', () => {
|
||||||
|
container.register('SimpleDep', { value: 'test' });
|
||||||
|
|
||||||
|
const resolved = inject('SimpleDep');
|
||||||
|
expect(resolved.value).toBe('test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve and run the initializer function', () => {
|
||||||
|
container.register('DepWithInit', { value: 'before' });
|
||||||
|
|
||||||
|
const resolved = inject('DepWithInit', (dep: any) => {
|
||||||
|
dep.value = 'after';
|
||||||
|
|
||||||
|
return dep;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(resolved.value).toBe('after');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve and instantiate the dependency if init is true and constructor exists', () => {
|
||||||
|
class WithConstructor {
|
||||||
|
value = 'constructed';
|
||||||
|
}
|
||||||
|
|
||||||
|
container.register('Constructable', WithConstructor);
|
||||||
|
|
||||||
|
const resolved = inject('Constructable', true);
|
||||||
|
expect(resolved.value).toBe('constructed');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if dependency is not found and not necessary', () => {
|
||||||
|
const resolved = inject('NonExistentDep', undefined, false);
|
||||||
|
expect(resolved).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw DependencyResolutionError if dependency is not found and necessary', () => {
|
||||||
|
expect(() => inject('MissingDep')).toThrow(
|
||||||
|
DependencyResolutionError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw InitializationError if init function throws', () => {
|
||||||
|
container.register('InitThrows', {});
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
inject('InitThrows', () => {
|
||||||
|
throw new Error('fail');
|
||||||
|
}),
|
||||||
|
).toThrow(InitializationError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NoInstantiationMethodError if init = true and no constructor exists', () => {
|
||||||
|
container.register('NonConstructable', {});
|
||||||
|
|
||||||
|
expect(() => inject('NonConstructable', true)).toThrow(
|
||||||
|
NoInstantiationMethodError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw if no constructor and necessary = false', () => {
|
||||||
|
container.register('SafeSkip', {});
|
||||||
|
expect(() => inject('SafeSkip', true, false)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if initializer fails and not necessary', () => {
|
||||||
|
container.register('InitErrorOptional', {});
|
||||||
|
|
||||||
|
const result = inject(
|
||||||
|
'InitErrorOptional',
|
||||||
|
() => {
|
||||||
|
throw new Error('ignored');
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if dependency is null and not necessary', () => {
|
||||||
|
container.register('NullDep', null);
|
||||||
|
const result = inject('NullDep', true, false);
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@@ -1,7 +1,13 @@
|
|||||||
import { TSinjex } from 'src/classes/TSinjex';
|
import {
|
||||||
import { register } from 'src/functions/register';
|
test_injectFunction,
|
||||||
import { resolve } from 'src/functions/resolve';
|
test_RegisterFunction,
|
||||||
import { test_RegisterFunction, test_ResolveFunction } from './Functions.spec';
|
test_ResolveFunction,
|
||||||
|
} from './Functions.spec.js';
|
||||||
|
import { TSinjex } from '../classes/TSinjex.js';
|
||||||
|
import { inject } from '../functions/inject.js';
|
||||||
|
import { register } from '../functions/register.js';
|
||||||
|
import { resolve } from '../functions/resolve.js';
|
||||||
|
|
||||||
test_RegisterFunction(TSinjex, register);
|
test_RegisterFunction(TSinjex, register);
|
||||||
test_ResolveFunction(TSinjex, resolve);
|
test_ResolveFunction(TSinjex, resolve);
|
||||||
|
test_injectFunction(TSinjex, inject);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { ITSinjex_, ITSinjex } from '../interfaces/ITSinjex';
|
/* istanbul ignore file */
|
||||||
|
import { ITSinjex_, ITSinjex } from '../interfaces/ITSinjex.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the implementation of the `ITSinjex` interface.
|
* Test the implementation of the `ITSinjex` interface.
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { test_ITSinjex } from './ITSinjex.spec';
|
import { test_ITSinjex } from './ITSinjex.spec.js';
|
||||||
import { TSinjex } from '../classes/TSinjex';
|
import { TSinjex } from '../classes/TSinjex.js';
|
||||||
|
|
||||||
test_ITSinjex(TSinjex);
|
test_ITSinjex(TSinjex);
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import type { Inject } from '../decorators/Inject';
|
import type { Inject } from '../decorators/Inject.js';
|
||||||
import type { Register } from '../decorators/Register';
|
import type { Register } from '../decorators/Register.js';
|
||||||
import type { RegisterInstance } from '../decorators/RegisterInstance';
|
import type { RegisterInstance } from '../decorators/RegisterInstance.js';
|
||||||
import type { register } from '../functions/register';
|
import type { register } from '../functions/register.js';
|
||||||
import type { resolve } from '../functions/resolve';
|
import type { resolve } from '../functions/resolve.js';
|
||||||
import { ImplementsStatic } from '../helper/ImplementsStatic';
|
import { ImplementsStatic } from '../helper/ImplementsStatic.js';
|
||||||
import { DependencyResolutionError } from '../interfaces/Exceptions';
|
import { DependencyResolutionError } from '../interfaces/Exceptions.js';
|
||||||
import { IDependency } from '../interfaces/IDependency';
|
import { IDependency } from '../interfaces/IDependency.js';
|
||||||
import { ITSinjex, ITSinjex_ } from '../interfaces/ITSinjex';
|
import { ITSinjex, ITSinjex_ } from '../interfaces/ITSinjex.js';
|
||||||
import { Identifier } from '../types/Identifier';
|
import { Identifier } from '../types/Identifier.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # 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,6 +1,12 @@
|
|||||||
import { TSinjex } from '../classes/TSinjex';
|
import { TSinjex } from '../classes/TSinjex.js';
|
||||||
import { Identifier } from '../types/Identifier';
|
import {
|
||||||
import { InitDelegate } from '../types/InitDelegate';
|
DependencyResolutionError,
|
||||||
|
InitializationError,
|
||||||
|
InjectorError,
|
||||||
|
NoInstantiationMethodError,
|
||||||
|
} from '../interfaces/Exceptions.js';
|
||||||
|
import { Identifier } from '../types/Identifier.js';
|
||||||
|
import { InitDelegate } from '../types/InitDelegate.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@@ -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 isNecessary 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,
|
isNecessary = 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, isNecessary);
|
||||||
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(),
|
||||||
// Not necessary to set the property
|
isNecessary,
|
||||||
// set(value: PropertieType) {
|
identifier,
|
||||||
// this[privatePropertyKey] = value;
|
DependencyResolutionError,
|
||||||
// },
|
);
|
||||||
enumerable: true,
|
|
||||||
|
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,
|
||||||
|
isNecessary,
|
||||||
|
identifier,
|
||||||
|
InitializationError,
|
||||||
|
);
|
||||||
|
else if (isNecessary)
|
||||||
|
throw new NoInstantiationMethodError(identifier);
|
||||||
|
} else if (isNecessary)
|
||||||
|
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,
|
configurable: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Make the property configurable to allow replacing it
|
||||||
|
*/
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,133 @@
|
|||||||
import { TSinjex } from '../classes/TSinjex';
|
import { InitDelegate } from 'src/types/InitDelegate.js';
|
||||||
import { Identifier } from '../types/Identifier';
|
import { TSinjex } from '../classes/TSinjex.js';
|
||||||
|
import { Identifier } from '../types/Identifier.js';
|
||||||
|
|
||||||
|
//#region Overloads
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A decorator to register a class in the **TSinjex** DI (Dependency Injection) container.
|
||||||
|
* @template TargetType The type of the class to be registered.
|
||||||
|
* @param identifier The identifier used to register the class in the DI container.
|
||||||
|
* @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.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* \@Register('MyClassIdentifier')
|
||||||
|
* class MyClass {
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* \@Register('MyClassIdentifier', true)
|
||||||
|
* class MyClass {
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function Register<
|
||||||
|
TargetType extends new (...args: unknown[]) => InstanceType<TargetType>,
|
||||||
|
>(
|
||||||
|
identifier: Identifier,
|
||||||
|
deprecated?: boolean,
|
||||||
|
): (constructor: TargetType, ...args: unknown[]) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @param identifier The identifier used to register the instance in the DI container.
|
||||||
|
* @see {@link Identifier} for more information on identifiers.
|
||||||
|
* @param shouldRegister Set to 'instance' to register the instance in the DI container
|
||||||
|
* with an empty constructor.
|
||||||
|
* @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.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* \@RegisterInstance('MyClassInstanceIdentifier', 'instance')
|
||||||
|
* class MyClass {
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* \@RegisterInstance('MyClassInstanceIdentifier', 'instance', true)
|
||||||
|
* class MyClass {
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function Register<
|
||||||
|
TargetType extends new (..._args: unknown[]) => InstanceType<TargetType>,
|
||||||
|
>(
|
||||||
|
identifier: Identifier,
|
||||||
|
shouldRegister: 'instance',
|
||||||
|
deprecated?: boolean,
|
||||||
|
): (constructor: TargetType, ...args: unknown[]) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @param identifier The identifier used to register the instance in the DI container.
|
||||||
|
* @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.
|
||||||
|
* @param deprecated If true, the dependency is deprecated and a warning
|
||||||
|
* is logged only once upon the first resolution of the dependency.
|
||||||
|
* @see {@link InitDelegate} for more information on initializer functions.
|
||||||
|
* @returns The decorator function to be applied on the class.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* \@RegisterInstance('MyClassInstanceIdentifier', (constructor) => new constructor())
|
||||||
|
* class MyClass {
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* \@RegisterInstance('MyClassInstanceIdentifier', (constructor) => new constructor(), true)
|
||||||
|
* class MyClass {
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function Register<
|
||||||
|
TargetType extends new (..._args: unknown[]) => InstanceType<TargetType>,
|
||||||
|
>(
|
||||||
|
identifier: Identifier,
|
||||||
|
init?: InitDelegate<
|
||||||
|
TargetType & { new (..._args: unknown[]): InstanceType<TargetType> },
|
||||||
|
InstanceType<TargetType>
|
||||||
|
>,
|
||||||
|
deprecated?: boolean,
|
||||||
|
): (constructor: TargetType, ...args: unknown[]) => void;
|
||||||
|
|
||||||
|
//#endregion Overloads
|
||||||
|
|
||||||
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||||
|
export function Register<
|
||||||
|
TargetType extends new (...args: unknown[]) => InstanceType<TargetType>,
|
||||||
|
>(
|
||||||
|
identifier: Identifier,
|
||||||
|
arg1?:
|
||||||
|
| undefined
|
||||||
|
| boolean
|
||||||
|
| InitDelegate<TargetType, InstanceType<TargetType>>
|
||||||
|
| 'instance',
|
||||||
|
arg2?: boolean,
|
||||||
|
): (constructor: TargetType, ...args: unknown[]) => void {
|
||||||
|
const deprecated = typeof arg1 === 'boolean' ? arg1 : arg2;
|
||||||
|
const init = typeof arg1 === 'function' ? arg1 : undefined;
|
||||||
|
const shouldRegisterInstance = arg1 === 'instance';
|
||||||
|
|
||||||
|
if (init == undefined && shouldRegisterInstance !== true) {
|
||||||
|
return _register(identifier, deprecated);
|
||||||
|
} else {
|
||||||
|
return _registerInstance(identifier, init, deprecated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@@ -17,7 +145,7 @@ import { Identifier } from '../types/Identifier';
|
|||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function Register<
|
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 (constructor: TargetType, ...args: unknown[]): void {
|
return function (constructor: TargetType, ...args: unknown[]): void {
|
||||||
@@ -28,3 +156,111 @@ export function Register<
|
|||||||
diContainer.register(identifier, constructor, deprecated);
|
diContainer.register(identifier, constructor, deprecated);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @param identifier The identifier used to register the instance in the DI container.
|
||||||
|
* @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.
|
||||||
|
* @param deprecated If true, the dependency is deprecated and a warning
|
||||||
|
* is logged only once upon the first resolution of the dependency.
|
||||||
|
* @see {@link InitDelegate} for more information on initializer functions.
|
||||||
|
* @returns The decorator function to be applied on the class.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* \@RegisterInstance('MyClassInstanceIdentifier', (constructor) => new constructor())
|
||||||
|
* class MyClass {
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
function _registerInstance<
|
||||||
|
TargetType extends new (..._args: unknown[]) => InstanceType<TargetType>,
|
||||||
|
>(
|
||||||
|
identifier: Identifier,
|
||||||
|
init?: InitDelegate<
|
||||||
|
TargetType & { new (..._args: unknown[]): InstanceType<TargetType> },
|
||||||
|
InstanceType<TargetType>
|
||||||
|
>,
|
||||||
|
deprecated?: boolean,
|
||||||
|
) {
|
||||||
|
return function (constructor: TargetType, ...args: unknown[]): void {
|
||||||
|
// Get the instance of the DI container
|
||||||
|
const diContainer = TSinjex.getInstance();
|
||||||
|
let instance: InstanceType<TargetType>;
|
||||||
|
|
||||||
|
// Create a proxy to instantiate the class when needed (Lazy Initialization)
|
||||||
|
let lazyProxy: unknown = new Proxy(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get(target, prop, receiver) {
|
||||||
|
({ instance, lazyProxy } = initializeInstance<TargetType>(
|
||||||
|
instance,
|
||||||
|
init,
|
||||||
|
constructor,
|
||||||
|
args,
|
||||||
|
lazyProxy,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Return the requested property of the instance
|
||||||
|
return instance[prop as keyof InstanceType<TargetType>];
|
||||||
|
},
|
||||||
|
set(target, prop, value, receiver) {
|
||||||
|
({ instance, lazyProxy } = initializeInstance<TargetType>(
|
||||||
|
instance,
|
||||||
|
init,
|
||||||
|
constructor,
|
||||||
|
args,
|
||||||
|
lazyProxy,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Set the requested property of the instance
|
||||||
|
return (instance[prop as keyof InstanceType<TargetType>] =
|
||||||
|
value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register the lazy proxy in the DI container
|
||||||
|
diContainer.register(identifier, lazyProxy, deprecated);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the instance of the class.
|
||||||
|
* @template TargetType The type of the class whose instance is to be initialized.
|
||||||
|
* @param instance The instance of the class to be initialized.
|
||||||
|
* @param init The optional initializer function to initialize the instance.
|
||||||
|
* @param constructor The constructor of the class.
|
||||||
|
* @param args The arguments to be passed to the constructor of the class.
|
||||||
|
* @param lazyProxy The lazy proxy to instantiate the class when needed.
|
||||||
|
* @returns The initialized instance and the lazy proxy.
|
||||||
|
*/
|
||||||
|
function initializeInstance<
|
||||||
|
TargetType extends new (..._args: unknown[]) => InstanceType<TargetType>,
|
||||||
|
>(
|
||||||
|
instance: InstanceType<TargetType>,
|
||||||
|
init:
|
||||||
|
| InitDelegate<
|
||||||
|
TargetType &
|
||||||
|
(new (..._args: unknown[]) => InstanceType<TargetType>),
|
||||||
|
InstanceType<TargetType>
|
||||||
|
>
|
||||||
|
| undefined,
|
||||||
|
constructor: TargetType,
|
||||||
|
args: unknown[],
|
||||||
|
lazyProxy: unknown,
|
||||||
|
): { instance: InstanceType<TargetType>; lazyProxy: unknown } {
|
||||||
|
if (instance == null) {
|
||||||
|
if (init) {
|
||||||
|
instance = init(constructor);
|
||||||
|
} else {
|
||||||
|
instance = new constructor(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lazyProxy = instance;
|
||||||
|
|
||||||
|
return { instance, lazyProxy };
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { TSinjex } from '../classes/TSinjex';
|
import { Register } from './Register.js';
|
||||||
import { Identifier } from '../types/Identifier';
|
import { Identifier } from '../types/Identifier.js';
|
||||||
import { InitDelegate } from '../types/InitDelegate';
|
import { InitDelegate } from '../types/InitDelegate.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@@ -9,6 +9,8 @@ import { InitDelegate } from '../types/InitDelegate';
|
|||||||
* @see {@link Identifier} for more information on identifiers.
|
* @see {@link Identifier} for more information on identifiers.
|
||||||
* @param init An optional initializer function which get the constructor of the class
|
* @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.
|
||||||
|
* @param deprecated If true, the dependency is deprecated and a warning
|
||||||
|
* is logged only once upon the first resolution of the dependency.
|
||||||
* @see {@link InitDelegate} for more information on initializer functions.
|
* @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.
|
||||||
* @example
|
* @example
|
||||||
@@ -18,6 +20,7 @@ import { InitDelegate } from '../types/InitDelegate';
|
|||||||
* // ...
|
* // ...
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
|
* @deprecated Use {@link Register} instead. This decorator already uses the {@link Register} decorator internally.
|
||||||
*/
|
*/
|
||||||
export function RegisterInstance<
|
export function RegisterInstance<
|
||||||
TargetType extends new (..._args: unknown[]) => InstanceType<TargetType>,
|
TargetType extends new (..._args: unknown[]) => InstanceType<TargetType>,
|
||||||
@@ -27,47 +30,10 @@ export function RegisterInstance<
|
|||||||
TargetType & { new (..._args: unknown[]): InstanceType<TargetType> },
|
TargetType & { new (..._args: unknown[]): InstanceType<TargetType> },
|
||||||
InstanceType<TargetType>
|
InstanceType<TargetType>
|
||||||
>,
|
>,
|
||||||
) {
|
deprecated?: boolean,
|
||||||
return function (constructor: TargetType, ...args: unknown[]): void {
|
): (constructor: TargetType, ...args: unknown[]) => void {
|
||||||
// Get the instance of the DI container
|
const initDelegate = typeof init === 'function' ? init : undefined;
|
||||||
const diContainer = TSinjex.getInstance();
|
|
||||||
let instance: InstanceType<TargetType>;
|
|
||||||
|
|
||||||
// Create a proxy to instantiate the class when needed (Lazy Initialization)
|
if (initDelegate) return Register(identifier, initDelegate, deprecated);
|
||||||
let lazyProxy: unknown = new Proxy(
|
else return Register(identifier, 'instance', deprecated);
|
||||||
{},
|
|
||||||
{
|
|
||||||
get(target, prop, receiver) {
|
|
||||||
if (instance == null) {
|
|
||||||
if (init) {
|
|
||||||
instance = init(constructor);
|
|
||||||
} else {
|
|
||||||
instance = new constructor(...args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lazyProxy = instance;
|
|
||||||
|
|
||||||
// Return the requested property of the instance
|
|
||||||
return instance[prop as keyof InstanceType<TargetType>];
|
|
||||||
},
|
|
||||||
set(target, prop, value, receiver) {
|
|
||||||
if (instance == null) {
|
|
||||||
if (init) {
|
|
||||||
instance = init(constructor);
|
|
||||||
} else {
|
|
||||||
instance = new constructor(...args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lazyProxy = instance;
|
|
||||||
|
|
||||||
// Set the requested property of the instance
|
|
||||||
return (instance[prop as keyof InstanceType<TargetType>] =
|
|
||||||
value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register the lazy proxy in the DI container
|
|
||||||
diContainer.register(identifier, lazyProxy);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
220
src/functions/inject.ts
Normal file
220
src/functions/inject.ts
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import { TSinjex } from '../classes/TSinjex.js';
|
||||||
|
import {
|
||||||
|
DependencyResolutionError,
|
||||||
|
InitializationError,
|
||||||
|
InjectorError,
|
||||||
|
NoInstantiationMethodError,
|
||||||
|
} from '../interfaces/Exceptions.js';
|
||||||
|
import { Identifier } from '../types/Identifier.js';
|
||||||
|
import { InitDelegate } from '../types/InitDelegate.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a dependency by its identifier without initialization or instantiation.
|
||||||
|
* @template T The expected type of the dependency.
|
||||||
|
* @param identifier The identifier used to resolve the dependency from the container.
|
||||||
|
* @returns The resolved dependency.
|
||||||
|
* @throws A {@link DependencyResolutionError} if the dependency is not found.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const logger = inject<Logger>('Logger');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function inject<T>(identifier: Identifier): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves and instantiates a dependency using its constructor.
|
||||||
|
* @template T The expected class type of the dependency.
|
||||||
|
* @param identifier The identifier used to resolve the dependency from the container.
|
||||||
|
* @param shouldInit Set to `true` to instantiate the dependency after resolution.
|
||||||
|
* @returns The resolved and instantiated dependency.
|
||||||
|
* @throws A {@link DependencyResolutionError} if the dependency is not found.
|
||||||
|
* @throws A {@link NoInstantiationMethodError} if the dependency has no constructor.
|
||||||
|
* @throws An {@link InitializationError} if instantiation fails.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const instance = inject<Service>('Service', true);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function inject<T>(identifier: Identifier, shouldInit: true): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves and instantiates a dependency using its constructor, optionally failing silently.
|
||||||
|
* @template T The expected class type of the dependency.
|
||||||
|
* @param identifier The identifier used to resolve the dependency from the container.
|
||||||
|
* @param shouldInit Set to `true` to instantiate the dependency.
|
||||||
|
* @param isNecessary If `false`, resolution or instantiation errors return `undefined` instead of throwing.
|
||||||
|
* @returns The resolved and instantiated dependency, or `undefined` if resolution or instantiation fails.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const instance = inject<Service>('OptionalService', true, false);
|
||||||
|
* if (instance) instance.doSomething();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function inject<T>(
|
||||||
|
identifier: Identifier,
|
||||||
|
shouldInit: true,
|
||||||
|
isNecessary: false,
|
||||||
|
): T | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a dependency without instantiating it, optionally failing silently.
|
||||||
|
* @template T The expected type of the dependency.
|
||||||
|
* @param identifier The identifier used to resolve the dependency from the container.
|
||||||
|
* @param shouldInit Set to `false` to skip instantiation.
|
||||||
|
* @param isNecessary If `false`, resolution errors return `undefined` instead of throwing.
|
||||||
|
* @returns The resolved dependency, or `undefined` if not found.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const config = inject<Config>('Config', false, false) ?? getDefaultConfig();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function inject<T>(
|
||||||
|
identifier: Identifier,
|
||||||
|
shouldInit: false,
|
||||||
|
isNecessary: false,
|
||||||
|
): T | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a dependency and applies a custom initializer function to transform the result.
|
||||||
|
* @template T The original dependency type.
|
||||||
|
* @template U The final return type after initialization.
|
||||||
|
* @param identifier The identifier used to resolve the dependency.
|
||||||
|
* @param init A function to transform or initialize the dependency.
|
||||||
|
* @returns The transformed dependency.
|
||||||
|
* @throws A {@link DependencyResolutionError} if the dependency is not found.
|
||||||
|
* @throws An {@link InitializationError} if the initializer throws.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const client = inject<Api>('Api', (api) => api.getClient());
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function inject<T, U>(
|
||||||
|
identifier: Identifier,
|
||||||
|
init: InitDelegate<T, U>,
|
||||||
|
): U;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a dependency and applies a custom initializer function, optionally failing silently.
|
||||||
|
* @template T The original dependency type.
|
||||||
|
* @template U The final return type after initialization.
|
||||||
|
* @param identifier The identifier used to resolve the dependency.
|
||||||
|
* @param init A function to transform or initialize the dependency.
|
||||||
|
* @param isNecessary If `false`, resolution or initializer errors return `undefined` instead of throwing.
|
||||||
|
* @returns The transformed dependency, or `undefined` if resolution or initialization fails.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const db = inject<Database, Pool>('Database', (d) => d.getPool(), false);
|
||||||
|
* if (db) db.query('SELECT * FROM users');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function inject<T, U>(
|
||||||
|
identifier: Identifier,
|
||||||
|
init: InitDelegate<T, U>,
|
||||||
|
isNecessary: false,
|
||||||
|
): U | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function to inject a dependency from a DI (Dependency Injection) container into a variable.
|
||||||
|
* This is the actual implementation that handles all overload variants.
|
||||||
|
* @template T The original dependency type.
|
||||||
|
* @template U The final return type after optional initialization or transformation.
|
||||||
|
* @param identifier The identifier used to resolve the dependency.
|
||||||
|
* @see {@link Identifier} for more information on identifiers.
|
||||||
|
* @param init Optional: either `true` to instantiate via constructor, `false` to skip, or a function to transform the dependency.
|
||||||
|
* @see {@link InitDelegate} for more information on initializer functions.
|
||||||
|
* @param isNecessary If `true`, throws on failure; if `false`, returns `undefined` on resolution or initialization errors.
|
||||||
|
* @returns The resolved dependency or result of initialization, or `undefined` if not necessary and resolution fails.
|
||||||
|
* @throws A {@link DependencyResolutionError} if the dependency is not found (and necessary).
|
||||||
|
* @throws A {@link NoInstantiationMethodError} if instantiation is requested but no constructor exists.
|
||||||
|
* @throws An {@link InitializationError} if the initializer throws an error.
|
||||||
|
* @throws A {@link InjectorError} for unknown errors during resolution or transformation.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const service = inject<Service>('Service');
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const instance = inject<Service>('Service', true);
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const logger = inject<ILogger>('ILogger_', (x) => x.getLogger('Module'), false);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function inject<T, U>(
|
||||||
|
identifier: Identifier,
|
||||||
|
init?: InitDelegate<T, U> | true | false,
|
||||||
|
isNecessary = true,
|
||||||
|
): T | U | undefined {
|
||||||
|
let instance: T | U | undefined;
|
||||||
|
|
||||||
|
const dependency: T | undefined = tryAndCatch(
|
||||||
|
() => TSinjex.getInstance().resolve<T>(identifier, isNecessary),
|
||||||
|
isNecessary,
|
||||||
|
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 || init === false) instance = dependency;
|
||||||
|
else if (initFunction != null)
|
||||||
|
instance = tryAndCatch(
|
||||||
|
initFunction,
|
||||||
|
isNecessary,
|
||||||
|
identifier,
|
||||||
|
InitializationError,
|
||||||
|
);
|
||||||
|
else if (isNecessary) throw new NoInstantiationMethodError(identifier);
|
||||||
|
} else if (isNecessary) throw new DependencyResolutionError(identifier);
|
||||||
|
|
||||||
|
return instance as T | U;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 { TSinjex } from '../classes/TSinjex';
|
import { TSinjex } from '../classes/TSinjex.js';
|
||||||
import { Identifier } from '../types/Identifier';
|
import { Identifier } from '../types/Identifier.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a dependency.
|
* Register a dependency.
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { TSinjex } from '../classes/TSinjex';
|
import { TSinjex } from '../classes/TSinjex.js';
|
||||||
import { DependencyResolutionError } from '../interfaces/Exceptions';
|
import { DependencyResolutionError } from '../interfaces/Exceptions.js';
|
||||||
import { Identifier } from '../types/Identifier';
|
import { Identifier } from '../types/Identifier.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve a dependency.
|
* Resolve a dependency.
|
||||||
@@ -15,12 +15,12 @@ export function resolve<T>(identifier: Identifier): T;
|
|||||||
* Resolve a dependency
|
* Resolve a dependency
|
||||||
* @param identifier The identifier used to register the class in the DI container.
|
* @param identifier The identifier used to register the class in the DI container.
|
||||||
* @see {@link Identifier} for more information on identifiers.
|
* @see {@link Identifier} for more information on identifiers.
|
||||||
* @param necessary The dependency is **not** necessary.
|
* @param isNecessary The dependency is **not** necessary.
|
||||||
* @returns The resolved dependency or undefined if the dependency is not found.
|
* @returns The resolved dependency or undefined if the dependency is not found.
|
||||||
*/
|
*/
|
||||||
export function resolve<T>(
|
export function resolve<T>(
|
||||||
identifier: Identifier,
|
identifier: Identifier,
|
||||||
necessary: false,
|
isNecessary: false,
|
||||||
): T | undefined;
|
): T | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
22
src/index.ts
22
src/index.ts
@@ -1,18 +1,22 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
|
|
||||||
// Main
|
// Main
|
||||||
export * from './classes/TSinjex';
|
export * from './classes/TSinjex.js';
|
||||||
|
|
||||||
// Decorators
|
// Decorators
|
||||||
export * from './decorators/Inject';
|
export * from './decorators/Inject.js';
|
||||||
export * from './decorators/Register';
|
export * from './decorators/Register.js';
|
||||||
export * from './decorators/RegisterInstance';
|
export * from './decorators/RegisterInstance.js';
|
||||||
|
|
||||||
|
// Helper
|
||||||
|
export * from './helper/ImplementsStatic.js';
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
export * from './functions/resolve';
|
export * from './functions/resolve.js';
|
||||||
export * from './functions/register';
|
export * from './functions/register.js';
|
||||||
|
export * from './functions/inject.js';
|
||||||
|
|
||||||
// Interfaces & Types
|
// Interfaces & Types
|
||||||
export type * from './interfaces/ITSinjex';
|
export type * from './interfaces/ITSinjex.js';
|
||||||
export type * from './types/InitDelegate';
|
export type * from './types/InitDelegate.js';
|
||||||
export type * from './types/GenericContructor';
|
export type * from './types/GenericContructor.js';
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { ITSinjex } from './ITSinjex';
|
import { Identifier } from 'src/types/Identifier.js';
|
||||||
|
import { ITSinjex } from './ITSinjex.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General error class for {@link ITSinjex} interface.
|
* General error class for {@link ITSinjex} interface.
|
||||||
@@ -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 { DependencyResolutionError } from './Exceptions';
|
import { DependencyResolutionError } from './Exceptions.js';
|
||||||
import { Identifier } from '../types/Identifier';
|
import { Identifier } from '../types/Identifier.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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;
|
||||||
|
@@ -7,13 +7,13 @@
|
|||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"module": "ESNext",
|
"module": "NodeNext",
|
||||||
"target": "ES6",
|
"target": "ES2020",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "NodeNext",
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
|
Reference in New Issue
Block a user