Skip to content

Commit

Permalink
Merge pull request #66 from thefill/feature/SimplifyInjector
Browse files Browse the repository at this point in the history
Feature/simplify injector
  • Loading branch information
thefill committed Aug 10, 2019
2 parents 543bd63 + 2240e45 commit 00f2eb2
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 123 deletions.
27 changes: 1 addition & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,31 +154,6 @@ fighter2.punch();</pre>

## Advanced usage

### Delay initialisation of services until used (on injection request)

Have enough of overhead when all those services initialises at once? Register them and request initialisation only when injection is requested.

<pre class="runkit-source">const jetli = require('jetli@3.0.1').jetli;

class Attack {
constructor(){
this.id = Math.round(Math.random() * 100);
console.log(`Attack no. ${this.id} ready!`);
}
punch(){
console.log(`Attack no. ${this.id} executed!`);
}
}

await jetli.set('attack', Attack, true);
console.log('No initialisation at this point');

const fighter1 = await jetli.get('attack');
const fighter2 = await jetli.get('attack');

fighter1.punch();
fighter2.punch();</pre>

### Pass arguments to services constructor

<pre class="runkit-source">const jetli = require('jetli@3.0.1').jetli;
Expand Down Expand Up @@ -255,7 +230,7 @@ console.log(serviceB.getId());</pre>

### Mock services for test purposes

Its rather trivial to mock module dependencies if you have total control whats injected where, right? ith Jetli you can reset any previously registered/injected dependencies and introduce your own mocks / stubs.
Its rather trivial to mock module dependencies if you have total control whats injected where, right? With Jetli you can reset any previously registered/injected dependencies and introduce your own mocks / stubs.

<pre class="runkit-source">const jetli = require('jetli@3.0.1').jetli;

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jetli",
"version": "3.0.1",
"version": "4.0.0",
"description": "Simple, lightweight dependency injector - supports factories, classes and primitives.",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
Expand Down
96 changes: 44 additions & 52 deletions src/classes/jetli/jetli.class.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,52 +158,67 @@ describe('Jetli class', () => {
});
});

