From 51c5fc4c587a23d06a17af9b611c811481b49f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lenon?= Date: Mon, 25 Oct 2021 14:45:35 -0300 Subject: [PATCH] feat: Add support to objects/arrays and constructor props --- README.md | 34 +++++++++++++-- index.ts | 2 + package-lock.json | 13 ++++++ package.json | 6 ++- src/Container.ts | 38 ++++++++++++---- src/Contracts/ContainerContract.ts | 7 +-- tests/ioc.spec.ts | 69 +++++++++++++++++++++--------- tests/stubs/Dependency.ts | 11 +++++ tests/stubs/Singleton.ts | 11 +++++ 9 files changed, 153 insertions(+), 38 deletions(-) create mode 100644 index.ts create mode 100644 tests/stubs/Dependency.ts create mode 100644 tests/stubs/Singleton.ts diff --git a/README.md b/README.md index 9b42ab8..6311171 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,25 @@ npm install @secjs/ioc ## Usage -> Container register dependencies using Singleton pattern +### Set + +> Container can register dependencies for you, every container.get will create a new instance for UserService. + +```js +import { Container } from '@SecJS/IoC' +import { UserService } from 'app/Services/UserService' + +const container = new Container() + +container.set(UserService) +const userService = container.get('UserService', 'props', 'to', 'UserService Constructor here') + +console.log(userService.findAll()) +``` + +### Singleton + +> Container can register singleton dependencies, when singleton container.get will return the same UserService instance all time. ```js import { Container } from '@SecJS/IoC' @@ -30,12 +48,22 @@ import { UserService } from 'app/Services/UserService' const container = new Container() -container.set(UserService.name, new UserService()) -const userService = container.get(UserService.name) +container.singleton(UserService, 'props', 'to', 'UserService Constructor here') +const userService = container.get('UserService') console.log(userService.findAll()) ``` +> Singleton can register static objects and arrays too, but for arrays and objects the name is required: + +```js +container.singleton([], 'myArray') +container.singleton({}, 'myObject') + +console.log(container.get('myArray')) // [] +console.log(container.get('myObject')) // {} +``` + --- Made with 🖤 by [jlenon7](https://github.com/jlenon7) :wave: diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..dabe7cd --- /dev/null +++ b/index.ts @@ -0,0 +1,2 @@ +export * from './src/Container' +export * from './src/Contracts/ContainerContract' diff --git a/package-lock.json b/package-lock.json index faad5e4..52c82c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "@secjs/ioc", "version": "1.0.3", "license": "MIT", + "dependencies": { + "@secjs/exceptions": "^1.0.3" + }, "devDependencies": { "@types/debug": "4.1.5", "@types/jest": "27.0.1", @@ -1140,6 +1143,11 @@ "node": ">= 8" } }, + "node_modules/@secjs/exceptions": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@secjs/exceptions/-/exceptions-1.0.3.tgz", + "integrity": "sha512-lxAv24IZ/5tse9Z63dhXFOR0ru4UP6ahTwl1HIFhxgwfaqNaInJd/DmtOCOIl4sb8Sc8MxbYUgWgxZT1G1L7cw==" + }, "node_modules/@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -8502,6 +8510,11 @@ "fastq": "^1.6.0" } }, + "@secjs/exceptions": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@secjs/exceptions/-/exceptions-1.0.3.tgz", + "integrity": "sha512-lxAv24IZ/5tse9Z63dhXFOR0ru4UP6ahTwl1HIFhxgwfaqNaInJd/DmtOCOIl4sb8Sc8MxbYUgWgxZT1G1L7cw==" + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", diff --git a/package.json b/package.json index e66bb48..b4c373f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@secjs/ioc", - "version": "1.0.3", + "version": "1.0.4", "description": "", "scripts": { "build": "tsc", @@ -15,7 +15,9 @@ "repository": "https://github.com/secjs/ioc", "author": "João Lenon ", "license": "MIT", - "dependencies": {}, + "dependencies": { + "@secjs/exceptions": "^1.0.3" + }, "devDependencies": { "@types/debug": "4.1.5", "@types/jest": "27.0.1", diff --git a/src/Container.ts b/src/Container.ts index ee3fc4b..2c5e705 100644 --- a/src/Container.ts +++ b/src/Container.ts @@ -1,3 +1,4 @@ +import { InternalServerException } from '@secjs/exceptions' import { ContainerContract } from './Contracts/ContainerContract' export class Container implements ContainerContract { @@ -7,25 +8,44 @@ export class Container implements ContainerContract { return this.services } - get(name: string): D { + get(name: string, ...constructor: any): D { if (!this.services.has(name)) { throw new Error(`Dependency ${name} not found`) } - const Dep = this.services.get(name) + const { isSingleton, Dependency } = this.services.get(name) - if (Dep instanceof Function) { - return new Dep() + if (!isSingleton) { + return new Dependency(...constructor) } - return Dep + return Dependency } - set(name: string, Dep: { new (): D }): void { - this.services.set(name, Dep) + set(Dep: any, name?: string): void { + this.services.set(name || Dep.name, { + isSingleton: false, + Dependency: Dep, + }) } - singleton(name: string, Dep: { new (): D }): void { - this.services.set(name, new Dep()) + singleton(Dep: any, name?: string, ...constructor: any): void { + let dependency + + if (typeof Dep === 'object' || Array.isArray(Dep)) { + dependency = Dep + + if (!name) + throw new InternalServerException( + 'Name cannot be empty on objects and arrays.', + ) + } else { + dependency = new Dep(...constructor) + } + + this.services.set(name || Dep.name, { + isSingleton: true, + Dependency: dependency, + }) } } diff --git a/src/Contracts/ContainerContract.ts b/src/Contracts/ContainerContract.ts index 540b747..6c97e8d 100644 --- a/src/Contracts/ContainerContract.ts +++ b/src/Contracts/ContainerContract.ts @@ -1,5 +1,6 @@ export interface ContainerContract { - get(name: string): D - set(name: string, dependency: { new (): D }): void - singleton(name: string, dependency: { new (): D }): void + get(name: string, ...constructor: any): D + getAll(): Map + set(dependency: any, name?: string): void + singleton(dependency: any, name?: string, ...constructor: any): void } diff --git a/tests/ioc.spec.ts b/tests/ioc.spec.ts index a36c3e9..4f97294 100644 --- a/tests/ioc.spec.ts +++ b/tests/ioc.spec.ts @@ -1,16 +1,6 @@ import { Container } from '../src/Container' - -class Test { - public testValue - - constructor() { - this.testValue = 'test' - } - - changeValue(value) { - this.testValue = value - } -} +import { Singleton } from './stubs/Singleton' +import { Dependency } from './stubs/Dependency' describe('\n IoC Container 😸', () => { let container: Container @@ -20,24 +10,61 @@ describe('\n IoC Container 😸', () => { }) it('should be able to set dependencies inside of ioc container', () => { - container.set(Test.name, Test) + container.set(Dependency) - const dependency = container.get(Test.name) - expect(dependency.testValue).toBe('test') + const dependency = container.get( + Dependency.name, + 'hello', + 'world', + ) + expect(dependency.testValue).toBe('hello: world') dependency.changeValue('test2') - const dependency2 = container.get(Test.name) - expect(dependency2.testValue).toBe('test') + const dependency2 = container.get( + Dependency.name, + 'hello', + 'nodejs', + ) + expect(dependency2.testValue).toBe('hello: nodejs') }) it('should be able to set singleton dependencies inside of ioc container', () => { - container.singleton(Test.name, Test) + container.singleton(Singleton, 'Singleton', 'hello', 'world') - const dependency = container.get(Test.name) - expect(dependency.testValue).toBe('test') + const dependency = container.get(Singleton.name) + expect(dependency.testValue).toBe('hello: world') dependency.changeValue('test2') - const dependency2 = container.get(Test.name) + const dependency2 = container.get(Singleton.name) expect(dependency2.testValue).toBe('test2') }) + + it('should throw and error when trying to set an object/array dependency without name', () => { + try { + container.singleton({ test: 'hello' }) + } catch (error) { + expect(error.status).toBe(500) + expect(error.name).toBe('InternalServerException') + expect(error.content).toBe('Name cannot be empty on objects and arrays.') + } + + try { + container.singleton(['hello']) + } catch (error) { + expect(error.status).toBe(500) + expect(error.name).toBe('InternalServerException') + expect(error.content).toBe('Name cannot be empty on objects and arrays.') + } + }) + + it('should be able to set objects/arrays inside the container', () => { + container.singleton({ hello: 'configs' }, 'configs') + container.singleton(['TestService'], 'services') + + const configs = container.get('configs') + expect(configs).toStrictEqual({ hello: 'configs' }) + + const services = container.get('services') + expect(services).toStrictEqual(['TestService']) + }) }) diff --git a/tests/stubs/Dependency.ts b/tests/stubs/Dependency.ts new file mode 100644 index 0000000..ba85024 --- /dev/null +++ b/tests/stubs/Dependency.ts @@ -0,0 +1,11 @@ +export class Dependency { + public testValue + + constructor(value1: string, value2: string) { + this.testValue = `${value1}: ${value2}` + } + + changeValue(value) { + this.testValue = value + } +} diff --git a/tests/stubs/Singleton.ts b/tests/stubs/Singleton.ts new file mode 100644 index 0000000..e47e26c --- /dev/null +++ b/tests/stubs/Singleton.ts @@ -0,0 +1,11 @@ +export class Singleton { + public testValue + + constructor(value1: string, value2: string) { + this.testValue = `${value1}: ${value2}` + } + + changeValue(value) { + this.testValue = value + } +}