Browse Source

init

master
nitowa 1 year ago
commit
de0d8cfd8e
14 changed files with 1397 additions and 0 deletions
  1. 2
    0
      .gitignore
  2. 3
    0
      .npmignore
  3. 0
    0
      index.ts
  4. 1131
    0
      package-lock.json
  5. 42
    0
      package.json
  6. 21
    0
      src/Decorator.ts
  7. 73
    0
      src/Injector.ts
  8. 17
    0
      src/Types.ts
  9. 15
    0
      test/BasicTest/ComponentA.ts
  10. 29
    0
      test/BasicTest/ComponentB.ts
  11. 22
    0
      test/BasicTest/Test.ts
  12. 26
    0
      test/BasicTest/TestComponent.ts
  13. 2
    0
      test/CONSTANTS.ts
  14. 14
    0
      tsconfig.json

+ 2
- 0
.gitignore View File

@@ -0,0 +1,2 @@
1
+js
2
+node_modules

+ 3
- 0
.npmignore View File

@@ -0,0 +1,3 @@
1
+test
2
+js
3
+node_modules

+ 0
- 0
index.ts View File


+ 1131
- 0
package-lock.json
File diff suppressed because it is too large
View File


+ 42
- 0
package.json View File

@@ -0,0 +1,42 @@
1
+{
2
+  "name": "dependjs",
3
+  "version": "0.0.1",
4
+  "description": "dependjs is a javascript dependency injector",
5
+  "main": "./js/Index.js",
6
+  "repository": {
7
+    "type": "git",
8
+    "url": "https://gitea.nitowa.xyz/npm-packages/dependjs.git"
9
+  },
10
+  "bugs": {
11
+    "url": "https://gitea.nitowa.xyz/npm-packages/dependjs/issues",
12
+    "email": "peter.millauer@gmail.com"
13
+  },
14
+  "homepage": "https://gitea.nitowa.xyz/docs/dependjs",
15
+  "keywords": [
16
+    "dependency injection",
17
+    "inversion of control"
18
+  ],
19
+  "author": "Peter Millauer <peter.millauer@gmail.com>",
20
+  "scripts": {
21
+    "tsc": "tsc",
22
+    "build": "npm run clean && tsc",
23
+    "clean": "rm -rf js",
24
+    "test": "npm run clean && npm run build && mocha --recursive --bail=true js/test"
25
+  },
26
+  "license": "MIT",
27
+  "dependencies": {
28
+    "reflect-metadata": "^0.1.13"
29
+  },
30
+  "devDependencies": {
31
+    "@types/node": "^11.13.19",
32
+    "@types/chai": "^4.2.21",
33
+    "@types/expect": "^1.20.4",
34
+    "@types/mocha": "^5.2.7",
35
+    "chai": "^4.3.4",
36
+    "chai-as-promised": "^7.1.1",
37
+    "mocha": "^6.2.0"
38
+  },
39
+  "files": [
40
+    "js"
41
+  ]
42
+}

+ 21
- 0
src/Decorator.ts View File

@@ -0,0 +1,21 @@
1
+import { Injector } from "./Injector";
2
+import { Type, GenericClassDecorator, Constructor } from "./Types";
3
+
4
+/**
5
+ * @returns {GenericClassDecorator<Type<any>>}
6
+ * @constructor
7
+ */
8
+export function Singleton(_interface?: Constructor<any>): GenericClassDecorator<Type<any>> {
9
+  return (clazz: Type<any>) => {
10
+    Injector['modules'].push({
11
+      implements: _interface ?? clazz,
12
+      ctor: clazz
13
+    })
14
+  }
15
+}
16
+
17
+export function Inject(clazz: Constructor<any>) {
18
+  return function (prototype: Object, key: string) {
19
+    Injector['injectionQueue'].push({ injectionType: clazz, prototype: prototype, injectIntoKey: key })
20
+  }
21
+}

+ 73
- 0
src/Injector.ts View File

@@ -0,0 +1,73 @@
1
+import 'reflect-metadata';
2
+import { Constructor, Type } from './Types';
3
+
4
+class _Injector {
5
+
6
+  private injectionQueue: any[] = []
7
+  private modules: { implements?: Constructor<any>, ctor: Type<any> }[] = []
8
+  private moduleObjs: { [key in string]: any } = {}
9
+  private initialized = false
10
+
11
+  /**
12
+   * Resolves instances by injecting required services
13
+   * @param {Type<any>} target
14
+   * @returns {T}
15
+   */
16
+  public resolve<T>(target: Type<T>): T {
17
+    if (!this.initialized) {
18
+      this.initialize()
19
+      this.initialized = true
20
+    }
21
+
22
+    return this.moduleObjs[target.name] as any
23
+  }
24
+
25
+  public async resolveAsync<T>(target: Type<T>): Promise<T> {
26
+    if (!this.initialized) {
27
+      await this.initialize()
28
+      this.initialized = true
29
+    }
30
+
31
+    return this.moduleObjs[target.name] as any
32
+  }
33
+
34
+  private initialize = async (async?: boolean) => {
35
+    this.createSingletons()
36
+    this.injectDependencies()
37
+    if (async)
38
+      await this.initializeSingletons()
39
+    else
40
+      this.initializeSingletons()
41
+  }
42
+
43
+  private createSingletons = () => {
44
+    //instantiate all non-root modules
45
+    this.modules.forEach(m => {
46
+      const module = new m.ctor()
47
+      if (m.implements)
48
+        this.moduleObjs[m.implements.name] = module
49
+      this.moduleObjs[m.ctor.name] = module
50
+    })
51
+  }
52
+
53
+  private injectDependencies = () => {
54
+    while (this.injectionQueue.length > 0) {
55
+      const inj = this.injectionQueue.shift()
56
+
57
+      if (this.moduleObjs[inj.injectionType.name]) {
58
+        inj.prototype[inj.injectIntoKey] = this.moduleObjs[inj.injectionType.name]
59
+      } else {
60
+        throw new Error("Cannot resolve injection token " + inj.injectionType.name)
61
+      }
62
+    }
63
+  }
64
+
65
+  private initializeSingletons = () => {
66
+    Object.values(this.moduleObjs).forEach(element => element.initialize ? element.initialize() : undefined);
67
+  }
68
+}
69
+
70
+/**
71
+ * The Injector stores services and resolves requested instances.
72
+ */
73
+export const Injector = new _Injector()

