Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

knifecycle

Package Overview
Dependencies
Maintainers
1
Versions
102
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

knifecycle - npm Package Compare versions

Comparing version 2.7.0 to 3.0.0

10

CHANGELOG.md

@@ -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)

153

dist/build.js
"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",

@@ -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.&lt;String&gt;</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.&lt;function()&gt;</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.&lt;function()&gt;</code>
Instantiate the initializer builder service
Returns a Knifecycle instance (always the same)
**Kind**: global function
**Returns**: <code>Promise.&lt;function()&gt;</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.&lt;String&gt;</code>
### initInitializerBuilder~buildInitializer(dependencies) ⇒ <code>Promise.&lt;String&gt;</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.&lt;String&gt;</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.&lt;String&gt;</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.&lt;String&gt;</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.&lt;String&gt;</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.&lt;String&gt;</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 @@

@@ -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,

@@ -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', () => {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc