dependency-injection-cat
Advanced tools
Comparing version 0.0.12 to 0.0.13-beta
@@ -1,4 +0,57 @@ | ||
export * from './decorators'; | ||
export declare class container { | ||
static get<T>(beanName?: string): T; | ||
export interface IDIContext<TBeans> { | ||
getBeans(): TBeans; | ||
getBean<TBean extends keyof TBeans>(beanName: TBean): TBeans[TBean]; | ||
} | ||
export interface IInitContextProps { | ||
name: string; | ||
key?: any; | ||
} | ||
export interface IInitContextPropsWithConfig<TConfig> extends IInitContextProps { | ||
config: TConfig; | ||
} | ||
export declare type TBeanName = string; | ||
declare class Container { | ||
/** | ||
* TBeans should be a plain interface without extending | ||
*/ | ||
initContext<TBeans>(props: IInitContextProps): IDIContext<TBeans>; | ||
initContext<TBeans, TConfig>(props: IInitContextPropsWithConfig<TConfig>): IDIContext<TBeans>; | ||
/** | ||
* TBeans should be a plain interface without extending | ||
*/ | ||
getOrInitContext<TBeans>(props: IInitContextProps): IDIContext<TBeans>; | ||
getOrInitContext<TBeans, TConfig>(props: IInitContextPropsWithConfig<TConfig>): IDIContext<TBeans>; | ||
/** | ||
* TBeans should be a plain interface without extending | ||
*/ | ||
getContext<TBeans extends Record<TBeanName, any>>(props: IInitContextProps): IDIContext<TBeans>; | ||
clearContext(props: IInitContextProps): void; | ||
private static throwInitializationError; | ||
} | ||
export declare const container: Container; | ||
/** | ||
* TBeans should be a plain interface without extending | ||
*/ | ||
export declare abstract class CatContext<TBeans, TConfig = null> { | ||
protected constructor(); | ||
get config(): TConfig; | ||
} | ||
export declare abstract class GlobalCatContext { | ||
protected constructor(); | ||
} | ||
export declare type TClassConstructor<T> = new (...args: any[]) => T; | ||
export declare type TBeanScope = "prototype" | "singleton"; | ||
export interface IBeanConfig { | ||
scope?: TBeanScope; | ||
} | ||
export declare type ConfigurableMethodBean = (beanConfig: IBeanConfig) => PropertyDecorator; | ||
export declare type PropertyBean = <T>(clazz: TClassConstructor<T>, beanConfig?: IBeanConfig) => T; | ||
export declare type TBean = PropertyDecorator & ConfigurableMethodBean & PropertyBean; | ||
export declare const Bean: TBean; | ||
export declare const Qualifier: <T extends string>(beanName: T extends "" ? never : T) => ParameterDecorator; | ||
export declare type TPostConstruct = PropertyDecorator & MethodDecorator; | ||
export declare const PostConstruct: TPostConstruct; | ||
export declare type TBeforeDestruct = PropertyDecorator & MethodDecorator; | ||
export declare const BeforeDestruct: TBeforeDestruct; | ||
export {}; |
29
index.js
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.container = void 0; | ||
var tslib_1 = require("tslib"); | ||
var libraryName_1 = require("./constants/libraryName"); | ||
tslib_1.__exportStar(require("./decorators"), exports); | ||
var container = /** @class */ (function () { | ||
function container() { | ||
} | ||
container.get = function (beanName) { | ||
//TODO add link to configure guide | ||
throw new Error("It seems, like " + libraryName_1.libraryName + " was not configured correctly"); | ||
}; | ||
return container; | ||
}()); | ||
exports.container = container; | ||
__exportStar(require("./external/containter"), exports); | ||
__exportStar(require("./external/IDIContext"), exports); | ||
__exportStar(require("./external/CatContext"), exports); | ||
__exportStar(require("./external/GlobalCatContext"), exports); | ||
__exportStar(require("./external/decorators"), exports); |
{ | ||
"name": "dependency-injection-cat", | ||
"version": "0.0.12", | ||
"version": "0.0.13-beta", | ||
"main": "index.js", | ||
@@ -9,2 +9,15 @@ "types": "index.d.ts", | ||
"private": false, | ||
"keywords": [ | ||
"ioc", | ||
"inversion of control", | ||
"di", | ||
"dependency injection", | ||
"typescript", | ||
"babel", | ||
"webpack", | ||
"cat", | ||
"clean architecture", | ||
"container", | ||
"dependency injection container" | ||
], | ||
"repository": { | ||
@@ -14,47 +27,16 @@ "url": "https://github.com/artem1458/dependency-injection-cat" | ||
"peerDependencies": { | ||
"typescript": "^3.8.2" | ||
"typescript": "4.X.X" | ||
}, | ||
"devDependencies": { | ||
"@types/chokidar": "^2.1.3", | ||
"@types/glob": "^7.1.2", | ||
"@types/jest": "^26.0.3", | ||
"@types/jest-sinon": "^1.0.1", | ||
"@types/lodash": "^4.14.155", | ||
"@types/node": "^14.0.27", | ||
"@types/sinon": "^9.0.4", | ||
"@types/typescript": "^2.0.0", | ||
"@types/webpack": "^4.41.21", | ||
"@typescript-eslint/eslint-plugin": "^4.3.0", | ||
"@typescript-eslint/parser": "^4.3.0", | ||
"awesome-typescript-loader": "^5.2.1", | ||
"clean-webpack-plugin": "^3.0.0", | ||
"dependency-injection-cat": "./dist/src", | ||
"eslint": "^7.10.0", | ||
"html-webpack-plugin": "^4.3.0", | ||
"jest": "^26.1.0", | ||
"jest-sinon": "^1.0.4", | ||
"json-beautify": "^1.1.1", | ||
"less-loader": "^6.1.1", | ||
"memfs": "^3.2.0", | ||
"sinon": "^9.0.2", | ||
"style-loader": "^1.2.1", | ||
"ts-auto-mock": "^2.3.3", | ||
"ts-jest": "^26.1.1", | ||
"ts-loader": "^7.0.5", | ||
"ts-mockito": "^2.6.1", | ||
"ts-node": "^8.10.2", | ||
"tsconfig-paths-webpack-plugin": "^3.3.0", | ||
"ttypescript": "^1.5.10", | ||
"webpack": "^4.43.0", | ||
"webpack-cli": "^3.3.12", | ||
"webpack-dev-server": "^3.11.0", | ||
"webpack-watch-files-plugin": "^1.0.3" | ||
}, | ||
"dependencies": { | ||
"chalk": "^4.1.0", | ||
"chokidar": "^3.4.2", | ||
"glob": "^7.1.6", | ||
"lodash": "^4.17.20", | ||
"tsconfig-paths": "^3.9.0" | ||
"graphlib": "^2.1.8", | ||
"json-beautify": "^1.1.1", | ||
"line-column": "^1.0.2", | ||
"lodash": "^4.17.21", | ||
"md5": "^2.3.0", | ||
"minimatch": "^3.0.4", | ||
"tsconfig-paths": "^3.9.0", | ||
"upath": "^2.0.1" | ||
} | ||
} | ||
} |
859
README.md
@@ -1,31 +0,48 @@ | ||
![npm](https://img.shields.io/npm/v/dependency-injection-cat?style=flat) | ||
# dependency-injection-cat | ||
# Dependency Injection Cat | ||
Dependency Injection Cat is a TypeScript-only library which allows you to implement | ||
the Dependency Inversion pattern with Dependency Injection | ||
<!-- toc --> | ||
- [Installation](#installation) | ||
* [Config options](#config-options) | ||
* [Configuration with Webpack](#configuration-with-webpack) | ||
* [Configuration with ttypescript](#configuration-with-ttypescript) | ||
- [Usage](#usage) | ||
- [Bean](#bean) | ||
* [Rules](#bean-rules) | ||
* [Syntax](#bean-syntax) | ||
* [Configuration object](#bean-configuration-object) | ||
- [Qualifier](#qualifier) | ||
* [What is it?](#what-is-it) | ||
* [Rules](#qualifier-rules) | ||
* [Syntax](#qualifier-syntax) | ||
- [Container](#container) | ||
* [Rules](#container-rules) | ||
* [Syntax](#container-syntax) | ||
<!-- tocstop --> | ||
DI Cat is a truly clean DI-container, which allows you not to pollute your business logic with decorators from DI/IOC libraries! | ||
----- | ||
[![npm](https://img.shields.io/npm/v/dependency-injection-cat?style=flat)](https://www.npmjs.com/package/dependency-injection-cat) | ||
----- | ||
## Example | ||
```tsx | ||
//ApplicationContext.di.ts | ||
class ApplicationContext extends CatContext<IBeans> { | ||
useCase = Bean<IUseCase>(UseCase) | ||
mobxRepository = Bean<IRepository>(MobxRepository) | ||
} | ||
//UseCase.ts | ||
export class UseCase implements IUseCase { | ||
constructor( | ||
private repository: IRepository, | ||
) {} | ||
makeBusinessLogic() {} | ||
} | ||
//Your application.tsx | ||
export const UIComponent: React.FC = () => { | ||
const appContext = container.getContext<IBeans>({name: 'ApplicationContext'}); | ||
const {useCase} = appContext.getBeans(); | ||
return ( | ||
<button onClick={useCase.makeBusinessLogic}> Click me! </button> | ||
) | ||
} | ||
``` | ||
## Installation | ||
**Yarn** | ||
#### Yarn | ||
```bash | ||
yarn add dependency-injection-cat | ||
``` | ||
**NPM** | ||
#### NPM | ||
```bash | ||
@@ -35,66 +52,141 @@ npm install dependency-injection-cat | ||
#### Config options | ||
```ts | ||
interface TransformerConfig { | ||
diConfigPattern?: string; // Glob pattern, default value = '**/*.diconfig.ts' | ||
ignorePatterns?: string[]; // Array of Glob patterns, default value = ['**/node_modules/**'] | ||
#### Requirements for TSconfig | ||
```json5 | ||
//tsconfig.json | ||
{ | ||
"compilerOptions": { | ||
"baseUrl": "Base url should be specified!" | ||
} | ||
} | ||
``` | ||
#### Configuration with Webpack | ||
Dependency Injection Cat supports transpileOnly mode for faster builds! [More Info](https://github.com/TypeStrong/ts-loader#transpileonly) | ||
<br/> | ||
With Webpack, You can use any TypeScript-related loader that supports custom transformers, e.g. awesome-typescript-loader or ts-loader | ||
#### Webpack + Babel | ||
<h5>webpack.config.js</h5> | ||
```typescript | ||
//webpack.config.js | ||
const DiCatWebpackPlugin = require('dependency-injection-cat/plugins/webpack').default; | ||
const DiCatBabelTransformer = require('dependency-injection-cat/transformers/babel'); | ||
```js | ||
const dependencyInjectionCatTransformer = require('dependency-injection-cat/transformer').default; | ||
module.exports = { | ||
// ... | ||
//... | ||
module: { | ||
rules: [ | ||
{ | ||
test: /\.ts$/, | ||
loader: 'ts-loader', // or 'awesome-typescript-loader' | ||
test: /\.(t|j)sx?$/, | ||
loader: 'babel-loader', | ||
options: { | ||
transpileOnly: true, // If set transpileOnly: true, you're loosing TypeChecking | ||
//If you're using ttypescript | ||
compiler: 'ttypescript', | ||
//If you don't use ttypescript, you should pass transformer | ||
getCustomTransformers: program => ({ | ||
before: [ | ||
dependencyInjectionCatTransformer(program), | ||
], | ||
}), | ||
}, | ||
}, | ||
], | ||
//Should be false, because babel caching files | ||
//and don't call di-cat babel transformer, if your not *.di.* files was changed | ||
cacheDirectory: false, | ||
plugins: [ | ||
[ | ||
DiCatBabelTransformer, | ||
{ | ||
//Here is configuration options, see below | ||
} | ||
] | ||
] | ||
} | ||
} | ||
] | ||
}, | ||
}; | ||
plugins: [ | ||
//Without this plugin, a compilation with DI errors will be successful | ||
new DiCatWebpackPlugin(), | ||
] | ||
} | ||
``` | ||
<h5>With custom options</h5> | ||
In webpack.config.js you can pass a second parameter to the transformer: | ||
```json5 | ||
//tsconfig.json | ||
{ | ||
"compilerOptions": { | ||
//Should be specified | ||
"baseUrl": "your base url" | ||
} | ||
} | ||
``` | ||
```js | ||
before: [ | ||
dependencyInjectionCatTransformer(program, { | ||
diConfigPattern: '**/config/**/*.diconfig.ts' | ||
}) | ||
] | ||
#### Webpack + TS-Loader | ||
```typescript | ||
//webpack.config.js | ||
const DiCatWebpackPlugin = require('dependency-injection-cat/plugins/webpack').default; | ||
const diCatTransformer = require('dependency-injection-cat/transformers/typescript').default; | ||
module.exports = { | ||
//... | ||
module: { | ||
rules: [ | ||
{ | ||
test: /\.(t|j)sx?$/, | ||
use: { | ||
loader: 'ts-loader', | ||
options: { | ||
getCustomTransformers: (program) => ({ | ||
before: [diCatTransformer(program, { | ||
//Here is configuration options, see below | ||
})], | ||
}), | ||
} | ||
} | ||
} | ||
] | ||
}, | ||
plugins: [ | ||
//Without this plugin, a compilation with DI errors will be successful | ||
new DiCatWebpackPlugin(), | ||
] | ||
} | ||
``` | ||
#### Configuration with ttypescript | ||
Check out ttypescript's [README](https://github.com/cevek/ttypescript/blob/master/README.md) for more information | ||
```json5 | ||
//tsconfig.json | ||
{ | ||
"compilerOptions": { | ||
//Should be specified | ||
"baseUrl": "your base url" | ||
} | ||
} | ||
``` | ||
<h5>tsconfig.json</h5> | ||
#### Webpack + TS-Loader + ttypescript | ||
```json | ||
```typescript | ||
//webpack.config.js | ||
const DiCatWebpackPlugin = require('dependency-injection-cat/plugins/webpack').default; | ||
module.exports = { | ||
//... | ||
module: { | ||
rules: [ | ||
{ | ||
test: /\.(t|j)sx?$/, | ||
use: { | ||
loader: 'ts-loader', | ||
options: { | ||
compiler: 'ttypescript' | ||
} | ||
} | ||
} | ||
] | ||
}, | ||
plugins: [ | ||
//Without this plugin, a compilation with DI errors will be successful | ||
new DiCatWebpackPlugin(), | ||
] | ||
} | ||
``` | ||
```json5 | ||
//tsconfig.json | ||
{ | ||
"compilerOptions": { | ||
//Should be specified | ||
"baseUrl": "your base url", | ||
"plugins": [ | ||
{ | ||
"transform": "dependency-injection-cat/transformer" | ||
"transform": "dependency-injection-cat/transformers/typescript", | ||
//Here is configuration options, see below | ||
} | ||
@@ -106,11 +198,17 @@ ] | ||
<h5>With custom options</h5> | ||
#### TTypescript | ||
```json | ||
```json5 | ||
//tsconfig.json | ||
{ | ||
"compilerOptions": { | ||
//Should be specified | ||
"baseUrl": "your base url", | ||
"plugins": [ | ||
{ | ||
"transform": "dependency-injection-cat/transformer", | ||
"diConfigPattern": "**/config/**/*.diconfig.ts" | ||
"transform": "dependency-injection-cat/transformers/typescript", | ||
//Here is configuration options, see below | ||
}, | ||
{ | ||
"transform": "dependency-injection-cat/plugins/typescript/ReportErrorsTypescriptPlugin" | ||
} | ||
@@ -122,223 +220,522 @@ ] | ||
## Usage | ||
## Configuration options | ||
```ts | ||
// requesters.diconfig.ts | ||
import { Bean, Qualifier } from 'dependency-injection-cat'; | ||
import { IRequester } from '../IRequester'; | ||
import { Requester } from '../Requester'; | ||
import { ILogger } from '../ILogger'; | ||
```typescript | ||
{ | ||
diConfigPattern: string | undefined; // Glob pattern, default value. Default: '**/*.di.ts' | ||
ignorePatterns: Array<string> | undefined; // Array of Glob patterns, default value. Default: ['**/node_modules/**'] | ||
disableLogoPrint: boolean | undefined; // Disable exposing dependency-injections-cat logo into console | ||
} | ||
``` | ||
export class Requesters { | ||
@Bean | ||
requester(logger: ILogger): IRequester { | ||
return new Requester(logger); | ||
} | ||
//or | ||
requester = Bean<IRequester>(Requester); | ||
## Hot Reload | ||
Currently, correct hot reload supported only with DI-Cat Webpack plugin, and for non-global CatContexts. | ||
#### Known issues: | ||
- When using webpack+babel - webpack or babel don't trigger rebuild of files where defined only types/interfaces, | ||
...etc. Because of this issue, when adding new values to the TBean interface, context will not recompile, and missing | ||
required beans will not be reported. To solve this issue, just make some changes in file where CatContext are defined, | ||
it will trigger recompilation. | ||
## CatContext | ||
CatContext it's a place, where you should define your **Beans** | ||
```typescript | ||
class CatContext<TBeans, TConfig = null> {} | ||
``` | ||
##### Rules | ||
- Name of context should be unique | ||
- Name of context "Global" is preserved for DI container, see **GlobalCatContext** | ||
### TBeans | ||
TBeans is an interface, of Beans that will be given out of context. Should be the same as in **container** access calls. | ||
##### Rules | ||
- Should be a plain interface without extending, should not have indexed signatures | ||
- Should be placed in your source files, not from node_modules | ||
- **Bean** name should not be **getBean** or **getBeans**, it's reserved names for di-container | ||
```typescript | ||
export interface IBeans { | ||
useCase: IUseCase; | ||
//Another beans... | ||
} | ||
``` | ||
### TConfig | ||
If you need to pass additional parameters to your classes, for example ID, or something else, you should specify a type | ||
for TConfig (default is null) | ||
```typescript | ||
export interface IConfig { | ||
id: string; | ||
} | ||
// loggers.diconfig.ts | ||
import { Bean } from 'dependency-injection-cat'; | ||
import { ILogger } from '../ILogger'; | ||
import { Logger } from '../Logger'; | ||
class ApplicationContext extends CatContext<IBeans, IConfig> { | ||
@Bean | ||
useCase(): IUseCase { | ||
const {id} = this.config; | ||
export class Loggers { | ||
@Bean | ||
logger(): ILogger { | ||
return new Logger(); | ||
} | ||
//or | ||
@Bean | ||
logger = Bean<ILogger>(Logger); | ||
return new UseCase(id); | ||
} | ||
} | ||
``` | ||
// main.ts | ||
## GlobalCatContext | ||
Sometimes you want to describe common dependencies that will be used all across your application, for these purposes you | ||
should use GlobalCatContext. | ||
#### Rules | ||
- You can not get beans from the Global Context using container access | ||
#### Syntax and usage | ||
```typescript | ||
//GlobalApplicationContext.di.ts | ||
class GlobalApplicationContext extends GlobalCatContext { | ||
@Bean | ||
logger(): ILogger { | ||
return new Logger(); | ||
} | ||
} | ||
//ApplicationContext.di.ts | ||
class AppContext extends CatContext<IBeans> { | ||
@Bean | ||
useCase( | ||
//If Bean for ILogger will be defined in current context, bean from current context will be injected | ||
logger: ILogger | ||
): IUseCase { | ||
return new UseCase(logger) | ||
} | ||
} | ||
``` | ||
## Container | ||
Using container you can control your contexts. You can't access container inside files where declared contexts. | ||
#### Rules | ||
- Name of context in container access calls, should not be "Global", it's reserved for the **GlobalCatContext** | ||
### TBeans | ||
TBeans is an interface, of Beans that will be given out of context. Should be the same as in **CatContext**. | ||
### initContext | ||
InitContext creates instance of **context** by key (if specified). Also you can pass config to your context using | ||
initContext. | ||
```typescript | ||
container.initContext<TBeans, TConfig>({ | ||
key? : any, //Can be any value, you can use it for creating pool of contextMap. | ||
name: string, //It's the name of the class in which you specified the Beans. Should be a string literal. | ||
config? : TConfig, //Config that will be transferred to the context | ||
}); | ||
``` | ||
#### initContext usage | ||
```typescript | ||
//InitContext without config and keys | ||
import { container } from 'dependency-injection-cat'; | ||
import { IRequester } from './IRequester'; | ||
import { IBeans } from './IBeans'; | ||
const requester = container.get<IRequester>(); | ||
const applicationContext = container.initContext<IBeans>({name: 'ApplicationContext'}); | ||
``` | ||
requester.makeRequest(); | ||
```typescript | ||
//InitContext with config and keys | ||
import { container } from 'dependency-injection-cat'; | ||
import { IBeans } from './IBeans'; | ||
import { IConfig } from './IConfig'; | ||
const applicationContextBySomeKey = container.initContext<IBeans, IConfig>({ | ||
name: 'ApplicationContext', | ||
key: 'userId', | ||
config: {userId: 'userId'}, | ||
}); | ||
``` | ||
### getContext | ||
getContext returns instance of **context** by key (if specified). Can be used only after initialization of context, or | ||
error will be thrown. | ||
```typescript | ||
container.getContext<TBeans>({ | ||
key? : any, | ||
name: string, //It's the name of the class in which you specified the Beans. Should be a string literal. | ||
}); | ||
``` | ||
#### getContext usage | ||
```typescript | ||
//GetContext without keys | ||
import { container } from 'dependency-injection-cat'; | ||
const context = container.getContext<TBeans>({name: 'ApplicationContext'}); | ||
``` | ||
```typescript | ||
//GetContext with keys | ||
import { container } from 'dependency-injection-cat'; | ||
const context = container.getContext<TBeans>({ | ||
name: 'ApplicationContext', | ||
key: 'userId', | ||
}); | ||
``` | ||
### getOrInitContext | ||
GetOrInitContext return instance of **context** by key (if it was previously initialized), if wasn't - will be | ||
created and returned a new instance of context. | ||
```typescript | ||
container.getOrInitContext<TBeans, TConfig>({ | ||
key? : any, //Can be any value, you can use it for creating pool of contextMap. | ||
name: string, //It's the name of the class in which you specified the Beans. Should be a string literal. | ||
config? : TConfig, //Config that will be transferred to the context | ||
}); | ||
``` | ||
#### getOrInitContext usage | ||
```typescript | ||
//Accessing or initialization context without config and keys | ||
import { container } from 'dependency-injection-cat'; | ||
import { IBeans } from './IBeans'; | ||
const applicationContext = container.getOrInitContext<IBeans>({name: 'ApplicationContext'}); | ||
``` | ||
```typescript | ||
//Accessing or initialization context with config and keys | ||
import { container } from 'dependency-injection-cat'; | ||
import { IBeans } from './IBeans'; | ||
import { IConfig } from './IConfig'; | ||
const applicationContextBySomeKey = container.getOrInitContext<IBeans, IConfig>({ | ||
name: 'ApplicationContext', | ||
key: 'userId', | ||
config: {userId: 'userId'}, | ||
}); | ||
``` | ||
### clearContext | ||
clearContext should be used to clear instances of **Beans**. Can be used, for example when un-mounting components. | ||
```typescript | ||
container.clearContext({ | ||
name: string, //It's the name of the class in which you specified the Beans. Should be a string literal. | ||
key? : any, | ||
}) | ||
``` | ||
#### clearContext usage | ||
```typescript | ||
//ClearContext without keys | ||
import { container } from 'dependency-injection-cat'; | ||
container.clearContext({name: 'ApplicationContext'}); | ||
``` | ||
```typescript | ||
//ClearContext without keys | ||
import { container } from 'dependency-injection-cat'; | ||
container.clearContext({ | ||
name: 'ApplicationContext', | ||
key: 'userId', | ||
}); | ||
``` | ||
## Bean | ||
<h4 id="bean-rules">Rules</h4> | ||
A Bean is an object that is instantiated, assembled, and managed by IOC container (**Definition from Spring Framework documentation**) | ||
Beans can have dependencies, and they also should be defined as a beans or passed manually. | ||
<!-- toc --> | ||
- Bean should be a class member (property or method) | ||
- Beans should not have cyclic dependencies (it will throw compilation error) | ||
- Beans should not have duplicate declarations | ||
- Beans can be used only in **diconfig.ts** files (or files with another pattern declared in transformer config) | ||
- Bean type should not be empty (it will throw compilation error) | ||
- Bean type should not be primitive (it will throw compilation error) | ||
- All bean dependencies should be typed, and type should not be primitive | ||
<!-- tocstop --> | ||
### Bean configuration | ||
<h4 id="bean-syntax">Syntax</h4> | ||
Beans support 2 kinds of syntax | ||
```typescript | ||
interface IBeanConfiguration { | ||
scope?: 'prototype' | 'singleton'; | ||
/* Should be a string literal. Default value is 'singleton'. When singleton, will be created only 1 instance of the Bean, when 'prototype', for each getting of Bean will be created a new instance */ | ||
} | ||
``` | ||
<h5>First</h5> | ||
### Bean features | ||
```ts | ||
import { Bean } from 'dependency-injection-cat'; | ||
- Detect cyclic dependencies at compile time | ||
- Detect missing **Beans** at compile time | ||
class SuperClass { | ||
//Without any dependencies | ||
@Bean | ||
someMethod(): Interface { | ||
return new ImplementationOfInterface(); | ||
} | ||
### Property Bean | ||
//With Bean dependencies | ||
@Bean | ||
someMethod( | ||
dependency1: InterfaceOfDependency1, | ||
dependency2: InterfaceOfDependency2, | ||
): Interface { | ||
return new ImplementationOfInterface(dependency1, dependency2); | ||
} | ||
Property Beans resolving class dependencies automatically from the constructor of a class that passed to **Bean**. | ||
//With Bean configuration | ||
@Bean({ qualifier: 'someCoolImpl', scope: 'prototype' }) | ||
someMethod( | ||
dependency1: InterfaceOfDependency1, | ||
dependency2: InterfaceOfDependency2, | ||
): Interface { | ||
return new ImplementationOfInterface(dependency1, dependency2); | ||
} | ||
#### Rules | ||
//Beans do not support arrow-functions methods, this will throw an error | ||
@Bean | ||
someMethod = (): Interface => new ImplementationOfInterface(); | ||
- Each class constructor parameter should be defined as a **Bean** in the **Context** | ||
- Each class constructor parameter should have a type, type should not be primitive. To pass primitive values to the | ||
constructor, check out **Method Beans** | ||
//Beans should have complex types, this will throw an error | ||
@Bean | ||
someMethod(): number | string | any { | ||
return new ImplementationOfInterface(); | ||
} | ||
#### Syntax | ||
//Should not have cyclyc dependencies, this will throw an error | ||
@Bean | ||
someMethod( | ||
dependency1: InterfaceOfDependency1, //Cyclic dependency | ||
): Interface { | ||
return new ImplementationOfInterface(dependency1); | ||
} | ||
```typescript | ||
class ApplicationContext extends CatContext<IBeans> { | ||
//First argument passed in Bean should be a class, di-container will try to resolve class dependencies | ||
useCase = Bean(UseCaseClass); | ||
@Bean | ||
someMethod2( | ||
dependency1: Interface, //Cyclic dependency | ||
): InterfaceOfDependency1 { | ||
return new ImplementationOfDependency(dependency1); | ||
} | ||
//To specify type of Bean you can use Generic parameter, or set type of property | ||
useCase = Bean<IUseCase>(UseCase); | ||
useCase: IUseCase = Bean(UseCase); | ||
useCase: IUseCase = Bean<IUseCase>(UseCase); | ||
//If type is not specified, class will be used as a type | ||
useCase = Bean(UseCaseClass) | ||
//Equivalent to | ||
useCase = Bean<UseCaseClass>(UseCaseClass) | ||
useCase: UseCaseClass = Bean(UseCaseClass) | ||
useCase: UseCaseClass = Bean<UseCaseClass>(UseCaseClass) | ||
//Bean configuration should be passed as a second argument. Should be an object literal. | ||
useCase: IUseCase = Bean(UseCase, {scope: 'prototype'}); | ||
} | ||
``` | ||
<h5>Second</h5> | ||
#### Usage | ||
```ts | ||
import { Bean } from 'dependency-injection-cat'; | ||
```typescript | ||
//UseCaseDependency | ||
export class UseCaseDependency implements IUseCaseDependency { | ||
//...businessLogic | ||
} | ||
class SuperClass { | ||
//If you don't need to pass specific dependencies in Bean, it will resolve all dependencies automatically | ||
someBeanProperty = Bean<Interface>(ImplementationOfInterface); | ||
//UseCase.ts | ||
export class UseCase implements IUseCase { | ||
constructor( | ||
//Any name can be used | ||
private useCaseDependency: IUseCaseDependency, | ||
) { | ||
} | ||
//With Bean configuration | ||
//First argument in Bean should always be implementation of interface, second is configuration object | ||
//When using this syntax, implementation should be a class | ||
//You should pass Bean type in generic | ||
someBeanProperty = Bean<Interface>(ImplementationOfInterface, { qualifier: 'someCoolImpl' }); | ||
//...businessLogic | ||
} | ||
//ApplicationContext.di.ts | ||
import { CatContext, Bean } from 'dependency-injection-cat'; | ||
class ApplicationContext extends CatContext<IBeans> { | ||
useCaseDependency = Bean<IUseCaseDependency>(UseCaseDependency); | ||
useCase = Bean<IUseCase>(UseCase); | ||
} | ||
``` | ||
<h4 id="bean-configuration-object">Bean configuration object</h4> | ||
### Method Bean | ||
```ts | ||
interface BeanConfiguration { | ||
//By default all beans are singleton, if you will set scope 'prototype' Bean will no longer be a singleton | ||
scope?: 'prototype' | 'singleton'; | ||
//Read about Qualifiers and their rules below | ||
qualifier?: string; | ||
Method Beans are more flexible, for example it allows you to pass configuration values to your class constructor. | ||
#### Rules | ||
- Method Bean should always have return type, and it should not be primitive (string, number...). To register Bean with | ||
primitive type you should use type alias. | ||
#### Syntax and Usage | ||
```typescript | ||
class ApplicationContext extends CatContext<IBeans> { | ||
//Bean don't have any dependencies | ||
@Bean | ||
useCase(): IUseCase { | ||
return new UseCase(); | ||
} | ||
//Bean have dependencies | ||
dependency: IUseCaseDependency = Bean(UseCaseDependency); | ||
@Bean | ||
useCase( | ||
useCaseDependency: IUseCaseDependency, | ||
): IUseCase { | ||
return new UseCase(useCaseDependency); | ||
} | ||
//Configure Bean | ||
@Bean({scope: 'prototype'}) | ||
useCase(): IUseCase { | ||
return new UseCase(); | ||
} | ||
} | ||
``` | ||
## Qualifier | ||
<h4 id="what-is-it">What is it?</h4> | ||
### Arrow Function Bean | ||
In fact, Qualifier it's just a name of Bean | ||
You can use it, if you have a few different implementations of interface | ||
Arrow Function Beans allows you to write more compact code, they work same as "Method Beans" | ||
<h4 id="qualifier-rules">Rules</h4> | ||
#### Rules | ||
<!-- toc --> | ||
- Qualifier should be a string | ||
- Qualifier should not be empty string | ||
- Qualifier should not be dynamically calculated (no template strings, or references to constants/object properties) | ||
<!-- tocstop --> | ||
- Same as for "Method Beans" | ||
<h4 id="qualifier-syntax">Syntax</h4> | ||
#### Syntax and Usage | ||
```ts | ||
import { Bean, Qualifier } from 'dependency-injection-cat'; | ||
```typescript | ||
class ApplicationContext extends CatContext<IBeans> { | ||
//Bean don't have any dependencies | ||
@Bean useCase = (): IUseCase => new UseCase(); | ||
//OR with body | ||
@Bean useCase = (): IUseCase => { | ||
return new UseCase(); | ||
} | ||
class SuperClass { | ||
//Correct example | ||
@Bean | ||
someMethod( | ||
@Qualifier('someQualifier') dependency1: InterfaceOfDependency1, | ||
): Interface { | ||
return new ImplementationOfInterface(); | ||
} | ||
//Bean have dependencies | ||
dependency: IUseCaseDependency = Bean(UseCaseDependency); | ||
@Bean useCase = (useCaseDependency: IUseCaseDependency): IUseCase => new UseCase(useCaseDependency); | ||
//OR with body | ||
@Bean useCase = (useCaseDependency: IUseCaseDependency): IUseCase => { | ||
return new UseCase(useCaseDependency); | ||
} | ||
//Configure Bean | ||
@Bean({scope: 'prototype'}) useCase = (): IUseCase => new UseCase(); | ||
} | ||
``` | ||
```ts | ||
//Wrong examples | ||
import { Bean, Qualifier } from 'dependency-injection-cat'; | ||
const superQualifierName = 'superQualifierNameValue'; | ||
### Expression Bean | ||
class SuperClass { | ||
@Bean | ||
someMethod( | ||
@Qualifier(superQualifierName) dependency1: InterfaceOfDependency1, | ||
): Interface { | ||
return new ImplementationOfInterface(); | ||
Expression Beans allows you to define plain value/instance as a Bean, instead of using "Method Beans" | ||
Expression Beans can not have any dependencies, but can be a dependency | ||
#### Rules | ||
- Expression Bean should always have a type, and it should not be primitive (string, number...). To register Bean with | ||
primitive type you should use type alias. | ||
#### Syntax and Usage | ||
```typescript | ||
class ApplicationContext extends CatContext<IBeans> { | ||
@Bean useCase: IUseCase = new UseCase(); | ||
@Bean importedInstanceOfUseCase: IUseCase = importedInstanceOfUseCase; | ||
//Configure Bean | ||
@Bean({scope: 'prototype'}) useCase: IUseCase = new UseCase(); | ||
} | ||
``` | ||
## Context Lifecycle | ||
Context have its own lifecycle, when you're initializing context with `container.initContext` or `container.getOrInitContext` | ||
and when you're clearing context with `container.clearContext` | ||
If you need to make some subscriptions/unsubscription/side effects - you can use **@PostConstruct** and **@BeforeDestruct** decorators | ||
One method can be decorated with **@PostConstruct** and **@BeforeDestruct** at the same time | ||
### PostConstruct, BeforeDestruct | ||
PostConstruct decorator allows you to call method right after context initialization (after calling `container.initContext`), it can be used to make some subscriptions, etc... | ||
BeforeDestruct decorator allows you to call method right before context clearing (after calling `container.clearContext`), it can be used to make some unsubscription, etc... | ||
You can add arguments to annotated method, beans will be injected instead of arguments | ||
You can have several Lifecycle methods, they will be called in order of declaring | ||
#### Syntax | ||
```typescript | ||
import { Bean, PostConstruct, BeforeDestruct, CatContext } from 'dependency-injection-cat'; | ||
class ApplicationContext extends CatContext<IBeans> { | ||
@PostConstruct /* OR / AND */ @BeforeDestruct | ||
subscribeToEvents(subscriber: ISubscriber): void { | ||
subscriber.subscribe(); | ||
} | ||
@Bean | ||
someMethod( | ||
@Qualifier('') dependency1: InterfaceOfDependency1, | ||
): Interface { | ||
return new ImplementationOfInterface(); | ||
} | ||
//OR | ||
@PostConstruct /* OR / AND */ @BeforeDestruct | ||
subscribeToEvents = (subscriber: ISubscriber): void => subscriber.subscribe(); | ||
subscriber: ISubscriber = Bean(Subscriber); | ||
} | ||
``` | ||
### Container | ||
Container has only one method "get" | ||
### Qualifier | ||
<h4 id="container-rules">Rules</h4> | ||
Qualifier needed, when you have 2 or more **Beans** in the **Context** with same type. By default, the qualifier is the | ||
name of the parameter in the class constructor, or in the **Method Bean**` | ||
Also, you can use qualifier, when injecting **Beans** from **GlobalCatContext** | ||
<!-- toc --> | ||
- You should pass type as generic in get method of Container | ||
<!-- tocstop --> | ||
#### Rules | ||
<h4 id="container-syntax">Syntax</h4> | ||
- @Qualifier decorator can be used only in **Method Bean** | ||
- Argument passed to Qualifier should be a string literal and should not be an empty string | ||
- Qualifier should be a name of Bean (class property name, or method name) defined in current **Context** | ||
```ts | ||
//Any TypeScript file in project | ||
import { container } from 'dependency-injection-cat'; | ||
#### Syntax | ||
//Without Qualifier | ||
const someBean = container.get<Interface>(); | ||
```typescript | ||
//Implicit declaration of the qualifier | ||
class ApplicationContext extends CatContext<IBeans> { | ||
httpRequester: IRequester = Bean(HttpRequester); | ||
graphQLRequester: IRequester = Bean(GraphQLRequester); | ||
//With Qualifier | ||
const someBean = container.get<Interface>('someQualifier'); | ||
@Bean | ||
useCase( | ||
graphQLRequester: IRequester, //Will be injected implementation "GraphQLRequester" | ||
): IUseCase { | ||
return new UseCase(graphQLRequester); | ||
} | ||
} | ||
``` | ||
## Author | ||
* [**Artem Kornev**](https://github.com/artem1458) | ||
```typescript | ||
//When Bean placed in current context | ||
class ApplicationContext extends CatContext<IBeans> { | ||
httpRequester: IRequester = Bean(HttpRequester); | ||
graphQLRequester: IRequester = Bean(GraphQLRequester); | ||
## License | ||
This project is under the MIT License | ||
@Bean | ||
useCase( | ||
@Qualifier('graphQLRequester') requester: IRequester, | ||
): IUseCase { | ||
return new UseCase(requester); | ||
} | ||
} | ||
``` | ||
```typescript | ||
//When Bean placed in Global context | ||
//GlobalApplicationContext.di.ts | ||
class GlobalApplicationContext extends GlobalCatContext { | ||
graphQLRequester: IRequester = Bean(GraphQLRequester); | ||
httpRequester: IRequester = Bean(HttpRequester); | ||
} | ||
//ApplicationContext.di.ts | ||
class ApplicationContext extends CatContext<IBeans> { | ||
@Bean | ||
useCase( | ||
@Qualifier('graphQLRequester') requester: IRequester, | ||
): IUseCase { | ||
return new UseCase(requester); | ||
} | ||
} | ||
``` | ||
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
322250
0
5932
739
11
145
7
1
+ Addedgraphlib@^2.1.8
+ Addedjson-beautify@^1.1.1
+ Addedline-column@^1.0.2
+ Addedmd5@^2.3.0
+ Addedminimatch@^3.0.4
+ Addedupath@^2.0.1
+ Addedcharenc@0.0.2(transitive)
+ Addedcrypt@0.0.2(transitive)
+ Addedgraphlib@2.1.8(transitive)
+ Addedis-buffer@1.1.6(transitive)
+ Addedisarray@1.0.0(transitive)
+ Addedisobject@2.1.0(transitive)
+ Addedjson-beautify@1.1.1(transitive)
+ Addedline-column@1.0.2(transitive)
+ Addedmd5@2.3.0(transitive)
+ Addedtypescript@4.9.5(transitive)
+ Addedupath@2.0.1(transitive)
- Removedchokidar@^3.4.2
- Removedanymatch@3.1.3(transitive)
- Removedbinary-extensions@2.3.0(transitive)
- Removedbraces@3.0.3(transitive)
- Removedchokidar@3.6.0(transitive)
- Removedfill-range@7.1.1(transitive)
- Removedfsevents@2.3.3(transitive)
- Removedglob-parent@5.1.2(transitive)
- Removedis-binary-path@2.1.0(transitive)
- Removedis-extglob@2.1.1(transitive)
- Removedis-glob@4.0.3(transitive)
- Removedis-number@7.0.0(transitive)
- Removednormalize-path@3.0.0(transitive)
- Removedpicomatch@2.3.1(transitive)
- Removedreaddirp@3.6.0(transitive)
- Removedto-regex-range@5.0.1(transitive)
- Removedtypescript@3.9.10(transitive)
Updatedlodash@^4.17.21