+ 17
- 0
src/Types.ts View File

@@ -0,0 +1,17 @@
1
+/**
2
+ * Type for what object is instances of. Also applicable to "Constructor of T" as Types/Classes/Constructors are interchangable in TS.
3
+ */
4
+export interface Type<T> {
5
+  new(...args: any[]): T;
6
+}
7
+
8
+export type Constructor<T> = Function & { prototype: T }
9
+
10
+/**
11
+ * Generic `ClassDecorator` type
12
+ */
13
+export type GenericClassDecorator<T> = (target: T) => void;
14
+
15
+export interface ISingleton{
16
+  initialize?(): void | Promise<void>
17
+}

+ 15
- 0
test/BasicTest/ComponentA.ts View File

@@ -0,0 +1,15 @@
1
+import { Singleton } from "../../src/Decorator";
2
+import { COMPONENT_A_VALUE } from "../CONSTANTS";
3
+
4
+@Singleton()
5
+export class ComponentA{
6
+    private value: string
7
+    
8
+    getFromThis(): string {
9
+        return this.value
10
+    }
11
+
12
+    initialize(): void{
13
+        this.value = COMPONENT_A_VALUE
14
+    }
15
+}

+ 29
- 0
test/BasicTest/ComponentB.ts View File

@@ -0,0 +1,29 @@
1
+import { Inject, Singleton } from "../../src/Decorator"
2
+import { COMPONENT_B_VALUE } from "../CONSTANTS"
3
+import { ComponentA } from "./ComponentA"
4
+
5
+export abstract class IComponentB{
6
+    getFromA: () => string
7
+    getFromThis: () => string
8
+}
9
+
10
+@Singleton(IComponentB)
11
+export class ComponentB implements IComponentB{
12
+
13
+    @Inject(ComponentA)
14
+    private componentA: ComponentA
15
+
16
+    private value: string
17
+
18
+    getFromA(): string {
19
+        return this.componentA.getFromThis()
20
+    }
21
+
22
+    getFromThis(): string {
23
+        return this.value
24
+    }
25
+
26
+    initialize(): void{
27
+        this.value = COMPONENT_B_VALUE
28
+    }
29
+}

+ 22
- 0
test/BasicTest/Test.ts View File

@@ -0,0 +1,22 @@
1
+import { Injector } from '../../src/Injector'
2
+import { TestComponent } from './TestComponent'
3
+import { assert, expect } from 'chai';
4
+import { COMPONENT_A_VALUE, COMPONENT_B_VALUE } from '../CONSTANTS';
5
+
6
+var should = require('chai').should();
7
+var chai = require("chai");
8
+var chaiAsPromised = require("chai-as-promised");
9
+
10
+chai.use(chaiAsPromised);
11
+
12
+describe('dependjs', () => {
13
+    it('is able to resolve linear dependencies', () => {
14
+
15
+        const testComp = Injector.resolve(TestComponent)
16
+        
17
+        expect(testComp.getFromA()).to.be.equal(COMPONENT_A_VALUE)
18
+        expect(testComp.getAThroughB()).to.be.equal(COMPONENT_A_VALUE)
19
+        expect(testComp.getFromB()).to.be.equal(COMPONENT_B_VALUE)
20
+
21
+    })
22
+})

+ 26
- 0
test/BasicTest/TestComponent.ts View File

@@ -0,0 +1,26 @@
1
+import { Inject, Singleton } from "../../src/Decorator";
2
+import {ComponentA} from "./ComponentA"
3
+import {IComponentB} from "./ComponentB"
4
+
5
+@Singleton()
6
+export class TestComponent{
7
+
8
+    @Inject(IComponentB)
9
+    private compoenntB: IComponentB
10
+
11
+    @Inject(ComponentA)
12
+    private componentA: ComponentA
13
+
14
+    getFromA(): string{
15
+        return this.componentA.getFromThis()
16
+    }
17
+
18
+    getAThroughB(): string{
19
+        return this.compoenntB.getFromA()
20
+    }
21
+
22
+    getFromB():string{
23
+        return this.compoenntB.getFromThis()
24
+    }
25
+
26
+}

+ 2
- 0
test/CONSTANTS.ts View File

@@ -0,0 +1,2 @@
1
+export const COMPONENT_A_VALUE = "ComponentA"
2
+export const COMPONENT_B_VALUE = "ComponentB"

+ 14
- 0
tsconfig.json View File

@@ -0,0 +1,14 @@
1
+{
2
+    "compilerOptions": {
3
+      "strictPropertyInitialization": false,
4
+      "noImplicitAny": false,
5
+      "target": "ESnext",
6
+      "module": "commonjs",
7
+      "declaration": true,
8
+      "outDir": "./js",
9
+      "strict": true,
10
+      "experimentalDecorators": true
11
+    },
12
+    "include": ["src/**/*.ts", "test/**/*.ts", "Index.ts"],
13
+    "exclude": ["node_modules"]
14
+  }

Loading…
Cancel
Save