Skip to content

Commit

Permalink
Merge pull request #1 from MaurerKrisztian/move-project
Browse files Browse the repository at this point in the history
move project
  • Loading branch information
MaurerKrisztian authored Oct 29, 2022
2 parents 98ace1e + 2649a21 commit 7c32a7b
Show file tree
Hide file tree
Showing 71 changed files with 2,605 additions and 2 deletions.
5 changes: 3 additions & 2 deletions README.md
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
37 changes: 37 additions & 0 deletions examples/inject/TokenInjection.example.ts
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();
47 changes: 47 additions & 0 deletions examples/inject/TypeInject.example.ts
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();
16 changes: 16 additions & 0 deletions jest.config.ts
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"
},
}
182 changes: 182 additions & 0 deletions lib/Container.ts
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}`);
}
}
}
}
76 changes: 76 additions & 0 deletions lib/DefinitionRepository.ts
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;
}

}
15 changes: 15 additions & 0 deletions lib/Keys.ts
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");

}
8 changes: 8 additions & 0 deletions lib/Utils.ts
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;
}
}
Loading

0 comments on commit 7c32a7b

Please sign in to comment.