knifecycle
Advanced tools
Comparing version 2.7.0 to 3.0.0
@@ -0,1 +1,11 @@ | ||
<a name="3.0.0"></a> | ||
# [3.0.0](https://github.com/nfroidure/knifecycle/compare/v2.7.0...v3.0.0) (2018-10-11) | ||
### Features | ||
* **$instance:** Add a way to inject the Knifecycle instance ([5321401](https://github.com/nfroidure/knifecycle/commit/5321401)) | ||
<a name="2.7.0"></a> | ||
@@ -2,0 +12,0 @@ # [2.7.0](https://github.com/nfroidure/knifecycle/compare/v2.6.2...v2.7.0) (2018-10-08) |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = void 0; | ||
var _util = require("./util"); | ||
@@ -27,44 +32,78 @@ | ||
*/ | ||
module.exports = buildInitializer; | ||
var _default = (0, _util.initializer)({ | ||
name: 'buildInitializer', | ||
type: 'service', | ||
inject: ['$autoload'] | ||
}, initInitializerBuilder); | ||
/** | ||
* Create a JavaScript module that initialize | ||
* a set of dependencies with hardcoded | ||
* import/awaits. | ||
* @param {Object} constants | ||
* An hash for simple constants | ||
* @param {Function} loader | ||
* The dependency auto-loader | ||
* @param {String[]} dependencies | ||
* The main dependencies | ||
* @return {Promise<String>} | ||
* The JavaScript module content | ||
* Instantiate the initializer builder service | ||
* @param {Object} services | ||
* The services to inject | ||
* @param {Object} services.$autoload | ||
* The dependencies autoloader | ||
* @return {Promise<Function>} | ||
* A promise of the buildInitializer function | ||
* @example | ||
* import buildInitializer from 'knifecycle/src/build'; | ||
* import initInitializerBuilder from 'knifecycle/dist/build'; | ||
* | ||
* buildInitializer(constants, loader, ['entryPoint']); | ||
* const buildInitializer = await initInitializerBuilder({ | ||
* $autoload: async () => {}, | ||
* }); | ||
*/ | ||
function buildInitializer(_x, _x2, _x3) { | ||
return _buildInitializer.apply(this, arguments); | ||
exports.default = _default; | ||
function initInitializerBuilder(_x) { | ||
return _initInitializerBuilder.apply(this, arguments); | ||
} | ||
function _buildInitializer() { | ||
_buildInitializer = _asyncToGenerator(function* (constants, loader, dependencies) { | ||
const dependencyTrees = yield Promise.all(dependencies.map(dependency => buildDependencyTree(constants, loader, dependency))); | ||
const dependenciesHash = buildDependenciesHash(dependencyTrees.filter(identity)); | ||
const batches = (0, _sequence.buildInitializationSequence)({ | ||
__name: 'main', | ||
__childNodes: dependencyTrees.filter(identity) | ||
}); | ||
batches.pop(); | ||
return `${batches.map((batch, index) => ` | ||
function _initInitializerBuilder() { | ||
_initInitializerBuilder = _asyncToGenerator(function* ({ | ||
$autoload | ||
}) { | ||
return buildInitializer; | ||
/** | ||
* Create a JavaScript module that initialize | ||
* a set of dependencies with hardcoded | ||
* import/awaits. | ||
* @param {String[]} dependencies | ||
* The main dependencies | ||
* @return {Promise<String>} | ||
* The JavaScript module content | ||
* @example | ||
* import initInitializerBuilder from 'knifecycle/dist/build'; | ||
* | ||
* const buildInitializer = await initInitializerBuilder({ | ||
* $autoload: async () => {}, | ||
* }); | ||
* | ||
* const content = await buildInitializer(['entryPoint']); | ||
*/ | ||
function buildInitializer(_x4) { | ||
return _buildInitializer.apply(this, arguments); | ||
} | ||
function _buildInitializer() { | ||
_buildInitializer = _asyncToGenerator(function* (dependencies) { | ||
const dependencyTrees = yield Promise.all(dependencies.map(dependency => buildDependencyTree({ | ||
$autoload | ||
}, dependency))); | ||
const dependenciesHash = buildDependenciesHash(dependencyTrees.filter(identity)); | ||
const batches = (0, _sequence.buildInitializationSequence)({ | ||
__name: 'main', | ||
__childNodes: dependencyTrees.filter(identity) | ||
}); | ||
batches.pop(); | ||
return `${batches.map((batch, index) => ` | ||
// Definition batch #${index}${batch.map(name => { | ||
if (!dependenciesHash[name].__initializer) { | ||
return ` | ||
const ${name} = ${JSON.stringify(constants[name], null, 2)};`; | ||
} | ||
if ('constant' === dependenciesHash[name].__initializer[_util.SPECIAL_PROPS.TYPE]) { | ||
return ` | ||
const ${name} = ${JSON.stringify(dependenciesHash[name].__initializer[_util.SPECIAL_PROPS.VALUE], null, 2)};`; | ||
} | ||
return ` | ||
return ` | ||
import ${dependenciesHash[name].__initializerName} from '${dependenciesHash[name].__path}';`; | ||
}).join('')}`).join('\n')} | ||
}).join('')}`).join('\n')} | ||
@@ -74,15 +113,15 @@ export async function initialize(services = {}) {${batches.map((batch, index) => ` | ||
const batch${index} = {${batch.map(name => { | ||
if (!dependenciesHash[name].__initializer) { | ||
return ` | ||
if ('constant' === dependenciesHash[name].__initializer[_util.SPECIAL_PROPS.TYPE]) { | ||
return ` | ||
${name}: Promise.resolve(${name}),`; | ||
} | ||
} | ||
return ` | ||
return ` | ||
${name}: ${dependenciesHash[name].__initializerName}({${dependenciesHash[name].__inject ? `${dependenciesHash[name].__inject.map(_util.parseDependencyDeclaration).map(({ | ||
serviceName, | ||
mappedName | ||
}) => ` | ||
serviceName, | ||
mappedName | ||
}) => ` | ||
${serviceName}: services['${mappedName}'],`).join('')}` : ''} | ||
})${'provider' === dependenciesHash[name].__type ? '.then(provider => provider.service)' : ''},`; | ||
}).join('')} | ||
}).join('')} | ||
}; | ||
@@ -95,10 +134,10 @@ | ||
${batch.map(name => { | ||
return ` | ||
return ` | ||
services['${name}'] = await batch${index}['${name}'];`; | ||
}).join('')} | ||
}).join('')} | ||
`).join('')} | ||
return {${dependencies.map(_util.parseDependencyDeclaration).map(({ | ||
serviceName, | ||
mappedName | ||
}) => ` | ||
serviceName, | ||
mappedName | ||
}) => ` | ||
${serviceName}: services['${mappedName}'],`).join('')} | ||
@@ -108,7 +147,10 @@ }; | ||
`; | ||
}); | ||
return _buildInitializer.apply(this, arguments); | ||
} | ||
}); | ||
return _buildInitializer.apply(this, arguments); | ||
return _initInitializerBuilder.apply(this, arguments); | ||
} | ||
function buildDependencyTree(_x4, _x5, _x6) { | ||
function buildDependencyTree(_x2, _x3) { | ||
return _buildDependencyTree.apply(this, arguments); | ||
@@ -118,3 +160,5 @@ } | ||
function _buildDependencyTree() { | ||
_buildDependencyTree = _asyncToGenerator(function* (constants, loader, dependencyDeclaration) { | ||
_buildDependencyTree = _asyncToGenerator(function* ({ | ||
$autoload | ||
}, dependencyDeclaration) { | ||
const { | ||
@@ -125,9 +169,2 @@ mappedName, | ||
if (constants[mappedName]) { | ||
return Promise.resolve({ | ||
__name: mappedName, | ||
__constant: constants[mappedName] | ||
}); | ||
} | ||
try { | ||
@@ -137,3 +174,3 @@ const { | ||
initializer | ||
} = yield loader(mappedName); | ||
} = yield $autoload(mappedName); | ||
const node = { | ||
@@ -150,3 +187,5 @@ __name: mappedName, | ||
if (initializer[_util.SPECIAL_PROPS.INJECT] && initializer[_util.SPECIAL_PROPS.INJECT].length) { | ||
const childNodes = yield Promise.all(initializer[_util.SPECIAL_PROPS.INJECT].map(childDependencyDeclaration => buildDependencyTree(constants, loader, childDependencyDeclaration))); | ||
const childNodes = yield Promise.all(initializer[_util.SPECIAL_PROPS.INJECT].map(childDependencyDeclaration => buildDependencyTree({ | ||
$autoload | ||
}, childDependencyDeclaration))); | ||
node.__childNodes = childNodes.filter(identity); | ||
@@ -153,0 +192,0 @@ return node; |
@@ -9,4 +9,6 @@ "use strict"; | ||
var _util = require("./util"); | ||
var _ = _interopRequireWildcard(require(".")); | ||
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -19,9 +21,14 @@ | ||
describe('buildInitializer', () => { | ||
function aProvider() {} | ||
function aProvider() { | ||
return _aProvider.apply(this, arguments); | ||
} | ||
const mockedConstants = { | ||
NODE_ENV: 'development' | ||
}; | ||
function _aProvider() { | ||
_aProvider = _asyncToGenerator(function* () {}); | ||
return _aProvider.apply(this, arguments); | ||
} | ||
const mockedDepsHash = { | ||
dep1: (0, _util.initializer)({ | ||
NODE_ENV: (0, _.constant)('NODE_ENV', 'development'), | ||
dep1: (0, _.initializer)({ | ||
inject: [], | ||
@@ -32,3 +39,3 @@ options: {}, | ||
}, aProvider), | ||
dep2: (0, _util.initializer)({ | ||
dep2: (0, _.initializer)({ | ||
inject: ['dep1', 'NODE_ENV'], | ||
@@ -39,3 +46,3 @@ options: {}, | ||
}, aProvider), | ||
dep3: (0, _util.initializer)({ | ||
dep3: (0, _.initializer)({ | ||
inject: ['dep2', 'dep1', '?depOpt'], | ||
@@ -47,14 +54,39 @@ options: {}, | ||
}; | ||
const initAutoloader = (0, _.initializer)({ | ||
name: '$autoload', | ||
type: 'service', | ||
inject: [], | ||
options: { | ||
singleton: true | ||
} | ||
}, | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
return ( | ||
/*#__PURE__*/ | ||
function () { | ||
var _$autoload = _asyncToGenerator(function* (name) { | ||
return mockedDepsHash[name] ? Promise.resolve({ | ||
path: `./services/${name}`, | ||
initializer: mockedDepsHash[name] | ||
}) : Promise.reject(new _yerror.default('E_UNMATCHED_DEPENDENCY', name)); | ||
}); | ||
function mockedLoader(name) { | ||
return mockedDepsHash[name] ? Promise.resolve({ | ||
path: `./services/${name}`, | ||
initializer: mockedDepsHash[name] | ||
}) : Promise.reject(new _yerror.default('E_UNMATCHED_DEPENDENCY', name)); | ||
} | ||
return function $autoload(_x) { | ||
return _$autoload.apply(this, arguments); | ||
}; | ||
}() | ||
); | ||
})); | ||
it('should build an initialization module', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
const content = yield (0, _build.default)(mockedConstants, mockedLoader, ['dep1', 'finalMappedDep>dep3']); | ||
const $ = new _.default(); | ||
$.register((0, _.constant)('PWD', '~/my-project')); | ||
$.register(initAutoloader); | ||
$.register(_build.default); | ||
const { | ||
buildInitializer | ||
} = yield $.run(['buildInitializer']); | ||
const content = yield buildInitializer(['dep1', 'finalMappedDep>dep3']); | ||
@@ -61,0 +93,0 @@ _assert.default.equal(content, ` |
@@ -36,2 +36,8 @@ "use strict"; | ||
}); | ||
Object.defineProperty(exports, "alsoInject", { | ||
enumerable: true, | ||
get: function () { | ||
return _util.alsoInject; | ||
} | ||
}); | ||
Object.defineProperty(exports, "type", { | ||
@@ -116,2 +122,3 @@ enumerable: true, | ||
const INJECTOR = '$injector'; | ||
const INSTANCE = '$instance'; | ||
const SILO_CONTEXT = '$siloContext'; | ||
@@ -135,5 +142,3 @@ const FATAL_ERROR = '$fatalError'; | ||
const E_NON_SINGLETON_CONSTANT_INITIALIZER = 'E_NON_SINGLETON_CONSTANT_INITIALIZER'; | ||
const E_BAD_VALUED_NON_CONSTANT_INITIALIZER = 'E_BAD_VALUED_NON_CONSTANT_INITIALIZER'; // Constants that should use Symbol whenever possible | ||
const INSTANCE = '__instance'; | ||
const E_BAD_VALUED_NON_CONSTANT_INITIALIZER = 'E_BAD_VALUED_NON_CONSTANT_INITIALIZER'; | ||
/* Architecture Note #1: Knifecycle | ||
@@ -143,5 +148,6 @@ | ||
injection](https://en.wikipedia.org/wiki/Dependency_injection) | ||
and [inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control) | ||
with [inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control) | ||
tool. It will always be tied to this goal since I prefer | ||
composing software instead of using frameworks. | ||
composing software instead of using frameworks and DI/IC is | ||
a major part to design strong software in my opinion. | ||
@@ -153,3 +159,4 @@ It is designed to have a low footprint on services code. | ||
can be reused elsewhere (even when not using DI) with no changes | ||
at all. | ||
at all since they are just simple functions with annotations | ||
set as a property. | ||
*/ | ||
@@ -179,2 +186,4 @@ | ||
constructor() { | ||
var _this2 = this; | ||
this._silosCounter = 0; | ||
@@ -187,54 +196,56 @@ this._silosContexts = new Set(); | ||
this._singletonsServicesShutdownsPromises = new Map(); | ||
this.provider(INJECTOR, (0, _util.inject)([SILO_CONTEXT], ({ | ||
$siloContext | ||
}) => Promise.resolve({ | ||
service: dependenciesDeclarations => this._initializeDependencies($siloContext, $siloContext.name, dependenciesDeclarations, { | ||
injectOnly: true | ||
}) | ||
this.register((0, _util.constant)(INSTANCE, this)); | ||
this.register((0, _util.initializer)({ | ||
name: INJECTOR, | ||
type: 'provider', | ||
inject: [SILO_CONTEXT] | ||
}, | ||
/*#__PURE__*/ | ||
function () { | ||
var _ref = _asyncToGenerator(function* ({ | ||
$siloContext | ||
}) { | ||
return { | ||
service: dependenciesDeclarations => _this2._initializeDependencies($siloContext, $siloContext.name, dependenciesDeclarations, { | ||
injectOnly: true | ||
}) | ||
}; | ||
}); | ||
return function (_x) { | ||
return _ref.apply(this, arguments); | ||
}; | ||
}())); | ||
this.register((0, _util.initializer)({ | ||
name: DESTROY, | ||
type: 'provider', | ||
inject: [], | ||
options: { | ||
singleton: true | ||
} | ||
}, | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
return { | ||
service: () => { | ||
_this2.shutdownPromise = _this2.shutdownPromise || Promise.all([..._this2._silosContexts].map(siloContext => { | ||
const $dispose = siloContext.servicesDescriptors.get(DISPOSE).service; | ||
return $dispose(); | ||
})); | ||
debug('Shutting down Knifecycle instance.'); | ||
return _this2.shutdownPromise; | ||
} | ||
}; | ||
}))); | ||
this.provider(DESTROY, () => Promise.resolve({ | ||
service: () => { | ||
this.shutdownPromise = this.shutdownPromise || Promise.all([...this._silosContexts].map(siloContext => { | ||
const $dispose = siloContext.servicesDescriptors.get(DISPOSE).service; | ||
return $dispose(); | ||
})); | ||
debug('Shutting down Knifecycle instance.'); | ||
return this.shutdownPromise; | ||
} | ||
}, { | ||
singleton: true | ||
})); | ||
} | ||
/** | ||
* Returns a Knifecycle instance (always the same) | ||
* @return {Knifecycle} | ||
* The created/saved instance | ||
* @deprecated | ||
* Simply use `new Knifecycle()` and eventually recreate | ||
* this singleton by your side. | ||
* @example | ||
* | ||
* import { getInstance } from 'knifecycle' | ||
* | ||
* const $ = getInstance(); | ||
*/ | ||
static getInstance() { | ||
Knifecycle[INSTANCE] = Knifecycle[INSTANCE] || new Knifecycle(); | ||
debug('Spawning an instance.'); | ||
return Knifecycle[INSTANCE]; | ||
} | ||
/* Architecture Note #1.3: Declaring services | ||
The first step to use `knifecycle` is to declare | ||
services. There are two way of declaring services: | ||
- constants: a constant is a simple value that will | ||
never change. It can be literal values, objects | ||
or even functions. | ||
- initializers: they are asynchronous functions | ||
that handle the initialization phase. | ||
Initializers can be of two types: | ||
/* Architecture Note #1.3: Registering initializers | ||
The first step to use `knifecycle` is to create a new | ||
`Knifecycle` instance and register the previously | ||
created initializers. | ||
Initializers can be of three types: | ||
- constants: a `constant` initializer resolves to | ||
a constant value. | ||
- services: a `service` initializer directly | ||
resolve to the actual service it builds. It can | ||
be objects, functions or literal values. | ||
be objects, functions or litteral values. | ||
- providers: they instead resolve to an object that | ||
@@ -253,117 +264,2 @@ contains the service built into the `service` property | ||
/** | ||
* Register a constant initializer | ||
* @param {String} constantName | ||
* The name of the service | ||
* @param {any} constantValue | ||
* The constant value | ||
* @return {Knifecycle} | ||
* The Knifecycle instance (for chaining) | ||
* @deprecated | ||
* Use the `constant` decorator with the `Knifecycle.register` method | ||
* @example | ||
* | ||
* import Knifecycle from 'knifecycle' | ||
* | ||
* const $ = new Knifecycle(); | ||
* | ||
* // Expose the process env | ||
* $.constant('ENV', process.env); | ||
* // Expose a time() function | ||
* $.constant('time', Date.now.bind(Date)); | ||
*/ | ||
constant(constantName, constantValue) { | ||
return this.register((0, _util.constant)(constantName, constantValue)); | ||
} | ||
/** | ||
* Register a service initializer | ||
* @param {String} serviceName | ||
* Service name | ||
* @param {Function} serviceBuilder | ||
* An asynchronous function returning the actual service | ||
* @param {Object} options | ||
* Options attached to the initializer | ||
* @return {Knifecycle} | ||
* The Knifecycle instance (for chaining) | ||
* @deprecated | ||
* Use the `service` decorator with the `Knifecycle.register` method | ||
* @example | ||
* | ||
* import Knifecycle from 'knifecycle' | ||
* import fs from 'fs'; | ||
* | ||
* const $ = new Knifecycle(); | ||
* | ||
* $.service('config', configServiceInitializer, { | ||
* singleton: true, | ||
* }); | ||
* | ||
* function configServiceInitializer({ CONFIG_PATH }) { | ||
* return new Promise((resolve, reject) { | ||
* fs.readFile(CONFIG_PATH, function(err, data) { | ||
* if(err) { | ||
* return reject(err); | ||
* } | ||
* try { | ||
* resolve(JSON.parse(data)); | ||
* } catch (err) { | ||
* reject(err); | ||
* } | ||
* }, 'utf-8'); | ||
* } | ||
*/ | ||
service(serviceName, serviceBuilder, options) { | ||
return this.register((0, _util.service)(serviceName, serviceBuilder, options)); | ||
} | ||
/** | ||
* Register a provider initializer | ||
* @param {String} serviceName | ||
* Service name resolved by the provider | ||
* @param {Function} initializer | ||
* An initializer returning the service promise | ||
* @param {Object} options | ||
* Options attached to the initializer | ||
* @return {Knifecycle} | ||
* The Knifecycle instance (for chaining) | ||
* @deprecated | ||
* Use the `initializer` decorator with the `Knifecycle.register` method | ||
* @example | ||
* | ||
* import Knifecycle from 'knifecycle' | ||
* import fs from 'fs'; | ||
* | ||
* const $ = new Knifecycle(); | ||
* | ||
* $.register(provider('config', function configProvider() { | ||
* return new Promise((resolve, reject) { | ||
* fs.readFile('config.js', function(err, data) { | ||
* let config; | ||
* if(err) { | ||
* return reject(err); | ||
* } | ||
* try { | ||
* config = JSON.parse(data.toString); | ||
* } catch (err) { | ||
* return reject(err); | ||
* } | ||
* resolve({ | ||
* service: config, | ||
* }); | ||
* }); | ||
* }); | ||
* })); | ||
*/ | ||
provider(serviceName, initializer, options = {}) { | ||
this.register((0, _util.reuseSpecialProps)(initializer, initializer, { | ||
[_util.SPECIAL_PROPS.NAME]: serviceName, | ||
[_util.SPECIAL_PROPS.OPTIONS]: options | ||
})); | ||
return this; | ||
} | ||
/** | ||
* Register an initializer | ||
@@ -408,3 +304,18 @@ * @param {Function} initializer | ||
throw new _yerror.default(E_BAD_VALUED_NON_CONSTANT_INITIALIZER, initializer[_util.SPECIAL_PROPS.NAME]); | ||
} // Temporary cast service/constant initializers into | ||
} // Constants are singletons and constant so we can set it | ||
// to singleton services descriptors map directly | ||
if ('constant' === initializer[_util.SPECIAL_PROPS.TYPE]) { | ||
const handlesSet = new Set(); | ||
this._singletonsServicesHandles.set(initializer[_util.SPECIAL_PROPS.NAME], handlesSet); | ||
this._singletonsServicesDescriptors.set(initializer[_util.SPECIAL_PROPS.NAME], Promise.resolve({ | ||
// We do not directly use initializer[SPECIAL_PROPS.VALUE] here | ||
// since it looks like there is a bug with Babel build that | ||
// change functions to empty litteral objects | ||
service: initializer() | ||
})); | ||
} // Temporary cast service initializers into | ||
// providers. Best would be to threat each differently | ||
@@ -414,6 +325,5 @@ // at dependencies initialization level to boost performances | ||
if ('service' === initializer[_util.SPECIAL_PROPS.TYPE] || 'constant' === initializer[_util.SPECIAL_PROPS.TYPE]) { | ||
if ('service' === initializer[_util.SPECIAL_PROPS.TYPE]) { | ||
initializer = (0, _util.reuseSpecialProps)(initializer, serviceAdapter.bind(null, initializer[_util.SPECIAL_PROPS.NAME], initializer)); | ||
initializer[_util.SPECIAL_PROPS.TYPE] = 'provider'; | ||
initializer[_util.SPECIAL_PROPS.VALUE] = {}.undef; | ||
initializer[_util.SPECIAL_PROPS.TYPE] = 'provider'; //initializer[SPECIAL_PROPS.VALUE] = {}.undef; | ||
} | ||
@@ -431,5 +341,6 @@ | ||
debug(`${this._initializers.has(initializer[_util.SPECIAL_PROPS.NAME]) ? 'Overridden' : 'Registered'} an initializer: ${initializer[_util.SPECIAL_PROPS.NAME]}`); | ||
this._initializers.set(initializer[_util.SPECIAL_PROPS.NAME], initializer); | ||
debug('Registered a new initializer:', initializer[_util.SPECIAL_PROPS.NAME]); | ||
return this; | ||
@@ -521,8 +432,8 @@ } | ||
/* Architecture Note #1.4: Execution silos | ||
Once all the services are declared, we need a way to bring | ||
them to life. Execution silos are where the magic happen. | ||
Once every initializers are registered, we need a way to bring | ||
them to life. Execution silos are where the magic happens. | ||
For each call of the `run` method with given dependencies, | ||
a new silo is created and the required environment to | ||
run the actual code is leveraged. | ||
Depending of your application design, you could run it | ||
Depending on your application design, you could run it | ||
in only one execution silo or into several ones | ||
@@ -551,9 +462,9 @@ according to the isolation level your wish to reach. | ||
run(dependenciesDeclarations) { | ||
var _this2 = this; | ||
var _this3 = this; | ||
return _asyncToGenerator(function* () { | ||
const _this = _this2; | ||
const _this = _this3; | ||
const internalDependencies = [...new Set(dependenciesDeclarations.concat(DISPOSE))]; | ||
const siloContext = { | ||
name: `silo-${_this2._silosCounter++}`, | ||
name: `silo-${_this3._silosCounter++}`, | ||
servicesDescriptors: new Map(), | ||
@@ -565,3 +476,3 @@ servicesSequence: [], | ||
if (_this2.shutdownPromise) { | ||
if (_this3.shutdownPromise) { | ||
throw new _yerror.default(E_INSTANCE_DESTROYED); | ||
@@ -588,3 +499,3 @@ } // Create a provider for the special fatal error service | ||
service: function () { | ||
var _ref = _asyncToGenerator(function* () { | ||
var _ref3 = _asyncToGenerator(function* () { | ||
siloContext.shutdownPromise = siloContext.shutdownPromise || _shutdownNextServices(siloContext.servicesSequence); | ||
@@ -594,6 +505,6 @@ debug('Shutting down services'); | ||
_this2._silosContexts.delete(siloContext); // Shutdown services in their instanciation order | ||
_this3._silosContexts.delete(siloContext); // Shutdown services in their instanciation order | ||
function _shutdownNextServices(_x) { | ||
function _shutdownNextServices(_x2) { | ||
return _shutdownNextServices2.apply(this, arguments); | ||
@@ -611,3 +522,3 @@ } | ||
function () { | ||
var _ref2 = _asyncToGenerator(function* (serviceName) { | ||
var _ref4 = _asyncToGenerator(function* (serviceName) { | ||
const singletonServiceDescriptor = yield _this._singletonsServicesDescriptors.get(serviceName); | ||
@@ -651,4 +562,4 @@ const serviceDescriptor = singletonServiceDescriptor || (yield siloContext.servicesDescriptors.get(serviceName)); | ||
return function (_x2) { | ||
return _ref2.apply(this, arguments); | ||
return function (_x3) { | ||
return _ref4.apply(this, arguments); | ||
}; | ||
@@ -663,3 +574,3 @@ }())); | ||
return function service() { | ||
return _ref.apply(this, arguments); | ||
return _ref3.apply(this, arguments); | ||
}; | ||
@@ -670,5 +581,5 @@ }(), | ||
_this2._silosContexts.add(siloContext); | ||
_this3._silosContexts.add(siloContext); | ||
const servicesHash = yield _this2._initializeDependencies(siloContext, siloContext.name, internalDependencies, { | ||
const servicesHash = yield _this3._initializeDependencies(siloContext, siloContext.name, internalDependencies, { | ||
injectOnly: false, | ||
@@ -710,3 +621,3 @@ autoloading: false | ||
}) { | ||
var _this3 = this; | ||
var _this4 = this; | ||
@@ -721,10 +632,10 @@ return _asyncToGenerator(function* () { | ||
let initializer = yield _this3._findInitializer(siloContext, serviceName, { | ||
let initializer = yield _this4._findInitializer(siloContext, serviceName, { | ||
injectOnly, | ||
autoloading | ||
}); | ||
serviceDescriptorPromise = _this3._singletonsServicesDescriptors.get(serviceName); | ||
serviceDescriptorPromise = _this4._singletonsServicesDescriptors.get(serviceName); | ||
if (serviceDescriptorPromise) { | ||
_this3._singletonsServicesHandles.get(serviceName).add(siloContext.name); | ||
_this4._singletonsServicesHandles.get(serviceName).add(siloContext.name); | ||
} else { | ||
@@ -745,3 +656,3 @@ serviceDescriptorPromise = siloContext.servicesDescriptors.get(serviceName); | ||
serviceDescriptorPromise = _this3._initializeServiceDescriptor(siloContext, serviceName, initializer, { | ||
serviceDescriptorPromise = _this4._initializeServiceDescriptor(siloContext, serviceName, initializer, { | ||
autoloading: autoloading || AUTOLOAD === serviceName, | ||
@@ -755,5 +666,5 @@ injectOnly | ||
_this3._singletonsServicesHandles.set(serviceName, handlesSet); | ||
_this4._singletonsServicesHandles.set(serviceName, handlesSet); | ||
_this3._singletonsServicesDescriptors.set(serviceName, serviceDescriptorPromise); | ||
_this4._singletonsServicesDescriptors.set(serviceName, serviceDescriptorPromise); | ||
} else { | ||
@@ -764,3 +675,3 @@ siloContext.servicesDescriptors.set(serviceName, serviceDescriptorPromise); | ||
if (AUTOLOAD === _this3.serviceName) { | ||
if (AUTOLOAD === _this4.serviceName) { | ||
siloContext.servicesSequence.unshift([AUTOLOAD]); | ||
@@ -777,6 +688,6 @@ } | ||
}) { | ||
var _this4 = this; | ||
var _this5 = this; | ||
return _asyncToGenerator(function* () { | ||
let initializer = _this4._initializers.get(serviceName); | ||
let initializer = _this5._initializers.get(serviceName); | ||
@@ -786,3 +697,3 @@ if (initializer) { | ||
} // The auto loader must only have static dependencies | ||
// and we hace to do this check here to avoid caching | ||
// and we have to do this check here to avoid caching | ||
// non-autoloading request and then be blocked by an | ||
@@ -798,3 +709,3 @@ // autoloader dep that waits for that cached load | ||
let initializerPromise = _this4._initializerResolvers.get(serviceName); | ||
let initializerPromise = _this5._initializerResolvers.get(serviceName); | ||
@@ -806,3 +717,3 @@ if (initializerPromise) { | ||
initializerPromise = _asyncToGenerator(function* () { | ||
if (!_this4._initializers.get(AUTOLOAD)) { | ||
if (!_this5._initializers.get(AUTOLOAD)) { | ||
throw new _yerror.default(E_UNMATCHED_DEPENDENCY, serviceName); | ||
@@ -814,3 +725,3 @@ } | ||
try { | ||
const autoloadingDescriptor = yield _this4._getServiceDescriptor(siloContext, AUTOLOAD, { | ||
const autoloadingDescriptor = yield _this5._getServiceDescriptor(siloContext, AUTOLOAD, { | ||
injectOnly, | ||
@@ -834,9 +745,9 @@ autoloading: true | ||
_this4.register(initializer); | ||
_this5.register(initializer); | ||
_this4._initializerResolvers.delete(serviceName); // Here we need to pick-up the registered initializer to | ||
_this5._initializerResolvers.delete(serviceName); // Here we need to pick-up the registered initializer to | ||
// have a universally usable intitializer | ||
return _this4._initializers.get(serviceName); | ||
return _this5._initializers.get(serviceName); | ||
} catch (err) { | ||
@@ -848,3 +759,3 @@ debug(`Could not load ${serviceName} via the auto loader.`); | ||
_this4._initializerResolvers.set(serviceName, initializerPromise); | ||
_this5._initializerResolvers.set(serviceName, initializerPromise); | ||
@@ -872,3 +783,3 @@ return yield initializerPromise; | ||
}) { | ||
var _this5 = this; | ||
var _this6 = this; | ||
@@ -884,9 +795,9 @@ return _asyncToGenerator(function* () { | ||
// a new one | ||
yield _this5._singletonsServicesShutdownsPromises.get(serviceName) || Promise.resolve(); // Anyway delete any shutdown promise before instanciating | ||
yield _this6._singletonsServicesShutdownsPromises.get(serviceName) || Promise.resolve(); // Anyway delete any shutdown promise before instanciating | ||
// a new service | ||
_this5._singletonsServicesShutdownsPromises.delete(serviceName); | ||
_this6._singletonsServicesShutdownsPromises.delete(serviceName); | ||
siloContext.servicesShutdownsPromises.delete(serviceName); | ||
const servicesHash = yield _this5._initializeDependencies(siloContext, serviceName, initializer[_util.SPECIAL_PROPS.INJECT], { | ||
const servicesHash = yield _this6._initializeDependencies(siloContext, serviceName, initializer[_util.SPECIAL_PROPS.INJECT], { | ||
injectOnly, | ||
@@ -950,3 +861,3 @@ autoloading | ||
}) { | ||
var _this6 = this; | ||
var _this7 = this; | ||
@@ -958,3 +869,3 @@ return _asyncToGenerator(function* () { | ||
function () { | ||
var _ref4 = _asyncToGenerator(function* (serviceDeclaration) { | ||
var _ref6 = _asyncToGenerator(function* (serviceDeclaration) { | ||
const { | ||
@@ -966,3 +877,3 @@ mappedName, | ||
try { | ||
const serviceDescriptor = yield _this6._getServiceDescriptor(siloContext, mappedName, { | ||
const serviceDescriptor = yield _this7._getServiceDescriptor(siloContext, mappedName, { | ||
injectOnly, | ||
@@ -981,4 +892,4 @@ autoloading | ||
return function (_x3) { | ||
return _ref4.apply(this, arguments); | ||
return function (_x4) { | ||
return _ref6.apply(this, arguments); | ||
}; | ||
@@ -991,3 +902,3 @@ }())); | ||
function () { | ||
var _ref5 = _asyncToGenerator(function* (serviceDescriptor) { | ||
var _ref7 = _asyncToGenerator(function* (serviceDescriptor) { | ||
if (!serviceDescriptor) { | ||
@@ -1000,4 +911,4 @@ return {}.undef; | ||
return function (_x4) { | ||
return _ref5.apply(this, arguments); | ||
return function (_x5) { | ||
return _ref7.apply(this, arguments); | ||
}; | ||
@@ -1004,0 +915,0 @@ }())); |
@@ -39,16 +39,8 @@ "use strict"; | ||
}); | ||
describe('getInstance', () => { | ||
it('should return an instance', () => { | ||
(0, _assert.default)(_index.Knifecycle.getInstance()); | ||
}); | ||
it('should always return the same instance', () => { | ||
_assert.default.equal(_index.Knifecycle.getInstance(), _index.Knifecycle.getInstance()); | ||
}); | ||
}); | ||
describe('constant', () => { | ||
it('should register an object', () => { | ||
$.constant('ENV', ENV); | ||
$.register((0, _index.constant)('ENV', ENV)); | ||
}); | ||
it('should register a function', () => { | ||
$.constant('time', time); | ||
$.register((0, _index.constant)('time', time)); | ||
}); | ||
@@ -170,7 +162,7 @@ }); | ||
it('should register provider', () => { | ||
$.provider('hash', hashProvider); | ||
$.register((0, _index.provider)('hash', hashProvider)); | ||
}); | ||
it('should fail with direct circular dependencies', () => { | ||
_assert.default.throws(() => { | ||
$.provider('hash', (0, _index.inject)(['hash'], hashProvider)); | ||
$.register((0, _index.provider)('hash', (0, _index.inject)(['hash'], hashProvider))); | ||
}, err => { | ||
@@ -186,3 +178,3 @@ _assert.default.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY'); | ||
_assert.default.throws(() => { | ||
$.provider('hash', (0, _index.inject)(['hash>lol'], hashProvider)); | ||
$.register((0, _index.provider)('hash', (0, _index.inject)(['hash>lol'], hashProvider))); | ||
}, err => { | ||
@@ -198,6 +190,6 @@ _assert.default.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY'); | ||
_assert.default.throws(() => { | ||
$.provider('hash', (0, _index.inject)(['hash3'], hashProvider)); | ||
$.provider('hash1', (0, _index.inject)(['hash'], hashProvider)); | ||
$.provider('hash2', (0, _index.inject)(['hash1'], hashProvider)); | ||
$.provider('hash3', (0, _index.inject)(['hash'], hashProvider)); | ||
$.register((0, _index.provider)('hash', (0, _index.inject)(['hash3'], hashProvider))); | ||
$.register((0, _index.provider)('hash1', (0, _index.inject)(['hash'], hashProvider))); | ||
$.register((0, _index.provider)('hash2', (0, _index.inject)(['hash1'], hashProvider))); | ||
$.register((0, _index.provider)('hash3', (0, _index.inject)(['hash'], hashProvider))); | ||
}, err => { | ||
@@ -213,6 +205,6 @@ _assert.default.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY'); | ||
_assert.default.throws(() => { | ||
$.provider('hash', (0, _index.inject)(['hash1'], hashProvider)); | ||
$.provider('hash1', (0, _index.inject)(['hash2'], hashProvider)); | ||
$.provider('hash2', (0, _index.inject)(['hash3'], hashProvider)); | ||
$.provider('hash3', (0, _index.inject)(['hash'], hashProvider)); | ||
$.register((0, _index.provider)('hash', (0, _index.inject)(['hash1'], hashProvider))); | ||
$.register((0, _index.provider)('hash1', (0, _index.inject)(['hash2'], hashProvider))); | ||
$.register((0, _index.provider)('hash2', (0, _index.inject)(['hash3'], hashProvider))); | ||
$.register((0, _index.provider)('hash3', (0, _index.inject)(['hash'], hashProvider))); | ||
}, err => { | ||
@@ -228,6 +220,6 @@ _assert.default.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY'); | ||
_assert.default.throws(() => { | ||
$.provider('hash', (0, _index.inject)(['hash3>aHash3'], hashProvider)); | ||
$.provider('hash1', (0, _index.inject)(['hash>aHash'], hashProvider)); | ||
$.provider('hash2', (0, _index.inject)(['hash1>aHash1'], hashProvider)); | ||
$.provider('hash3', (0, _index.inject)(['hash>aHash'], hashProvider)); | ||
$.register((0, _index.provider)('hash', (0, _index.inject)(['hash3>aHash3'], hashProvider))); | ||
$.register((0, _index.provider)('hash1', (0, _index.inject)(['hash>aHash'], hashProvider))); | ||
$.register((0, _index.provider)('hash2', (0, _index.inject)(['hash1>aHash1'], hashProvider))); | ||
$.register((0, _index.provider)('hash3', (0, _index.inject)(['hash>aHash'], hashProvider))); | ||
}, err => { | ||
@@ -234,0 +226,0 @@ _assert.default.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY'); |
@@ -9,2 +9,3 @@ "use strict"; | ||
exports.inject = inject; | ||
exports.alsoInject = alsoInject; | ||
exports.extra = extra; | ||
@@ -33,2 +34,25 @@ exports.options = options; | ||
const debug = (0, _debug.default)('knifecycle'); | ||
/* Architecture Note #1.2: Creating initializers | ||
`knifecycle` uses initializers at its a core. An initializer is basically | ||
an asynchronous function with some annotations: | ||
- name: it uniquely identifies the initializer so that it can be | ||
referred to as another initializer dependency. | ||
- type: an initializer can be of three types at the moment | ||
(constant, service or provider). The initializer annotations | ||
varies accordsing to those types as we'll see later on. | ||
- injected dependencies: an array of dependencies declarations that | ||
declares which initializer htis initializer depends on. Constants | ||
logically cannot have dependencies. | ||
- options: various options like for exemple, if the initializer | ||
implements the singleton pattern or not. | ||
- value: only used for constant, this property allows to know | ||
the value the initializer resolves to without actually executing it. | ||
- extra: an extra property for custom use that will be propagated | ||
by the various other decorators you'll find in this library. | ||
`Knifecycle` provides a set of decorators that allows you to simply | ||
create new initializers. | ||
*/ | ||
const SPECIAL_PROPS_PREFIX = '$'; | ||
@@ -105,13 +129,10 @@ exports.SPECIAL_PROPS_PREFIX = SPECIAL_PROPS_PREFIX; | ||
/** | ||
* Decorator creating a new initializer with some | ||
* dependencies declarations appended to it. | ||
* Decorator creating a new initializer with different | ||
* dependencies declarations set to it. | ||
* @param {String[]} dependenciesDeclarations | ||
* List of dependencies declarations to declare which | ||
* services the initializer needs to resolve its | ||
* own service. | ||
* own service | ||
* @param {Function} initializer | ||
* The initializer to tweak | ||
* @param {Boolean} [merge=false] | ||
* Whether dependencies should be merged with existing | ||
* ones or not | ||
* @return {Function} | ||
@@ -131,3 +152,3 @@ * Returns a new initializer | ||
function inject(dependenciesDeclarations, initializer, merge = false) { | ||
function inject(dependenciesDeclarations, initializer) { | ||
if ('constant' === initializer[SPECIAL_PROPS.TYPE]) { | ||
@@ -138,3 +159,3 @@ throw new _yerror.default(E_BAD_INJECT_IN_CONSTANT, initializer[SPECIAL_PROPS.NAME], dependenciesDeclarations); | ||
const uniqueInitializer = reuseSpecialProps(initializer, initializer, { | ||
[SPECIAL_PROPS.INJECT]: merge ? (initializer[SPECIAL_PROPS.INJECT] || []).concat(dependenciesDeclarations) : dependenciesDeclarations | ||
[SPECIAL_PROPS.INJECT]: dependenciesDeclarations | ||
}); | ||
@@ -146,2 +167,26 @@ debug('Wrapped an initializer with dependencies:', dependenciesDeclarations); | ||
* Decorator creating a new initializer with some | ||
* more dependencies declarations appended to it. | ||
* @param {String[]} dependenciesDeclarations | ||
* List of dependencies declarations to append | ||
* @param {Function} initializer | ||
* The initializer to tweak | ||
* @return {Function} | ||
* Returns a new initializer | ||
* @example | ||
* | ||
* import { alsoInject, getInstance } from 'knifecycle' | ||
* import myServiceInitializer from './service'; | ||
* | ||
* getInstance() | ||
* .service('myService', | ||
* alsoInject(['ENV'], myServiceInitializer) | ||
* ); | ||
*/ | ||
function alsoInject(dependenciesDeclarations, initializer) { | ||
return inject((initializer[SPECIAL_PROPS.INJECT] || []).concat(dependenciesDeclarations), initializer); | ||
} | ||
/** | ||
* Decorator creating a new initializer with some | ||
* extra informations appended to it. It is just | ||
@@ -498,3 +543,3 @@ * a way for user to store some additional | ||
} | ||
/* Architecture Note #1.3.1: Dependencies declaration syntax | ||
/* Architecture Note #1.2.1: Dependencies declaration syntax | ||
@@ -501,0 +546,0 @@ The dependencies syntax is of the following form: |
@@ -142,2 +142,11 @@ "use strict"; | ||
}); | ||
describe('alsoInject', () => { | ||
it('should allow to decorate an initializer with dependencies', () => { | ||
const newInitializer = (0, _util.alsoInject)(['ENV'], (0, _util.inject)(['TEST'], aProvider)); | ||
_assert.default.notEqual(newInitializer, aProvider); | ||
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], ['TEST', 'ENV']); | ||
}); | ||
}); | ||
describe('options', () => { | ||
@@ -144,0 +153,0 @@ it('should allow to decorate an initializer with options', () => { |
{ | ||
"name": "knifecycle", | ||
"version": "2.7.0", | ||
"version": "3.0.0", | ||
"description": "Manage your NodeJS processes's lifecycle.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
244
README.md
@@ -81,13 +81,11 @@ [//]: # ( ) | ||
Let's say we are building a CLI script. He is how we would | ||
Let's say we are building a CLI script. Here is how we would | ||
proceed with Knifecycle: | ||
First, we need to | ||
handle a configuration file so we are creating an | ||
initializer to instanciate our `CONFIG` service: | ||
First, we need to handle a configuration file so we are | ||
creating an initializer to instanciate our `CONFIG` service: | ||
```js | ||
// bin.js | ||
import fs from 'fs'; | ||
import Knifecycle, { initializer, inject, name } from 'knifecycle'; | ||
import Knifecycle, { initializer, constant, inject, name } from 'knifecycle'; | ||
@@ -101,3 +99,3 @@ // First of all we create a new Knifecycle instance | ||
// easily testable | ||
$.constant('ENV', process.env); | ||
$.register(constant('ENV', process.env)); | ||
@@ -107,3 +105,3 @@ // Let's do so for CLI args with another constant | ||
// that would parse args in a complexer way | ||
$.constant('ARGS', process.argv); | ||
$.register(constant('ARGS', process.argv)); | ||
@@ -204,3 +202,3 @@ // We want our CLI tool to rely on some configuration | ||
// DB_URI constant it needs | ||
$.constant('DB_URI', 'posgresql://xxxx').register(initDB); | ||
$.register(constant('DB_URI', 'posgresql://xxxx').register(initDB)); | ||
@@ -210,3 +208,3 @@ // Say we need to use two different DB server | ||
// some of its properties | ||
$.constant('DB_URI2', 'posgresql://yyyy'); | ||
$.register(constant('DB_URI2', 'posgresql://yyyy')); | ||
$.register( | ||
@@ -228,5 +226,5 @@ // First we remap the injected dependencies. It will | ||
// module contains a few useful ones | ||
$.constant('now', Date.now.bind(Date)) | ||
.constant('log', console.log.bind(console)) | ||
.constant('exit', process.exit.bind(process)); | ||
$.register(constant('now', Date.now.bind(Date))) | ||
.register(constant('log', console.log.bind(console))) | ||
.register(constant('exit', process.exit.bind(process))); | ||
@@ -371,6 +369,4 @@ // Finally, let's declare an `$autoload` service | ||
<dl> | ||
<dt><a href="#buildInitializer">buildInitializer(constants, loader, dependencies)</a> ⇒ <code>Promise.<String></code></dt> | ||
<dd><p>Create a JavaScript module that initialize | ||
a set of dependencies with hardcoded | ||
import/awaits.</p> | ||
<dt><a href="#initInitializerBuilder">initInitializerBuilder(services)</a> ⇒ <code>Promise.<function()></code></dt> | ||
<dd><p>Instantiate the initializer builder service</p> | ||
</dd> | ||
@@ -383,5 +379,9 @@ <dt><a href="#reuseSpecialProps">reuseSpecialProps(from, to, [amend])</a> ⇒ <code>function</code></dt> | ||
</dd> | ||
<dt><a href="#inject">inject(dependenciesDeclarations, initializer, [merge])</a> ⇒ <code>function</code></dt> | ||
<dt><a href="#inject">inject(dependenciesDeclarations, initializer)</a> ⇒ <code>function</code></dt> | ||
<dd><p>Decorator creating a new initializer with different | ||
dependencies declarations set to it.</p> | ||
</dd> | ||
<dt><a href="#alsoInject">alsoInject(dependenciesDeclarations, initializer)</a> ⇒ <code>function</code></dt> | ||
<dd><p>Decorator creating a new initializer with some | ||
dependencies declarations appended to it.</p> | ||
more dependencies declarations appended to it.</p> | ||
</dd> | ||
@@ -431,14 +431,8 @@ <dt><a href="#extra">extra(extraInformations, initializer, [merge])</a> ⇒ <code>function</code></dt> | ||
* [new Knifecycle()](#new_Knifecycle_new) | ||
* _instance_ | ||
* ~~[.constant(constantName, constantValue)](#Knifecycle+constant) ⇒ [<code>Knifecycle</code>](#Knifecycle)~~ | ||
* ~~[.service(serviceName, serviceBuilder, options)](#Knifecycle+service) ⇒ [<code>Knifecycle</code>](#Knifecycle)~~ | ||
* ~~[.provider(serviceName, initializer, options)](#Knifecycle+provider) ⇒ [<code>Knifecycle</code>](#Knifecycle)~~ | ||
* [.register(initializer)](#Knifecycle+register) ⇒ [<code>Knifecycle</code>](#Knifecycle) | ||
* [.toMermaidGraph(options)](#Knifecycle+toMermaidGraph) ⇒ <code>String</code> | ||
* [.run(dependenciesDeclarations)](#Knifecycle+run) ⇒ <code>Promise</code> | ||
* [._getServiceDescriptor(siloContext, serviceName, options, serviceProvider)](#Knifecycle+_getServiceDescriptor) ⇒ <code>Promise</code> | ||
* [._initializeServiceDescriptor(siloContext, serviceName, options)](#Knifecycle+_initializeServiceDescriptor) ⇒ <code>Promise</code> | ||
* [._initializeDependencies(siloContext, serviceName, servicesDeclarations, options)](#Knifecycle+_initializeDependencies) ⇒ <code>Promise</code> | ||
* _static_ | ||
* ~~[.getInstance()](#Knifecycle.getInstance) ⇒ [<code>Knifecycle</code>](#Knifecycle)~~ | ||
* [.register(initializer)](#Knifecycle+register) ⇒ [<code>Knifecycle</code>](#Knifecycle) | ||
* [.toMermaidGraph(options)](#Knifecycle+toMermaidGraph) ⇒ <code>String</code> | ||
* [.run(dependenciesDeclarations)](#Knifecycle+run) ⇒ <code>Promise</code> | ||
* [._getServiceDescriptor(siloContext, serviceName, options, serviceProvider)](#Knifecycle+_getServiceDescriptor) ⇒ <code>Promise</code> | ||
* [._initializeServiceDescriptor(siloContext, serviceName, options)](#Knifecycle+_initializeServiceDescriptor) ⇒ <code>Promise</code> | ||
* [._initializeDependencies(siloContext, serviceName, servicesDeclarations, options)](#Knifecycle+_initializeDependencies) ⇒ <code>Promise</code> | ||
@@ -457,111 +451,2 @@ <a name="new_Knifecycle_new"></a> | ||
``` | ||
<a name="Knifecycle+constant"></a> | ||
### ~~knifecycle.constant(constantName, constantValue) ⇒ [<code>Knifecycle</code>](#Knifecycle)~~ | ||
***Deprecated*** | ||
Register a constant initializer | ||
**Kind**: instance method of [<code>Knifecycle</code>](#Knifecycle) | ||
**Returns**: [<code>Knifecycle</code>](#Knifecycle) - The Knifecycle instance (for chaining) | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| constantName | <code>String</code> | The name of the service | | ||
| constantValue | <code>any</code> | The constant value | | ||
**Example** | ||
```js | ||
import Knifecycle from 'knifecycle' | ||
const $ = new Knifecycle(); | ||
// Expose the process env | ||
$.constant('ENV', process.env); | ||
// Expose a time() function | ||
$.constant('time', Date.now.bind(Date)); | ||
``` | ||
<a name="Knifecycle+service"></a> | ||
### ~~knifecycle.service(serviceName, serviceBuilder, options) ⇒ [<code>Knifecycle</code>](#Knifecycle)~~ | ||
***Deprecated*** | ||
Register a service initializer | ||
**Kind**: instance method of [<code>Knifecycle</code>](#Knifecycle) | ||
**Returns**: [<code>Knifecycle</code>](#Knifecycle) - The Knifecycle instance (for chaining) | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| serviceName | <code>String</code> | Service name | | ||
| serviceBuilder | <code>function</code> | An asynchronous function returning the actual service | | ||
| options | <code>Object</code> | Options attached to the initializer | | ||
**Example** | ||
```js | ||
import Knifecycle from 'knifecycle' | ||
import fs from 'fs'; | ||
const $ = new Knifecycle(); | ||
$.service('config', configServiceInitializer, { | ||
singleton: true, | ||
}); | ||
function configServiceInitializer({ CONFIG_PATH }) { | ||
return new Promise((resolve, reject) { | ||
fs.readFile(CONFIG_PATH, function(err, data) { | ||
if(err) { | ||
return reject(err); | ||
} | ||
try { | ||
resolve(JSON.parse(data)); | ||
} catch (err) { | ||
reject(err); | ||
} | ||
}, 'utf-8'); | ||
} | ||
``` | ||
<a name="Knifecycle+provider"></a> | ||
### ~~knifecycle.provider(serviceName, initializer, options) ⇒ [<code>Knifecycle</code>](#Knifecycle)~~ | ||
***Deprecated*** | ||
Register a provider initializer | ||
**Kind**: instance method of [<code>Knifecycle</code>](#Knifecycle) | ||
**Returns**: [<code>Knifecycle</code>](#Knifecycle) - The Knifecycle instance (for chaining) | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| serviceName | <code>String</code> | Service name resolved by the provider | | ||
| initializer | <code>function</code> | An initializer returning the service promise | | ||
| options | <code>Object</code> | Options attached to the initializer | | ||
**Example** | ||
```js | ||
import Knifecycle from 'knifecycle' | ||
import fs from 'fs'; | ||
const $ = new Knifecycle(); | ||
$.register(provider('config', function configProvider() { | ||
return new Promise((resolve, reject) { | ||
fs.readFile('config.js', function(err, data) { | ||
let config; | ||
if(err) { | ||
return reject(err); | ||
} | ||
try { | ||
config = JSON.parse(data.toString); | ||
} catch (err) { | ||
return reject(err); | ||
} | ||
resolve({ | ||
service: config, | ||
}); | ||
}); | ||
}); | ||
})); | ||
``` | ||
<a name="Knifecycle+register"></a> | ||
@@ -686,20 +571,26 @@ | ||
<a name="Knifecycle.getInstance"></a> | ||
<a name="initInitializerBuilder"></a> | ||
### ~~Knifecycle.getInstance() ⇒ [<code>Knifecycle</code>](#Knifecycle)~~ | ||
***Deprecated*** | ||
## initInitializerBuilder(services) ⇒ <code>Promise.<function()></code> | ||
Instantiate the initializer builder service | ||
Returns a Knifecycle instance (always the same) | ||
**Kind**: global function | ||
**Returns**: <code>Promise.<function()></code> - A promise of the buildInitializer function | ||
**Kind**: static method of [<code>Knifecycle</code>](#Knifecycle) | ||
**Returns**: [<code>Knifecycle</code>](#Knifecycle) - The created/saved instance | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| services | <code>Object</code> | The services to inject | | ||
| services.$autoload | <code>Object</code> | The dependencies autoloader | | ||
**Example** | ||
```js | ||
import { getInstance } from 'knifecycle' | ||
import initInitializerBuilder from 'knifecycle/dist/build'; | ||
const $ = getInstance(); | ||
const buildInitializer = await initInitializerBuilder({ | ||
$autoload: async () => {}, | ||
}); | ||
``` | ||
<a name="buildInitializer"></a> | ||
<a name="initInitializerBuilder..buildInitializer"></a> | ||
## buildInitializer(constants, loader, dependencies) ⇒ <code>Promise.<String></code> | ||
### initInitializerBuilder~buildInitializer(dependencies) ⇒ <code>Promise.<String></code> | ||
Create a JavaScript module that initialize | ||
@@ -709,3 +600,3 @@ a set of dependencies with hardcoded | ||
**Kind**: global function | ||
**Kind**: inner method of [<code>initInitializerBuilder</code>](#initInitializerBuilder) | ||
**Returns**: <code>Promise.<String></code> - The JavaScript module content | ||
@@ -715,4 +606,2 @@ | ||
| --- | --- | --- | | ||
| constants | <code>Object</code> | An hash for simple constants | | ||
| loader | <code>function</code> | The dependency auto-loader | | ||
| dependencies | <code>Array.<String></code> | The main dependencies | | ||
@@ -722,5 +611,9 @@ | ||
```js | ||
import buildInitializer from 'knifecycle/src/build'; | ||
import initInitializerBuilder from 'knifecycle/dist/build'; | ||
buildInitializer(constants, loader, ['entryPoint']); | ||
const buildInitializer = await initInitializerBuilder({ | ||
$autoload: async () => {}, | ||
}); | ||
const content = await buildInitializer(['entryPoint']); | ||
``` | ||
@@ -756,5 +649,5 @@ <a name="reuseSpecialProps"></a> | ||
## inject(dependenciesDeclarations, initializer, [merge]) ⇒ <code>function</code> | ||
Decorator creating a new initializer with some | ||
dependencies declarations appended to it. | ||
## inject(dependenciesDeclarations, initializer) ⇒ <code>function</code> | ||
Decorator creating a new initializer with different | ||
dependencies declarations set to it. | ||
@@ -764,7 +657,6 @@ **Kind**: global function | ||
| Param | Type | Default | Description | | ||
| --- | --- | --- | --- | | ||
| dependenciesDeclarations | <code>Array.<String></code> | | List of dependencies declarations to declare which services the initializer needs to resolve its own service. | | ||
| initializer | <code>function</code> | | The initializer to tweak | | ||
| [merge] | <code>Boolean</code> | <code>false</code> | Whether dependencies should be merged with existing ones or not | | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| dependenciesDeclarations | <code>Array.<String></code> | List of dependencies declarations to declare which services the initializer needs to resolve its own service | | ||
| initializer | <code>function</code> | The initializer to tweak | | ||
@@ -781,2 +673,26 @@ **Example** | ||
``` | ||
<a name="alsoInject"></a> | ||
## alsoInject(dependenciesDeclarations, initializer) ⇒ <code>function</code> | ||
Decorator creating a new initializer with some | ||
more dependencies declarations appended to it. | ||
**Kind**: global function | ||
**Returns**: <code>function</code> - Returns a new initializer | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| dependenciesDeclarations | <code>Array.<String></code> | List of dependencies declarations to append | | ||
| initializer | <code>function</code> | The initializer to tweak | | ||
**Example** | ||
```js | ||
import { alsoInject, getInstance } from 'knifecycle' | ||
import myServiceInitializer from './service'; | ||
getInstance() | ||
.service('myService', | ||
alsoInject(['ENV'], myServiceInitializer) | ||
); | ||
``` | ||
<a name="extra"></a> | ||
@@ -783,0 +699,0 @@ |
187
src/build.js
@@ -1,2 +0,2 @@ | ||
import { SPECIAL_PROPS, parseDependencyDeclaration } from './util'; | ||
import { SPECIAL_PROPS, parseDependencyDeclaration, initializer } from './util'; | ||
import { buildInitializationSequence } from './sequence'; | ||
@@ -21,78 +21,113 @@ | ||
module.exports = buildInitializer; | ||
export default initializer( | ||
{ | ||
name: 'buildInitializer', | ||
type: 'service', | ||
inject: ['$autoload'], | ||
}, | ||
initInitializerBuilder, | ||
); | ||
/** | ||
* Create a JavaScript module that initialize | ||
* a set of dependencies with hardcoded | ||
* import/awaits. | ||
* @param {Object} constants | ||
* An hash for simple constants | ||
* @param {Function} loader | ||
* The dependency auto-loader | ||
* @param {String[]} dependencies | ||
* The main dependencies | ||
* @return {Promise<String>} | ||
* The JavaScript module content | ||
* Instantiate the initializer builder service | ||
* @param {Object} services | ||
* The services to inject | ||
* @param {Object} services.$autoload | ||
* The dependencies autoloader | ||
* @return {Promise<Function>} | ||
* A promise of the buildInitializer function | ||
* @example | ||
* import buildInitializer from 'knifecycle/src/build'; | ||
* import initInitializerBuilder from 'knifecycle/dist/build'; | ||
* | ||
* buildInitializer(constants, loader, ['entryPoint']); | ||
* const buildInitializer = await initInitializerBuilder({ | ||
* $autoload: async () => {}, | ||
* }); | ||
*/ | ||
async function buildInitializer(constants, loader, dependencies) { | ||
const dependencyTrees = await Promise.all( | ||
dependencies.map(dependency => | ||
buildDependencyTree(constants, loader, dependency), | ||
), | ||
); | ||
const dependenciesHash = buildDependenciesHash( | ||
dependencyTrees.filter(identity), | ||
); | ||
const batches = buildInitializationSequence({ | ||
__name: 'main', | ||
__childNodes: dependencyTrees.filter(identity), | ||
}); | ||
batches.pop(); | ||
async function initInitializerBuilder({ $autoload }) { | ||
return buildInitializer; | ||
return `${batches | ||
.map( | ||
(batch, index) => ` | ||
/** | ||
* Create a JavaScript module that initialize | ||
* a set of dependencies with hardcoded | ||
* import/awaits. | ||
* @param {String[]} dependencies | ||
* The main dependencies | ||
* @return {Promise<String>} | ||
* The JavaScript module content | ||
* @example | ||
* import initInitializerBuilder from 'knifecycle/dist/build'; | ||
* | ||
* const buildInitializer = await initInitializerBuilder({ | ||
* $autoload: async () => {}, | ||
* }); | ||
* | ||
* const content = await buildInitializer(['entryPoint']); | ||
*/ | ||
async function buildInitializer(dependencies) { | ||
const dependencyTrees = await Promise.all( | ||
dependencies.map(dependency => | ||
buildDependencyTree({ $autoload }, dependency), | ||
), | ||
); | ||
const dependenciesHash = buildDependenciesHash( | ||
dependencyTrees.filter(identity), | ||
); | ||
const batches = buildInitializationSequence({ | ||
__name: 'main', | ||
__childNodes: dependencyTrees.filter(identity), | ||
}); | ||
batches.pop(); | ||
return `${batches | ||
.map( | ||
(batch, index) => ` | ||
// Definition batch #${index}${batch | ||
.map(name => { | ||
if (!dependenciesHash[name].__initializer) { | ||
.map(name => { | ||
if ( | ||
'constant' === | ||
dependenciesHash[name].__initializer[SPECIAL_PROPS.TYPE] | ||
) { | ||
return ` | ||
const ${name} = ${JSON.stringify( | ||
dependenciesHash[name].__initializer[SPECIAL_PROPS.VALUE], | ||
null, | ||
2, | ||
)};`; | ||
} | ||
return ` | ||
const ${name} = ${JSON.stringify(constants[name], null, 2)};`; | ||
} | ||
return ` | ||
import ${dependenciesHash[name].__initializerName} from '${ | ||
dependenciesHash[name].__path | ||
}';`; | ||
}) | ||
.join('')}`, | ||
) | ||
.join('\n')} | ||
dependenciesHash[name].__path | ||
}';`; | ||
}) | ||
.join('')}`, | ||
) | ||
.join('\n')} | ||
export async function initialize(services = {}) {${batches | ||
.map( | ||
(batch, index) => ` | ||
.map( | ||
(batch, index) => ` | ||
// Initialization batch #${index} | ||
const batch${index} = {${batch | ||
.map(name => { | ||
if (!dependenciesHash[name].__initializer) { | ||
.map(name => { | ||
if ( | ||
'constant' === | ||
dependenciesHash[name].__initializer[SPECIAL_PROPS.TYPE] | ||
) { | ||
return ` | ||
${name}: Promise.resolve(${name}),`; | ||
} | ||
return ` | ||
${name}: Promise.resolve(${name}),`; | ||
} | ||
return ` | ||
${name}: ${dependenciesHash[name].__initializerName}({${ | ||
dependenciesHash[name].__inject | ||
? `${dependenciesHash[name].__inject | ||
.map(parseDependencyDeclaration) | ||
.map( | ||
({ serviceName, mappedName }) => | ||
` | ||
dependenciesHash[name].__inject | ||
? `${dependenciesHash[name].__inject | ||
.map(parseDependencyDeclaration) | ||
.map( | ||
({ serviceName, mappedName }) => | ||
` | ||
${serviceName}: services['${mappedName}'],`, | ||
) | ||
.join('')}` | ||
: '' | ||
} | ||
) | ||
.join('')}` | ||
: '' | ||
} | ||
})${ | ||
@@ -103,4 +138,4 @@ 'provider' === dependenciesHash[name].__type | ||
},`; | ||
}) | ||
.join('')} | ||
}) | ||
.join('')} | ||
}; | ||
@@ -113,10 +148,10 @@ | ||
${batch | ||
.map(name => { | ||
return ` | ||
.map(name => { | ||
return ` | ||
services['${name}'] = await batch${index}['${name}'];`; | ||
}) | ||
.join('')} | ||
}) | ||
.join('')} | ||
`, | ||
) | ||
.join('')} | ||
) | ||
.join('')} | ||
return {${dependencies | ||
@@ -133,5 +168,6 @@ .map(parseDependencyDeclaration) | ||
`; | ||
} | ||
} | ||
async function buildDependencyTree(constants, loader, dependencyDeclaration) { | ||
async function buildDependencyTree({ $autoload }, dependencyDeclaration) { | ||
const { mappedName, optional } = parseDependencyDeclaration( | ||
@@ -141,11 +177,4 @@ dependencyDeclaration, | ||
if (constants[mappedName]) { | ||
return Promise.resolve({ | ||
__name: mappedName, | ||
__constant: constants[mappedName], | ||
}); | ||
} | ||
try { | ||
const { path, initializer } = await loader(mappedName); | ||
const { path, initializer } = await $autoload(mappedName); | ||
const node = { | ||
@@ -173,3 +202,3 @@ __name: mappedName, | ||
initializer[SPECIAL_PROPS.INJECT].map(childDependencyDeclaration => | ||
buildDependencyTree(constants, loader, childDependencyDeclaration), | ||
buildDependencyTree({ $autoload }, childDependencyDeclaration), | ||
), | ||
@@ -176,0 +205,0 @@ ); |
import assert from 'assert'; | ||
import YError from 'yerror'; | ||
import buildInitializer from './build'; | ||
import { initializer } from './util'; | ||
import initInitializerBuilder from './build'; | ||
import Knifecycle, { initializer, constant } from '.'; | ||
describe('buildInitializer', () => { | ||
function aProvider() {} | ||
const mockedConstants = { | ||
NODE_ENV: 'development', | ||
}; | ||
async function aProvider() {} | ||
const mockedDepsHash = { | ||
NODE_ENV: constant('NODE_ENV', 'development'), | ||
dep1: initializer( | ||
@@ -40,16 +38,33 @@ { | ||
}; | ||
function mockedLoader(name) { | ||
return mockedDepsHash[name] | ||
? Promise.resolve({ | ||
path: `./services/${name}`, | ||
initializer: mockedDepsHash[name], | ||
}) | ||
: Promise.reject(new YError('E_UNMATCHED_DEPENDENCY', name)); | ||
} | ||
const initAutoloader = initializer( | ||
{ | ||
name: '$autoload', | ||
type: 'service', | ||
inject: [], | ||
options: { | ||
singleton: true, | ||
}, | ||
}, | ||
async () => { | ||
return async function $autoload(name) { | ||
return mockedDepsHash[name] | ||
? Promise.resolve({ | ||
path: `./services/${name}`, | ||
initializer: mockedDepsHash[name], | ||
}) | ||
: Promise.reject(new YError('E_UNMATCHED_DEPENDENCY', name)); | ||
}; | ||
}, | ||
); | ||
it('should build an initialization module', async () => { | ||
const content = await buildInitializer(mockedConstants, mockedLoader, [ | ||
'dep1', | ||
'finalMappedDep>dep3', | ||
]); | ||
const $ = new Knifecycle(); | ||
$.register(constant('PWD', '~/my-project')); | ||
$.register(initAutoloader); | ||
$.register(initInitializerBuilder); | ||
const { buildInitializer } = await $.run(['buildInitializer']); | ||
const content = await buildInitializer(['dep1', 'finalMappedDep>dep3']); | ||
assert.equal( | ||
@@ -56,0 +71,0 @@ content, |
239
src/index.js
@@ -9,2 +9,3 @@ /* eslint max-len: ["warn", { "ignoreComments": true }] */ | ||
inject, | ||
alsoInject, | ||
type, | ||
@@ -31,2 +32,3 @@ options, | ||
const INJECTOR = '$injector'; | ||
const INSTANCE = '$instance'; | ||
const SILO_CONTEXT = '$siloContext'; | ||
@@ -55,5 +57,2 @@ const FATAL_ERROR = '$fatalError'; | ||
// Constants that should use Symbol whenever possible | ||
const INSTANCE = '__instance'; | ||
/* Architecture Note #1: Knifecycle | ||
@@ -63,5 +62,6 @@ | ||
injection](https://en.wikipedia.org/wiki/Dependency_injection) | ||
and [inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control) | ||
with [inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control) | ||
tool. It will always be tied to this goal since I prefer | ||
composing software instead of using frameworks. | ||
composing software instead of using frameworks and DI/IC is | ||
a major part to design strong software in my opinion. | ||
@@ -73,3 +73,4 @@ It is designed to have a low footprint on services code. | ||
can be reused elsewhere (even when not using DI) with no changes | ||
at all. | ||
at all since they are just simple functions with annotations | ||
set as a property. | ||
*/ | ||
@@ -105,6 +106,11 @@ | ||
this._singletonsServicesShutdownsPromises = new Map(); | ||
this.provider( | ||
INJECTOR, | ||
inject([SILO_CONTEXT], ({ $siloContext }) => | ||
Promise.resolve({ | ||
this.register(constant(INSTANCE, this)); | ||
this.register( | ||
initializer( | ||
{ | ||
name: INJECTOR, | ||
type: 'provider', | ||
inject: [SILO_CONTEXT], | ||
}, | ||
async ({ $siloContext }) => ({ | ||
service: dependenciesDeclarations => | ||
@@ -120,5 +126,13 @@ this._initializeDependencies( | ||
); | ||
this.provider(DESTROY, () => | ||
Promise.resolve( | ||
this.register( | ||
initializer( | ||
{ | ||
name: DESTROY, | ||
type: 'provider', | ||
inject: [], | ||
options: { | ||
singleton: true, | ||
}, | ||
}, | ||
async () => ({ | ||
service: () => { | ||
@@ -140,6 +154,3 @@ this.shutdownPromise = | ||
}, | ||
}, | ||
{ | ||
singleton: true, | ||
}, | ||
}), | ||
), | ||
@@ -149,35 +160,14 @@ ); | ||
/** | ||
* Returns a Knifecycle instance (always the same) | ||
* @return {Knifecycle} | ||
* The created/saved instance | ||
* @deprecated | ||
* Simply use `new Knifecycle()` and eventually recreate | ||
* this singleton by your side. | ||
* @example | ||
* | ||
* import { getInstance } from 'knifecycle' | ||
* | ||
* const $ = getInstance(); | ||
*/ | ||
static getInstance() { | ||
Knifecycle[INSTANCE] = Knifecycle[INSTANCE] || new Knifecycle(); | ||
debug('Spawning an instance.'); | ||
return Knifecycle[INSTANCE]; | ||
} | ||
/* Architecture Note #1.3: Registering initializers | ||
/* Architecture Note #1.3: Declaring services | ||
The first step to use `knifecycle` is to create a new | ||
`Knifecycle` instance and register the previously | ||
created initializers. | ||
The first step to use `knifecycle` is to declare | ||
services. There are two way of declaring services: | ||
- constants: a constant is a simple value that will | ||
never change. It can be literal values, objects | ||
or even functions. | ||
- initializers: they are asynchronous functions | ||
that handle the initialization phase. | ||
Initializers can be of two types: | ||
Initializers can be of three types: | ||
- constants: a `constant` initializer resolves to | ||
a constant value. | ||
- services: a `service` initializer directly | ||
resolve to the actual service it builds. It can | ||
be objects, functions or literal values. | ||
be objects, functions or litteral values. | ||
- providers: they instead resolve to an object that | ||
@@ -197,116 +187,2 @@ contains the service built into the `service` property | ||
/** | ||
* Register a constant initializer | ||
* @param {String} constantName | ||
* The name of the service | ||
* @param {any} constantValue | ||
* The constant value | ||
* @return {Knifecycle} | ||
* The Knifecycle instance (for chaining) | ||
* @deprecated | ||
* Use the `constant` decorator with the `Knifecycle.register` method | ||
* @example | ||
* | ||
* import Knifecycle from 'knifecycle' | ||
* | ||
* const $ = new Knifecycle(); | ||
* | ||
* // Expose the process env | ||
* $.constant('ENV', process.env); | ||
* // Expose a time() function | ||
* $.constant('time', Date.now.bind(Date)); | ||
*/ | ||
constant(constantName, constantValue) { | ||
return this.register(constant(constantName, constantValue)); | ||
} | ||
/** | ||
* Register a service initializer | ||
* @param {String} serviceName | ||
* Service name | ||
* @param {Function} serviceBuilder | ||
* An asynchronous function returning the actual service | ||
* @param {Object} options | ||
* Options attached to the initializer | ||
* @return {Knifecycle} | ||
* The Knifecycle instance (for chaining) | ||
* @deprecated | ||
* Use the `service` decorator with the `Knifecycle.register` method | ||
* @example | ||
* | ||
* import Knifecycle from 'knifecycle' | ||
* import fs from 'fs'; | ||
* | ||
* const $ = new Knifecycle(); | ||
* | ||
* $.service('config', configServiceInitializer, { | ||
* singleton: true, | ||
* }); | ||
* | ||
* function configServiceInitializer({ CONFIG_PATH }) { | ||
* return new Promise((resolve, reject) { | ||
* fs.readFile(CONFIG_PATH, function(err, data) { | ||
* if(err) { | ||
* return reject(err); | ||
* } | ||
* try { | ||
* resolve(JSON.parse(data)); | ||
* } catch (err) { | ||
* reject(err); | ||
* } | ||
* }, 'utf-8'); | ||
* } | ||
*/ | ||
service(serviceName, serviceBuilder, options) { | ||
return this.register(service(serviceName, serviceBuilder, options)); | ||
} | ||
/** | ||
* Register a provider initializer | ||
* @param {String} serviceName | ||
* Service name resolved by the provider | ||
* @param {Function} initializer | ||
* An initializer returning the service promise | ||
* @param {Object} options | ||
* Options attached to the initializer | ||
* @return {Knifecycle} | ||
* The Knifecycle instance (for chaining) | ||
* @deprecated | ||
* Use the `initializer` decorator with the `Knifecycle.register` method | ||
* @example | ||
* | ||
* import Knifecycle from 'knifecycle' | ||
* import fs from 'fs'; | ||
* | ||
* const $ = new Knifecycle(); | ||
* | ||
* $.register(provider('config', function configProvider() { | ||
* return new Promise((resolve, reject) { | ||
* fs.readFile('config.js', function(err, data) { | ||
* let config; | ||
* if(err) { | ||
* return reject(err); | ||
* } | ||
* try { | ||
* config = JSON.parse(data.toString); | ||
* } catch (err) { | ||
* return reject(err); | ||
* } | ||
* resolve({ | ||
* service: config, | ||
* }); | ||
* }); | ||
* }); | ||
* })); | ||
*/ | ||
provider(serviceName, initializer, options = {}) { | ||
this.register( | ||
reuseSpecialProps(initializer, initializer, { | ||
[SPECIAL_PROPS.NAME]: serviceName, | ||
[SPECIAL_PROPS.OPTIONS]: options, | ||
}), | ||
); | ||
return this; | ||
} | ||
/** | ||
* Register an initializer | ||
@@ -364,9 +240,25 @@ * @param {Function} initializer | ||
// Temporary cast service/constant initializers into | ||
// Constants are singletons and constant so we can set it | ||
// to singleton services descriptors map directly | ||
if ('constant' === initializer[SPECIAL_PROPS.TYPE]) { | ||
const handlesSet = new Set(); | ||
this._singletonsServicesHandles.set( | ||
initializer[SPECIAL_PROPS.NAME], | ||
handlesSet, | ||
); | ||
this._singletonsServicesDescriptors.set( | ||
initializer[SPECIAL_PROPS.NAME], | ||
Promise.resolve({ | ||
// We do not directly use initializer[SPECIAL_PROPS.VALUE] here | ||
// since it looks like there is a bug with Babel build that | ||
// change functions to empty litteral objects | ||
service: initializer(), | ||
}), | ||
); | ||
} | ||
// Temporary cast service initializers into | ||
// providers. Best would be to threat each differently | ||
// at dependencies initialization level to boost performances | ||
if ( | ||
'service' === initializer[SPECIAL_PROPS.TYPE] || | ||
'constant' === initializer[SPECIAL_PROPS.TYPE] | ||
) { | ||
if ('service' === initializer[SPECIAL_PROPS.TYPE]) { | ||
initializer = reuseSpecialProps( | ||
@@ -377,3 +269,3 @@ initializer, | ||
initializer[SPECIAL_PROPS.TYPE] = 'provider'; | ||
initializer[SPECIAL_PROPS.VALUE] = {}.undef; | ||
//initializer[SPECIAL_PROPS.VALUE] = {}.undef; | ||
} | ||
@@ -396,4 +288,10 @@ | ||
debug( | ||
`${ | ||
this._initializers.has(initializer[SPECIAL_PROPS.NAME]) | ||
? 'Overridden' | ||
: 'Registered' | ||
} an initializer: ${initializer[SPECIAL_PROPS.NAME]}`, | ||
); | ||
this._initializers.set(initializer[SPECIAL_PROPS.NAME], initializer); | ||
debug('Registered a new initializer:', initializer[SPECIAL_PROPS.NAME]); | ||
return this; | ||
@@ -512,4 +410,4 @@ } | ||
/* Architecture Note #1.4: Execution silos | ||
Once all the services are declared, we need a way to bring | ||
them to life. Execution silos are where the magic happen. | ||
Once every initializers are registered, we need a way to bring | ||
them to life. Execution silos are where the magic happens. | ||
For each call of the `run` method with given dependencies, | ||
@@ -519,3 +417,3 @@ a new silo is created and the required environment to | ||
Depending of your application design, you could run it | ||
Depending on your application design, you could run it | ||
in only one execution silo or into several ones | ||
@@ -777,3 +675,3 @@ according to the isolation level your wish to reach. | ||
// The auto loader must only have static dependencies | ||
// and we hace to do this check here to avoid caching | ||
// and we have to do this check here to avoid caching | ||
// non-autoloading request and then be blocked by an | ||
@@ -1008,2 +906,3 @@ // autoloader dep that waits for that cached load | ||
inject, | ||
alsoInject, | ||
options, | ||
@@ -1010,0 +909,0 @@ extra, |
@@ -40,19 +40,9 @@ /* eslint max-nested-callbacks:0 */ | ||
describe('getInstance', () => { | ||
it('should return an instance', () => { | ||
assert(Knifecycle.getInstance()); | ||
}); | ||
it('should always return the same instance', () => { | ||
assert.equal(Knifecycle.getInstance(), Knifecycle.getInstance()); | ||
}); | ||
}); | ||
describe('constant', () => { | ||
it('should register an object', () => { | ||
$.constant('ENV', ENV); | ||
$.register(constant('ENV', ENV)); | ||
}); | ||
it('should register a function', () => { | ||
$.constant('time', time); | ||
$.register(constant('time', time)); | ||
}); | ||
@@ -191,3 +181,3 @@ }); | ||
it('should register provider', () => { | ||
$.provider('hash', hashProvider); | ||
$.register(provider('hash', hashProvider)); | ||
}); | ||
@@ -198,3 +188,3 @@ | ||
() => { | ||
$.provider('hash', inject(['hash'], hashProvider)); | ||
$.register(provider('hash', inject(['hash'], hashProvider))); | ||
}, | ||
@@ -212,3 +202,3 @@ err => { | ||
() => { | ||
$.provider('hash', inject(['hash>lol'], hashProvider)); | ||
$.register(provider('hash', inject(['hash>lol'], hashProvider))); | ||
}, | ||
@@ -226,6 +216,6 @@ err => { | ||
() => { | ||
$.provider('hash', inject(['hash3'], hashProvider)); | ||
$.provider('hash1', inject(['hash'], hashProvider)); | ||
$.provider('hash2', inject(['hash1'], hashProvider)); | ||
$.provider('hash3', inject(['hash'], hashProvider)); | ||
$.register(provider('hash', inject(['hash3'], hashProvider))); | ||
$.register(provider('hash1', inject(['hash'], hashProvider))); | ||
$.register(provider('hash2', inject(['hash1'], hashProvider))); | ||
$.register(provider('hash3', inject(['hash'], hashProvider))); | ||
}, | ||
@@ -243,6 +233,6 @@ err => { | ||
() => { | ||
$.provider('hash', inject(['hash1'], hashProvider)); | ||
$.provider('hash1', inject(['hash2'], hashProvider)); | ||
$.provider('hash2', inject(['hash3'], hashProvider)); | ||
$.provider('hash3', inject(['hash'], hashProvider)); | ||
$.register(provider('hash', inject(['hash1'], hashProvider))); | ||
$.register(provider('hash1', inject(['hash2'], hashProvider))); | ||
$.register(provider('hash2', inject(['hash3'], hashProvider))); | ||
$.register(provider('hash3', inject(['hash'], hashProvider))); | ||
}, | ||
@@ -266,6 +256,6 @@ err => { | ||
() => { | ||
$.provider('hash', inject(['hash3>aHash3'], hashProvider)); | ||
$.provider('hash1', inject(['hash>aHash'], hashProvider)); | ||
$.provider('hash2', inject(['hash1>aHash1'], hashProvider)); | ||
$.provider('hash3', inject(['hash>aHash'], hashProvider)); | ||
$.register(provider('hash', inject(['hash3>aHash3'], hashProvider))); | ||
$.register(provider('hash1', inject(['hash>aHash'], hashProvider))); | ||
$.register(provider('hash2', inject(['hash1>aHash1'], hashProvider))); | ||
$.register(provider('hash3', inject(['hash>aHash'], hashProvider))); | ||
}, | ||
@@ -272,0 +262,0 @@ err => { |
@@ -6,2 +6,24 @@ import YError from 'yerror'; | ||
/* Architecture Note #1.2: Creating initializers | ||
`knifecycle` uses initializers at its a core. An initializer is basically | ||
an asynchronous function with some annotations: | ||
- name: it uniquely identifies the initializer so that it can be | ||
referred to as another initializer dependency. | ||
- type: an initializer can be of three types at the moment | ||
(constant, service or provider). The initializer annotations | ||
varies accordsing to those types as we'll see later on. | ||
- injected dependencies: an array of dependencies declarations that | ||
declares which initializer htis initializer depends on. Constants | ||
logically cannot have dependencies. | ||
- options: various options like for exemple, if the initializer | ||
implements the singleton pattern or not. | ||
- value: only used for constant, this property allows to know | ||
the value the initializer resolves to without actually executing it. | ||
- extra: an extra property for custom use that will be propagated | ||
by the various other decorators you'll find in this library. | ||
`Knifecycle` provides a set of decorators that allows you to simply | ||
create new initializers. | ||
*/ | ||
export const SPECIAL_PROPS_PREFIX = '$'; | ||
@@ -69,13 +91,10 @@ export const SPECIAL_PROPS = { | ||
/** | ||
* Decorator creating a new initializer with some | ||
* dependencies declarations appended to it. | ||
* Decorator creating a new initializer with different | ||
* dependencies declarations set to it. | ||
* @param {String[]} dependenciesDeclarations | ||
* List of dependencies declarations to declare which | ||
* services the initializer needs to resolve its | ||
* own service. | ||
* own service | ||
* @param {Function} initializer | ||
* The initializer to tweak | ||
* @param {Boolean} [merge=false] | ||
* Whether dependencies should be merged with existing | ||
* ones or not | ||
* @return {Function} | ||
@@ -93,3 +112,3 @@ * Returns a new initializer | ||
*/ | ||
export function inject(dependenciesDeclarations, initializer, merge = false) { | ||
export function inject(dependenciesDeclarations, initializer) { | ||
if ('constant' === initializer[SPECIAL_PROPS.TYPE]) { | ||
@@ -104,7 +123,3 @@ throw new YError( | ||
const uniqueInitializer = reuseSpecialProps(initializer, initializer, { | ||
[SPECIAL_PROPS.INJECT]: merge | ||
? (initializer[SPECIAL_PROPS.INJECT] || []).concat( | ||
dependenciesDeclarations, | ||
) | ||
: dependenciesDeclarations, | ||
[SPECIAL_PROPS.INJECT]: dependenciesDeclarations, | ||
}); | ||
@@ -119,2 +134,28 @@ | ||
* Decorator creating a new initializer with some | ||
* more dependencies declarations appended to it. | ||
* @param {String[]} dependenciesDeclarations | ||
* List of dependencies declarations to append | ||
* @param {Function} initializer | ||
* The initializer to tweak | ||
* @return {Function} | ||
* Returns a new initializer | ||
* @example | ||
* | ||
* import { alsoInject, getInstance } from 'knifecycle' | ||
* import myServiceInitializer from './service'; | ||
* | ||
* getInstance() | ||
* .service('myService', | ||
* alsoInject(['ENV'], myServiceInitializer) | ||
* ); | ||
*/ | ||
export function alsoInject(dependenciesDeclarations, initializer) { | ||
return inject( | ||
(initializer[SPECIAL_PROPS.INJECT] || []).concat(dependenciesDeclarations), | ||
initializer, | ||
); | ||
} | ||
/** | ||
* Decorator creating a new initializer with some | ||
* extra informations appended to it. It is just | ||
@@ -472,3 +513,3 @@ * a way for user to store some additional | ||
/* Architecture Note #1.3.1: Dependencies declaration syntax | ||
/* Architecture Note #1.2.1: Dependencies declaration syntax | ||
@@ -475,0 +516,0 @@ The dependencies syntax is of the following form: |
@@ -10,2 +10,3 @@ import assert from 'assert'; | ||
inject, | ||
alsoInject, | ||
options, | ||
@@ -113,2 +114,11 @@ extra, | ||
describe('alsoInject', () => { | ||
it('should allow to decorate an initializer with dependencies', () => { | ||
const newInitializer = alsoInject(['ENV'], inject(['TEST'], aProvider)); | ||
assert.notEqual(newInitializer, aProvider); | ||
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], ['TEST', 'ENV']); | ||
}); | ||
}); | ||
describe('options', () => { | ||
@@ -115,0 +125,0 @@ it('should allow to decorate an initializer with options', () => { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Unpopular package
QualityThis package is not very popular.
Found 1 instance in 1 package
1669
0
102
2
2
4
291166
20
6583
972