Skip to content

Commit

Permalink
Added possibility to resolve values from nested namespaces
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Voronov committed Dec 17, 2016
1 parent 1f971a2 commit dea42bd
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 8 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## 1.2.0

### Added
* ``resolveAll`` now can resolve values from nested namespaces. Optional.

```javascript
const modules = container.resolveAll('my-root-namespace', true);

for (const prop in modules) {
if (modules.hasOwnProperty(prop)) {
const values = modules[prop];

values.forEach((currentValue) => {
console.log(currentValue);
});
}
}
```

## 1.1.0

### Added
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "namespaces-js",
"version": "1.1.0",
"version": "1.2.0",
"description": "Angular-flavored DI container.",
"main": "lib/index.js",
"directories": {
Expand Down Expand Up @@ -42,6 +42,7 @@
"is-nil": "^1.0.1",
"is-object": "^1.0.1",
"is-string": "^1.0.4",
"starts-with": "^1.0.2",
"toposort": "^1.0.0"
},
"devDependencies": {
Expand Down
8 changes: 5 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,14 @@ class Container extends Namespace {
/**
* Resolves all modules from a given namespace.
* @param {(Namespace|string)} namespace - Namespace or namespace name
* @returns {Map<string, any>} Map of module values, where key is module name.
* @param {boolean} nested - Value that detects whether it needs to resolve nested namespaces.
* If 'true', all resolved values will be put into an array.
* @returns {Map<string, (any|Array<any>)>} Map of module values, where key is a module name.
*/
resolveAll(namespace) {
resolveAll(namespace, nested) {
requires('namespace', namespace);

return this[FIELDS.resolver].resolveAll(getNamespace(namespace));
return this[FIELDS.resolver].resolveAll(getNamespace(namespace), nested);
}
}

Expand Down
27 changes: 23 additions & 4 deletions src/resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,34 @@ class Resolver {
/**
* Resolves all modules by a given namespace name.
* @param {string} namespace - Target namespace name.
* @returns {Map<string, any>} Map of module values, where key is a module name.
* @param {boolean} nested - Value that detects whether it needs to resolve nested namespaces.
* If 'true', all resolved values will be put into an array.
* @returns {Map<string, (any|Array<any>)>} Map of module values, where key is a module name.
*/
resolveAll(namespace) {
resolveAll(namespace, nested = false) {
assert(isString(namespace), `${INVALID_NAMESPACE_TYPE} "${typeof namespace}"`);

const result = {};
let namespaces = [namespace];

this[FIELDS.storage].forEachIn(namespace, (module, path) => {
result[module.getName()] = this.resolve(path);
if (nested) {
namespaces = namespaces.concat(this[FIELDS.storage].namespaces(namespace));
}

forEach(namespaces, (currentNamespace) => {
this[FIELDS.storage].forEachIn(currentNamespace, (module, path) => {
const name = module.getName();

if (!nested) {
result[name] = this.resolve(path);
} else {
if (!result[name]) {
result[name] = [];
}

result[name].push(this.resolve(path));
}
});
});

return result;
Expand Down
22 changes: 22 additions & 0 deletions src/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import isNil from 'is-nil';
import isString from 'is-string';
import isFunction from 'is-function';
import forEach from 'for-each';
import startsWith from 'starts-with';
import Module from './module';
import path from './utils/path';
import { requires, assert } from './utils/assertions';
Expand Down Expand Up @@ -63,6 +64,27 @@ class Storage {
return this[FIELDS.size];
}

/**
* Returns an array of registered namespaces.
* @param {string} parent - Parent namespace.
* @returns {Array} An array of registered namespaces.
*/
namespaces(parent) {
const namespaces = [];

forEach(this[FIELDS.namespaces], (_, key) => {
if (parent) {
if (key !== parent && startsWith(key, parent)) {
namespaces.push(key);
}
} else {
namespaces.push(key);
}
});

return namespaces;
}

/**
* Adds a module to a storage.
* @param {Module} module - Target module to add.
Expand Down
57 changes: 57 additions & 0 deletions test/unit/resolver.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,63 @@ describe('Resolver', () => {
expect(arr.length).to.equal(2);
});
});

context('When "nested=true"', () => {
it('should resolve values from nested namespaces', () => {
const foo = container.namespace('foo');
foo.service('A', function() { this.name = 'A'; });
foo.service('B', ['foo/A'], function() { this.name = 'B'; });
foo.service('C', ['foo/B'], function() { this.name = 'C'; });
foo.service('D', ['foo/B', 'foo/C'], function() { this.name = 'D'; });

const bar = foo.namespace('bar');
bar.service('E', ['foo/A'], function() { this.name = 'E'; });
bar.service('F', ['foo/bar/E', 'foo/C'], function() { this.name = 'F'; });

const resolved = container.resolveAll('foo', true);
const arr = [];

for (const name in resolved) {
if (resolved.hasOwnProperty(name)) {
arr.push({
name,
value: resolved[name][0]
});
}
}

expect(arr.length).to.equal(6);
});

it('should resolve values from nested namespaces and avoid collisions', () => {
const foo = container.namespace('foo');
foo.service('A', function() { this.name = 'A'; });
foo.service('B', ['foo/A'], function() { this.name = 'B'; });
foo.service('C', ['foo/B'], function() { this.name = 'C'; });
foo.service('D', ['foo/B', 'foo/C'], function() { this.name = 'D'; });

const bar = foo.namespace('bar');
bar.service('E', ['foo/A'], function() { this.name = 'E'; });
bar.service('F', ['foo/bar/E', 'foo/C'], function() { this.name = 'F'; });

const qaz = bar.namespace('qaz');
qaz.service('G', ['foo/A'], function() { this.name = 'E'; });
qaz.service('F', ['foo/bar/E', 'foo/C'], function() { this.name = 'F'; });

const resolved = container.resolveAll('foo', true);
const arr = [];

for (const name in resolved) {
if (resolved.hasOwnProperty(name)) {
resolved[name].forEach((i) => {
arr.push(i)
});
}
}

expect(arr.length).to.equal(8);
});
});
});
});

Expand Down
36 changes: 36 additions & 0 deletions test/unit/storage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,42 @@ describe('Storage', function () {
});
});

describe('.namespaces', () => {
it('should return an array of containing namespaces', () => {
storage = new Storage('/', false);

expect(storage.namespaces()).to.be.empty;

storage.addItem(new Module('foo', 'qaz', [], function init() {}));
storage.addItem(new Module('foo/qaz', 'bar', [], function init() {}));
storage.addItem(new Module('foo/qaz/wsx', 'bar', [], function init() {}));


const namespaces = storage.namespaces();
expect(namespaces.length).to.eql(3);

expect(namespaces[0]).to.eql('foo');
expect(namespaces[1]).to.eql('foo/qaz');
expect(namespaces[2]).to.eql('foo/qaz/wsx');
});

it('should return an array of containing nested namespaces', () => {
storage = new Storage('/', false);

expect(storage.namespaces()).to.be.empty;

storage.addItem(new Module('foo', 'qaz', [], function init() {}));
storage.addItem(new Module('foo/qaz', 'bar', [], function init() {}));
storage.addItem(new Module('foo/qaz/wsx', 'bar', [], function init() {}));


const namespaces = storage.namespaces('foo/qaz');
expect(namespaces.length).to.eql(1);

expect(namespaces[0]).to.eql('foo/qaz/wsx');
});
});

describe('.forEachIn', () => {
context('When namespace not passed or not a string', () => {
it('should throw an error', () => {
Expand Down

0 comments on commit dea42bd

Please sign in to comment.