describe('should not initialise constructor by default via set method', () => {
Object.keys(stubbedConstructorTypes).forEach((typeName) => {
it(`for ${typeName}`, async () => {
const dependency: Mock = stubbedConstructorTypes[typeName];
describe('should confirm true if injection already set with key', () => {
Object.keys(primitiveTypes).forEach((typeName) => {
it(typeName, async () => {
const dependency = primitiveTypes[typeName];
await jetli.set(typeName, dependency);

expect(dependency).not.toBeCalled();
expect(jetli.isSet(typeName)).toEqual(true);
});
});
Object.keys(constructorTypes).forEach((typeName) => {
it(typeName, async () => {
const dependency = constructorTypes[typeName];
await jetli.set(typeName, dependency);

expect(jetli.isSet(typeName)).toEqual(true);
});
});
});

describe('should initialise constructor via set method', () => {
Object.keys(stubbedConstructorTypes).forEach((typeName) => {
it(`for ${typeName}`, async () => {
const dependency: Mock = stubbedConstructorTypes[typeName];
await jetli.set(typeName, dependency, false);
describe('should confirm true if injection already set with injectable object', () => {
Object.keys(constructorTypes).forEach((typeName) => {
it(typeName, async () => {
const dependency = constructorTypes[typeName];
await jetli.get(dependency);

expect(dependency).toBeCalledTimes(1);
expect(jetli.isSet(dependency)).toEqual(true);
});
});
});

describe('should initialise constructor once via set method', () => {
Object.keys(stubbedConstructorTypes).forEach((typeName) => {
it(`for ${typeName}`, async () => {
const dependency: Mock = stubbedConstructorTypes[typeName];
await jetli.set(typeName, dependency, false);
await jetli.get(typeName);
await jetli.get(typeName);
await jetli.get(typeName);
it('should confirm false if injection already set with key', async () => {
expect(jetli.isSet('some-name')).toEqual(false);
});

expect(dependency).toBeCalledTimes(1);
describe('should confirm false if injection already set with injectable object', () => {
Object.keys(primitiveTypes).forEach((typeName) => {
if (typeName !== 'string') {
it(typeName, async () => {
const dependency = primitiveTypes[typeName];
expect(jetli.isSet(dependency)).toEqual(false);
});
}
});
});

describe('should confirm false if injection key cant be extracted from provided data', () => {
Object.keys(constructorTypes).forEach((typeName) => {
it(typeName, async () => {
const dependency = constructorTypes[typeName];
expect(jetli.isSet(dependency)).toEqual(false);
});
});
});

describe('should initialise constructor with correct arguments via set method', () => {
describe('should not initialise constructor via set method', () => {
Object.keys(stubbedConstructorTypes).forEach((typeName) => {
it(`for ${typeName}`, async () => {
const argument1 = 'abc';
const argument2 = 123;
const argument3 = [1, 2, 3];
const dependency: Mock = stubbedConstructorTypes[typeName];
await jetli.set(typeName, dependency, false, argument1, argument2, argument3);
await jetli.set(typeName, dependency);

expect(dependency).toBeCalledWith(argument1, argument2, argument3);
expect(dependency).not.toBeCalled();
});
});
});
Expand Down Expand Up @@ -380,7 +395,7 @@ describe('Jetli class', () => {
public initialised = false;

public init = jest.fn(() => {
return Promise.reject();
return Promise.reject('error');
});
}

Expand All @@ -397,7 +412,7 @@ describe('Jetli class', () => {
public initialised = false;

public init = jest.fn(() => {
return Promise.reject();
return Promise.reject('error');
});
}

Expand All @@ -409,29 +424,6 @@ describe('Jetli class', () => {
});

});

it('when initialisation delayed till request', async () => {
// tslint:disable-next-line
class ServiceA implements IInjection {
public initialised = false;

public init = jest.fn(() => {
return Promise.reject();
});
}

try {
await jetli.set('some-id', ServiceA);
} catch (error) {
expect(error).toBeFalsy();
}

try {
const serviceA = await jetli.get('some-id');
} catch (error) {
expect(error).toBeTruthy();
}
});
});

describe('should throw error for requesting unset dependency via get method', () => {
Expand Down Expand Up @@ -570,7 +562,7 @@ describe('Jetli class', () => {
const argument2 = 123;
const argument3 = [1, 2, 3];
const dependency: Mock = stubbedConstructorTypes[typeName];
await jetli.set(typeName, dependency, true, argument1, argument2, argument3);
await jetli.set(typeName, dependency, argument1, argument2, argument3);
await jetli.get(typeName);

expect(dependency).toBeCalledWith(argument1, argument2, argument3);
Expand Down
33 changes: 22 additions & 11 deletions src/classes/jetli/jetli.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,15 @@ export class Jetli implements IJetli {
* immediately if initialise set to true.
* @param {string} key
* @param {IDependency} dependency
* @param {boolean} initialiseOnRequest Should Jetli initialise immediately,
* or delay till injection requested
* @param constructorArgs {...any} Arguments that should be passed to dependency
* constructor
*/
public async set<T = any>(
key: string,
dependency: IDependency,
initialiseOnRequest = true,
...constructorArgs
): Promise<void> {
if (
Jetli.inStore(this.dependencies, key) ||
Jetli.inStore(this.initialisedDependencies, key)
) {
if (this.isSet(key)) {
throw new Error(`Injectable with key ${key} already set`);
}

Expand All @@ -60,11 +54,26 @@ export class Jetli implements IJetli {
dependency: dependency,
args: constructorArgs
};
}

// if required immediate initialisation
if (!initialiseOnRequest) {
await this.initialiseDependency<T>(key);
/**
* Checks if dependency already set
* @param {IDependency | string} dependency
* @returns {boolean}
*/
public isSet(dependency: IDependency | string): boolean {
let key;
if (typeof dependency === 'string') {
key = dependency;
} else if (dependency && dependency.name) {
key = dependency.name;
} else {
// we cant get key
return false;
}

return Jetli.inStore(this.dependencies, key) ||
Jetli.inStore(this.initialisedDependencies, key);
}

/**
Expand Down Expand Up @@ -161,7 +170,9 @@ export class Jetli implements IJetli {
// initialise injectable
await finalDependency.init(this);
} catch (error) {
throw new Error(`Error while initialising dependency for key ${key}.`);
// tslint:disable-next-line
console.error(`Error while initialising dependency for key ${key}.`);
throw error;
}
}
}
Expand Down
41 changes: 8 additions & 33 deletions src/docs/readme.body.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Jetli allows you to inject consistently classes, functions and primitives across

Injecting instances of classes is trivial with jetli - just use 'get' method without any additional options.

<pre class="runkit-source">const jetli = require('jetli@3.0.1').jetli;
<pre class="runkit-source">const jetli = require('jetli@4.0.0').jetli;

class Attack {
constructor(){
Expand All @@ -60,7 +60,7 @@ Functions, already instantiated objects or primitive values like array, string a

Registration is provided via 'set' method and requires you to provide string token that identifies the injectable element.

<pre class="runkit-source">const jetli = require('jetli@3.0.1').jetli;
<pre class="runkit-source">const jetli = require('jetli@4.0.0').jetli;

class Attack {
constructor(){
Expand All @@ -83,7 +83,7 @@ fighter2.punch();</pre>

As explained in previous example primitives can be easily used across your applications with associated string id provided during registration.

<pre class="runkit-source">const jetli = require('jetli@3.0.1').jetli;
<pre class="runkit-source">const jetli = require('jetli@4.0.0').jetli;

const someNumber = 123;
const someString = 'punch';
Expand All @@ -108,7 +108,7 @@ To use Jetli to full extend implement services that expose init method. This met

If you already initialised injectable and dont want jetli to call "init" make sure to set "initialise" property to true;

<pre class="runkit-source">const jetli = require('jetli@3.0.1').jetli;
<pre class="runkit-source">const jetli = require('jetli@4.0.0').jetli;

await jetli.set('someNumber', 123);

Expand Down Expand Up @@ -136,34 +136,9 @@ fighter2.punch();</pre>

## Advanced usage

### Delay initialisation of services until used (on injection request)

Have enough of overhead when all those services initialises at once? Register them and request initialisation only when injection is requested.

<pre class="runkit-source">const jetli = require('jetli@3.0.1').jetli;

class Attack {
constructor(){
this.id = Math.round(Math.random() * 100);
console.log(`Attack no. ${this.id} ready!`);
}
punch(){
console.log(`Attack no. ${this.id} executed!`);
}
}

await jetli.set('attack', Attack, true);
console.log('No initialisation at this point');

const fighter1 = await jetli.get('attack');
const fighter2 = await jetli.get('attack');

fighter1.punch();
fighter2.punch();</pre>

### Pass arguments to services constructor

<pre class="runkit-source">const jetli = require('jetli@3.0.1').jetli;
<pre class="runkit-source">const jetli = require('jetli@4.0.0').jetli;

class Attack {
constructor(id){
Expand All @@ -187,7 +162,7 @@ fighter2.punch();</pre>

Jetli uses battle-tested method to fight 'cyclic dependencies' - optional initialisation callback. Injector searches for optional "init" method to call it and as an argument to provide instance of injector itself. This method provide safe moment to inject all dependencies required by service - you can be sure that all dependencies will be already initialised.

<pre class="runkit-source">const jetli = require('jetli@3.0.1').jetli;
<pre class="runkit-source">const jetli = require('jetli@4.0.0').jetli;

class ServiceA {
constructor(){
Expand Down Expand Up @@ -237,9 +212,9 @@ console.log(serviceB.getId());</pre>

### Mock services for test purposes

Its rather trivial to mock module dependencies if you have total control whats injected where, right? ith Jetli you can reset any previously registered/injected dependencies and introduce your own mocks / stubs.
Its rather trivial to mock module dependencies if you have total control whats injected where, right? With Jetli you can reset any previously registered/injected dependencies and introduce your own mocks / stubs.

<pre class="runkit-source">const jetli = require('jetli@3.0.1').jetli;
<pre class="runkit-source">const jetli = require('jetli@4.0.0').jetli;

class Attack {
constructor(){
Expand Down

0 comments on commit 00f2eb2

Please sign in to comment.