Compare commits
7 Commits
v1.2.0
...
feature/de
Author | SHA1 | Date | |
---|---|---|---|
c55f39d1e5
|
|||
3ac76b09d3
|
|||
46c9a8b990
|
|||
9e1b7a8d7b
|
|||
3a52f14dcd
|
|||
4fa78afbc2
|
|||
67f587d0d0
|
@@ -1,13 +0,0 @@
|
||||
node_modules/
|
||||
|
||||
main.js
|
||||
|
||||
**/*.js
|
||||
*.js
|
||||
|
||||
**/*.mjs
|
||||
*.mjs
|
||||
|
||||
dist/*
|
||||
|
||||
*.cjs
|
299
.eslintrc
299
.eslintrc
@@ -1,299 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"deprecation",
|
||||
"prettier",
|
||||
"import",
|
||||
"jsdoc",
|
||||
"override",
|
||||
"@stylistic"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:jsdoc/recommended-typescript"
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"rules": {
|
||||
"override/require-override": "error",
|
||||
"override/require-static-override": "off",
|
||||
"prettier/prettier": "warn",
|
||||
"array-callback-return": [
|
||||
"error"
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"caughtErrorsIgnorePattern": "^_",
|
||||
"args": "none"
|
||||
}
|
||||
],
|
||||
"no-warning-comments": [
|
||||
"warn",
|
||||
{
|
||||
"terms": [
|
||||
"@todo"
|
||||
],
|
||||
"location": "anywhere"
|
||||
}
|
||||
],
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"deprecation/deprecation": "warn",
|
||||
"no-console": "off",
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"warn",
|
||||
{
|
||||
"selector": "classProperty",
|
||||
"modifiers": [
|
||||
"private"
|
||||
],
|
||||
"format": [],
|
||||
"custom": {
|
||||
"regex": "^(_{1,2}I[A-Z][a-zA-Z0-9]*_?|_{1,2}[a-z][a-zA-Z0-9]*)$",
|
||||
"match": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"selector": "classProperty",
|
||||
"modifiers": [
|
||||
"protected"
|
||||
],
|
||||
"format": [],
|
||||
"custom": {
|
||||
"regex": "^(_{1,2}I[A-Z][a-zA-Z0-9]*_?|_{1,2}[a-z][a-zA-Z0-9]*)$",
|
||||
"match": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"selector": [
|
||||
"typeProperty",
|
||||
"classProperty"
|
||||
],
|
||||
"types": [
|
||||
"boolean"
|
||||
],
|
||||
"format": [
|
||||
"PascalCase"
|
||||
],
|
||||
"prefix": [
|
||||
"is",
|
||||
"has",
|
||||
"can",
|
||||
"did",
|
||||
"will",
|
||||
"should"
|
||||
],
|
||||
"leadingUnderscore": "allow"
|
||||
},
|
||||
{
|
||||
"selector": "memberLike",
|
||||
"modifiers": [
|
||||
"public"
|
||||
],
|
||||
"format": [
|
||||
"camelCase"
|
||||
],
|
||||
"leadingUnderscore": "forbid",
|
||||
"filter": {
|
||||
"regex": "^(Events|Styles|Classes|Then)$",
|
||||
"match": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"selector": "typeProperty",
|
||||
"modifiers": [
|
||||
"public"
|
||||
],
|
||||
"format": null,
|
||||
"filter": {
|
||||
"regex": ".*-event$",
|
||||
"match": true
|
||||
},
|
||||
"custom": {
|
||||
"regex": "^[a-z]+(-[a-z]+)*-event$",
|
||||
"match": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"selector": "typeProperty",
|
||||
"modifiers": [
|
||||
"public"
|
||||
],
|
||||
"format": [
|
||||
"camelCase"
|
||||
],
|
||||
"filter": {
|
||||
"regex": ".*-event$|^(Events|Styles|Classes|Then)$",
|
||||
"match": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-expressions": "off",
|
||||
"@stylistic/padding-line-between-statements": [
|
||||
"warn",
|
||||
{
|
||||
"blankLine": "always",
|
||||
"prev": "*",
|
||||
"next": [
|
||||
"return",
|
||||
"if",
|
||||
"multiline-const",
|
||||
"function",
|
||||
"multiline-expression",
|
||||
"multiline-let",
|
||||
"block-like"
|
||||
]
|
||||
},
|
||||
{
|
||||
"blankLine": "always",
|
||||
"prev": [
|
||||
"function"
|
||||
],
|
||||
"next": "*"
|
||||
}
|
||||
],
|
||||
"import/order": [
|
||||
"warn",
|
||||
{
|
||||
"groups": [
|
||||
[
|
||||
"builtin",
|
||||
"external"
|
||||
],
|
||||
[
|
||||
"internal"
|
||||
],
|
||||
[
|
||||
"parent",
|
||||
"sibling"
|
||||
]
|
||||
],
|
||||
"newlines-between": "never",
|
||||
"alphabetize": {
|
||||
"order": "asc",
|
||||
"caseInsensitive": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"jsdoc/no-undefined-types": [
|
||||
"warn",
|
||||
{
|
||||
"disableReporting": true,
|
||||
"markVariablesAsUsed": true
|
||||
}
|
||||
],
|
||||
"jsdoc/require-jsdoc": [
|
||||
"warn",
|
||||
{
|
||||
"require": {
|
||||
"FunctionDeclaration": true,
|
||||
"MethodDefinition": true,
|
||||
"ClassDeclaration": true,
|
||||
"ArrowFunctionExpression": false,
|
||||
"FunctionExpression": false
|
||||
},
|
||||
"minLineCount": 10
|
||||
}
|
||||
],
|
||||
"jsdoc/require-param": [
|
||||
"warn",
|
||||
{
|
||||
"exemptedBy": [
|
||||
"deprecated",
|
||||
"inheritdoc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"jsdoc/require-description": [
|
||||
"warn",
|
||||
{
|
||||
"contexts": [
|
||||
"FunctionDeclaration",
|
||||
"MethodDefinition",
|
||||
"ClassDeclaration",
|
||||
"ClassExpression"
|
||||
],
|
||||
"descriptionStyle": "body",
|
||||
"exemptedBy": [
|
||||
"deprecated",
|
||||
"inheritdoc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"jsdoc/require-returns": [
|
||||
"warn",
|
||||
{
|
||||
"checkGetters": false,
|
||||
"exemptedBy": [
|
||||
"deprecated",
|
||||
"inheritdoc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"jsdoc/check-tag-names": [
|
||||
"warn",
|
||||
{
|
||||
"definedTags": [
|
||||
"remarks",
|
||||
"jest-environment",
|
||||
"singleton"
|
||||
]
|
||||
}
|
||||
],
|
||||
"jsdoc/check-alignment": "warn",
|
||||
"jsdoc/check-indentation": "warn",
|
||||
"jsdoc/no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
"contexts": [
|
||||
{
|
||||
"context": "MethodDefinition[kind='get']",
|
||||
"comment": "JsdocBlock:has(JsdocTag[tag='returns'])",
|
||||
"message": "JSDoc @returns comments are not allowed in getters."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/prefer-readonly": "warn",
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"warn",
|
||||
{
|
||||
"allowExpressions": true,
|
||||
"allowTypedFunctionExpressions": true,
|
||||
"allowHigherOrderFunctions": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/prefer-string-starts-ends-with": "warn",
|
||||
"@typescript-eslint/no-misused-promises": "warn",
|
||||
"@typescript-eslint/prefer-optional-chain": "warn"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.test.ts",
|
||||
"*.spec.ts"
|
||||
],
|
||||
"rules": {
|
||||
"jsdoc/require-jsdoc": "off",
|
||||
"jsdoc/require-param": "off",
|
||||
"jsdoc/require-description": "off",
|
||||
"jsdoc/require-returns": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@@ -1,12 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
open-pull-requests-limit: 10
|
||||
schedule:
|
||||
interval: "weekly"
|
117
.github/workflows/CreateRelease.yml
vendored
117
.github/workflows/CreateRelease.yml
vendored
@@ -1,117 +0,0 @@
|
||||
name: Create Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'dev/*'
|
||||
paths:
|
||||
- 'package.json'
|
||||
workflow_dispatch: # Allows manual execution of the workflow.
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.8.0'
|
||||
|
||||
- name: Install dependencies, run tests and build
|
||||
run: npm run prepare:deploy
|
||||
|
||||
- name: Get the version
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(npm run version:show | tail -n 1)
|
||||
echo "VERSION=v$VERSION" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Get previous release tag
|
||||
id: get_previous_release
|
||||
run: |
|
||||
echo "Fetching previous release tag..."
|
||||
previous_tag=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
||||
if [ -z "$previous_tag" ]; then
|
||||
echo "No previous tag found, using initial commit."
|
||||
previous_tag=$(git rev-list --max-parents=0 HEAD)
|
||||
fi
|
||||
echo "Previous tag: $previous_tag"
|
||||
echo "PREVIOUS_TAG=$previous_tag" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Check if version changed
|
||||
id: check_version
|
||||
run: |
|
||||
# Check if the version already exists as a tag
|
||||
if git rev-parse "refs/tags/${{ env.VERSION }}" >/dev/null 2>&1; then
|
||||
echo "skip_release=true" >> $GITHUB_OUTPUT
|
||||
echo "Version ${{ env.VERSION }} already exists as a tag. No release will be created."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Compare current version with previous tag
|
||||
if [ "${{ env.VERSION }}" == "${{ env.PREVIOUS_TAG }}" ]; then
|
||||
echo "skip_release=true" >> $GITHUB_OUTPUT
|
||||
echo "Version has not changed. No release will be created."
|
||||
exit 0
|
||||
fi
|
||||
echo "skip_release=false" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Generate release notes
|
||||
id: generate_notes
|
||||
if: steps.check_version.outputs.skip_release == 'false'
|
||||
run: |
|
||||
echo "Generating release notes from ${{ env.PREVIOUS_TAG }} to HEAD..."
|
||||
repo_url=$(git config --get remote.origin.url)
|
||||
notes=$(git log ${{ env.PREVIOUS_TAG }}..HEAD --pretty=format:"- [\`%h\`]($repo_url/commit/%H): %s%n")
|
||||
echo "See [CHANGELOG.md](./CHANGELOG.md) for more details."
|
||||
echo "$notes"
|
||||
echo ""
|
||||
echo "### Changes in this release" > release_notes.md
|
||||
echo "$notes" >> release_notes.md
|
||||
shell: bash
|
||||
|
||||
- name: Set Git user
|
||||
if: steps.check_version.outputs.skip_release == 'false'
|
||||
run: |
|
||||
git config --local user.name "GitHub Actions"
|
||||
git config --local user.email "actions@github.com"
|
||||
shell: bash
|
||||
|
||||
- name: Create and push tag
|
||||
id: create_tag
|
||||
if: steps.check_version.outputs.skip_release == 'false'
|
||||
run: |
|
||||
git tag ${{ env.VERSION }}
|
||||
git push origin ${{ env.VERSION }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
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
|
||||
if: steps.check_version.outputs.skip_release == 'false'
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ env.VERSION }}
|
||||
name: Release ${{ env.VERSION }}
|
||||
body_path: release_notes.md
|
||||
prerelease: ${{ env.PRE_RELEASE }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
44
.github/workflows/DeployTypeDoc.yml
vendored
44
.github/workflows/DeployTypeDoc.yml
vendored
@@ -1,44 +0,0 @@
|
||||
name: Deploy Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch: # Allows manual execution of the workflow.
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.8.0'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Run TypeDoc Generation (TypeDoc, Test Coverage, fixes and badges)
|
||||
run: npm run docs:generate
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
git config --global user.name 'github-actions[bot]'
|
||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
git clone --single-branch --branch gh-pages https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} gh-pages
|
||||
rm -rf gh-pages/*
|
||||
cp -r .locale/docs/* gh-pages/
|
||||
mkdir -p gh-pages/coverage
|
||||
cp -r .locale/coverage/* gh-pages/coverage/
|
||||
cd gh-pages
|
||||
git add .
|
||||
git commit -m 'Deploy documentation and coverage'
|
||||
git push origin gh-pages
|
31
.github/workflows/PullRequestTest.yml
vendored
31
.github/workflows/PullRequestTest.yml
vendored
@@ -1,31 +0,0 @@
|
||||
name: Run Build and Tests on Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch: # Allows manual execution of the workflow.
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.8.0'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Run Tests
|
||||
run: npm run test:verbose
|
||||
|
||||
- name: Build the Project
|
||||
run: npm run build:tsc
|
23
.github/workflows/ValidateBranchName.yml
vendored
23
.github/workflows/ValidateBranchName.yml
vendored
@@ -1,23 +0,0 @@
|
||||
name: Validate Branch Name on Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- UNDEFINED
|
||||
|
||||
jobs:
|
||||
validate-branch-name-on-pull-request:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Validate Branch Name on Pull Request
|
||||
run: |
|
||||
BRANCH_NAME=${GITHUB_HEAD_REF}
|
||||
if [[ ! "$BRANCH_NAME" =~ ^(feature\/|fix\/|refactoring\/|testing\/|dependabot\/|gh-pages) ]]; then
|
||||
echo "Invalid branch name: $BRANCH_NAME"
|
||||
echo "Branch name must start with 'feature/', 'fix/', 'refactoring/', 'testing/', dependabot/" or "gh-pages"
|
||||
exit 1
|
||||
fi
|
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,9 +1 @@
|
||||
# Internal
|
||||
.vscode
|
||||
.locale
|
||||
.VSCodeCounter
|
||||
|
||||
*.ignore.*
|
||||
|
||||
dist/*
|
||||
node_modules/*
|
||||
coverage/
|
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"bracketSpacing": true,
|
||||
"useTabs": false,
|
||||
"tabWidth": 4,
|
||||
"endOfLine": "auto",
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
".prettierrc",
|
||||
".eslintrc"
|
||||
],
|
||||
"options": {
|
||||
"parser": "json"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
20
.vscode/launch.json
vendored
Normal file
20
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Deno Tests",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"runtimeExecutable": "/home/maxp/.deno/bin/deno",
|
||||
"runtimeArgs": [
|
||||
"test",
|
||||
"--inspect-brk",
|
||||
"--allow-all",
|
||||
"tests/*.ts"
|
||||
],
|
||||
"attachSimplePort": 9229,
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"editor.tabSize": 4,
|
||||
"editor.insertSpaces": true,
|
||||
"deno.enable": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "denoland.vscode-deno",
|
||||
"editor.detectIndentation": false,
|
||||
"editor.indentSize": "tabSize"
|
||||
}
|
34
README.md
34
README.md
@@ -1,34 +0,0 @@
|
||||

|
||||
|
||||
[    ](https://pxammaxp.github.io/TSinjex/coverage/lcov-report/index.html)
|
||||
|
||||
# TSinjex
|
||||
|
||||
## Configuration
|
||||
|
||||
### Identifiers
|
||||
|
||||
Strings and symbols are possible for the **identifiers**.
|
||||
|
||||
### 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
|
||||
|
||||
```ts
|
||||
module.exports = {
|
||||
setupFilesAfterEnv: ['./scripts/jest.setup.js'],
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(test).ts'],
|
||||
moduleDirectories: ['node_modules', 'src'],
|
||||
moduleNameMapper: {
|
||||
'^src/(.*)$': '<rootDir>/src/$1', // Map src to the source folder
|
||||
'^ts-injex$': '<rootDir>/node_modules/ts-injex/src', // Map ts-injex to the source folder
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!ts-injex)' // **Dont** ignore ts-injex on preset `ts-jest`
|
||||
],
|
||||
};
|
||||
```
|
@@ -1,122 +0,0 @@
|
||||
#!/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();
|
35
deno.jsonc
Normal file
35
deno.jsonc
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"compilerOptions": {},
|
||||
"lint": {
|
||||
"files": {
|
||||
"include": [
|
||||
"src/"
|
||||
],
|
||||
}
|
||||
},
|
||||
"fmt": {
|
||||
"files": {
|
||||
"include": [
|
||||
"src/",
|
||||
"tests/"
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"lineWidth": 100,
|
||||
"indentWidth": 4,
|
||||
"useTabs": false
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"include": [
|
||||
"tests/"
|
||||
],
|
||||
},
|
||||
"tasks": {
|
||||
"test": "deno test --coverage tests/*.ts",
|
||||
"lint": "deno lint",
|
||||
"fmt": "deno fmt",
|
||||
"build": "echo 'Kein Build nötig bei Deno'"
|
||||
}
|
||||
}
|
39
deno.lock
generated
Normal file
39
deno.lock
generated
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"version": "5",
|
||||
"remote": {
|
||||
"https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975",
|
||||
"https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834",
|
||||
"https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293",
|
||||
"https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7",
|
||||
"https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74",
|
||||
"https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd",
|
||||
"https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff",
|
||||
"https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46",
|
||||
"https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b",
|
||||
"https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c",
|
||||
"https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491",
|
||||
"https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68",
|
||||
"https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3",
|
||||
"https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7",
|
||||
"https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29",
|
||||
"https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a",
|
||||
"https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a",
|
||||
"https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8",
|
||||
"https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693",
|
||||
"https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31",
|
||||
"https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5",
|
||||
"https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8",
|
||||
"https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb",
|
||||
"https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917",
|
||||
"https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47",
|
||||
"https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68",
|
||||
"https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3",
|
||||
"https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73",
|
||||
"https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19",
|
||||
"https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5",
|
||||
"https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6",
|
||||
"https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2",
|
||||
"https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e",
|
||||
"https://deno.land/std@0.224.0/testing/asserts.ts": "d0cdbabadc49cc4247a50732ee0df1403fdcd0f95360294ad448ae8c240f3f5c"
|
||||
}
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
module.exports = {
|
||||
setupFilesAfterEnv: ['./scripts/jest.setup.js'],
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
transform: {
|
||||
'^.+\\.ts$': ['ts-jest', { useESM: true }],
|
||||
},
|
||||
testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(test).ts'],
|
||||
testPathIgnorePatterns: ['\\.spec\\.ts$', '\\.performance\\.test\\.ts$'],
|
||||
moduleDirectories: ['node_modules', 'src'],
|
||||
moduleNameMapper: {
|
||||
'^src/(.*)\\.js$': '<rootDir>/src/$1',
|
||||
'^src/(.*)$': '<rootDir>/src/$1',
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||
},
|
||||
collectCoverage: true,
|
||||
coverageDirectory: '.locale/coverage',
|
||||
coverageReporters: ['text', 'lcov'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 70,
|
||||
functions: 70,
|
||||
lines: 70,
|
||||
statements: 70,
|
||||
},
|
||||
},
|
||||
};
|
@@ -1,39 +0,0 @@
|
||||
module.exports = {
|
||||
setupFilesAfterEnv: ['./scripts/jest.setup.js'],
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'^.+\\.ts$': ['ts-jest', { useESM: true }],
|
||||
},
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(test).ts'],
|
||||
testPathIgnorePatterns: ['\\.spec\\.ts$', '\\.performance\\.test\\.ts$'],
|
||||
moduleDirectories: ['node_modules', 'src'],
|
||||
moduleNameMapper: {
|
||||
'^src/(.*)\\.js$': '<rootDir>/src/$1',
|
||||
'^src/(.*)$': '<rootDir>/src/$1',
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||
},
|
||||
collectCoverage: true,
|
||||
coverageDirectory: '.locale/coverage',
|
||||
coverageReporters: [
|
||||
'text',
|
||||
['lcov', { projectRoot: '..' }],
|
||||
'json-summary',
|
||||
],
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.{ts,tsx}',
|
||||
'!src/**/*.d.ts',
|
||||
'!src/**/*.performance.test.ts',
|
||||
'!src/**/*.spec.ts',
|
||||
'!src/**/*.test.ts',
|
||||
'!src/auto-imports.ts',
|
||||
],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 90,
|
||||
functions: 90,
|
||||
lines: 90,
|
||||
statements: 90,
|
||||
},
|
||||
},
|
||||
};
|
7172
package-lock.json
generated
7172
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
66
package.json
66
package.json
@@ -1,66 +0,0 @@
|
||||
{
|
||||
"name": "ts-injex",
|
||||
"version": "1.2.0",
|
||||
"description": "Simple boilerplate code free dependency injection system for TypeScript.",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"bin": {
|
||||
"tsinjex-generate": "./bin/generate-imports.cjs"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "npm run build",
|
||||
"build": "npm run build:tsc",
|
||||
"build:tsc": "tsc",
|
||||
"lint": "eslint --ext .ts .",
|
||||
"lint:fix": "eslint --fix --ext .ts .",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch --onlyChanged",
|
||||
"test:file": "jest --watch --onlyChanged --coverage=true --verbose",
|
||||
"test:verbose": "jest --verbose",
|
||||
"test:coverage": "jest --config jest.config.coverage.cjs --coverage",
|
||||
"docs": "typedoc",
|
||||
"docs:generate": "npm run docs && npm run docs:generate:coverage && npm run docs:fix:coverage && npm run docs:generate:badge && npm run docs:fix:escape",
|
||||
"docs:generate:coverage": "npm run test:coverage || exit 0",
|
||||
"docs:fix:coverage": "node scripts/fix-coverage-paths.cjs",
|
||||
"docs:generate:badge": "node scripts/generate-badge.cjs",
|
||||
"docs:fix:escape": "node scripts/replace-doc-escaping.cjs",
|
||||
"version:show": "node -e \"console.log(require('./package.json').version)\"",
|
||||
"prepare:deploy": "npm install && npm run test:verbose && npm run build:tsc"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PxaMMaxP/TSinjex.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Max P. (@Github: PxaMMaxP)",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.4",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.14.11",
|
||||
"@stylistic/eslint-plugin": "^2.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.1.0",
|
||||
"@typescript-eslint/parser": "^8.1.0",
|
||||
"eslint-plugin-deprecation": "^3.0.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsdoc": "^50.2.2",
|
||||
"eslint-plugin-override": "https://github.com/PxaMMaxP/eslint-plugin-override",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.2.3",
|
||||
"typedoc": "^0.26.5",
|
||||
"istanbul-badges-readme": "^1.9.0",
|
||||
"axios": "^1.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"jest-environment-jsdom": "^29.7.0"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"src/**/*",
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"package.json"
|
||||
]
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const coverageDir = path.join(__dirname, '..', '.locale', 'coverage');
|
||||
const typedocUrl = '../../';
|
||||
|
||||
const getAllFiles = (dir, files = []) => {
|
||||
fs.readdirSync(dir).forEach(file => {
|
||||
const fullPath = path.join(dir, file);
|
||||
if (fs.statSync(fullPath).isDirectory()) {
|
||||
getAllFiles(fullPath, files);
|
||||
} else {
|
||||
files.push(fullPath);
|
||||
}
|
||||
});
|
||||
return files;
|
||||
};
|
||||
|
||||
// Alle HTML-Dateien im coverage-Ordner finden
|
||||
const htmlFiles = getAllFiles(coverageDir).filter(file => file.endsWith('.html'));
|
||||
|
||||
// Alle HTML-Dateien bearbeiten
|
||||
htmlFiles.forEach(filePath => {
|
||||
fs.readFile(filePath, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error(`Error reading file ${filePath}:`, err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Relative Pfade anpassen
|
||||
let fixedData = data.replace(/(src|href)="(?!\.)/g, '$1="./');
|
||||
|
||||
// Link zur TypeDoc-Dokumentation hinzufügen
|
||||
const linkHtml = `<div style="position: fixed; bottom: 10px; right: 10px;"><a href="${typedocUrl}">Zur TypeDoc-Dokumentation</a></div>`;
|
||||
fixedData = fixedData.replace('</body>', `${linkHtml}</body>`);
|
||||
|
||||
fs.writeFile(filePath, fixedData, 'utf8', (err) => {
|
||||
if (err) {
|
||||
console.error(`Error writing file ${filePath}:`, err);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Fixed paths and added link in ${filePath}`);
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,55 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { exec } = require('child_process');
|
||||
const axios = require('axios');
|
||||
|
||||
// Step 1: Create README.md in the coverage directory
|
||||
const coverageReadmePath = path.join(__dirname, '..', '.locale', 'coverage', 'README.md');
|
||||
const readmeContent = `
|
||||

|
||||

|
||||

|
||||

|
||||
`;
|
||||
|
||||
fs.writeFileSync(coverageReadmePath, readmeContent, 'utf8');
|
||||
|
||||
// Step 2: Execute the istanbul-badges-readme tool
|
||||
exec('npx istanbul-badges-readme --coverageDir=./.locale/coverage --readmeDir=./.locale/coverage', (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
console.error(`Error executing istanbul-badges-readme: ${stderr}`);
|
||||
return;
|
||||
}
|
||||
console.log('Badges generated successfully.');
|
||||
|
||||
// Step 3: Extract the badge links from README.md
|
||||
const updatedReadmeContent = fs.readFileSync(coverageReadmePath, 'utf8');
|
||||
const badgeLines = updatedReadmeContent.split('\n').filter(line => line.includes('https://img.shields.io'));
|
||||
|
||||
// Ensure the target directory exists
|
||||
const badgesDir = path.join(__dirname, '..', '.locale', 'coverage', 'badges');
|
||||
if (!fs.existsSync(badgesDir)) {
|
||||
fs.mkdirSync(badgesDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Badge types and their order
|
||||
const badgeTypes = ['statements', 'branches', 'functions', 'lines'];
|
||||
|
||||
// Save the badge images
|
||||
badgeLines.forEach(async (line, index) => {
|
||||
const match = line.match(/\((https:\/\/img\.shields\.io\/badge\/[^)]+)\)/);
|
||||
if (match) {
|
||||
const url = match[1];
|
||||
const response = await axios.get(url, { responseType: 'arraybuffer' });
|
||||
const buffer = Buffer.from(response.data, 'binary');
|
||||
const fileName = `badge-${badgeTypes[index]}.svg`;
|
||||
const filePath = path.join(badgesDir, fileName);
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
console.log(`Saved ${fileName}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Step 4: Delete the README.md file
|
||||
fs.unlinkSync(coverageReadmePath);
|
||||
console.log('README.md file deleted.');
|
||||
});
|
@@ -1,17 +0,0 @@
|
||||
// jest.setup.js
|
||||
const getCircularReplacer = () => {
|
||||
const seen = new WeakSet();
|
||||
return (key, value) => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (seen.has(value)) {
|
||||
return;
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
};
|
||||
|
||||
global.customJSONStringify = (object) => {
|
||||
return JSON.stringify(object, getCircularReplacer());
|
||||
};
|
@@ -1,42 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const docsDir = path.join(__dirname, '..', '.locale', 'docs');
|
||||
|
||||
const getAllFiles = (dir, files = []) => {
|
||||
fs.readdirSync(dir).forEach(file => {
|
||||
const fullPath = path.join(dir, file);
|
||||
if (fs.statSync(fullPath).isDirectory()) {
|
||||
getAllFiles(fullPath, files);
|
||||
} else {
|
||||
files.push(fullPath);
|
||||
}
|
||||
});
|
||||
return files;
|
||||
};
|
||||
|
||||
// Alle HTML-Dateien im docs-Ordner finden
|
||||
const htmlFiles = getAllFiles(docsDir).filter(file => file.endsWith('.html'));
|
||||
|
||||
// Alle HTML-Dateien bearbeiten
|
||||
htmlFiles.forEach(filePath => {
|
||||
fs.readFile(filePath, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error(`Error reading file ${filePath}:`, err);
|
||||
return;
|
||||
}
|
||||
|
||||
// `\@` durch `@` ersetzen
|
||||
let fixedData = data.replace(/\\@/g, '@');
|
||||
|
||||
fs.writeFile(filePath, fixedData, 'utf8', (err) => {
|
||||
if (err) {
|
||||
console.error(`Error writing file ${filePath}:`, err);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Fixed escaping in ${filePath}`);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@@ -1,394 +0,0 @@
|
||||
/* istanbul ignore file */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Inject } from '../decorators/Inject.js';
|
||||
import { DependencyResolutionError } from '../interfaces/Exceptions.js';
|
||||
import { ITSinjex_, ITSinjex } from '../interfaces/ITSinjex.js';
|
||||
import { ForceConstructor } from '../types/GenericContructor.js';
|
||||
|
||||
/**
|
||||
* Test the Inject decorator.
|
||||
* @param Container The implementation to test.
|
||||
* @param inject The Inject decorator to test.
|
||||
*/
|
||||
export function test_InjectDecorator(
|
||||
Container: ITSinjex_,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
inject: Function,
|
||||
): void {
|
||||
describe('Inject Decorator Tests', () => {
|
||||
let container: ITSinjex;
|
||||
|
||||
beforeEach(() => {
|
||||
// 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 inject dependency when necessary is true', () => {
|
||||
container.register('MockDependencyIdentifier', {
|
||||
value: 'test-value',
|
||||
});
|
||||
|
||||
class TestClass {
|
||||
@Inject('MockDependencyIdentifier')
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new TestClass();
|
||||
expect(instance.getDependency().value).toBe('test-value');
|
||||
});
|
||||
|
||||
it('should inject dependency and run initializer', () => {
|
||||
container.register('MockDependencyIdentifier', {
|
||||
value: 'test-value',
|
||||
});
|
||||
|
||||
class TestClass {
|
||||
@Inject('MockDependencyIdentifier', (x: string) => {
|
||||
(x as unknown as { value: string }).value =
|
||||
'test-value-init';
|
||||
|
||||
return x;
|
||||
})
|
||||
dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this.dependency;
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new TestClass();
|
||||
expect(instance.getDependency().value).toBe('test-value-init');
|
||||
});
|
||||
|
||||
it('should throw an error when necessary is true and the initializer throws an error', () => {
|
||||
let _error: Error | undefined = undefined;
|
||||
|
||||
container.register('InitThrowDependencie', {
|
||||
value: 'test-value',
|
||||
});
|
||||
|
||||
try {
|
||||
class TestClass {
|
||||
@Inject(
|
||||
'InitThrowDependencie',
|
||||
() => {
|
||||
throw new Error('Initializer error');
|
||||
},
|
||||
true,
|
||||
)
|
||||
dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this.dependency;
|
||||
}
|
||||
}
|
||||
|
||||
const _instance = new TestClass();
|
||||
console.log(_instance.getDependency());
|
||||
} catch (error) {
|
||||
_error = error;
|
||||
}
|
||||
expect(_error).toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
it('should throw an error when necessary is true and dependency is not found', () => {
|
||||
let _error: Error | undefined = undefined;
|
||||
|
||||
try {
|
||||
class TestClass {
|
||||
@Inject('NonExistentDependencyIdentifier')
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
}
|
||||
|
||||
const _instance = new TestClass();
|
||||
console.log(_instance.getDependency());
|
||||
} catch (error) {
|
||||
_error = error;
|
||||
}
|
||||
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.*'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function test_RegisterDecorator(
|
||||
Container: ITSinjex_,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
register: Function,
|
||||
): void {
|
||||
describe('Register Decorator Tests', () => {
|
||||
let container: ITSinjex;
|
||||
|
||||
beforeEach(() => {
|
||||
// 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 register a dependency', () => {
|
||||
@register('MockDependencyIdentifier')
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
}
|
||||
|
||||
expect(container.resolve('MockDependencyIdentifier')).toBe(
|
||||
TestClass,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function test_RegisterInstanceDecorator(
|
||||
Container: ITSinjex_,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
registerInstance: Function,
|
||||
mode: 'instance' | 'standalone' = 'standalone',
|
||||
): void {
|
||||
describe('RegisterInstance Decorator Tests', () => {
|
||||
let container: ITSinjex;
|
||||
|
||||
beforeEach(() => {
|
||||
// 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 register an instance of a dependency', () => {
|
||||
@registerInstance(
|
||||
'InstanceIdentifier',
|
||||
mode === 'instance' ? 'instance' : undefined,
|
||||
)
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
|
||||
public mark: string = 'instance';
|
||||
}
|
||||
|
||||
expect(
|
||||
container.resolve<TestClass>('InstanceIdentifier').mark,
|
||||
).toBe('instance');
|
||||
});
|
||||
|
||||
it('should register an instance of a dependency an run the init function', () => {
|
||||
@registerInstance(
|
||||
'InstanceIdentifier',
|
||||
(x: ForceConstructor<TestClass>) => {
|
||||
const instance = new x();
|
||||
instance.mark = 'init';
|
||||
|
||||
return instance;
|
||||
},
|
||||
)
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
|
||||
public mark: string = 'instance';
|
||||
}
|
||||
|
||||
expect(
|
||||
container.resolve<TestClass>('InstanceIdentifier').mark,
|
||||
).toBe('init');
|
||||
});
|
||||
|
||||
it('should register an instance of a dependency and get it on set', () => {
|
||||
@registerInstance(
|
||||
'InstanceIdentifier',
|
||||
mode === 'instance' ? 'instance' : undefined,
|
||||
)
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
|
||||
public mark: string = 'instance';
|
||||
public test: string = 'test';
|
||||
}
|
||||
|
||||
container.resolve<TestClass>('InstanceIdentifier').test = 'test2';
|
||||
|
||||
expect(
|
||||
container.resolve<TestClass>('InstanceIdentifier').test,
|
||||
).toBe('test2');
|
||||
});
|
||||
|
||||
it('should register an instance of a dependency an run the init function on set', () => {
|
||||
@registerInstance(
|
||||
'InstanceIdentifier',
|
||||
(x: ForceConstructor<TestClass>) => {
|
||||
const instance = new x();
|
||||
instance.mark = 'init';
|
||||
|
||||
return instance;
|
||||
},
|
||||
)
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
|
||||
public mark: string = 'instance';
|
||||
public test: string = 'test';
|
||||
}
|
||||
|
||||
container.resolve<TestClass>('InstanceIdentifier').test = 'test2';
|
||||
|
||||
expect(
|
||||
container.resolve<TestClass>('InstanceIdentifier').test,
|
||||
).toBe('test2');
|
||||
});
|
||||
});
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
/* eslint-disable deprecation/deprecation */
|
||||
import { TSinjex } from 'src/classes/TSinjex.js';
|
||||
import { Inject } from 'src/decorators/Inject.js';
|
||||
import { Register } from 'src/decorators/Register.js';
|
||||
import { RegisterInstance } from 'src/decorators/RegisterInstance.js';
|
||||
import {
|
||||
test_InjectDecorator,
|
||||
test_RegisterDecorator,
|
||||
test_RegisterInstanceDecorator,
|
||||
} from './Decorators.spec.js';
|
||||
|
||||
test_InjectDecorator(TSinjex, Inject);
|
||||
|
||||
test_RegisterDecorator(TSinjex, Register);
|
||||
|
||||
test_RegisterInstanceDecorator(TSinjex, RegisterInstance);
|
||||
|
||||
test_RegisterInstanceDecorator(TSinjex, Register, 'instance');
|
@@ -1,184 +0,0 @@
|
||||
/* istanbul ignore file */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
DependencyResolutionError,
|
||||
InitializationError,
|
||||
NoInstantiationMethodError,
|
||||
} from '../interfaces/Exceptions.js';
|
||||
import { ITSinjex, ITSinjex_ } from '../interfaces/ITSinjex.js';
|
||||
|
||||
export function test_RegisterFunction(
|
||||
Container: ITSinjex_,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
register: Function,
|
||||
): void {
|
||||
describe('Register Function Tests', () => {
|
||||
let container: ITSinjex;
|
||||
|
||||
beforeEach(() => {
|
||||
// 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 register a dependency', () => {
|
||||
const identifier = 'MockDependencyIdentifier';
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
}
|
||||
|
||||
register(identifier, TestClass, false);
|
||||
|
||||
const resolvedDependency = container.resolve(identifier);
|
||||
expect(resolvedDependency).toBe(TestClass);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function test_ResolveFunction(
|
||||
Container: ITSinjex_,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
resolve: Function,
|
||||
): void {
|
||||
describe('Resolve Function Tests', () => {
|
||||
let container: ITSinjex;
|
||||
|
||||
beforeEach(() => {
|
||||
// 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 a dependency', () => {
|
||||
const identifier = 'MockDependencyIdentifier';
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
}
|
||||
|
||||
container.register(identifier, TestClass);
|
||||
|
||||
const resolvedDependency = resolve(identifier);
|
||||
expect(resolvedDependency).toBe(TestClass);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,13 +0,0 @@
|
||||
import {
|
||||
test_injectFunction,
|
||||
test_RegisterFunction,
|
||||
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_ResolveFunction(TSinjex, resolve);
|
||||
test_injectFunction(TSinjex, inject);
|
@@ -1,79 +0,0 @@
|
||||
/* istanbul ignore file */
|
||||
import { ITSinjex_, ITSinjex } from '../interfaces/ITSinjex.js';
|
||||
|
||||
/**
|
||||
* Test the implementation of the `ITSinjex` interface.
|
||||
* @param Container The implementation to test.
|
||||
* Must implement {@link ITSinjex}, {@link ITSinjex_}
|
||||
*/
|
||||
export function test_ITSinjex(Container: ITSinjex_): void {
|
||||
describe('IDIContainer Implementation Tests', () => {
|
||||
let container: ITSinjex;
|
||||
|
||||
beforeEach(() => {
|
||||
// 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 register and resolve a dependency', () => {
|
||||
const identifier = 'myDependency';
|
||||
const dependency = { value: 42 };
|
||||
|
||||
container.register(identifier, dependency);
|
||||
|
||||
const resolvedDependency =
|
||||
container.resolve<typeof dependency>(identifier);
|
||||
expect(resolvedDependency).toBe(dependency);
|
||||
});
|
||||
|
||||
it('should register and resolve a dependency static', () => {
|
||||
const identifier = 'myDependency';
|
||||
const dependency = { value: 42 };
|
||||
|
||||
Container.register(identifier, dependency);
|
||||
|
||||
const resolvedDependency =
|
||||
Container.resolve<typeof dependency>(identifier);
|
||||
expect(resolvedDependency).toBe(dependency);
|
||||
});
|
||||
|
||||
it('should throw an error when resolving a non-registered dependency static', () => {
|
||||
const identifier = 'nonExistentDependency';
|
||||
|
||||
expect(() => Container.resolve<unknown>(identifier)).toThrow();
|
||||
});
|
||||
|
||||
it('should return undefined when resolving a non-registered, non-necessary dependency', () => {
|
||||
const resolvedDependency = Container.resolve<unknown>(
|
||||
'nonExistentDependency',
|
||||
false,
|
||||
);
|
||||
expect(resolvedDependency).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should warn when resolving a deprecated dependency', () => {
|
||||
const identifier = 'deprecatedDependency';
|
||||
const dependency = { value: 42 };
|
||||
|
||||
// Spy on console.warn
|
||||
const warnSpy = jest
|
||||
.spyOn(console, 'warn')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
Container.register(identifier, dependency, true);
|
||||
|
||||
const resolvedDependency =
|
||||
Container.resolve<typeof dependency>(identifier);
|
||||
expect(resolvedDependency).toBe(dependency);
|
||||
|
||||
// Expect console.warn to be called
|
||||
expect(warnSpy).toHaveBeenCalled();
|
||||
|
||||
// Restore the original console.warn
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
import { test_ITSinjex } from './ITSinjex.spec.js';
|
||||
import { TSinjex } from '../classes/TSinjex.js';
|
||||
|
||||
test_ITSinjex(TSinjex);
|
@@ -1,14 +1,3 @@
|
||||
import type { Inject } from '../decorators/Inject.js';
|
||||
import type { Register } from '../decorators/Register.js';
|
||||
import type { RegisterInstance } from '../decorators/RegisterInstance.js';
|
||||
import type { register } from '../functions/register.js';
|
||||
import type { resolve } from '../functions/resolve.js';
|
||||
import { ImplementsStatic } from '../helper/ImplementsStatic.js';
|
||||
import { DependencyResolutionError } from '../interfaces/Exceptions.js';
|
||||
import { IDependency } from '../interfaces/IDependency.js';
|
||||
import { ITSinjex, ITSinjex_ } from '../interfaces/ITSinjex.js';
|
||||
import { Identifier } from '../types/Identifier.js';
|
||||
|
||||
/**
|
||||
* # TSinjex
|
||||
* The main class for the Dependency Injection Container **TSinjex**.
|
||||
@@ -21,6 +10,11 @@ import { Identifier } from '../types/Identifier.js';
|
||||
* @see {@link register} for registering a dependency (class or instance) as a function.
|
||||
* @see {@link resolve} for resolving a dependency as a function.
|
||||
*/
|
||||
|
||||
import { ImplementsStatic } from "../helper/mod.ts";
|
||||
import { DependencyResolutionError, IDependency, ITSinjex, ITSinjex_ } from "../interfaces/mod.ts";
|
||||
import { Identifier } from "../types/mod.ts";
|
||||
|
||||
@ImplementsStatic<ITSinjex_>()
|
||||
export class TSinjex implements ITSinjex {
|
||||
/**
|
||||
@@ -123,5 +117,9 @@ export class TSinjex implements ITSinjex {
|
||||
return dependency.dependency as T;
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._dependencies.clear();
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
1
src/classes/mod.ts
Normal file
1
src/classes/mod.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { TSinjex } from "./TSinjex.ts";
|
@@ -1,151 +1,54 @@
|
||||
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';
|
||||
import { TSinjex } from "../classes/mod.ts";
|
||||
import { InitializationError } from "../interfaces/mod.ts";
|
||||
import { Identifier, InitDelegate } from "../types/mod.ts";
|
||||
|
||||
/**
|
||||
* A decorator to inject a dependency from a DI (Dependency Injection) container into a class property.
|
||||
* @template T The type of the dependency 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.
|
||||
* @see {@link Identifier} for more information on identifiers.
|
||||
* @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.
|
||||
* @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
|
||||
* and not found, or throws an error if the dependency is necessary and not found.
|
||||
* @throws **Only throws errors if the dependency is necessary.**
|
||||
* @throws 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
|
||||
* ```ts
|
||||
* class MyClass {
|
||||
* \@Inject<MyDependency>('MyDependencyIdentifier')
|
||||
* private myDependency!: MyDependency;
|
||||
* }
|
||||
* ```
|
||||
* @example
|
||||
* ```ts
|
||||
* class MyClass {
|
||||
* \@Inject('ILogger_', (x: ILogger_) => x.getLogger('Tags'), false)
|
||||
* private _logger?: ILogger;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function Inject<T, U>(
|
||||
export function Inject<InstanzType, DependencyType, FieldType extends object>(
|
||||
identifier: Identifier,
|
||||
init?: InitDelegate<T, U> | true,
|
||||
init?: InitDelegate<DependencyType, FieldType>,
|
||||
isNecessary = true,
|
||||
) {
|
||||
return function (target: unknown, propertyKey: string | symbol): void {
|
||||
/**
|
||||
* Function to evaluate the dependency lazily
|
||||
* to avoid circular dependencies, not found dependencies, etc.
|
||||
* @returns The resolved dependency or undefined if the dependency is not found.
|
||||
*/
|
||||
const resolve = (): T | undefined => {
|
||||
return TSinjex.getInstance().resolve<T>(identifier, isNecessary);
|
||||
};
|
||||
|
||||
Object.defineProperty(target, propertyKey, {
|
||||
get() {
|
||||
let instance: T | U | undefined;
|
||||
|
||||
const dependency: T | undefined = tryAndCatch(
|
||||
() => resolve(),
|
||||
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) 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,
|
||||
});
|
||||
|
||||
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;
|
||||
): (
|
||||
target: undefined,
|
||||
context: ClassFieldDecoratorContext<InstanzType, FieldType>,
|
||||
) => (initialValue: FieldType) => FieldType {
|
||||
return function (
|
||||
_target: undefined,
|
||||
context: ClassFieldDecoratorContext<InstanzType, FieldType>,
|
||||
): (initialValue: FieldType) => FieldType {
|
||||
if (context.kind !== "field") {
|
||||
throw new Error("Inject decorator can only be used on fields.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 } };
|
||||
const initializer = () => {
|
||||
let instance: DependencyType | FieldType | undefined;
|
||||
|
||||
return (
|
||||
_obj?.prototype != null &&
|
||||
typeof _obj.prototype.constructor === 'function'
|
||||
const dependency: DependencyType | undefined = TSinjex.getInstance()
|
||||
.resolve<DependencyType>(identifier, isNecessary);
|
||||
|
||||
if (init == null || dependency == null) {
|
||||
instance = dependency;
|
||||
} else {
|
||||
try {
|
||||
instance = init(dependency);
|
||||
} catch (error) {
|
||||
if (isNecessary) {
|
||||
throw new InitializationError(
|
||||
identifier,
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
);
|
||||
} else {
|
||||
console.warn(
|
||||
`Error initializing not necessary dependency ${identifier.toString()}: ${error}`,
|
||||
);
|
||||
instance = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return instance as FieldType;
|
||||
};
|
||||
|
||||
return function (_initialValue: FieldType): FieldType {
|
||||
return initializer();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,266 +1,88 @@
|
||||
import { InitDelegate } from 'src/types/InitDelegate.js';
|
||||
import { TSinjex } from '../classes/TSinjex.js';
|
||||
import { Identifier } from '../types/Identifier.js';
|
||||
import { TSinjex } from "../classes/mod.ts";
|
||||
import { ClassConstructor, Identifier, InitDelegate } from "../types/mod.ts";
|
||||
|
||||
//#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>,
|
||||
>(
|
||||
export function Register<ClassType extends ClassConstructor>(
|
||||
identifier: Identifier,
|
||||
init?: InitDelegate<ClassType, InstanceType<ClassType>>,
|
||||
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);
|
||||
): (target: ClassType, context: ClassDecoratorContext<ClassType>) => void {
|
||||
return function (
|
||||
target: ClassType,
|
||||
context: ClassDecoratorContext<ClassType>,
|
||||
): void {
|
||||
if (context.kind !== "class") {
|
||||
throw new Error("Register decorator can only be used on classes.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
function _register<
|
||||
TargetType extends new (...args: unknown[]) => InstanceType<TargetType>,
|
||||
>(identifier: Identifier, deprecated?: boolean) {
|
||||
return function (constructor: TargetType, ...args: unknown[]): void {
|
||||
// Get the instance of the DI container
|
||||
const diContainer = TSinjex.getInstance();
|
||||
let _instance: InstanceType<ClassType> | undefined;
|
||||
|
||||
// Register the class in the DI container
|
||||
diContainer.register(identifier, constructor, deprecated);
|
||||
if (init == undefined) {
|
||||
diContainer.register(identifier, target, deprecated);
|
||||
} else {
|
||||
diContainer.register(
|
||||
identifier,
|
||||
createLazyProxy(
|
||||
_instance,
|
||||
init,
|
||||
target,
|
||||
),
|
||||
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>;
|
||||
function createLazyProxy<ClassType extends ClassConstructor>(
|
||||
instance: InstanceType<ClassType> | undefined,
|
||||
init: InitDelegate<ClassType, InstanceType<ClassType>>,
|
||||
constructor: ClassType,
|
||||
): InstanceType<ClassType> {
|
||||
const initializeInstance = (
|
||||
instance: InstanceType<ClassType> | undefined,
|
||||
init: InitDelegate<ClassType, InstanceType<ClassType>>,
|
||||
constructor: ClassType,
|
||||
) => {
|
||||
if (instance == undefined) {
|
||||
if (init != undefined) {
|
||||
instance = init(constructor);
|
||||
} else {
|
||||
instance = new constructor() as InstanceType<ClassType>;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a proxy to instantiate the class when needed (Lazy Initialization)
|
||||
let lazyProxy: unknown = new Proxy(
|
||||
{},
|
||||
return { instance: instance, lazyProxy: instance };
|
||||
};
|
||||
|
||||
let lazyProxy = new Proxy(
|
||||
{} as InstanceType<ClassType>,
|
||||
{
|
||||
get(target, prop, receiver) {
|
||||
({ instance, lazyProxy } = initializeInstance<TargetType>(
|
||||
get(_target, prop, _receiver) {
|
||||
({ instance, lazyProxy } = initializeInstance(
|
||||
instance,
|
||||
init,
|
||||
constructor,
|
||||
args,
|
||||
lazyProxy,
|
||||
));
|
||||
|
||||
if (!instance) throw new Error("Instance is not defined");
|
||||
|
||||
// Return the requested property of the instance
|
||||
return instance[prop as keyof InstanceType<TargetType>];
|
||||
return instance[prop as keyof InstanceType<ClassType>];
|
||||
},
|
||||
set(target, prop, value, receiver) {
|
||||
({ instance, lazyProxy } = initializeInstance<TargetType>(
|
||||
set(_target, prop, value, _receiver) {
|
||||
({ instance, lazyProxy } = initializeInstance(
|
||||
instance,
|
||||
init,
|
||||
constructor,
|
||||
args,
|
||||
lazyProxy,
|
||||
));
|
||||
|
||||
if (!instance) throw new Error("Instance is not defined");
|
||||
|
||||
// Set the requested property of the instance
|
||||
return (instance[prop as keyof InstanceType<TargetType>] =
|
||||
value);
|
||||
return (instance[prop as keyof InstanceType<ClassType>] = 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 };
|
||||
return lazyProxy;
|
||||
}
|
||||
|
@@ -1,39 +0,0 @@
|
||||
import { Register } from './Register.js';
|
||||
import { Identifier } from '../types/Identifier.js';
|
||||
import { InitDelegate } from '../types/InitDelegate.js';
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
* @deprecated Use {@link Register} instead. This decorator already uses the {@link Register} decorator internally.
|
||||
*/
|
||||
export function RegisterInstance<
|
||||
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 {
|
||||
const initDelegate = typeof init === 'function' ? init : undefined;
|
||||
|
||||
if (initDelegate) return Register(identifier, initDelegate, deprecated);
|
||||
else return Register(identifier, 'instance', deprecated);
|
||||
}
|
2
src/decorators/mod.ts
Normal file
2
src/decorators/mod.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { Register } from "./Register.ts";
|
||||
export { Inject } from "./Inject.ts";
|
@@ -1,220 +0,0 @@
|
||||
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'
|
||||
);
|
||||
}
|
0
src/functions/mod.ts
Normal file
0
src/functions/mod.ts
Normal file
@@ -1,39 +0,0 @@
|
||||
import { TSinjex } from '../classes/TSinjex.js';
|
||||
import { Identifier } from '../types/Identifier.js';
|
||||
|
||||
/**
|
||||
* Register a dependency.
|
||||
* @param identifier The identifier used to register the class in the DI container.
|
||||
* @see {@link Identifier} for more information on identifiers..
|
||||
* @param dependency The dependency to register.
|
||||
*/
|
||||
export function register<T>(identifier: Identifier, dependency: T): void;
|
||||
|
||||
/**
|
||||
* Register a dependency.
|
||||
* @param identifier The identifier used to register the class in the DI container.
|
||||
* @see {@link Identifier} for more information on identifiers.
|
||||
* @param dependency The dependency to register.
|
||||
* @param deprecated A warning is logged when the dependency is resolved.
|
||||
*/
|
||||
export function register<T>(
|
||||
identifier: Identifier,
|
||||
dependency: T,
|
||||
deprecated?: true,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Register a dependency.
|
||||
* @param identifier The identifier used to register the class in the DI container.
|
||||
* @see {@link Identifier} for more information on identifiers.
|
||||
* @param dependency The dependency to register.
|
||||
* @param deprecated If true, the dependency is deprecated => a warning
|
||||
* is logged when the dependency is resolved.
|
||||
*/
|
||||
export function register<T>(
|
||||
identifier: Identifier,
|
||||
dependency: T,
|
||||
deprecated?: boolean,
|
||||
): void {
|
||||
TSinjex.getInstance().register(identifier, dependency, deprecated);
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
import { TSinjex } from '../classes/TSinjex.js';
|
||||
import { DependencyResolutionError } from '../interfaces/Exceptions.js';
|
||||
import { Identifier } from '../types/Identifier.js';
|
||||
|
||||
/**
|
||||
* Resolve a dependency.
|
||||
* @param identifier The identifier used to register the class in the DI container.
|
||||
* @see {@link Identifier} for more information on identifiers.
|
||||
* @returns The resolved dependency.
|
||||
* @throws A {@link DependencyResolutionError} if the dependency is not found.
|
||||
*/
|
||||
export function resolve<T>(identifier: Identifier): T;
|
||||
|
||||
/**
|
||||
* Resolve a dependency
|
||||
* @param identifier The identifier used to register the class in the DI container.
|
||||
* @see {@link Identifier} for more information on identifiers.
|
||||
* @param isNecessary The dependency is **not** necessary.
|
||||
* @returns The resolved dependency or undefined if the dependency is not found.
|
||||
*/
|
||||
export function resolve<T>(
|
||||
identifier: Identifier,
|
||||
isNecessary: false,
|
||||
): T | undefined;
|
||||
|
||||
/**
|
||||
* Resolve a dependency.
|
||||
* @param identifier The identifier used to register the class in the DI container.
|
||||
* @see {@link Identifier} for more information on identifiers.
|
||||
* @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
|
||||
* 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.
|
||||
*/
|
||||
export function resolve<T>(
|
||||
identifier: Identifier,
|
||||
necessary?: boolean,
|
||||
): T | undefined {
|
||||
return TSinjex.getInstance().resolve<T>(identifier, necessary);
|
||||
}
|
@@ -6,5 +6,5 @@
|
||||
* @returns A decorator function
|
||||
*/
|
||||
export function ImplementsStatic<I>() {
|
||||
return <T extends I>(constructor: T, ...args: unknown[]) => {};
|
||||
return <T extends I>(_constructor: T, ..._args: unknown[]) => {};
|
||||
}
|
||||
|
1
src/helper/mod.ts
Normal file
1
src/helper/mod.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { ImplementsStatic } from "./ImplementsStatic.ts";
|
22
src/index.ts
22
src/index.ts
@@ -1,22 +0,0 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
// Main
|
||||
export * from './classes/TSinjex.js';
|
||||
|
||||
// Decorators
|
||||
export * from './decorators/Inject.js';
|
||||
export * from './decorators/Register.js';
|
||||
export * from './decorators/RegisterInstance.js';
|
||||
|
||||
// Helper
|
||||
export * from './helper/ImplementsStatic.js';
|
||||
|
||||
// Functions
|
||||
export * from './functions/resolve.js';
|
||||
export * from './functions/register.js';
|
||||
export * from './functions/inject.js';
|
||||
|
||||
// Interfaces & Types
|
||||
export type * from './interfaces/ITSinjex.js';
|
||||
export type * from './types/InitDelegate.js';
|
||||
export type * from './types/GenericContructor.js';
|
@@ -1,5 +1,4 @@
|
||||
import { Identifier } from 'src/types/Identifier.js';
|
||||
import { ITSinjex } from './ITSinjex.js';
|
||||
import { Identifier } from "../types/mod.ts";
|
||||
|
||||
/**
|
||||
* General error class for {@link ITSinjex} interface.
|
||||
@@ -11,7 +10,7 @@ export class TSinjexError extends Error {
|
||||
*/
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'TSinjex';
|
||||
this.name = "TSinjex";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +25,7 @@ export class DependencyResolutionError extends TSinjexError {
|
||||
*/
|
||||
constructor(identifier: Identifier) {
|
||||
super(`Dependency ${identifier.toString()} could not be resolved.`);
|
||||
this.name = 'TSinjexResolutionError';
|
||||
this.name = "TSinjexResolutionError";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +43,7 @@ export class InjectorError extends TSinjexError {
|
||||
super(
|
||||
`Error injecting dependency ${identifier.toString()} with error: "${originalError}"`,
|
||||
);
|
||||
this.name = 'TSinjexInjectorError';
|
||||
this.name = "TSinjexInjectorError";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +60,7 @@ export class NoInstantiationMethodError extends TSinjexError {
|
||||
super(
|
||||
`No instantiation method found for dependency ${identifier.toString()}.`,
|
||||
);
|
||||
this.name = 'TSinjexNoInstantiationMethodError';
|
||||
this.name = "TSinjexNoInstantiationMethodError";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +78,6 @@ export class InitializationError extends TSinjexError {
|
||||
super(
|
||||
`Error initializing dependency ${identifier.toString()} with error: "${originalError}"`,
|
||||
);
|
||||
this.name = 'TSinjexInitializationError';
|
||||
this.name = "TSinjexInitializationError";
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { DependencyResolutionError } from './Exceptions.js';
|
||||
import { Identifier } from '../types/Identifier.js';
|
||||
import { Identifier } from "../types/mod.ts";
|
||||
|
||||
/**
|
||||
* Static TSInjex Interface
|
||||
|
9
src/interfaces/mod.ts
Normal file
9
src/interfaces/mod.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export type { ITSinjex, ITSinjex_ } from "./ITSinjex.ts";
|
||||
export type { IDependency } from "./IDependency.ts";
|
||||
export {
|
||||
DependencyResolutionError,
|
||||
InitializationError,
|
||||
InjectorError,
|
||||
NoInstantiationMethodError,
|
||||
TSinjexError,
|
||||
} from "./Exceptions.ts";
|
5
src/mod.ts
Normal file
5
src/mod.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export { TSinjex } from "./classes/mod.ts";
|
||||
export type { Identifier, InitDelegate } from "./types/mod.ts";
|
||||
export type { ITSinjex, ITSinjex_ } from "./interfaces/mod.ts";
|
||||
export { Inject, Register } from "./decorators/mod.ts";
|
||||
export type { ImplementsStatic } from "./helper/mod.ts";
|
@@ -11,3 +11,8 @@ export type GenericConstructor<
|
||||
* This type is used to force a class to has a constructor.
|
||||
*/
|
||||
export type ForceConstructor<T> = new (...args: unknown[]) => T;
|
||||
|
||||
/**
|
||||
* Represents any concrete (non-abstract) class constructor.
|
||||
*/
|
||||
export type ClassConstructor = new (...args: unknown[]) => object;
|
5
src/types/mod.ts
Normal file
5
src/types/mod.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// Export all type aliases for external use
|
||||
|
||||
export type { Identifier } from "./Identifier.ts";
|
||||
export type { InitDelegate } from "./InitDelegate.ts";
|
||||
export type { ClassConstructor, ForceConstructor, GenericConstructor } from "./Constructor.ts";
|
229
tests/Decorators.ts
Normal file
229
tests/Decorators.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
// deno-coverage-ignore-file
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import {
|
||||
assertEquals,
|
||||
assertInstanceOf,
|
||||
assertStrictEquals,
|
||||
assertThrows,
|
||||
} from "https://deno.land/std@0.224.0/assert/mod.ts";
|
||||
import { TSinjex } from "../src/classes/mod.ts";
|
||||
import { Inject, Register } from "../src/decorators/mod.ts";
|
||||
import { DependencyResolutionError } from "../src/interfaces/mod.ts";
|
||||
|
||||
const container = TSinjex.getInstance() as TSinjex;
|
||||
|
||||
Deno.test("should inject dependency when necessary is true", () => {
|
||||
container.clear();
|
||||
container.register("MockDependencyIdentifier", { value: "test-value" });
|
||||
|
||||
class TestClass {
|
||||
@Inject("MockDependencyIdentifier")
|
||||
private _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new TestClass();
|
||||
assertEquals(instance.getDependency().value, "test-value");
|
||||
});
|
||||
|
||||
Deno.test("should inject dependency and run initializer", () => {
|
||||
container.clear();
|
||||
container.register("MockDependencyIdentifier", { value: "test-value" });
|
||||
|
||||
class TestClass {
|
||||
@Inject("MockDependencyIdentifier", (x: any) => {
|
||||
x.value = "test-value-init";
|
||||
return x;
|
||||
})
|
||||
dependency!: any;
|
||||
public getDependency() {
|
||||
return this.dependency;
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new TestClass();
|
||||
assertEquals(instance.getDependency().value, "test-value-init");
|
||||
});
|
||||
|
||||
Deno.test("should throw error if initializer fails and dependency is necessary", () => {
|
||||
container.clear();
|
||||
container.register("InitThrowDependencie", { value: "test-value" });
|
||||
|
||||
class TestClass {
|
||||
@Inject<TestClass, any, any>("InitThrowDependencie", () => {
|
||||
throw new Error("Initializer error");
|
||||
}, true)
|
||||
dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this.dependency;
|
||||
}
|
||||
}
|
||||
|
||||
assertThrows(
|
||||
() => {
|
||||
const instance = new TestClass();
|
||||
instance.getDependency()();
|
||||
},
|
||||
Error,
|
||||
"Initializer error",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("should throw DependencyResolutionError if dependency not found", () => {
|
||||
container.clear();
|
||||
|
||||
class TestClass {
|
||||
@Inject("NonExistentDependencyIdentifier")
|
||||
private _dependency!: any;
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
}
|
||||
|
||||
assertThrows(() => {
|
||||
const instance = new TestClass();
|
||||
instance.getDependency()();
|
||||
}, DependencyResolutionError);
|
||||
});
|
||||
|
||||
Deno.test("should replace the property with the resolved dependency", () => {
|
||||
container.clear();
|
||||
container.register("MockDependencyIdentifier", { value: "test-value" });
|
||||
|
||||
class TestClass {
|
||||
@Inject("MockDependencyIdentifier")
|
||||
private _dependency!: any;
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
public isDependencyTypeofFunction() {
|
||||
return typeof this._dependency === "function";
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new TestClass();
|
||||
assertEquals(instance.getDependency().value, "test-value");
|
||||
assertEquals(instance.isDependencyTypeofFunction(), false);
|
||||
assertEquals(instance.getDependency().value, "test-value");
|
||||
});
|
||||
|
||||
Deno.test("Register Decorator: should register a dependency", () => {
|
||||
container.clear();
|
||||
|
||||
@Register("MockDependencyIdentifier")
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
}
|
||||
|
||||
assertStrictEquals(
|
||||
container.resolve("MockDependencyIdentifier"),
|
||||
TestClass,
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("RegisterInstance: should register an instance of a dependency", () => {
|
||||
container.clear();
|
||||
|
||||
@Register("InstanceIdentifier", (x) => new x())
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
|
||||
public mark: string = "instance";
|
||||
}
|
||||
|
||||
const resolved = container.resolve<TestClass>("InstanceIdentifier");
|
||||
assertEquals(resolved?.mark, "instance");
|
||||
});
|
||||
|
||||
Deno.test("RegisterInstance: should run init function during registration", () => {
|
||||
container.clear();
|
||||
|
||||
@Register("InstanceIdentifier", (x: new () => TestClass) => {
|
||||
const instance = new x();
|
||||
instance.mark = "init";
|
||||
return instance;
|
||||
})
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
|
||||
public mark: string = "instance";
|
||||
}
|
||||
|
||||
const resolved = container.resolve<TestClass>("InstanceIdentifier");
|
||||
assertEquals(resolved?.mark, "init");
|
||||
});
|
||||
|
||||
Deno.test("RegisterInstance: instance should persist modifications", () => {
|
||||
container.clear();
|
||||
|
||||
@Register("InstanceIdentifier", (x) => new x())
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
|
||||
public mark: string = "instance";
|
||||
public test: string = "test";
|
||||
}
|
||||
|
||||
const instance1 = container.resolve<TestClass>("InstanceIdentifier");
|
||||
if (instance1 == null) {
|
||||
throw new Error("Instance1 is null");
|
||||
}
|
||||
instance1.test = "test2";
|
||||
|
||||
const instance2 = container.resolve<TestClass>("InstanceIdentifier");
|
||||
if (instance2 == null) {
|
||||
throw new Error("Instance2 is null");
|
||||
}
|
||||
assertEquals(instance2.test, "test2");
|
||||
});
|
||||
|
||||
Deno.test("RegisterInstance: init function should persist modifications", () => {
|
||||
container.clear();
|
||||
|
||||
@Register("InstanceIdentifier", (x: new () => TestClass) => {
|
||||
const instance = new x();
|
||||
instance.mark = "init";
|
||||
return instance;
|
||||
})
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
|
||||
public mark: string = "instance";
|
||||
public test: string = "test";
|
||||
}
|
||||
|
||||
const instance1 = container.resolve<TestClass>("InstanceIdentifier");
|
||||
if (instance1 == null) {
|
||||
throw new Error("Instance1 is null");
|
||||
}
|
||||
instance1.test = "test2";
|
||||
|
||||
const instance2 = container.resolve<TestClass>("InstanceIdentifier");
|
||||
if (instance2 == null) {
|
||||
throw new Error("Instance2 is null");
|
||||
}
|
||||
assertEquals(instance2.test, "test2");
|
||||
});
|
78
tests/TSInjex.ts
Normal file
78
tests/TSInjex.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
// deno-coverage-ignore-file
|
||||
// deno-lint-ignore-file
|
||||
import {
|
||||
assertEquals,
|
||||
assertStrictEquals,
|
||||
assertThrows,
|
||||
} from "https://deno.land/std@0.224.0/assert/mod.ts";
|
||||
import { TSinjex } from "../src/classes/mod.ts";
|
||||
|
||||
const container = TSinjex.getInstance() as TSinjex;
|
||||
|
||||
Deno.test("should register and resolve a dependency (instance)", () => {
|
||||
container.clear();
|
||||
|
||||
const identifier = "myDependency";
|
||||
const dependency = { value: 42 };
|
||||
|
||||
container.register(identifier, dependency);
|
||||
|
||||
const resolved = container.resolve<typeof dependency>(identifier);
|
||||
assertStrictEquals(resolved, dependency);
|
||||
});
|
||||
|
||||
Deno.test("should register and resolve a dependency (static)", () => {
|
||||
container.clear();
|
||||
|
||||
const identifier = "myDependency";
|
||||
const dependency = { value: 42 };
|
||||
|
||||
TSinjex.register(identifier, dependency);
|
||||
const resolved = TSinjex.resolve<typeof dependency>(identifier);
|
||||
|
||||
assertStrictEquals(resolved, dependency);
|
||||
});
|
||||
|
||||
Deno.test("should throw error when resolving non-registered dependency (static)", () => {
|
||||
container.clear();
|
||||
|
||||
const identifier = "nonExistentDependency";
|
||||
assertThrows(() => {
|
||||
TSinjex.resolve<unknown>(identifier);
|
||||
});
|
||||
});
|
||||
|
||||
Deno.test("should return undefined when resolving non-necessary dependency", () => {
|
||||
container.clear();
|
||||
|
||||
const result = TSinjex.resolve<unknown>("nonExistentDependency", false);
|
||||
assertEquals(result, undefined);
|
||||
});
|
||||
|
||||
Deno.test("should warn when resolving a deprecated dependency", () => {
|
||||
container.clear();
|
||||
|
||||
const identifier = "deprecatedDependency";
|
||||
const dependency = { value: 42 };
|
||||
|
||||
// Mock console.warn
|
||||
const originalWarn = console.warn;
|
||||
let wasCalled = false;
|
||||
let lastMessage = "";
|
||||
|
||||
console.warn = (msg: string) => {
|
||||
wasCalled = true;
|
||||
lastMessage = msg;
|
||||
};
|
||||
|
||||
try {
|
||||
TSinjex.register(identifier, dependency, true);
|
||||
const resolved = TSinjex.resolve<typeof dependency>(identifier);
|
||||
assertStrictEquals(resolved, dependency);
|
||||
if (!wasCalled) {
|
||||
throw new Error("console.warn was not called");
|
||||
}
|
||||
} finally {
|
||||
console.warn = originalWarn; // Restore
|
||||
}
|
||||
});
|
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"inlineSourceMap": false,
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"module": "NodeNext",
|
||||
"target": "ES2020",
|
||||
"allowJs": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "NodeNext",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"strictNullChecks": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES5",
|
||||
"ES6",
|
||||
"ES7",
|
||||
"ES2021.WeakRef"
|
||||
],
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
18
typedoc.json
18
typedoc.json
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"entryPoints": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"out": ".locale/docs",
|
||||
"tsconfig": "tsconfig.json",
|
||||
"excludePrivate": false,
|
||||
"excludeProtected": false,
|
||||
"excludeExternals": false,
|
||||
"includeVersion": true,
|
||||
"readme": "README.md",
|
||||
"exclude": [
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts"
|
||||
],
|
||||
"theme": "default",
|
||||
"hideGenerator": true
|
||||
}
|
Reference in New Issue
Block a user