Compare commits
13 Commits
v1.0.0
...
feature/de
Author | SHA1 | Date | |
---|---|---|---|
c55f39d1e5
|
|||
3ac76b09d3
|
|||
46c9a8b990
|
|||
9e1b7a8d7b
|
|||
3a52f14dcd
|
|||
4fa78afbc2
|
|||
67f587d0d0
|
|||
a70869a941 | |||
7a6253a386 | |||
31de73db00 | |||
688a9d4ee4 | |||
39dbd6d816 | |||
0718ff9d68 |
@@ -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
|
coverage/
|
||||||
.vscode
|
|
||||||
.locale
|
|
||||||
.VSCodeCounter
|
|
||||||
|
|
||||||
*.ignore.*
|
|
||||||
|
|
||||||
dist/*
|
|
||||||
node_modules/*
|
|
@@ -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"
|
||||||
|
}
|
16
CHANGELOG.md
16
CHANGELOG.md
@@ -17,6 +17,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
|
## [1.2.0]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- docs: added complete Typedoc-style documentation for all `inject()` overloads
|
||||||
|
Includes parameter descriptions, usage examples, and detailed error annotations for each variant.
|
||||||
|
|
||||||
|
## [1.1.0]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- feat: exported `inject()` function from the public API via `index.ts`
|
||||||
|
The function is now available as part of the main module exports.
|
||||||
|
|
||||||
## [1.0.0]
|
## [1.0.0]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -133,6 +147,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
---
|
---
|
||||||
|
|
||||||
[unreleased]: https://github.com/20Max01/TSinjex/compare/v1.0.0...HEAD
|
[unreleased]: https://github.com/20Max01/TSinjex/compare/v1.0.0...HEAD
|
||||||
|
[1.1.0]: https://github.com/20Max01/TSinjex/compare/v1.1.0...v1.2.0
|
||||||
|
[1.1.0]: https://github.com/20Max01/TSinjex/compare/v1.0.0...v1.1.0
|
||||||
[1.0.0]: https://github.com/20Max01/TSinjex/compare/v0.4.0...v1.0.0
|
[1.0.0]: https://github.com/20Max01/TSinjex/compare/v0.4.0...v1.0.0
|
||||||
[0.4.0]: https://github.com/20Max01/TSinjex/compare/v0.3.0...v0.4.0
|
[0.4.0]: https://github.com/20Max01/TSinjex/compare/v0.3.0...v0.4.0
|
||||||
[0.3.0]: https://github.com/20Max01/TSinjex/compare/v0.2.0...v0.3.0
|
[0.3.0]: https://github.com/20Max01/TSinjex/compare/v0.2.0...v0.3.0
|
||||||
|
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.0.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
|
* # TSinjex
|
||||||
* The main class for the Dependency Injection Container **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 register} for registering a dependency (class or instance) as a function.
|
||||||
* @see {@link resolve} for resolving a dependency 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_>()
|
@ImplementsStatic<ITSinjex_>()
|
||||||
export class TSinjex implements ITSinjex {
|
export class TSinjex implements ITSinjex {
|
||||||
/**
|
/**
|
||||||
@@ -123,5 +117,9 @@ export class TSinjex implements ITSinjex {
|
|||||||
return dependency.dependency as T;
|
return dependency.dependency as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
this._dependencies.clear();
|
||||||
|
}
|
||||||
|
|
||||||
//#endregion
|
//#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 { TSinjex } from "../classes/mod.ts";
|
||||||
import {
|
import { InitializationError } from "../interfaces/mod.ts";
|
||||||
DependencyResolutionError,
|
import { Identifier, InitDelegate } from "../types/mod.ts";
|
||||||
InitializationError,
|
|
||||||
InjectorError,
|
|
||||||
NoInstantiationMethodError,
|
|
||||||
} from '../interfaces/Exceptions.js';
|
|
||||||
import { Identifier } from '../types/Identifier.js';
|
|
||||||
import { InitDelegate } from '../types/InitDelegate.js';
|
|
||||||
|
|
||||||
/**
|
export function Inject<InstanzType, DependencyType, FieldType extends object>(
|
||||||
* 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>(
|
|
||||||
identifier: Identifier,
|
identifier: Identifier,
|
||||||
init?: InitDelegate<T, U> | true,
|
init?: InitDelegate<DependencyType, FieldType>,
|
||||||
isNecessary = true,
|
isNecessary = true,
|
||||||
) {
|
): (
|
||||||
return function (target: unknown, propertyKey: string | symbol): void {
|
target: undefined,
|
||||||
/**
|
context: ClassFieldDecoratorContext<InstanzType, FieldType>,
|
||||||
* Function to evaluate the dependency lazily
|
) => (initialValue: FieldType) => FieldType {
|
||||||
* to avoid circular dependencies, not found dependencies, etc.
|
return function (
|
||||||
* @returns The resolved dependency or undefined if the dependency is not found.
|
_target: undefined,
|
||||||
*/
|
context: ClassFieldDecoratorContext<InstanzType, FieldType>,
|
||||||
const resolve = (): T | undefined => {
|
): (initialValue: FieldType) => FieldType {
|
||||||
return TSinjex.getInstance().resolve<T>(identifier, isNecessary);
|
if (context.kind !== "field") {
|
||||||
|
throw new Error("Inject decorator can only be used on fields.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializer = () => {
|
||||||
|
let instance: DependencyType | FieldType | undefined;
|
||||||
|
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.defineProperty(target, propertyKey, {
|
return function (_initialValue: FieldType): FieldType {
|
||||||
get() {
|
return initializer();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if an object has a constructor.
|
|
||||||
* @param obj The object to check.
|
|
||||||
* @returns True if the object has a constructor, false otherwise.
|
|
||||||
*/
|
|
||||||
function hasConstructor<T>(obj: T): obj is T & { new (): unknown } {
|
|
||||||
const _obj = obj as unknown as { prototype?: { constructor?: unknown } };
|
|
||||||
|
|
||||||
return (
|
|
||||||
_obj?.prototype != null &&
|
|
||||||
typeof _obj.prototype.constructor === 'function'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
@@ -1,266 +1,88 @@
|
|||||||
import { InitDelegate } from 'src/types/InitDelegate.js';
|
import { TSinjex } from "../classes/mod.ts";
|
||||||
import { TSinjex } from '../classes/TSinjex.js';
|
import { ClassConstructor, Identifier, InitDelegate } from "../types/mod.ts";
|
||||||
import { Identifier } from '../types/Identifier.js';
|
|
||||||
|
|
||||||
//#region Overloads
|
export function Register<ClassType extends ClassConstructor>(
|
||||||
|
|
||||||
/**
|
|
||||||
* A decorator to register a class in the **TSinjex** DI (Dependency Injection) container.
|
|
||||||
* @template TargetType The type of the class to be registered.
|
|
||||||
* @param identifier The identifier used to register the class in the DI container.
|
|
||||||
* @see {@link Identifier} for more information on identifiers.
|
|
||||||
* @param deprecated If true, the dependency is deprecated and a warning
|
|
||||||
* is logged only once upon the first resolution of the dependency.
|
|
||||||
* @returns The decorator function to be applied on the class.
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* \@Register('MyClassIdentifier')
|
|
||||||
* class MyClass {
|
|
||||||
* // ...
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* \@Register('MyClassIdentifier', true)
|
|
||||||
* class MyClass {
|
|
||||||
* // ...
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function Register<
|
|
||||||
TargetType extends new (...args: unknown[]) => InstanceType<TargetType>,
|
|
||||||
>(
|
|
||||||
identifier: Identifier,
|
identifier: Identifier,
|
||||||
|
init?: InitDelegate<ClassType, InstanceType<ClassType>>,
|
||||||
deprecated?: boolean,
|
deprecated?: boolean,
|
||||||
): (constructor: TargetType, ...args: unknown[]) => void;
|
): (target: ClassType, context: ClassDecoratorContext<ClassType>) => void {
|
||||||
|
return function (
|
||||||
/**
|
target: ClassType,
|
||||||
* A decorator to register an instance of a class in the DI (Dependency Injection) container.
|
context: ClassDecoratorContext<ClassType>,
|
||||||
* @template TargetType The type of the class whose instance is to be registered.
|
): void {
|
||||||
* @param identifier The identifier used to register the instance in the DI container.
|
if (context.kind !== "class") {
|
||||||
* @see {@link Identifier} for more information on identifiers.
|
throw new Error("Register decorator can only be used on classes.");
|
||||||
* @param shouldRegister Set to 'instance' to register the instance in the DI container
|
|
||||||
* with an empty constructor.
|
|
||||||
* @param deprecated If true, the dependency is deprecated and a warning
|
|
||||||
* is logged only once upon the first resolution of the dependency.
|
|
||||||
* @returns The decorator function to be applied on the class.
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* \@RegisterInstance('MyClassInstanceIdentifier', 'instance')
|
|
||||||
* class MyClass {
|
|
||||||
* // ...
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* \@RegisterInstance('MyClassInstanceIdentifier', 'instance', true)
|
|
||||||
* class MyClass {
|
|
||||||
* // ...
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function Register<
|
|
||||||
TargetType extends new (..._args: unknown[]) => InstanceType<TargetType>,
|
|
||||||
>(
|
|
||||||
identifier: Identifier,
|
|
||||||
shouldRegister: 'instance',
|
|
||||||
deprecated?: boolean,
|
|
||||||
): (constructor: TargetType, ...args: unknown[]) => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A decorator to register an instance of a class in the DI (Dependency Injection) container.
|
|
||||||
* @template TargetType The type of the class whose instance is to be registered.
|
|
||||||
* @param identifier The identifier used to register the instance in the DI container.
|
|
||||||
* @see {@link Identifier} for more information on identifiers.
|
|
||||||
* @param init An optional initializer function which get the constructor of the class
|
|
||||||
* as input and returns an instance of the class.
|
|
||||||
* @param deprecated If true, the dependency is deprecated and a warning
|
|
||||||
* is logged only once upon the first resolution of the dependency.
|
|
||||||
* @see {@link InitDelegate} for more information on initializer functions.
|
|
||||||
* @returns The decorator function to be applied on the class.
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* \@RegisterInstance('MyClassInstanceIdentifier', (constructor) => new constructor())
|
|
||||||
* class MyClass {
|
|
||||||
* // ...
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* \@RegisterInstance('MyClassInstanceIdentifier', (constructor) => new constructor(), true)
|
|
||||||
* class MyClass {
|
|
||||||
* // ...
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function Register<
|
|
||||||
TargetType extends new (..._args: unknown[]) => InstanceType<TargetType>,
|
|
||||||
>(
|
|
||||||
identifier: Identifier,
|
|
||||||
init?: InitDelegate<
|
|
||||||
TargetType & { new (..._args: unknown[]): InstanceType<TargetType> },
|
|
||||||
InstanceType<TargetType>
|
|
||||||
>,
|
|
||||||
deprecated?: boolean,
|
|
||||||
): (constructor: TargetType, ...args: unknown[]) => void;
|
|
||||||
|
|
||||||
//#endregion Overloads
|
|
||||||
|
|
||||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
||||||
export function Register<
|
|
||||||
TargetType extends new (...args: unknown[]) => InstanceType<TargetType>,
|
|
||||||
>(
|
|
||||||
identifier: Identifier,
|
|
||||||
arg1?:
|
|
||||||
| undefined
|
|
||||||
| boolean
|
|
||||||
| InitDelegate<TargetType, InstanceType<TargetType>>
|
|
||||||
| 'instance',
|
|
||||||
arg2?: boolean,
|
|
||||||
): (constructor: TargetType, ...args: unknown[]) => void {
|
|
||||||
const deprecated = typeof arg1 === 'boolean' ? arg1 : arg2;
|
|
||||||
const init = typeof arg1 === 'function' ? arg1 : undefined;
|
|
||||||
const shouldRegisterInstance = arg1 === 'instance';
|
|
||||||
|
|
||||||
if (init == undefined && shouldRegisterInstance !== true) {
|
|
||||||
return _register(identifier, deprecated);
|
|
||||||
} else {
|
|
||||||
return _registerInstance(identifier, init, deprecated);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A decorator to register a class in the **TSinjex** DI (Dependency Injection) container.
|
|
||||||
* @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();
|
|
||||||
|
|
||||||
// Register the class in the DI container
|
|
||||||
diContainer.register(identifier, constructor, deprecated);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A decorator to register an instance of a class in the DI (Dependency Injection) container.
|
|
||||||
* @template TargetType The type of the class whose instance is to be registered.
|
|
||||||
* @param identifier The identifier used to register the instance in the DI container.
|
|
||||||
* @see {@link Identifier} for more information on identifiers.
|
|
||||||
* @param init An optional initializer function which get the constructor of the class
|
|
||||||
* as input and returns an instance of the class.
|
|
||||||
* @param deprecated If true, the dependency is deprecated and a warning
|
|
||||||
* is logged only once upon the first resolution of the dependency.
|
|
||||||
* @see {@link InitDelegate} for more information on initializer functions.
|
|
||||||
* @returns The decorator function to be applied on the class.
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* \@RegisterInstance('MyClassInstanceIdentifier', (constructor) => new constructor())
|
|
||||||
* class MyClass {
|
|
||||||
* // ...
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
function _registerInstance<
|
|
||||||
TargetType extends new (..._args: unknown[]) => InstanceType<TargetType>,
|
|
||||||
>(
|
|
||||||
identifier: Identifier,
|
|
||||||
init?: InitDelegate<
|
|
||||||
TargetType & { new (..._args: unknown[]): InstanceType<TargetType> },
|
|
||||||
InstanceType<TargetType>
|
|
||||||
>,
|
|
||||||
deprecated?: boolean,
|
|
||||||
) {
|
|
||||||
return function (constructor: TargetType, ...args: unknown[]): void {
|
|
||||||
// Get the instance of the DI container
|
|
||||||
const diContainer = TSinjex.getInstance();
|
|
||||||
let instance: InstanceType<TargetType>;
|
|
||||||
|
|
||||||
// Create a proxy to instantiate the class when needed (Lazy Initialization)
|
|
||||||
let lazyProxy: unknown = new Proxy(
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
get(target, prop, receiver) {
|
|
||||||
({ instance, lazyProxy } = initializeInstance<TargetType>(
|
|
||||||
instance,
|
|
||||||
init,
|
|
||||||
constructor,
|
|
||||||
args,
|
|
||||||
lazyProxy,
|
|
||||||
));
|
|
||||||
|
|
||||||
// Return the requested property of the instance
|
|
||||||
return instance[prop as keyof InstanceType<TargetType>];
|
|
||||||
},
|
|
||||||
set(target, prop, value, receiver) {
|
|
||||||
({ instance, lazyProxy } = initializeInstance<TargetType>(
|
|
||||||
instance,
|
|
||||||
init,
|
|
||||||
constructor,
|
|
||||||
args,
|
|
||||||
lazyProxy,
|
|
||||||
));
|
|
||||||
|
|
||||||
// Set the requested property of the instance
|
|
||||||
return (instance[prop as keyof InstanceType<TargetType>] =
|
|
||||||
value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register the lazy proxy in the DI container
|
|
||||||
diContainer.register(identifier, lazyProxy, deprecated);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the instance of the class.
|
|
||||||
* @template TargetType The type of the class whose instance is to be initialized.
|
|
||||||
* @param instance The instance of the class to be initialized.
|
|
||||||
* @param init The optional initializer function to initialize the instance.
|
|
||||||
* @param constructor The constructor of the class.
|
|
||||||
* @param args The arguments to be passed to the constructor of the class.
|
|
||||||
* @param lazyProxy The lazy proxy to instantiate the class when needed.
|
|
||||||
* @returns The initialized instance and the lazy proxy.
|
|
||||||
*/
|
|
||||||
function initializeInstance<
|
|
||||||
TargetType extends new (..._args: unknown[]) => InstanceType<TargetType>,
|
|
||||||
>(
|
|
||||||
instance: InstanceType<TargetType>,
|
|
||||||
init:
|
|
||||||
| InitDelegate<
|
|
||||||
TargetType &
|
|
||||||
(new (..._args: unknown[]) => InstanceType<TargetType>),
|
|
||||||
InstanceType<TargetType>
|
|
||||||
>
|
|
||||||
| undefined,
|
|
||||||
constructor: TargetType,
|
|
||||||
args: unknown[],
|
|
||||||
lazyProxy: unknown,
|
|
||||||
): { instance: InstanceType<TargetType>; lazyProxy: unknown } {
|
|
||||||
if (instance == null) {
|
|
||||||
if (init) {
|
|
||||||
instance = init(constructor);
|
|
||||||
} else {
|
|
||||||
instance = new constructor(...args);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
lazyProxy = instance;
|
|
||||||
|
|
||||||
return { instance, lazyProxy };
|
const diContainer = TSinjex.getInstance();
|
||||||
|
let _instance: InstanceType<ClassType> | undefined;
|
||||||
|
|
||||||
|
if (init == undefined) {
|
||||||
|
diContainer.register(identifier, target, deprecated);
|
||||||
|
} else {
|
||||||
|
diContainer.register(
|
||||||
|
identifier,
|
||||||
|
createLazyProxy(
|
||||||
|
_instance,
|
||||||
|
init,
|
||||||
|
target,
|
||||||
|
),
|
||||||
|
deprecated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { instance: instance, lazyProxy: instance };
|
||||||
|
};
|
||||||
|
|
||||||
|
let lazyProxy = new Proxy(
|
||||||
|
{} as InstanceType<ClassType>,
|
||||||
|
{
|
||||||
|
get(_target, prop, _receiver) {
|
||||||
|
({ instance, lazyProxy } = initializeInstance(
|
||||||
|
instance,
|
||||||
|
init,
|
||||||
|
constructor,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!instance) throw new Error("Instance is not defined");
|
||||||
|
|
||||||
|
// Return the requested property of the instance
|
||||||
|
return instance[prop as keyof InstanceType<ClassType>];
|
||||||
|
},
|
||||||
|
set(_target, prop, value, _receiver) {
|
||||||
|
({ instance, lazyProxy } = initializeInstance(
|
||||||
|
instance,
|
||||||
|
init,
|
||||||
|
constructor,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!instance) throw new Error("Instance is not defined");
|
||||||
|
|
||||||
|
// Set the requested property of the instance
|
||||||
|
return (instance[prop as keyof InstanceType<ClassType>] = value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
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,113 +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';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function to inject a dependency from a DI (Dependency Injection) container into a variable.
|
|
||||||
* @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
|
|
||||||
* let myDependency = inject<MyDependency>('MyDependencyIdentifier');
|
|
||||||
* ```
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* let logger = inject<ILogger>('ILogger_', (x: ILogger_) => x.getLogger('Tags'), false);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function inject<T, U>(
|
|
||||||
identifier: Identifier,
|
|
||||||
init?: InitDelegate<T, U> | true,
|
|
||||||
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) 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
|
* @returns A decorator function
|
||||||
*/
|
*/
|
||||||
export function ImplementsStatic<I>() {
|
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";
|
21
src/index.ts
21
src/index.ts
@@ -1,21 +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';
|
|
||||||
|
|
||||||
// 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 { Identifier } from "../types/mod.ts";
|
||||||
import { ITSinjex } from './ITSinjex.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General error class for {@link ITSinjex} interface.
|
* General error class for {@link ITSinjex} interface.
|
||||||
@@ -11,7 +10,7 @@ export class TSinjexError extends Error {
|
|||||||
*/
|
*/
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = 'TSinjex';
|
this.name = "TSinjex";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ export class DependencyResolutionError extends TSinjexError {
|
|||||||
*/
|
*/
|
||||||
constructor(identifier: Identifier) {
|
constructor(identifier: Identifier) {
|
||||||
super(`Dependency ${identifier.toString()} could not be resolved.`);
|
super(`Dependency ${identifier.toString()} could not be resolved.`);
|
||||||
this.name = 'TSinjexResolutionError';
|
this.name = "TSinjexResolutionError";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@ export class InjectorError extends TSinjexError {
|
|||||||
super(
|
super(
|
||||||
`Error injecting dependency ${identifier.toString()} with error: "${originalError}"`,
|
`Error injecting dependency ${identifier.toString()} with error: "${originalError}"`,
|
||||||
);
|
);
|
||||||
this.name = 'TSinjexInjectorError';
|
this.name = "TSinjexInjectorError";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +60,7 @@ export class NoInstantiationMethodError extends TSinjexError {
|
|||||||
super(
|
super(
|
||||||
`No instantiation method found for dependency ${identifier.toString()}.`,
|
`No instantiation method found for dependency ${identifier.toString()}.`,
|
||||||
);
|
);
|
||||||
this.name = 'TSinjexNoInstantiationMethodError';
|
this.name = "TSinjexNoInstantiationMethodError";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +78,6 @@ export class InitializationError extends TSinjexError {
|
|||||||
super(
|
super(
|
||||||
`Error initializing dependency ${identifier.toString()} with error: "${originalError}"`,
|
`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/mod.ts";
|
||||||
import { Identifier } from '../types/Identifier.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static TSInjex Interface
|
* 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.
|
* This type is used to force a class to has a constructor.
|
||||||
*/
|
*/
|
||||||
export type ForceConstructor<T> = new (...args: unknown[]) => T;
|
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