Skip to content

Commit

Permalink
Merge pull request #196 from esroyo/feat/reset-providers-propagation
Browse files Browse the repository at this point in the history
feat: allows to propagete providers named reset to its dependents
  • Loading branch information
young-steveo committed Dec 21, 2023
2 parents 9db7e22 + f38b91c commit c9c311b
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 30 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,11 +381,14 @@ Param | Type | Details
**Provider** | *Function* | A constructor function that will be instantiated as a singleton. Should expose a function called `$get` that will be used as a factory to instantiate the service.

#### resetProviders(names)
Param | Type | Details
:--------------------------|:-----------|:--------
**names**<br />*(optional)*| *Array* | An array of strings which contains names of the providers to be reset.
Param | Type | Details
:------------------------------|:-----------|:--------
**names**<br />*(optional)* | *Array* | An array of strings which contains names of the providers to be reset.
**propagate**<br />*(optional)*| *Boolean* | Propagate the reset to all providers that depend on the previous list.

Used to reset providers for the next reference to re-instantiate the provider.

Used to reset providers for the next reference to re-instantiate the provider. If `names` param is passed, will reset only the named providers.
If `names` param is passed, will reset only the named providers. When reseting an specific list of providers, it is possible to also propagate the reset to providers that depend on those.

#### register(Obj)
#### container.$register(Obj)
Expand Down
4 changes: 4 additions & 0 deletions grunt/config/grunt-contrib-jasmine.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"jasmine" : {
"options": {
"version": "3.8.0",
"noSandbox": true
},
"tests" : {
"src" : "dist/bottle.js",
"options" : {
Expand Down
29 changes: 18 additions & 11 deletions src/Bottle/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,24 @@
* @return void
*/
var applyMiddleware = function applyMiddleware(middleware, name, instance, container) {
var bottle = this;
var descriptor = {
configurable : true,
enumerable : true
};
if (middleware.length) {
descriptor.get = function getWithMiddlewear() {
var index = 0;
var next = function nextMiddleware(err) {
enumerable : true,
get : function getWithMiddlewear() {
var captureTarget,serviceDependents, index, next;
if (bottle.capturingDepsOf.length) {
captureTarget = bottle.capturingDepsOf[bottle.capturingDepsOf.length - 1];
serviceDependents = bottle.dependents[name] = bottle.dependents[name] || [];
if (serviceDependents.indexOf(captureTarget) === -1) {
serviceDependents.push(captureTarget);
}
}
if (!middleware.length) {
return instance;
}
index = 0;
next = function nextMiddleware(err) {
if (err) {
throw err;
}
Expand All @@ -25,11 +35,8 @@ var applyMiddleware = function applyMiddleware(middleware, name, instance, conta
};
next();
return instance;
};
} else {
descriptor.value = instance;
descriptor.writable = true;
}
},
};

Object.defineProperty(container, name, descriptor);

Expand Down
12 changes: 8 additions & 4 deletions src/Bottle/provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ var getWithGlobal = function getWithGlobal(collection, name) {
* @return Bottle
*/
var createProvider = function createProvider(name, Provider) {
var providerName, properties, container, id, decorators, middlewares;
var bottle, providerName, properties, container, decorators, middlewares;

id = this.id;
bottle = this;
container = this.container;
decorators = this.decorators;
middlewares = this.middlewares;
Expand All @@ -55,14 +55,18 @@ var createProvider = function createProvider(name, Provider) {
var provider = container[providerName];
var instance;
if (provider) {
bottle.capturingDepsOf.push(name);
// filter through decorators
instance = getWithGlobal(decorators, name).reduce(reducer, provider.$get(container));
bottle.capturingDepsOf.pop();

delete container[providerName];
delete container[name];
}
return instance === undefined ? instance : applyMiddleware(getWithGlobal(middlewares, name),
name, instance, container);
if (instance === undefined) {
return instance;
}
return applyMiddleware.call(bottle, getWithGlobal(middlewares, name), name, instance, container);
}
};

Expand Down
40 changes: 34 additions & 6 deletions src/Bottle/reset-providers.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,58 @@
* @return void
*/
var removeProviderMap = function resetProvider(name) {
var parts = splitHead(name);
if (parts.length > 1) {
removeProviderMap.call(getNestedBottle.call(this, parts[0]), parts[1]);
}
delete this.providerMap[name];
delete this.container[name];
delete this.container[name + PROVIDER_SUFFIX];
};

/**
* Clears a reseted service from the dependencies tracker.
*
* @param String name
* @return void
*/
var removeFromDeps = function removeFromDeps(name) {
var parts = splitHead(name);
if (parts.length > 1) {
removeFromDeps.call(getNestedBottle.call(this, parts[0]), parts[1]);
}
Object.keys(this.dependents).forEach(function clearDependents(serviceName) {
if (this.dependents[serviceName]) {
this.dependents[serviceName] = this.dependents[serviceName]
.filter(function (dependent) { return dependent !== name; });
}
}, this);
};

/**
* Resets providers on a bottle instance. If 'names' array is provided, only the named providers will be reset.
*
* @param Array names
* @param Boolean [propagate]
* @return void
*/
var resetProviders = function resetProviders(names) {
var tempProviders = this.originalProviders;
var resetProviders = function resetProviders(names, propagate) {
var shouldFilter = Array.isArray(names);

Object.keys(this.originalProviders).forEach(function resetProvider(originalProviderName) {
if (shouldFilter && names.indexOf(originalProviderName) === -1) {
return;
}
var parts = originalProviderName.split(DELIMITER);
var parts = splitHead(originalProviderName);
if (parts.length > 1) {
parts.forEach(removeProviderMap, getNestedBottle.call(this, parts[0]));
resetProviders.call(getNestedBottle.call(this, parts[0]), [parts[1]], propagate);
}
if (shouldFilter && propagate && this.dependents[originalProviderName]) {
this.resetProviders(this.dependents[originalProviderName], propagate);
}
if (shouldFilter) {
removeFromDeps.call(this, originalProviderName);
}
removeProviderMap.call(this, originalProviderName);
this.provider(originalProviderName, tempProviders[originalProviderName]);
this.provider(originalProviderName, this.originalProviders[originalProviderName]);
}, this);
};
6 changes: 6 additions & 0 deletions src/Bottle/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
var createService = function createService(name, Service, isClass) {
var deps = arguments.length > 3 ? slice.call(arguments, 3) : [];
var bottle = this;
deps.forEach(function registerDependents(otherService) {
var serviceDependents = bottle.dependents[otherService] = bottle.dependents[otherService] || [];
if (serviceDependents.indexOf(name) === -1) {
serviceDependents.push(name);
}
});
return factory.call(this, name, function GenericFactory() {
var serviceFactory = Service; // alias for jshint
var args = deps.map(getNestedService, bottle.container);
Expand Down
2 changes: 2 additions & 0 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ Bottle = function Bottle(name) {

this.id = id++;

this.capturingDepsOf = [];
this.decorators = {};
this.dependents = {};
this.middlewares = {};
this.nested = {};
this.providerMap = {};
Expand Down
11 changes: 11 additions & 0 deletions src/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,14 @@ var getNestedBottle = function getNestedBottle(name) {
var getNestedService = function getNestedService(fullname) {
return fullname.split(DELIMITER).reduce(getNested, this);
};

/**
* Split a dot-notation string on head segment and rest segment.
*
* @param String fullname
* @return Array
*/
var splitHead = function splitHead(fullname) {
var parts = fullname.split(DELIMITER);
return parts.length > 1 ? [parts[0], parts.slice(1).join(DELIMITER)] : [parts[0]];
};
82 changes: 77 additions & 5 deletions test/spec/provider.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,16 +189,88 @@
expect(i).toEqual(2);
expect(j).toEqual(1);
});
it('allows for sub containers to re-initiate as well', function() {
it('allows to propagate the reset to the providers that depend on the selected service names', function() {
var i = 0;
var j = 0;
var k = 0;
var l = 0;
var b = new Bottle();
var FirstService= function() { i = ++i; };
var SecondProvider = function() {
j = ++j;
this.$get = function(container) { return { _first: container.First }; };
};
var ThirdFactory = function(container) { k = ++k; return { _first: container.First }; };
b.factory('Zero', function () { l = ++l; return 0; });
b.service('First', FirstService, 'Zero');
b.provider('Second', SecondProvider);
b.factory('Third', ThirdFactory);
expect(b.container.Second._first instanceof FirstService).toBe(true);
expect(b.container.Third._first instanceof FirstService).toBe(true);
expect(i).toEqual(1);
expect(j).toEqual(1);
expect(k).toEqual(1);
expect(l).toEqual(1);
b.resetProviders(['First'], true);
expect(b.container.Second._first instanceof FirstService).toBe(true);
expect(b.container.Third._first instanceof FirstService).toBe(true);
expect(i).toEqual(2);
expect(j).toEqual(2);
expect(k).toEqual(2);
expect(l).toEqual(1);
b.resetProviders(['Zero'], true);
expect(b.container.Second._first instanceof FirstService).toBe(true);
expect(b.container.Third._first instanceof FirstService).toBe(true);
expect(i).toEqual(3);
expect(j).toEqual(3);
expect(k).toEqual(3);
expect(l).toEqual(2);
});
it('will cleanup service dependents if a service is redefined', function() {
var i = 0;
var j = 0;
var k = 0;
var b = new Bottle();
var FirstService= function() { i = ++i; };
var SecondService = function() { j = ++j; };
var ThirdService = function() { k = ++k; };
b.service('First', FirstService);
b.service('Thing.Second', SecondService, 'First');
b.service('Third', ThirdService, 'Thing.Second');
expect(b.container.First instanceof FirstService).toBe(true);
expect(b.container.Thing.Second instanceof SecondService).toBe(true);
expect(b.container.Third instanceof ThirdService).toBe(true);
expect(i).toEqual(1);
expect(j).toEqual(1);
expect(k).toEqual(1);
b.resetProviders(['Thing.Second'], true);
// Redefined to have no dependencies
b.service('Thing.Second', SecondService);
expect(b.container.First instanceof FirstService).toBe(true);
expect(b.container.Thing.Second instanceof SecondService).toBe(true);
expect(b.container.Third instanceof ThirdService).toBe(true);
expect(i).toEqual(1);
expect(j).toEqual(2);
expect(k).toEqual(2);
// No propagation will happen given that no service depends on First anymore
b.resetProviders(['First'], true);
expect(b.container.First instanceof FirstService).toBe(true);
expect(b.container.Thing.Second instanceof SecondService).toBe(true);
expect(b.container.Third instanceof ThirdService).toBe(true);
expect(i).toEqual(2);
expect(j).toEqual(2);
expect(k).toEqual(2);
});
it('allows for deep sub containers to re-initiate as well', function() {
var i = 0;
var b = new Bottle();
var ThingProvider = function() { i = ++i; this.$get = function() { return this; }; };
b.provider('Thing.Something', ThingProvider);
expect(b.container.Thing.Something instanceof ThingProvider).toBe(true);
b.provider('Thing.In.Something', ThingProvider);
expect(b.container.Thing.In.Something instanceof ThingProvider).toBe(true);
// Intentionally calling twice to prove the construction is cached until reset
expect(b.container.Thing.Something instanceof ThingProvider).toBe(true);
expect(b.container.Thing.In.Something instanceof ThingProvider).toBe(true);
b.resetProviders();
expect(b.container.Thing.Something instanceof ThingProvider).toBe(true);
expect(b.container.Thing.In.Something instanceof ThingProvider).toBe(true);
expect(i).toEqual(2);
});
it('will not break if a nested container has multiple children', function() {
Expand Down

0 comments on commit c9c311b

Please sign in to comment.