Compare commits

..

18 Commits
v0.4.0 ... main

Author SHA1 Message Date
a70869a941 docs: reflect changes to changelog 2025-04-02 22:20:58 +02:00
7a6253a386 docs: Push version to 1.2.0 2025-04-02 22:20:58 +02:00
31de73db00 docs(api): add full JSDoc for all inject() overloads with examples 2025-04-02 22:20:58 +02:00
688a9d4ee4 docs: reflect changes to changelog 2025-04-02 22:01:10 +02:00
39dbd6d816 docs: Push version to 1.1.0 2025-04-02 22:01:10 +02:00
0718ff9d68 feat(api): expose inject() function via index.ts 2025-04-02 22:01:10 +02:00
115b3181e0 docs: reflect changes to changelog 2025-04-02 21:52:36 +02:00
c7fa78270c chore: update package-lock.json 2025-04-02 21:52:36 +02:00
3401656219 test(inject): add test suite to verify behavior of new inject() function 2025-04-02 21:52:36 +02:00
7e7fd996b3 feat(core): add inject() function to programmatically inject dependencies 2025-04-02 21:52:36 +02:00
a6fabc329b refactor(resolve): rename necessary parameter to isNecessary for clarity 2025-04-02 21:52:36 +02:00
81fb7f0071 refactor(inject): rename necessary parameter to isNecessary for clarity 2025-04-02 21:52:36 +02:00
2e89cdd619 test(config): modernize jest setup for ESM 2025-04-02 21:52:36 +02:00
b157f48261 fix: imports in tests 2025-04-02 21:52:36 +02:00
bbf68bff34 docs: reflect changes to changelog 2025-04-02 21:52:36 +02:00
b994a074c0 docs: Push version to 1.0.0 2025-04-02 21:52:36 +02:00
1da8efff94 feat(cli): add --without-extension flag to control import path formatting 2025-04-02 21:52:36 +02:00
c6e9fbd2a3 feat!: switch to native ESM with NodeNext module resolution and .js import paths
BREAKING CHANGE: Consumers must use ESM-compatible environments.
All import paths now include .js extensions.
CommonJS (require) is no longer supported.
2025-04-02 21:52:36 +02:00
23 changed files with 517 additions and 93 deletions

View File

@@ -17,6 +17,61 @@ 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]
### Added
- feat: Enable native ESM support using `"type": "module"` and `moduleResolution: "NodeNext"` in the compiler settings.
- feat: All internal imports now explicitly include `.js` extensions for full Node.js ESM compatibility.
- feat: Updated `tsconfig.json` to reflect changes for ESM builds (`module: "NodeNext"`, `target: "ES2020"`, etc.).
- feat(cli): add `--without-extension` (`-x`) flag to optionally omit file extensions in generated import paths
- feat: introduce `inject()` function as a programmatic alternative to the `@Inject` decorator
Supports optional initializers and constructor instantiation for resolved dependencies.
Designed for cases where decorators are not suitable or dynamic resolution is needed.
- test: added comprehensive test suite for `inject()` function, covering resolution, initialization, error cases and instantiation behavior
### Changed
- All source files using relative or internal imports were updated to use `.js` extensions to support Node.js ESM runtime resolution.
- test: update Jest config for ts-jest ESM compatibility and .js import support
- renamed internal parameter `necessary``isNecessary` for naming clarity
### Removed
- Removed implicit support for CommonJS-style imports without file extensions.
### Deprecated
- Support for CommonJS consumers using `require()` is no longer available. Use `import` with an ESM-compatible environment instead.
### Fixed
### Security
### ⚠️ Breaking Changes
- **BREAKING CHANGE**: This version migrates the entire codebase to native ES modules.
- Consumers must use Node.js in ESM mode or compatible bundlers.
- Import paths now include `.js` extensions.
- Using `require()` (CommonJS) to load this library will no longer work.
- All consuming projects must either:
- Use `"type": "module"` in their `package.json`, or
- Use an ESM-aware bundler (e.g. Webpack, Vite, etc.)
## [0.4.0] ## [0.4.0]
### Added ### Added
@@ -91,8 +146,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
--- ---
[unreleased]: https://github.com/20Max01/TSinjex/compare/0.0.14...HEAD [unreleased]: https://github.com/20Max01/TSinjex/compare/v1.0.0...HEAD
[0.0.14]: https://github.com/20Max01/TSinjex/compare/0.0.13...v0.0.14 [1.1.0]: https://github.com/20Max01/TSinjex/compare/v1.1.0...v1.2.0
[0.2.0]: https://github.com/20Max01/TSinjex/compare/v0.0.14...v0.2.0 [1.1.0]: https://github.com/20Max01/TSinjex/compare/v1.0.0...v1.1.0
[0.3.0]: https://github.com/20Max01/TSinjex/compare/v0.2.0...v0.3.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.2.0]: https://github.com/20Max01/TSinjex/compare/v0.0.14...v0.2.0
[0.0.14]: https://github.com/20Max01/TSinjex/compare/v0.0.13...v0.0.14

