init
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
js
|
||||||
|
node_modules
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
test
|
||||||
|
js
|
||||||
|
node_modules
|
||||||
Generated
+1131
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "dependjs",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "dependjs is a javascript dependency injector",
|
||||||
|
"main": "./js/Index.js",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://gitea.nitowa.xyz/npm-packages/dependjs.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://gitea.nitowa.xyz/npm-packages/dependjs/issues",
|
||||||
|
"email": "peter.millauer@gmail.com"
|
||||||
|
},
|
||||||
|
"homepage": "https://gitea.nitowa.xyz/docs/dependjs",
|
||||||
|
"keywords": [
|
||||||
|
"dependency injection",
|
||||||
|
"inversion of control"
|
||||||
|
],
|
||||||
|
"author": "Peter Millauer <peter.millauer@gmail.com>",
|
||||||
|
"scripts": {
|
||||||
|
"tsc": "tsc",
|
||||||
|
"build": "npm run clean && tsc",
|
||||||
|
"clean": "rm -rf js",
|
||||||
|
"test": "npm run clean && npm run build && mocha --recursive --bail=true js/test"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"reflect-metadata": "^0.1.13"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^11.13.19",
|
||||||
|
"@types/chai": "^4.2.21",
|
||||||
|
"@types/expect": "^1.20.4",
|
||||||
|
"@types/mocha": "^5.2.7",
|
||||||
|
"chai": "^4.3.4",
|
||||||
|
"chai-as-promised": "^7.1.1",
|
||||||
|
"mocha": "^6.2.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"js"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Injector } from "./Injector";
|
||||||
|
import { Type, GenericClassDecorator, Constructor } from "./Types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {GenericClassDecorator<Type<any>>}
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
export function Singleton(_interface?: Constructor<any>): GenericClassDecorator<Type<any>> {
|
||||||
|
return (clazz: Type<any>) => {
|
||||||
|
Injector['modules'].push({
|
||||||
|
implements: _interface ?? clazz,
|
||||||
|
ctor: clazz
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Inject(clazz: Constructor<any>) {
|
||||||
|
return function (prototype: Object, key: string) {
|
||||||
|
Injector['injectionQueue'].push({ injectionType: clazz, prototype: prototype, injectIntoKey: key })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import 'reflect-metadata';
|
||||||
|
import { Constructor, Type } from './Types';
|
||||||
|
|
||||||
|
class _Injector {
|
||||||
|
|
||||||
|
private injectionQueue: any[] = []
|
||||||
|
private modules: { implements?: Constructor<any>, ctor: Type<any> }[] = []
|
||||||
|
private moduleObjs: { [key in string]: any } = {}
|
||||||
|
private initialized = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves instances by injecting required services
|
||||||
|
* @param {Type<any>} target
|
||||||
|
* @returns {T}
|
||||||
|
*/
|
||||||
|
public resolve<T>(target: Type<T>): T {
|
||||||
|
if (!this.initialized) {
|
||||||
|
this.initialize()
|
||||||
|
this.initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.moduleObjs[target.name] as any
|
||||||
|
}
|
||||||
|
|
||||||
|
public async resolveAsync<T>(target: Type<T>): Promise<T> {
|
||||||
|
if (!this.initialized) {
|
||||||
|
await this.initialize()
|
||||||
|
this.initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.moduleObjs[target.name] as any
|
||||||
|
}
|
||||||
|
|
||||||
|
private initialize = async (async?: boolean) => {
|
||||||
|
this.createSingletons()
|
||||||
|
this.injectDependencies()
|
||||||
|
if (async)
|
||||||
|
await this.initializeSingletons()
|
||||||
|
else
|
||||||
|
this.initializeSingletons()
|
||||||
|
}
|
||||||
|
|
||||||
|
private createSingletons = () => {
|
||||||
|
//instantiate all non-root modules
|
||||||
|
this.modules.forEach(m => {
|
||||||
|
const module = new m.ctor()
|
||||||
|
if (m.implements)
|
||||||
|
this.moduleObjs[m.implements.name] = module
|
||||||
|
this.moduleObjs[m.ctor.name] = module
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private injectDependencies = () => {
|
||||||
|
while (this.injectionQueue.length > 0) {
|
||||||
|
const inj = this.injectionQueue.shift()
|
||||||
|
|
||||||
|
if (this.moduleObjs[inj.injectionType.name]) {
|
||||||
|
inj.prototype[inj.injectIntoKey] = this.moduleObjs[inj.injectionType.name]
|
||||||
|
} else {
|
||||||
|
throw new Error("Cannot resolve injection token " + inj.injectionType.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeSingletons = () => {
|
||||||
|
Object.values(this.moduleObjs).forEach(element => element.initialize ? element.initialize() : undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Injector stores services and resolves requested instances.
|
||||||
|
*/
|
||||||
|
export const Injector = new _Injector()
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Type for what object is instances of. Also applicable to "Constructor of T" as Types/Classes/Constructors are interchangable in TS.
|
||||||
|
*/
|
||||||
|
export interface Type<T> {
|
||||||
|
new(...args: any[]): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Constructor<T> = Function & { prototype: T }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic `ClassDecorator` type
|
||||||
|
*/
|
||||||
|
export type GenericClassDecorator<T> = (target: T) => void;
|
||||||
|
|
||||||
|
export interface ISingleton{
|
||||||
|
initialize?(): void | Promise<void>
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Singleton } from "../../src/Decorator";
|
||||||
|
import { COMPONENT_A_VALUE } from "../CONSTANTS";
|
||||||
|
|
||||||
|
@Singleton()
|
||||||
|
export class ComponentA{
|
||||||
|
private value: string
|
||||||
|
|
||||||
|
getFromThis(): string {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize(): void{
|
||||||
|
this.value = COMPONENT_A_VALUE
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Inject, Singleton } from "../../src/Decorator"
|
||||||
|
import { COMPONENT_B_VALUE } from "../CONSTANTS"
|
||||||
|
import { ComponentA } from "./ComponentA"
|
||||||
|
|
||||||
|
export abstract class IComponentB{
|
||||||
|
getFromA: () => string
|
||||||
|
getFromThis: () => string
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton(IComponentB)
|
||||||
|
export class ComponentB implements IComponentB{
|
||||||
|
|
||||||
|
@Inject(ComponentA)
|
||||||
|
private componentA: ComponentA
|
||||||
|
|
||||||
|
private value: string
|
||||||
|
|
||||||
|
getFromA(): string {
|
||||||
|
return this.componentA.getFromThis()
|
||||||
|
}
|
||||||
|
|
||||||
|
getFromThis(): string {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize(): void{
|
||||||
|
this.value = COMPONENT_B_VALUE
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Injector } from '../../src/Injector'
|
||||||
|
import { TestComponent } from './TestComponent'
|
||||||
|
import { assert, expect } from 'chai';
|
||||||
|
import { COMPONENT_A_VALUE, COMPONENT_B_VALUE } from '../CONSTANTS';
|
||||||
|
|
||||||
|
var should = require('chai').should();
|
||||||
|
var chai = require("chai");
|
||||||
|
var chaiAsPromised = require("chai-as-promised");
|
||||||
|
|
||||||
|
chai.use(chaiAsPromised);
|
||||||
|
|
||||||
|
describe('dependjs', () => {
|
||||||
|
it('is able to resolve linear dependencies', () => {
|
||||||
|
|
||||||
|
const testComp = Injector.resolve(TestComponent)
|
||||||
|
|
||||||
|
expect(testComp.getFromA()).to.be.equal(COMPONENT_A_VALUE)
|
||||||
|
expect(testComp.getAThroughB()).to.be.equal(COMPONENT_A_VALUE)
|
||||||
|
expect(testComp.getFromB()).to.be.equal(COMPONENT_B_VALUE)
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Inject, Singleton } from "../../src/Decorator";
|
||||||
|
import {ComponentA} from "./ComponentA"
|
||||||
|
import {IComponentB} from "./ComponentB"
|
||||||
|
|
||||||
|
@Singleton()
|
||||||
|
export class TestComponent{
|
||||||
|
|
||||||
|
@Inject(IComponentB)
|
||||||
|
private compoenntB: IComponentB
|
||||||
|
|
||||||
|
@Inject(ComponentA)
|
||||||
|
private componentA: ComponentA
|
||||||
|
|
||||||
|
getFromA(): string{
|
||||||
|
return this.componentA.getFromThis()
|
||||||
|
}
|
||||||
|
|
||||||
|
getAThroughB(): string{
|
||||||
|
return this.compoenntB.getFromA()
|
||||||
|
}
|
||||||
|
|
||||||
|
getFromB():string{
|
||||||
|
return this.compoenntB.getFromThis()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export const COMPONENT_A_VALUE = "ComponentA"
|
||||||
|
export const COMPONENT_B_VALUE = "ComponentB"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strictPropertyInitialization": false,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"target": "ESnext",
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"outDir": "./js",
|
||||||
|
"strict": true,
|
||||||
|
"experimentalDecorators": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "test/**/*.ts", "Index.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user