-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from MaurerKrisztian/move-project
move project
- Loading branch information
Showing
71 changed files
with
2,605 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
# type-chef-di | ||
Dependency injection container (IoC) | ||
### type-chef-di | ||
|
||
Documentation: https://zer0-2.gitbook.io/type-chef-di/fundamentals/injection/type-injection |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Inject } from "../../lib/decorators/Inject"; | ||
import { Container } from "../../lib"; | ||
|
||
|
||
class Service { | ||
|
||
constructor(@Inject("serviceStr") private readonly value: string) { | ||
} | ||
|
||
public say() { | ||
return `${this.value}`; | ||
} | ||
} | ||
class Client { | ||
|
||
constructor(@Inject("clientStr") private readonly value: string, @Inject("service") private readonly service: Service) { | ||
} | ||
|
||
public say() { | ||
return `I like ${this.value} and ${this.service.say()}`; | ||
} | ||
} | ||
|
||
|
||
async function run() { | ||
const container = new Container(); | ||
container.register("clientStr", "coffee"); | ||
container.register("serviceStr", "pizza"); | ||
container.register("service", Service); | ||
container.register("client", Client); | ||
const service = await container.resolve<Client>("client"); // new Service('pizza'); | ||
const service2 = await container.resolveByType<Client>(Client); // new Client('coffee', new Service('pizza')); | ||
console.log(service.say()); // client says: I like pizza and coffee | ||
console.log(service2.say()); // client says: I like pizza and coffee | ||
} | ||
|
||
run(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { Container, Injectable } from "../../lib"; | ||
|
||
@Injectable | ||
class SayService { | ||
|
||
public getString() { | ||
return "pizza"; | ||
} | ||
} | ||
|
||
@Injectable | ||
class SayService2 { | ||
|
||
public getString() { | ||
return "coffee"; | ||
} | ||
} | ||
|
||
|
||
@Injectable | ||
class Client { | ||
constructor(private readonly sayService: SayService, private readonly sayService2: SayService2) { | ||
} | ||
|
||
public say() { | ||
return `I like ${this.sayService.getString()} and ${this.sayService2.getString()}`; | ||
} | ||
} | ||
|
||
@Injectable | ||
class Service { | ||
constructor(private readonly client: Client) { | ||
} | ||
|
||
public check() { | ||
return `client says: ${this.client.say()}`; | ||
} | ||
} | ||
|
||
|
||
async function run() { | ||
const container = new Container({enableAutoCreate: true}); | ||
const service = await container.resolveByType<Service>(Service); // new Service(new Client(new SayService(), new SayService2())); | ||
console.log(service.check()); // client says: I like pizza and coffee | ||
} | ||
|
||
run(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
module.exports = { | ||
"roots": [ | ||
"<rootDir>/test" | ||
], | ||
"testMatch": [ | ||
"**/__tests__/**/*.+(ts|tsx|js)", | ||
"**/?(*.)+(spec|test).+(ts|tsx|js)" | ||
], | ||
"coveragePathIgnorePatterns": [ | ||
"node_modules", | ||
"<rootDir>/test" | ||
], | ||
"transform": { | ||
"^.+\\.(ts|tsx)$": "ts-jest" | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
import "reflect-metadata"; | ||
import { IContainer } from "./interfaces/IContainer"; | ||
import { InstantiationModeCO } from "./chainingOptions/InstantiationModeCO"; | ||
import { IInstantiatable, instantiationMode } from "./interfaces/IInstantiatable"; | ||
import { IResolver } from "./interfaces/IResolver"; | ||
import { ConstructorInstantiation } from "./definitions/ConstructorInstantiation"; | ||
import { ConstantInstantiation } from "./definitions/ConstantInstantiation"; | ||
import { IInterceptor } from "./interfaces/IInterceptor"; | ||
import { Initializers } from "./modifiers/Initializers"; | ||
import { Utils } from "./Utils"; | ||
import { DefinitionRepository } from "./DefinitionRepository"; | ||
import { Keys } from "./Keys"; | ||
import { v4 as uuidv4 } from "uuid"; | ||
|
||
export type singletonsType = Map<string, any>; | ||
|
||
export interface IContainerOption { | ||
enableAutoCreate: boolean; // if dependency not exist in the container, creat it and register | ||
} | ||
|
||
|
||
export class Container implements IContainer, IResolver { | ||
|
||
definitionsRepository = new DefinitionRepository(this.options); | ||
protected singletons: singletonsType = new Map<string, any>(); | ||
interceptors: IInterceptor[] = []; | ||
|
||
initializers = new Initializers(this); | ||
|
||
protected DEFAULT_INSTANTIATION: instantiationMode = "singleton"; | ||
|
||
constructor(public readonly options: IContainerOption = { | ||
enableAutoCreate: false | ||
}) { | ||
} | ||
|
||
public register(key: string, ctr: any): InstantiationModeCO { | ||
const decoratorTags = this.getTagsMeta(ctr); | ||
this.setDefinition(key, this.getDefaultInstantiationDef(key, ctr, decoratorTags)); | ||
return new InstantiationModeCO(this, key); | ||
} | ||
|
||
public registerTypes(constructors: any[]): void { | ||
for (const constructor of constructors) { | ||
this.register(uuidv4(), constructor); | ||
} | ||
} | ||
|
||
public async resolveByType<T>(constructor: any): Promise<T> { | ||
const def = this.definitionsRepository.getDefinitionByType(constructor); | ||
|
||
if (def === Keys.AUTO_CREATE_DEPENDENCY && this.options.enableAutoCreate) { | ||
this.registerTypes([constructor]); | ||
return this.resolveByType(constructor); | ||
} else if (def) { | ||
return (this.definitionsRepository.getDefinitionByType(constructor) as IInstantiatable).instantiate(); | ||
} else { | ||
throw new Error(`cannot resolve ${constructor}`); | ||
} | ||
} | ||
|
||
public async resolve<T>(key: string): Promise<T> { | ||
const instantiatable: IInstantiatable = this.definitionsRepository.getDefinition(key); | ||
|
||
switch (instantiatable.definition.instantiationMode) { | ||
case "prototype": { | ||
const originalInstance = await this.resolvePrototype<T>(instantiatable.definition.key); | ||
return this.applyModificationToInstance(originalInstance, instantiatable.definition); | ||
} | ||
case "singleton": { | ||
return this.resolveSingleton<T>(instantiatable); | ||
} | ||
default: { | ||
throw new Error(`Cannot resolve: ${key} because instantiationMode is: ${instantiatable.definition.instantiationMode}`); | ||
} | ||
} | ||
} | ||
|
||
getTagsMeta(ctr: any) { | ||
if (!Utils.isClass(ctr)) return; | ||
const meta = Reflect.getMetadata(Keys.ADD_TAGS_KEY, ctr.constructor) || {}; | ||
return meta[Keys.ADD_TAGS_KEY]; | ||
} | ||
|
||
hasKeyInDefinition(key: string): boolean { | ||
return this.definitionsRepository.definitions.has(key); | ||
} | ||
|
||
async applyModificationToInstance(instance: any, definition: any) { | ||
instance = await this.initializers.runInitializers(instance, definition); | ||
return instance; | ||
} | ||
|
||
async getBySpecificTags(tags: object): Promise<any[]> { | ||
const keys = this.definitionsRepository.getDefinitionKeysBySpecificTags(tags); | ||
|
||
const result = []; | ||
for (const key of keys) { | ||
result.push(await this.resolve(key)); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
async getByTags(tags: string[]): Promise<any[]> { | ||
const keys = this.definitionsRepository.getDefinitionKeysByTags(tags); | ||
|
||
const result = []; | ||
for (const key of keys) { | ||
result.push(await this.resolve(key)); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
addInterceptor(interceptor: IInterceptor): void { | ||
this.interceptors.push(interceptor); | ||
} | ||
|
||
async done(): Promise<any> { | ||
await this.containerTest(); | ||
this.runInterceptors(); | ||
} | ||
|
||
runInterceptors() { | ||
this.interceptors.forEach((interceptor: IInterceptor) => { | ||
interceptor.intercept(this); | ||
}); | ||
} | ||
|
||
private setDefinition(key: string, definition: IInstantiatable) { | ||
this.definitionsRepository.definitions.set(key, definition); | ||
} | ||
|
||
private getDefaultInstantiationDef(key: string, content: any, decoratorTags: any): IInstantiatable { | ||
|
||
if (Utils.isClass(content)) { | ||
const classInstance = new ConstructorInstantiation({ | ||
key, | ||
content, | ||
context: {}, | ||
instantiationMode: this.DEFAULT_INSTANTIATION, | ||
}, this); | ||
classInstance.tags = decoratorTags; | ||
return classInstance; | ||
} | ||
return new ConstantInstantiation({ | ||
key, | ||
content, | ||
instantiationMode: this.DEFAULT_INSTANTIATION | ||
}); | ||
} | ||
|
||
private async resolvePrototype<T>(key: string): Promise<T> { | ||
return this.definitionsRepository.getDefinition(key).instantiate(); | ||
} | ||
|
||
private async resolveSingleton<T>(instantiatable: IInstantiatable): Promise<T> { | ||
if (!this.singletons.has(instantiatable.definition.key)) { | ||
let newInstance = await instantiatable.instantiate(); | ||
newInstance = this.applyModificationToInstance(newInstance, instantiatable.definition); | ||
|
||
this.singletons.set(instantiatable.definition.key, newInstance); | ||
return this.singletons.get(instantiatable.definition.key); | ||
} | ||
return this.singletons.get(instantiatable.definition.key); | ||
} | ||
|
||
|
||
/* | ||
* resolve test for all keys. (run this after all key was registered) | ||
* */ | ||
async containerTest() { | ||
for (const key of this.definitionsRepository.definitions.keys()) { | ||
try { | ||
await this.resolve<any>(key); | ||
} catch (err) { | ||
throw new Error(`Not proper registration. details: ${err}`); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { IInstantiatable } from "./interfaces/IInstantiatable"; | ||
import { IContainerOption } from "./Container"; | ||
import { Keys } from "./Keys"; | ||
|
||
export class DefinitionRepository { | ||
definitions = new Map<string, IInstantiatable>(); | ||
|
||
|
||
constructor(private readonly options: IContainerOption) { | ||
} | ||
|
||
getDefinitions(): Map<string, IInstantiatable> { | ||
return this.definitions; | ||
} | ||
|
||
getDefinition(key: string): IInstantiatable { | ||
if (!this.definitions.has(key)) { | ||
throw new Error(`${key} instance is undefined`); | ||
} | ||
return this.definitions.get(key) as IInstantiatable; | ||
} | ||
|
||
getDefinitionByType(constructor: any): IInstantiatable | symbol { | ||
for (const [key, value] of this.definitions.entries()) { | ||
if (value.definition.content === constructor) { | ||
return value; | ||
} | ||
} | ||
|
||
if (this.options.enableAutoCreate) { | ||
return Keys.AUTO_CREATE_DEPENDENCY; | ||
} | ||
|
||
throw new Error(`definition not found by type: ${constructor}`); | ||
} | ||
|
||
getDefinitionKeysBySpecificTags(tagObj: any): string[] { | ||
const resultKeys: string[] = []; | ||
const tags = Object.keys(tagObj); | ||
|
||
|
||
this.definitions.forEach((value: IInstantiatable, key: string) => { | ||
let found = true; | ||
tags.forEach((tag) => { | ||
if (!(value.tags.hasOwnProperty(tag) && value.tags[tag] === tagObj[tag])) { | ||
found = false; | ||
} | ||
}); | ||
|
||
if (found) { | ||
resultKeys.push(key); | ||
} | ||
}); | ||
|
||
return resultKeys; | ||
} | ||
|
||
getDefinitionKeysByTags(tags: string[]): string[] { | ||
const resultKeys: string[] = []; | ||
this.definitions.forEach((value: IInstantiatable, key: string) => { | ||
tags.forEach((tag) => { | ||
if (value.tags.hasOwnProperty(tag)) { | ||
resultKeys.push(key); | ||
} | ||
}); | ||
}); | ||
return resultKeys; | ||
} | ||
|
||
addTags(key: string, tagsObj: object): void { | ||
const definition = this.getDefinition(key); | ||
const newTags = {...definition.tags, ...tagsObj}; | ||
definition.tags = newTags; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
export class Keys { | ||
static readonly INJECT_PROPERTY_DECORATOR_KEY: unique symbol = Symbol("INJECT_PROPERTY_DECORATOR_KEY"); | ||
static readonly FACTORY_METHOD_PROPERTY_DECORATOR_KEY: unique symbol = Symbol("FACTORY_METHOD_PROPERTY_DECORATOR_KEY"); | ||
static readonly INIT_METHOD_PROPERTY_DECORATOR_KEY: unique symbol = Symbol("INIT_METHOD_PROPERTY_DECORATOR_KEY"); | ||
static readonly SETTER_METHOD_PROPERTY_DECORATOR_KEY: unique symbol = Symbol("SETTER_METHOD_PROPERTY_DECORATOR_KEY"); | ||
static readonly IS_REQUIRED_PARAM: unique symbol = Symbol("IS_REQUIRED_PARAM"); | ||
static readonly PROPERTY_INJECT_KEY: unique symbol = Symbol("PROPERTY_INJECT_KEY"); | ||
static readonly BEFORE_METHOD_KEY: unique symbol = Symbol("BEFORE_METHOD_KEY"); | ||
static readonly AFTER_METHOD_KEY: unique symbol = Symbol("AFTER_METHOD_KEY"); | ||
static readonly METHOD_WRAPPER_KEY: unique symbol = Symbol("METHOD_WRAPPER_KEY"); | ||
static readonly ADD_TAGS_KEY: unique symbol = Symbol("ADD_TAGS_KEY"); | ||
static readonly OTHER_INJECTION_REQUIRED: unique symbol = Symbol("OTHER_INJECTION_REQUIRED"); | ||
static readonly AUTO_CREATE_DEPENDENCY: unique symbol = Symbol("AUTO_CREATE_DEPENDENCY"); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export class Utils { | ||
|
||
static isClass = (fn: any) => /^\s*class/.test(fn?.toString()); | ||
|
||
static getRequiredParamLength(constructor: any) { | ||
return constructor.length || 0; | ||
} | ||
} |
Oops, something went wrong.