View File

@@ -24,21 +24,27 @@ const argv = yargs
description: 'File pattern to search for (e.g., .ts, .js)', description: 'File pattern to search for (e.g., .ts, .js)',
default: '.ts', default: '.ts',
}) })
.option('without-extension', {
alias: 'x',
type: 'boolean',
description: 'Omit file extension in import paths',
default: false,
})
.help() .help()
.argv; .argv;
// Fixed RegEx patterns for decorator detection // Fixed RegEx patterns for decorator detection
const SEARCH_PATTERNS = [ const SEARCH_PATTERNS = [
/^@Register(?:<(.+)?>)?\(\s*["']{1}(.+)?["']{1}\s*,?\s*(true|false)?\s*\)/m, // Matches @Register(...) /^@Register(?:<(.+)?>)?\(\s*["']{1}(.+)?["']{1}\s*,?\s*(true|false)?\s*\)/m,
/^@RegisterInstance(?:<(.+)?>)?\(\s*["']{1}(.+)?["']{1}\s*,?\s*(.+)?\s*\)/m, // Matches @RegisterInstance(...) /^@RegisterInstance(?:<(.+)?>)?\(\s*["']{1}(.+)?["']{1}\s*,?\s*(.+)?\s*\)/m,
]; ];
const FILE_PATTERN = argv.pattern.startsWith('.') ? argv.pattern : `.${argv.pattern}`; // Ensure the pattern starts with a dot const FILE_PATTERN = argv.pattern.startsWith('.') ? argv.pattern : `.${argv.pattern}`;
/** /**
* Recursively searches for all files in a directory matching the specified pattern. * Recursively collects all files with a specific extension.
* @param {string} dirPath - The directory to search. * @param {string} dirPath - Root directory
* @returns {string[]} - List of matching files. * @returns {string[]} List of file paths
*/ */
function getAllFiles(dirPath) { function getAllFiles(dirPath) {
let files = fs.readdirSync(dirPath); let files = fs.readdirSync(dirPath);
@@ -57,9 +63,9 @@ function getAllFiles(dirPath) {
} }
/** /**
* Filters files that contain at least one of the specified regex patterns. * Checks files for decorator usage.
* @param {string[]} files - List of files to check. * @param {string[]} files
* @returns {string[]} - Files that contain at least one of the specified patterns. * @returns {string[]} Filtered files
*/ */
function findFilesWithPattern(files) { function findFilesWithPattern(files) {
return files.filter((file) => { return files.filter((file) => {
@@ -69,16 +75,21 @@ function findFilesWithPattern(files) {
} }
/** /**
* Generates an import file containing imports for all found files. * Generates ES-style import statements from file paths.
* @param {string[]} files - List of relevant files. * @param {string[]} files
* @returns {string} - Generated import code. * @returns {string}
*/ */
function generateImports(files) { function generateImports(files) {
return files.map((file) => `import '${file.replace(/\\/g, '/')}';`).join('\n') + '\n'; 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';
} }
/** /**
* Main function that executes the script. * Script entry point.
*/ */
function main() { function main() {
try { try {

View File

@@ -2,11 +2,17 @@ module.exports = {
setupFilesAfterEnv: ['./scripts/jest.setup.js'], setupFilesAfterEnv: ['./scripts/jest.setup.js'],
preset: 'ts-jest', preset: 'ts-jest',
testEnvironment: 'node', testEnvironment: 'node',
extensionsToTreatAsEsm: ['.ts'],
transform: {
'^.+\\.ts$': ['ts-jest', { useESM: true }],
},
testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(test).ts'], testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(test).ts'],
testPathIgnorePatterns: ['\\.spec\\.ts$', '\\.performance\\.test\\.ts$'], testPathIgnorePatterns: ['\\.spec\\.ts$', '\\.performance\\.test\\.ts$'],
moduleDirectories: ['node_modules', 'src'], moduleDirectories: ['node_modules', 'src'],
moduleNameMapper: { moduleNameMapper: {
'^src/(.*)\\.js$': '<rootDir>/src/$1',
'^src/(.*)$': '<rootDir>/src/$1', '^src/(.*)$': '<rootDir>/src/$1',
'^(\\.{1,2}/.*)\\.js$': '$1',
}, },
collectCoverage: true, collectCoverage: true,
coverageDirectory: '.locale/coverage', coverageDirectory: '.locale/coverage',
@@ -19,4 +25,4 @@ module.exports = {
statements: 70, statements: 70,
}, },
}, },
}; };

View File

@@ -1,23 +1,32 @@
module.exports = { module.exports = {
setupFilesAfterEnv: ['./scripts/jest.setup.js'], setupFilesAfterEnv: ['./scripts/jest.setup.js'],
preset: 'ts-jest',
testEnvironment: 'node', testEnvironment: 'node',
transform: {
'^.+\\.ts$': ['ts-jest', { useESM: true }],
},
extensionsToTreatAsEsm: ['.ts'],
testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(test).ts'], testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(test).ts'],
testPathIgnorePatterns: ['\\.spec\\.ts$', '\\.performance\\.test\\.ts$'], testPathIgnorePatterns: ['\\.spec\\.ts$', '\\.performance\\.test\\.ts$'],
moduleDirectories: ['node_modules', 'src'], moduleDirectories: ['node_modules', 'src'],
moduleNameMapper: { moduleNameMapper: {
'^src/(.*)\\.js$': '<rootDir>/src/$1',
'^src/(.*)$': '<rootDir>/src/$1', '^src/(.*)$': '<rootDir>/src/$1',
'^(\\.{1,2}/.*)\\.js$': '$1',
}, },
collectCoverage: true, collectCoverage: true,
coverageDirectory: '.locale/coverage', coverageDirectory: '.locale/coverage',
coverageReporters: ['text', ['lcov', { projectRoot: '..' }], 'json-summary'], coverageReporters: [
'text',
['lcov', { projectRoot: '..' }],
'json-summary',
],
collectCoverageFrom: [ collectCoverageFrom: [
'src/**/*.{ts,tsx}', 'src/**/*.{ts,tsx}',
'!src/**/*.d.ts', '!src/**/*.d.ts',
'!src/**/*.performance.test.ts', '!src/**/*.performance.test.ts',
'!src/**/*.spec.ts', '!src/**/*.spec.ts',
'!src/**/*.test.ts', '!src/**/*.test.ts',
'!src/auto-imports.ts' '!src/auto-imports.ts',
], ],
coverageThreshold: { coverageThreshold: {
global: { global: {

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "ts-injex", "name": "ts-injex",
"version": "0.4.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ts-injex", "name": "ts-injex",
"version": "0.4.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"eslint-plugin-prettier": "^5.2.1", "eslint-plugin-prettier": "^5.2.1",

View File

@@ -1,6 +1,6 @@
{ {
"name": "ts-injex", "name": "ts-injex",
"version": "0.4.0", "version": "1.2.0",
"description": "Simple boilerplate code free dependency injection system for TypeScript.", "description": "Simple boilerplate code free dependency injection system for TypeScript.",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",

View File

@@ -1,9 +1,9 @@
/* istanbul ignore file */ /* istanbul ignore file */
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { Inject } from 'src/decorators/Inject'; import { Inject } from '../decorators/Inject.js';
import { DependencyResolutionError } from 'src/interfaces/Exceptions'; import { DependencyResolutionError } from '../interfaces/Exceptions.js';
import { ForceConstructor } from 'src/types/GenericContructor'; import { ITSinjex_, ITSinjex } from '../interfaces/ITSinjex.js';
import { ITSinjex_, ITSinjex } from '../interfaces/ITSinjex'; import { ForceConstructor } from '../types/GenericContructor.js';
/** /**
* Test the Inject decorator. * Test the Inject decorator.

View File

@@ -1,13 +1,13 @@
/* eslint-disable deprecation/deprecation */ /* eslint-disable deprecation/deprecation */
import { TSinjex } from 'src/classes/TSinjex'; import { TSinjex } from 'src/classes/TSinjex.js';
import { Inject } from 'src/decorators/Inject'; import { Inject } from 'src/decorators/Inject.js';
import { Register } from 'src/decorators/Register'; import { Register } from 'src/decorators/Register.js';
import { RegisterInstance } from 'src/decorators/RegisterInstance'; import { RegisterInstance } from 'src/decorators/RegisterInstance.js';
import { import {
test_InjectDecorator, test_InjectDecorator,
test_RegisterDecorator, test_RegisterDecorator,
test_RegisterInstanceDecorator, test_RegisterInstanceDecorator,
} from './Decorators.spec'; } from './Decorators.spec.js';
test_InjectDecorator(TSinjex, Inject); test_InjectDecorator(TSinjex, Inject);

View File

@@ -1,6 +1,11 @@
/* istanbul ignore file */ /* istanbul ignore file */
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { ITSinjex, ITSinjex_ } from 'src/interfaces/ITSinjex'; import {
DependencyResolutionError,
InitializationError,
NoInstantiationMethodError,
} from '../interfaces/Exceptions.js';
import { ITSinjex, ITSinjex_ } from '../interfaces/ITSinjex.js';
export function test_RegisterFunction( export function test_RegisterFunction(
Container: ITSinjex_, Container: ITSinjex_,
@@ -69,3 +74,111 @@ export function test_ResolveFunction(
}); });
}); });
} }
/**
* Test the inject function.
* @param Container The DI container implementation to test against.
* @param inject The inject function to test.
*/
export function test_injectFunction(
Container: ITSinjex_,
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
inject: Function,
): void {
describe('inject Function Tests', () => {
let container: ITSinjex;
beforeEach(() => {
// Reset singleton
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(Container as any)['_instance'] = undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(Container as any)['_dependencies'] = undefined;
container = Container.getInstance();
});
it('should resolve and return the dependency as is', () => {
container.register('SimpleDep', { value: 'test' });
const resolved = inject('SimpleDep');
expect(resolved.value).toBe('test');
});
it('should resolve and run the initializer function', () => {
container.register('DepWithInit', { value: 'before' });
const resolved = inject('DepWithInit', (dep: any) => {
dep.value = 'after';
return dep;
});
expect(resolved.value).toBe('after');
});
it('should resolve and instantiate the dependency if init is true and constructor exists', () => {
class WithConstructor {
value = 'constructed';
}
container.register('Constructable', WithConstructor);
const resolved = inject('Constructable', true);
expect(resolved.value).toBe('constructed');
});
it('should return undefined if dependency is not found and not necessary', () => {
const resolved = inject('NonExistentDep', undefined, false);
expect(resolved).toBeUndefined();
});
it('should throw DependencyResolutionError if dependency is not found and necessary', () => {
expect(() => inject('MissingDep')).toThrow(
DependencyResolutionError,
);
});
it('should throw InitializationError if init function throws', () => {
container.register('InitThrows', {});
expect(() =>
inject('InitThrows', () => {
throw new Error('fail');
}),
).toThrow(InitializationError);
});
it('should throw NoInstantiationMethodError if init = true and no constructor exists', () => {
container.register('NonConstructable', {});
expect(() => inject('NonConstructable', true)).toThrow(
NoInstantiationMethodError,
);
});
it('should not throw if no constructor and necessary = false', () => {
container.register('SafeSkip', {});
expect(() => inject('SafeSkip', true, false)).not.toThrow();
});
it('should return undefined if initializer fails and not necessary', () => {
container.register('InitErrorOptional', {});
const result = inject(
'InitErrorOptional',
() => {
throw new Error('ignored');
},
false,
);
expect(result).toBeUndefined();
});
it('should return undefined if dependency is null and not necessary', () => {
container.register('NullDep', null);
const result = inject('NullDep', true, false);
expect(result).toBeUndefined();
});
});
}

View File

@@ -1,7 +1,13 @@
import { TSinjex } from 'src/classes/TSinjex'; import {
import { register } from 'src/functions/register'; test_injectFunction,
import { resolve } from 'src/functions/resolve'; test_RegisterFunction,
import { test_RegisterFunction, test_ResolveFunction } from './Functions.spec'; test_ResolveFunction,
} from './Functions.spec.js';
import { TSinjex } from '../classes/TSinjex.js';
import { inject } from '../functions/inject.js';
import { register } from '../functions/register.js';
import { resolve } from '../functions/resolve.js';
test_RegisterFunction(TSinjex, register); test_RegisterFunction(TSinjex, register);
test_ResolveFunction(TSinjex, resolve); test_ResolveFunction(TSinjex, resolve);
test_injectFunction(TSinjex, inject);

View File

@@ -1,5 +1,5 @@
/* istanbul ignore file */ /* istanbul ignore file */
import { ITSinjex_, ITSinjex } from '../interfaces/ITSinjex'; import { ITSinjex_, ITSinjex } from '../interfaces/ITSinjex.js';
/** /**
* Test the implementation of the `ITSinjex` interface. * Test the implementation of the `ITSinjex` interface.

View File

@@ -1,4 +1,4 @@
import { test_ITSinjex } from './ITSinjex.spec'; import { test_ITSinjex } from './ITSinjex.spec.js';
import { TSinjex } from '../classes/TSinjex'; import { TSinjex } from '../classes/TSinjex.js';
test_ITSinjex(TSinjex); test_ITSinjex(TSinjex);

View File

@@ -1,13 +1,13 @@
import type { Inject } from '../decorators/Inject'; import type { Inject } from '../decorators/Inject.js';
import type { Register } from '../decorators/Register'; import type { Register } from '../decorators/Register.js';
import type { RegisterInstance } from '../decorators/RegisterInstance'; import type { RegisterInstance } from '../decorators/RegisterInstance.js';
import type { register } from '../functions/register'; import type { register } from '../functions/register.js';
import type { resolve } from '../functions/resolve'; import type { resolve } from '../functions/resolve.js';
import { ImplementsStatic } from '../helper/ImplementsStatic'; import { ImplementsStatic } from '../helper/ImplementsStatic.js';
import { DependencyResolutionError } from '../interfaces/Exceptions'; import { DependencyResolutionError } from '../interfaces/Exceptions.js';
import { IDependency } from '../interfaces/IDependency'; import { IDependency } from '../interfaces/IDependency.js';
import { ITSinjex, ITSinjex_ } from '../interfaces/ITSinjex'; import { ITSinjex, ITSinjex_ } from '../interfaces/ITSinjex.js';
import { Identifier } from '../types/Identifier'; import { Identifier } from '../types/Identifier.js';
/** /**
* # TSinjex * # TSinjex

View File

@@ -1,12 +1,12 @@
import { TSinjex } from '../classes/TSinjex.js';
import { import {
DependencyResolutionError, DependencyResolutionError,
InitializationError, InitializationError,
InjectorError, InjectorError,
NoInstantiationMethodError, NoInstantiationMethodError,
} from 'src/interfaces/Exceptions'; } from '../interfaces/Exceptions.js';
import { TSinjex } from '../classes/TSinjex'; import { Identifier } from '../types/Identifier.js';
import { Identifier } from '../types/Identifier'; import { InitDelegate } from '../types/InitDelegate.js';
import { InitDelegate } from '../types/InitDelegate';
/** /**
* A decorator to inject a dependency from a DI (Dependency Injection) container into a class property. * A decorator to inject a dependency from a DI (Dependency Injection) container into a class property.
@@ -17,7 +17,7 @@ import { InitDelegate } from '../types/InitDelegate';
* @param init Optional an initializer function to transform the dependency before injection * @param init Optional an initializer function to transform the dependency before injection
* or true to instantiate the dependency if it has a constructor. * or true to instantiate the dependency if it has a constructor.
* @see {@link InitDelegate} for more information on initializer functions. * @see {@link InitDelegate} for more information on initializer functions.
* @param necessary If true, throws an error if the dependency is not found. * @param isNecessary If true, throws an error if the dependency is not found.
* @returns The resolved dependency or undefined if the dependency is not necessary * @returns The resolved dependency or undefined if the dependency is not necessary
* and not found, or throws an error if the dependency is necessary and not found. * and not found, or throws an error if the dependency is necessary and not found.
* @throws **Only throws errors if the dependency is necessary.** * @throws **Only throws errors if the dependency is necessary.**
@@ -43,7 +43,7 @@ import { InitDelegate } from '../types/InitDelegate';
export function Inject<T, U>( export function Inject<T, U>(
identifier: Identifier, identifier: Identifier,
init?: InitDelegate<T, U> | true, init?: InitDelegate<T, U> | true,
necessary = true, isNecessary = true,
) { ) {
return function (target: unknown, propertyKey: string | symbol): void { return function (target: unknown, propertyKey: string | symbol): void {
/** /**
@@ -52,7 +52,7 @@ export function Inject<T, U>(
* @returns The resolved dependency or undefined if the dependency is not found. * @returns The resolved dependency or undefined if the dependency is not found.
*/ */
const resolve = (): T | undefined => { const resolve = (): T | undefined => {
return TSinjex.getInstance().resolve<T>(identifier, necessary); return TSinjex.getInstance().resolve<T>(identifier, isNecessary);
}; };
Object.defineProperty(target, propertyKey, { Object.defineProperty(target, propertyKey, {
@@ -61,7 +61,7 @@ export function Inject<T, U>(
const dependency: T | undefined = tryAndCatch( const dependency: T | undefined = tryAndCatch(
() => resolve(), () => resolve(),
necessary, isNecessary,
identifier, identifier,
DependencyResolutionError, DependencyResolutionError,
); );
@@ -78,13 +78,13 @@ export function Inject<T, U>(
else if (initFunction != null) else if (initFunction != null)
instance = tryAndCatch( instance = tryAndCatch(
initFunction, initFunction,
necessary, isNecessary,
identifier, identifier,
InitializationError, InitializationError,
); );
else if (necessary) else if (isNecessary)
throw new NoInstantiationMethodError(identifier); throw new NoInstantiationMethodError(identifier);
} else if (necessary) } else if (isNecessary)
throw new DependencyResolutionError(identifier); throw new DependencyResolutionError(identifier);
/** /**

View File

@@ -1,6 +1,6 @@
import { InitDelegate } from 'src/types/InitDelegate'; import { InitDelegate } from 'src/types/InitDelegate.js';
import { TSinjex } from '../classes/TSinjex'; import { TSinjex } from '../classes/TSinjex.js';
import { Identifier } from '../types/Identifier'; import { Identifier } from '../types/Identifier.js';
//#region Overloads //#region Overloads

View File

@@ -1,6 +1,6 @@
import { Register } from './Register'; import { Register } from './Register.js';
import { Identifier } from '../types/Identifier'; import { Identifier } from '../types/Identifier.js';
import { InitDelegate } from '../types/InitDelegate'; import { InitDelegate } from '../types/InitDelegate.js';
/** /**
* A decorator to register an instance of a class in the DI (Dependency Injection) container. * A decorator to register an instance of a class in the DI (Dependency Injection) container.

220
src/functions/inject.ts Normal file
View File

@@ -0,0 +1,220 @@
import { TSinjex } from '../classes/TSinjex.js';
import {
DependencyResolutionError,
InitializationError,
InjectorError,
NoInstantiationMethodError,
} from '../interfaces/Exceptions.js';
import { Identifier } from '../types/Identifier.js';
import { InitDelegate } from '../types/InitDelegate.js';
/**
* Resolves a dependency by its identifier without initialization or instantiation.
* @template T The expected type of the dependency.
* @param identifier The identifier used to resolve the dependency from the container.
* @returns The resolved dependency.
* @throws A {@link DependencyResolutionError} if the dependency is not found.
* @example
* ```ts
* const logger = inject<Logger>('Logger');
* ```
*/
export function inject<T>(identifier: Identifier): T;
/**
* Resolves and instantiates a dependency using its constructor.
* @template T The expected class type of the dependency.
* @param identifier The identifier used to resolve the dependency from the container.
* @param shouldInit Set to `true` to instantiate the dependency after resolution.
* @returns The resolved and instantiated dependency.
* @throws A {@link DependencyResolutionError} if the dependency is not found.
* @throws A {@link NoInstantiationMethodError} if the dependency has no constructor.
* @throws An {@link InitializationError} if instantiation fails.
* @example
* ```ts
* const instance = inject<Service>('Service', true);
* ```
*/
export function inject<T>(identifier: Identifier, shouldInit: true): T;
/**
* Resolves and instantiates a dependency using its constructor, optionally failing silently.
* @template T The expected class type of the dependency.
* @param identifier The identifier used to resolve the dependency from the container.
* @param shouldInit Set to `true` to instantiate the dependency.
* @param isNecessary If `false`, resolution or instantiation errors return `undefined` instead of throwing.
* @returns The resolved and instantiated dependency, or `undefined` if resolution or instantiation fails.
* @example
* ```ts
* const instance = inject<Service>('OptionalService', true, false);
* if (instance) instance.doSomething();
* ```
*/
export function inject<T>(
identifier: Identifier,
shouldInit: true,
isNecessary: false,
): T | undefined;
/**
* Resolves a dependency without instantiating it, optionally failing silently.
* @template T The expected type of the dependency.
* @param identifier The identifier used to resolve the dependency from the container.
* @param shouldInit Set to `false` to skip instantiation.
* @param isNecessary If `false`, resolution errors return `undefined` instead of throwing.
* @returns The resolved dependency, or `undefined` if not found.
* @example
* ```ts
* const config = inject<Config>('Config', false, false) ?? getDefaultConfig();
* ```
*/
export function inject<T>(
identifier: Identifier,
shouldInit: false,
isNecessary: false,
): T | undefined;
/**
* Resolves a dependency and applies a custom initializer function to transform the result.
* @template T The original dependency type.
* @template U The final return type after initialization.
* @param identifier The identifier used to resolve the dependency.
* @param init A function to transform or initialize the dependency.
* @returns The transformed dependency.
* @throws A {@link DependencyResolutionError} if the dependency is not found.
* @throws An {@link InitializationError} if the initializer throws.
* @example
* ```ts
* const client = inject<Api>('Api', (api) => api.getClient());
* ```
*/
export function inject<T, U>(
identifier: Identifier,
init: InitDelegate<T, U>,
): U;
/**
* Resolves a dependency and applies a custom initializer function, optionally failing silently.
* @template T The original dependency type.
* @template U The final return type after initialization.
* @param identifier The identifier used to resolve the dependency.
* @param init A function to transform or initialize the dependency.
* @param isNecessary If `false`, resolution or initializer errors return `undefined` instead of throwing.
* @returns The transformed dependency, or `undefined` if resolution or initialization fails.
* @example
* ```ts
* const db = inject<Database, Pool>('Database', (d) => d.getPool(), false);
* if (db) db.query('SELECT * FROM users');
* ```
*/
export function inject<T, U>(
identifier: Identifier,
init: InitDelegate<T, U>,
isNecessary: false,
): U | undefined;
/**
* A function to inject a dependency from a DI (Dependency Injection) container into a variable.
* This is the actual implementation that handles all overload variants.
* @template T The original dependency type.
* @template U The final return type after optional initialization or transformation.
* @param identifier The identifier used to resolve the dependency.
* @see {@link Identifier} for more information on identifiers.
* @param init Optional: either `true` to instantiate via constructor, `false` to skip, or a function to transform the dependency.
* @see {@link InitDelegate} for more information on initializer functions.
* @param isNecessary If `true`, throws on failure; if `false`, returns `undefined` on resolution or initialization errors.
* @returns The resolved dependency or result of initialization, or `undefined` if not necessary and resolution fails.
* @throws A {@link DependencyResolutionError} if the dependency is not found (and necessary).
* @throws A {@link NoInstantiationMethodError} if instantiation is requested but no constructor exists.
* @throws An {@link InitializationError} if the initializer throws an error.
* @throws A {@link InjectorError} for unknown errors during resolution or transformation.
* @example
* ```ts
* const service = inject<Service>('Service');
* ```
* @example
* ```ts
* const instance = inject<Service>('Service', true);
* ```
* @example
* ```ts
* const logger = inject<ILogger>('ILogger_', (x) => x.getLogger('Module'), false);
* ```
*/
export function inject<T, U>(
identifier: Identifier,
init?: InitDelegate<T, U> | true | false,
isNecessary = true,
): T | U | undefined {
let instance: T | U | undefined;
const dependency: T | undefined = tryAndCatch(
() => TSinjex.getInstance().resolve<T>(identifier, isNecessary),
isNecessary,
identifier,
DependencyResolutionError,
);
if (dependency != null) {
const initFunction: (() => U) | undefined =
typeof init === 'function' && dependency != null
? (): U => init(dependency)
: init === true && hasConstructor(dependency)
? (): U => new dependency() as U
: undefined;
if (init == null || init === false) instance = dependency;
else if (initFunction != null)
instance = tryAndCatch(
initFunction,
isNecessary,
identifier,
InitializationError,
);
else if (isNecessary) throw new NoInstantiationMethodError(identifier);
} else if (isNecessary) throw new DependencyResolutionError(identifier);
return instance as T | U;
}
/**
* Tries to execute a function and catches any errors that occur.
* If the function is necessary and an error occurs, it throws the error
* with the specified error class and identifier.
* @param fn The function to execute.
* @param necessary If true, throws an error if an error occurs.
* @param identifier The identifier of the dependency.
* @param errorClass The error class to throw if an error occurs.
* @returns The result of the function or undefined if an error occurs and the function is not necessary.
*/
function tryAndCatch<ReturnType, ErrorType>(
fn: () => ReturnType,
necessary: boolean,
identifier?: Identifier,
errorClass?: ErrorType,
): ReturnType | undefined {
try {
return fn();
} catch (error) {
if (necessary)
throw new (errorClass != null ? errorClass : error)(
identifier ?? 'not specified',
error,
);
else return undefined;
}
}
/**
* Checks if an object has a constructor.
* @param obj The object to check.
* @returns True if the object has a constructor, false otherwise.
*/
function hasConstructor<T>(obj: T): obj is T & { new (): unknown } {
const _obj = obj as unknown as { prototype?: { constructor?: unknown } };
return (
_obj?.prototype != null &&
typeof _obj.prototype.constructor === 'function'
);
}

View File

@@ -1,5 +1,5 @@
import { TSinjex } from '../classes/TSinjex'; import { TSinjex } from '../classes/TSinjex.js';
import { Identifier } from '../types/Identifier'; import { Identifier } from '../types/Identifier.js';
/** /**
* Register a dependency. * Register a dependency.

View File

@@ -1,6 +1,6 @@
import { TSinjex } from '../classes/TSinjex'; import { TSinjex } from '../classes/TSinjex.js';
import { DependencyResolutionError } from '../interfaces/Exceptions'; import { DependencyResolutionError } from '../interfaces/Exceptions.js';
import { Identifier } from '../types/Identifier'; import { Identifier } from '../types/Identifier.js';
/** /**
* Resolve a dependency. * Resolve a dependency.
@@ -15,12 +15,12 @@ export function resolve<T>(identifier: Identifier): T;
* Resolve a dependency * Resolve a dependency
* @param identifier The identifier used to register the class in the DI container. * @param identifier The identifier used to register the class in the DI container.
* @see {@link Identifier} for more information on identifiers. * @see {@link Identifier} for more information on identifiers.
* @param necessary The dependency is **not** necessary. * @param isNecessary The dependency is **not** necessary.
* @returns The resolved dependency or undefined if the dependency is not found. * @returns The resolved dependency or undefined if the dependency is not found.
*/ */
export function resolve<T>( export function resolve<T>(
identifier: Identifier, identifier: Identifier,
necessary: false, isNecessary: false,
): T | undefined; ): T | undefined;
/** /**

View File

@@ -1,21 +1,22 @@
/* istanbul ignore file */ /* istanbul ignore file */
// Main // Main
export * from './classes/TSinjex'; export * from './classes/TSinjex.js';
// Decorators // Decorators
export * from './decorators/Inject'; export * from './decorators/Inject.js';
export * from './decorators/Register'; export * from './decorators/Register.js';
export * from './decorators/RegisterInstance'; export * from './decorators/RegisterInstance.js';
// Helper // Helper
export * from './helper/ImplementsStatic'; export * from './helper/ImplementsStatic.js';
// Functions // Functions
export * from './functions/resolve'; export * from './functions/resolve.js';
export * from './functions/register'; export * from './functions/register.js';
export * from './functions/inject.js';
// Interfaces & Types // Interfaces & Types
export type * from './interfaces/ITSinjex'; export type * from './interfaces/ITSinjex.js';
export type * from './types/InitDelegate'; export type * from './types/InitDelegate.js';
export type * from './types/GenericContructor'; export type * from './types/GenericContructor.js';

View File

@@ -1,5 +1,5 @@
import { Identifier } from 'src/types/Identifier'; import { Identifier } from 'src/types/Identifier.js';
import { ITSinjex } from './ITSinjex'; import { ITSinjex } from './ITSinjex.js';
/** /**
* General error class for {@link ITSinjex} interface. * General error class for {@link ITSinjex} interface.

View File

@@ -1,5 +1,5 @@
import { DependencyResolutionError } from './Exceptions'; import { DependencyResolutionError } from './Exceptions.js';
import { Identifier } from '../types/Identifier'; import { Identifier } from '../types/Identifier.js';
/** /**
* Static TSInjex Interface * Static TSInjex Interface

View File

@@ -7,13 +7,13 @@
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,
"outDir": "./dist", "outDir": "./dist",
"module": "ESNext", "module": "NodeNext",
"target": "ES6", "target": "ES2020",
"allowJs": true, "allowJs": true,
"noUnusedLocals": false, "noUnusedLocals": false,
"noUnusedParameters": false, "noUnusedParameters": false,
"noImplicitAny": true, "noImplicitAny": true,
"moduleResolution": "node", "moduleResolution": "NodeNext",
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"importHelpers": true, "importHelpers": true,
"isolatedModules": true, "isolatedModules": true,