Skip to content

Commit

Permalink
feat: Add support to objects/arrays and constructor props
Browse files Browse the repository at this point in the history
  • Loading branch information
jlenon7 committed Oct 25, 2021
1 parent f4d0fd0 commit 51c5fc4
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 38 deletions.
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,48 @@ 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>('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'
import { UserService } from 'app/Services/UserService'

const container = new Container()

container.set(UserService.name, new UserService())
const userService = container.get<UserService>(UserService.name)
container.singleton(UserService, 'props', 'to', 'UserService Constructor here')
const userService = container.get<UserService>('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:
2 changes: 2 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './src/Container'
export * from './src/Contracts/ContainerContract'
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@secjs/ioc",
"version": "1.0.3",
"version": "1.0.4",
"description": "",
"scripts": {
"build": "tsc",
Expand All @@ -15,7 +15,9 @@
"repository": "https://github.com/secjs/ioc",
"author": "João Lenon <[email protected]>",
"license": "MIT",
"dependencies": {},
"dependencies": {
"@secjs/exceptions": "^1.0.3"
},
"devDependencies": {
"@types/debug": "4.1.5",
"@types/jest": "27.0.1",
Expand Down
38 changes: 29 additions & 9 deletions src/Container.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InternalServerException } from '@secjs/exceptions'
import { ContainerContract } from './Contracts/ContainerContract'

export class Container implements ContainerContract {
Expand All @@ -7,25 +8,44 @@ export class Container implements ContainerContract {
return this.services
}

get<D>(name: string): D {
get<D>(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<D>(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<D>(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,
})
}
}
7 changes: 4 additions & 3 deletions src/Contracts/ContainerContract.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface ContainerContract {
get<D>(name: string): D
set<D>(name: string, dependency: { new (): D }): void
singleton<D>(name: string, dependency: { new (): D }): void
get<D>(name: string, ...constructor: any): D
getAll(): Map<string, any>
set(dependency: any, name?: string): void
singleton(dependency: any, name?: string, ...constructor: any): void
}
69 changes: 48 additions & 21 deletions tests/ioc.spec.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -20,24 +10,61 @@ describe('\n IoC Container 😸', () => {
})

it('should be able to set dependencies inside of ioc container', () => {
container.set<Test>(Test.name, Test)
container.set(Dependency)

const dependency = container.get<Test>(Test.name)
expect(dependency.testValue).toBe('test')
const dependency = container.get<Dependency>(
Dependency.name,
'hello',
'world',
)
expect(dependency.testValue).toBe('hello: world')
dependency.changeValue('test2')

const dependency2 = container.get<Test>(Test.name)
expect(dependency2.testValue).toBe('test')
const dependency2 = container.get<Dependency>(
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>(Test.name, Test)
container.singleton(Singleton, 'Singleton', 'hello', 'world')

const dependency = container.get<Test>(Test.name)
expect(dependency.testValue).toBe('test')
const dependency = container.get<Singleton>(Singleton.name)
expect(dependency.testValue).toBe('hello: world')
dependency.changeValue('test2')

const dependency2 = container.get<Test>(Test.name)
const dependency2 = container.get<Singleton>(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'])
})
})
11 changes: 11 additions & 0 deletions tests/stubs/Dependency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class Dependency {
public testValue

constructor(value1: string, value2: string) {
this.testValue = `${value1}: ${value2}`
}

changeValue(value) {
this.testValue = value
}
}
11 changes: 11 additions & 0 deletions tests/stubs/Singleton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class Singleton {
public testValue

constructor(value1: string, value2: string) {
this.testValue = `${value1}: ${value2}`
}

changeValue(value) {
this.testValue = value
}
}

0 comments on commit 51c5fc4

Please sign in to comment.