Socket
Socket
Sign inDemoInstall

knifecycle

Package Overview
Dependencies
Maintainers
1
Versions
100
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 12.0.4 to 13.0.0

4

CHANGELOG.md

@@ -0,1 +1,5 @@

# [13.0.0](https://github.com/nfroidure/knifecycle/compare/v12.0.4...v13.0.0) (2022-06-12)
## [12.0.4](https://github.com/nfroidure/knifecycle/compare/v12.0.3...v12.0.4) (2022-05-25)

@@ -2,0 +6,0 @@

6

dist/build.d.ts

@@ -1,3 +0,3 @@

import type { DependencyDeclaration, Initializer } from './util';
import type { Autoloader } from '.';
import type { DependencyDeclaration, Initializer } from './util.js';
import type { Autoloader } from './index.js';
export declare type BuildOptions = {

@@ -7,5 +7,5 @@ modules?: 'commonjs' | true;

export declare type BuildInitializer = (dependencies: DependencyDeclaration[], options?: BuildOptions) => Promise<string>;
declare const _default: import("./util").ServiceInitializer<{
declare const _default: import("./util.js").ServiceInitializer<{
$autoload: Autoloader<Initializer<unknown, Record<string, unknown>>>;
}, (dependencies: string[], options?: BuildOptions) => Promise<string>>;
export default _default;

@@ -1,12 +0,3 @@

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _util = require("./util");
var _sequence = require("./sequence");
import { SPECIAL_PROPS, parseDependencyDeclaration, initializer, } from './util.js';
import { buildInitializationSequence } from './sequence.js';
/* Architecture Note #2: Build

@@ -28,6 +19,6 @@

*/
var _default = (0, _util.initializer)({
name: 'buildInitializer',
type: 'service',
inject: ['$autoload']
export default initializer({
name: 'buildInitializer',
type: 'service',
inject: ['$autoload'],
}, initInitializerBuilder);

@@ -49,66 +40,72 @@ /**

*/
exports.default = _default;
async function initInitializerBuilder({
$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']);
*/
async function buildInitializer(dependencies, options = {}) {
const dependencyTrees = await 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 ('constant' === dependenciesHash[name].__initializer[_util.SPECIAL_PROPS.TYPE]) {
return `
const ${name} = ${JSON.stringify(dependenciesHash[name].__initializer[_util.SPECIAL_PROPS.VALUE], null, 2)};`;
}
return options.modules === 'commonjs' ? `
const ${dependenciesHash[name].__initializerName} = (() => { const m = require('${dependenciesHash[name].__path}'); return m && m.default || m; })();` : `
async function initInitializerBuilder({ $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']);
*/
async function buildInitializer(dependencies, options = {}) {
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 ('constant' ===
dependenciesHash[name].__initializer[SPECIAL_PROPS.TYPE]) {
return `
const ${name} = ${JSON.stringify(dependenciesHash[name].__initializer[SPECIAL_PROPS.VALUE], null, 2)};`;
}
return options.modules === 'commonjs'
? `
const ${dependenciesHash[name].__initializerName} = (() => { const m = require('${dependenciesHash[name].__path}'); return m && m.default || m; })();`
: `
import ${dependenciesHash[name].__initializerName} from '${dependenciesHash[name].__path}';`;
}).join('')}`).join('\n')}
})
.join('')}`)
.join('\n')}
${options.modules === 'commonjs' ? 'module.exports = {}; module.exports.initialize = ' : 'export '}async function initialize(services = {}) {${batches.map((batch, index) => `
${options.modules === 'commonjs'
? 'module.exports = {}; module.exports.initialize = '
: 'export '}async function initialize(services = {}) {${batches
.map((batch, index) => `
// Initialization batch #${index}
const batch${index} = {${batch.map(name => {
if ('constant' === dependenciesHash[name].__initializer[_util.SPECIAL_PROPS.TYPE]) {
return `
const batch${index} = {${batch
.map((name) => {
if ('constant' ===
dependenciesHash[name].__initializer[SPECIAL_PROPS.TYPE]) {
return `
${name}: Promise.resolve(${name}),`;
}
return `
${name}: ${dependenciesHash[name].__initializerName}({${dependenciesHash[name].__inject ? `${dependenciesHash[name].__inject.map(_util.parseDependencyDeclaration).map(({
serviceName,
mappedName
}) => `
${serviceName}: services['${mappedName}'],`).join('')}` : ''}
})${'provider' === dependenciesHash[name].__type ? '.then(provider => provider.service)' : ''},`;
}).join('')}
}
return `
${name}: ${dependenciesHash[name].__initializerName}({${dependenciesHash[name].__inject
? `${dependenciesHash[name].__inject
.map(parseDependencyDeclaration)
.map(({ serviceName, mappedName }) => `
${serviceName}: services['${mappedName}'],`)
.join('')}`
: ''}
})${'provider' === dependenciesHash[name].__type
? '.then(provider => provider.service)'
: ''},`;
})
.join('')}
};

@@ -120,85 +117,74 @@

);
${batch.map(name => {
return `
${batch
.map((name) => {
return `
services['${name}'] = await batch${index}['${name}'];`;
}).join('')}
`).join('')}
return {${dependencies.map(_util.parseDependencyDeclaration).map(({
serviceName,
mappedName
}) => `
${serviceName}: services['${mappedName}'],`).join('')}
})
.join('')}
`)
.join('')}
return {${dependencies
.map(parseDependencyDeclaration)
.map(({ serviceName, mappedName }) => `
${serviceName}: services['${mappedName}'],`)
.join('')}
};
}
`;
}
}
}
async function buildDependencyTree({
$autoload
}, dependencyDeclaration) {
const {
mappedName,
optional
} = (0, _util.parseDependencyDeclaration)(dependencyDeclaration);
try {
const {
path,
initializer
} = await $autoload(mappedName);
const node = {
__name: mappedName,
__initializer: initializer,
__inject: initializer && initializer[_util.SPECIAL_PROPS.INJECT] ? initializer[_util.SPECIAL_PROPS.INJECT] : [],
__type: initializer && initializer[_util.SPECIAL_PROPS.TYPE] ? initializer[_util.SPECIAL_PROPS.TYPE] : 'provider',
__initializerName: 'init' + upperCaseFirst(mappedName),
__path: path,
__childNodes: []
};
if (initializer[_util.SPECIAL_PROPS.INJECT] && initializer[_util.SPECIAL_PROPS.INJECT].length) {
const childNodes = await Promise.all(initializer[_util.SPECIAL_PROPS.INJECT].map(childDependencyDeclaration => buildDependencyTree({
$autoload
}, childDependencyDeclaration)));
node.__childNodes = childNodes.filter(identity);
return node;
} else {
return node;
async function buildDependencyTree({ $autoload, }, dependencyDeclaration) {
const { mappedName, optional } = parseDependencyDeclaration(dependencyDeclaration);
try {
const { path, initializer } = await $autoload(mappedName);
const node = {
__name: mappedName,
__initializer: initializer,
__inject: initializer && initializer[SPECIAL_PROPS.INJECT]
? initializer[SPECIAL_PROPS.INJECT]
: [],
__type: initializer && initializer[SPECIAL_PROPS.TYPE]
? initializer[SPECIAL_PROPS.TYPE]
: 'provider',
__initializerName: 'init' + upperCaseFirst(mappedName),
__path: path,
__childNodes: [],
};
if (initializer[SPECIAL_PROPS.INJECT] &&
initializer[SPECIAL_PROPS.INJECT].length) {
const childNodes = await Promise.all(initializer[SPECIAL_PROPS.INJECT].map((childDependencyDeclaration) => buildDependencyTree({ $autoload }, childDependencyDeclaration)));
node.__childNodes = childNodes.filter(identity);
return node;
}
else {
return node;
}
}
} catch (err) {
if (optional) {
return null;
catch (err) {
if (optional) {
return null;
}
throw err;
}
throw err;
}
}
function buildDependenciesHash(dependencyTrees, hash = {}) {
return dependencyTrees.reduce((hash, tree) => buildHashFromNode(tree, hash), hash);
return dependencyTrees.reduce((hash, tree) => buildHashFromNode(tree, hash), hash);
}
function buildHashFromNode(node, hash = {}) {
const nodeIsALeaf = !(node.__childNodes && node.__childNodes.length);
hash[node.__name] = node;
if (nodeIsALeaf) {
const nodeIsALeaf = !(node.__childNodes && node.__childNodes.length);
hash[node.__name] = node;
if (nodeIsALeaf) {
return hash;
}
node.__childNodes.forEach((childNode) => {
hash = buildHashFromNode(childNode, hash);
});
return hash;
}
node.__childNodes.forEach(childNode => {
hash = buildHashFromNode(childNode, hash);
});
return hash;
}
function identity(a) {
return a;
return a;
}
function upperCaseFirst(str) {
return str[0].toUpperCase() + str.slice(1);
return str[0].toUpperCase() + str.slice(1);
}
//# sourceMappingURL=build.js.map

@@ -1,66 +0,52 @@

"use strict";
var _assert = _interopRequireDefault(require("assert"));
var _yerror = _interopRequireDefault(require("yerror"));
var _build = _interopRequireDefault(require("./build"));
var _ = _interopRequireWildcard(require("."));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
import assert from 'assert';
import { YError } from 'yerror';
import initInitializerBuilder from './build.js';
import Knifecycle, { initializer, constant } from './index.js';
describe('buildInitializer', () => {
async function aProvider() {
return {
service: 'PROVIDER_SERVICE'
async function aProvider() {
return {
service: 'PROVIDER_SERVICE',
};
}
const mockedDepsHash = {
NODE_ENV: constant('NODE_ENV', 'development'),
dep1: initializer({
inject: [],
type: 'service',
name: 'dep1',
}, aProvider),
dep2: initializer({
inject: ['dep1', 'NODE_ENV'],
type: 'provider',
name: 'dep2',
}, aProvider),
dep3: initializer({
inject: ['dep2', 'dep1', '?depOpt'],
type: 'service',
name: 'dep3',
}, aProvider),
};
}
const mockedDepsHash = {
NODE_ENV: (0, _.constant)('NODE_ENV', 'development'),
dep1: (0, _.initializer)({
inject: [],
type: 'service',
name: 'dep1'
}, aProvider),
dep2: (0, _.initializer)({
inject: ['dep1', 'NODE_ENV'],
type: 'provider',
name: 'dep2'
}, aProvider),
dep3: (0, _.initializer)({
inject: ['dep2', 'dep1', '?depOpt'],
type: 'service',
name: 'dep3'
}, aProvider)
};
const initAutoloader = (0, _.initializer)({
name: '$autoload',
type: 'service',
inject: [],
singleton: true
}, async () => {
return async function $autoload(name) {
return mockedDepsHash[name] ? Promise.resolve({
path: `./services/${name}`,
initializer: mockedDepsHash[name]
}) : Promise.reject(new _yerror.default('E_UNMATCHED_DEPENDENCY', name));
};
});
it('should build an initialization module', async () => {
const $ = new _.default();
$.register((0, _.constant)('PWD', '~/my-project'));
$.register(initAutoloader);
$.register(_build.default);
const {
buildInitializer
} = await $.run(['buildInitializer']);
const content = await buildInitializer(['dep1', 'finalMappedDep>dep3']);
_assert.default.equal(content, `
const initAutoloader = initializer({
name: '$autoload',
type: 'service',
inject: [],
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 $ = 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(content, `
// Definition batch #0

@@ -129,16 +115,13 @@ import initDep1 from './services/dep1';

`);
});
it('should allows to use commonjs', async () => {
const $ = new _.default();
$.register((0, _.constant)('PWD', '~/my-project'));
$.register(initAutoloader);
$.register(_build.default);
const {
buildInitializer
} = await $.run(['buildInitializer']);
const content = await buildInitializer(['dep1', 'finalMappedDep>dep3'], {
modules: 'commonjs'
});
_assert.default.equal(content, `
it('should allows to use commonjs', async () => {
const $ = new Knifecycle();
$.register(constant('PWD', '~/my-project'));
$.register(initAutoloader);
$.register(initInitializerBuilder);
const { buildInitializer } = await $.run(['buildInitializer']);
const content = await buildInitializer(['dep1', 'finalMappedDep>dep3'], {
modules: 'commonjs',
});
assert.equal(content, `
// Definition batch #0

@@ -207,4 +190,4 @@ const initDep1 = (() => { const m = require('./services/dep1'); return m && m.default || m; })();

`);
});
});
});
//# sourceMappingURL=build.test.js.map

@@ -1,5 +0,5 @@

import { SPECIAL_PROPS, SPECIAL_PROPS_PREFIX, DECLARATION_SEPARATOR, OPTIONAL_FLAG, ALLOWED_INITIALIZER_TYPES, ALLOWED_SPECIAL_PROPS, parseInjections, readFunctionName, reuseSpecialProps, parseName, name, autoName, inject, useInject, mergeInject, autoInject, alsoInject, type, extra, singleton, initializer, constant, service, autoService, provider, autoProvider, wrapInitializer, handler, autoHandler, parseDependencyDeclaration, stringifyDependencyDeclaration, unwrapInitializerProperties } from './util';
import initInitializerBuilder from './build';
import type { ServiceName, Service, Disposer, FatalErrorPromise, Provider, Dependencies, DependencyDeclaration, ExtraInformations, ParsedDependencyDeclaration, ConstantProperties, ConstantInitializer, ProviderInitializerBuilder, ProviderProperties, ProviderInitializer, ProviderInputProperties, ServiceInitializerBuilder, ServiceProperties, ServiceInitializer, ServiceInputProperties, AsyncInitializerBuilder, AsyncInitializer, PartialAsyncInitializer, Initializer, ServiceInitializerWrapper, ProviderInitializerWrapper, HandlerFunction, Parameters } from './util';
import type { BuildInitializer } from './build';
import { SPECIAL_PROPS, SPECIAL_PROPS_PREFIX, DECLARATION_SEPARATOR, OPTIONAL_FLAG, ALLOWED_INITIALIZER_TYPES, ALLOWED_SPECIAL_PROPS, parseInjections, readFunctionName, reuseSpecialProps, parseName, name, autoName, inject, useInject, mergeInject, autoInject, alsoInject, type, extra, singleton, initializer, constant, service, autoService, provider, autoProvider, wrapInitializer, handler, autoHandler, parseDependencyDeclaration, stringifyDependencyDeclaration, unwrapInitializerProperties } from './util.js';
import initInitializerBuilder from './build.js';
import type { ServiceName, Service, Disposer, FatalErrorPromise, Provider, Dependencies, DependencyDeclaration, ExtraInformations, ParsedDependencyDeclaration, ConstantProperties, ConstantInitializer, ProviderInitializerBuilder, ProviderProperties, ProviderInitializer, ProviderInputProperties, ServiceInitializerBuilder, ServiceProperties, ServiceInitializer, ServiceInputProperties, AsyncInitializerBuilder, AsyncInitializer, PartialAsyncInitializer, Initializer, ServiceInitializerWrapper, ProviderInitializerWrapper, HandlerFunction, Parameters } from './util.js';
import type { BuildInitializer } from './build.js';
export type { ServiceName, Service, Disposer, FatalErrorPromise, Provider, Dependencies, DependencyDeclaration, ExtraInformations, ParsedDependencyDeclaration, ConstantProperties, ConstantInitializer, ProviderInitializerBuilder, ProviderProperties, ProviderInitializer, ProviderInputProperties, ServiceInitializerBuilder, ServiceProperties, ServiceInitializer, ServiceInputProperties, AsyncInitializerBuilder, AsyncInitializer, PartialAsyncInitializer, Initializer, ServiceInitializerWrapper, ProviderInitializerWrapper, HandlerFunction, Parameters, BuildInitializer, };

@@ -6,0 +6,0 @@ export interface Injector<T extends Record<string, unknown>> {

@@ -1,220 +0,7 @@

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "ALLOWED_INITIALIZER_TYPES", {
enumerable: true,
get: function () {
return _util.ALLOWED_INITIALIZER_TYPES;
}
});
Object.defineProperty(exports, "ALLOWED_SPECIAL_PROPS", {
enumerable: true,
get: function () {
return _util.ALLOWED_SPECIAL_PROPS;
}
});
Object.defineProperty(exports, "DECLARATION_SEPARATOR", {
enumerable: true,
get: function () {
return _util.DECLARATION_SEPARATOR;
}
});
exports.Knifecycle = void 0;
Object.defineProperty(exports, "OPTIONAL_FLAG", {
enumerable: true,
get: function () {
return _util.OPTIONAL_FLAG;
}
});
Object.defineProperty(exports, "SPECIAL_PROPS", {
enumerable: true,
get: function () {
return _util.SPECIAL_PROPS;
}
});
Object.defineProperty(exports, "SPECIAL_PROPS_PREFIX", {
enumerable: true,
get: function () {
return _util.SPECIAL_PROPS_PREFIX;
}
});
Object.defineProperty(exports, "alsoInject", {
enumerable: true,
get: function () {
return _util.alsoInject;
}
});
Object.defineProperty(exports, "autoHandler", {
enumerable: true,
get: function () {
return _util.autoHandler;
}
});
Object.defineProperty(exports, "autoInject", {
enumerable: true,
get: function () {
return _util.autoInject;
}
});
Object.defineProperty(exports, "autoName", {
enumerable: true,
get: function () {
return _util.autoName;
}
});
Object.defineProperty(exports, "autoProvider", {
enumerable: true,
get: function () {
return _util.autoProvider;
}
});
Object.defineProperty(exports, "autoService", {
enumerable: true,
get: function () {
return _util.autoService;
}
});
Object.defineProperty(exports, "constant", {
enumerable: true,
get: function () {
return _util.constant;
}
});
exports.default = void 0;
Object.defineProperty(exports, "extra", {
enumerable: true,
get: function () {
return _util.extra;
}
});
Object.defineProperty(exports, "handler", {
enumerable: true,
get: function () {
return _util.handler;
}
});
Object.defineProperty(exports, "initInitializerBuilder", {
enumerable: true,
get: function () {
return _build.default;
}
});
Object.defineProperty(exports, "initializer", {
enumerable: true,
get: function () {
return _util.initializer;
}
});
Object.defineProperty(exports, "inject", {
enumerable: true,
get: function () {
return _util.inject;
}
});
Object.defineProperty(exports, "mergeInject", {
enumerable: true,
get: function () {
return _util.mergeInject;
}
});
Object.defineProperty(exports, "name", {
enumerable: true,
get: function () {
return _util.name;
}
});
Object.defineProperty(exports, "parseDependencyDeclaration", {
enumerable: true,
get: function () {
return _util.parseDependencyDeclaration;
}
});
Object.defineProperty(exports, "parseInjections", {
enumerable: true,
get: function () {
return _util.parseInjections;
}
});
Object.defineProperty(exports, "parseName", {
enumerable: true,
get: function () {
return _util.parseName;
}
});
Object.defineProperty(exports, "provider", {
enumerable: true,
get: function () {
return _util.provider;
}
});
Object.defineProperty(exports, "readFunctionName", {
enumerable: true,
get: function () {
return _util.readFunctionName;
}
});
Object.defineProperty(exports, "reuseSpecialProps", {
enumerable: true,
get: function () {
return _util.reuseSpecialProps;
}
});
Object.defineProperty(exports, "service", {
enumerable: true,
get: function () {
return _util.service;
}
});
Object.defineProperty(exports, "singleton", {
enumerable: true,
get: function () {
return _util.singleton;
}
});
Object.defineProperty(exports, "stringifyDependencyDeclaration", {
enumerable: true,
get: function () {
return _util.stringifyDependencyDeclaration;
}
});
Object.defineProperty(exports, "type", {
enumerable: true,
get: function () {
return _util.type;
}
});
Object.defineProperty(exports, "unwrapInitializerProperties", {
enumerable: true,
get: function () {
return _util.unwrapInitializerProperties;
}
});
Object.defineProperty(exports, "useInject", {
enumerable: true,
get: function () {
return _util.useInject;
}
});
Object.defineProperty(exports, "wrapInitializer", {
enumerable: true,
get: function () {
return _util.wrapInitializer;
}
});
var _util = require("./util");
var _build = _interopRequireDefault(require("./build"));
var _yerror = _interopRequireDefault(require("yerror"));
var _debug = _interopRequireDefault(require("debug"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
const debug = (0, _debug.default)('knifecycle');
/* eslint max-len: ["warn", { "ignoreComments": true }] @typescript-eslint/no-this-alias: "warn" */
import { SPECIAL_PROPS, SPECIAL_PROPS_PREFIX, DECLARATION_SEPARATOR, OPTIONAL_FLAG, ALLOWED_INITIALIZER_TYPES, ALLOWED_SPECIAL_PROPS, parseInjections, readFunctionName, reuseSpecialProps, parseName, name, autoName, inject, useInject, mergeInject, autoInject, alsoInject, type, extra, singleton, initializer, constant, service, autoService, provider, autoProvider, wrapInitializer, handler, autoHandler, parseDependencyDeclaration, stringifyDependencyDeclaration, unwrapInitializerProperties, } from './util.js';
import initInitializerBuilder from './build.js';
import { YError } from 'yerror';
import initDebug from 'debug';
const debug = initDebug('knifecycle');
const DISPOSE = '$dispose';

@@ -254,3 +41,2 @@ const AUTOLOAD = '$autoload';

*/
/* Architecture Note #1.1: OOP

@@ -266,809 +52,664 @@ The `knifecycle` use case is one of the rare use case where

*/
class Knifecycle {
/**
* Create a new Knifecycle instance
* @return {Knifecycle}
* The Knifecycle instance
* @example
*
* import Knifecycle from 'knifecycle'
*
* const $ = new Knifecycle();
*/
constructor() {
_defineProperty(this, "_silosCounter", void 0);
_defineProperty(this, "_silosContexts", void 0);
_defineProperty(this, "_initializers", void 0);
_defineProperty(this, "_initializerResolvers", void 0);
_defineProperty(this, "_singletonsServicesHandles", void 0);
_defineProperty(this, "_singletonsServicesDescriptors", void 0);
_defineProperty(this, "_singletonsServicesShutdownsPromises", void 0);
_defineProperty(this, "shutdownPromise", void 0);
this._silosCounter = 0;
this._silosContexts = new Set();
this._initializers = new Map();
this._initializerResolvers = new Map();
this._singletonsServicesHandles = new Map();
this._singletonsServicesDescriptors = new Map();
this._singletonsServicesShutdownsPromises = new Map();
this.register((0, _util.constant)(INSTANCE, this));
const initInjectorProvider = (0, _util.provider)(async ({
$siloContext
}) => ({
service: async dependenciesDeclarations => _buildFinalHash(await this._initializeDependencies($siloContext, $siloContext.name, dependenciesDeclarations, {
injectorContext: true,
autoloading: false
}), dependenciesDeclarations)
}), INJECTOR, [SILO_CONTEXT], // Despite its global definition, the injector
// depends on the silo context and then needs
// to be instanciated once per silo.
false);
this.register(initInjectorProvider);
}
/* 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 litteral values.
- providers: they instead resolve to an object that
contains the service built into the `service` property
but also an optional `dispose` property exposing a
method to properly stop the service and a
`fatalErrorPromise` that will be rejected if an
unrecoverable error happens.
Initializers can be declared as singletons. This means
that they will be instanciated once for all for each
executions silos using them (we will cover this
topic later on).
*/
/**
* Register an initializer
* @param {Function} initializer
* An initializer
* @return {Knifecycle}
* The Knifecycle instance (for chaining)
*/
register(initializer) {
if (this.shutdownPromise) {
throw new _yerror.default(E_INSTANCE_DESTROYED);
_silosCounter;
_silosContexts;
_initializers;
_initializerResolvers;
_singletonsServicesHandles;
_singletonsServicesDescriptors;
_singletonsServicesShutdownsPromises;
shutdownPromise;
/**
* Create a new Knifecycle instance
* @return {Knifecycle}
* The Knifecycle instance
* @example
*
* import Knifecycle from 'knifecycle'
*
* const $ = new Knifecycle();
*/
constructor() {
this._silosCounter = 0;
this._silosContexts = new Set();
this._initializers = new Map();
this._initializerResolvers = new Map();
this._singletonsServicesHandles = new Map();
this._singletonsServicesDescriptors = new Map();
this._singletonsServicesShutdownsPromises = new Map();
this.register(constant(INSTANCE, this));
const initInjectorProvider = provider(async ({ $siloContext }) => ({
service: async (dependenciesDeclarations) => _buildFinalHash(await this._initializeDependencies($siloContext, $siloContext.name, dependenciesDeclarations, { injectorContext: true, autoloading: false }), dependenciesDeclarations),
}), INJECTOR, [SILO_CONTEXT],
// Despite its global definition, the injector
// depends on the silo context and then needs
// to be instanciated once per silo.
false);
this.register(initInjectorProvider);
}
(0, _util.unwrapInitializerProperties)(initializer); // Temporary cast constants into providers
// Best would be to threat each differently
// at dependencies initialization level to boost performances
if (initializer[_util.SPECIAL_PROPS.TYPE] === 'constant') {
const value = initializer[_util.SPECIAL_PROPS.VALUE];
if ('undefined' === typeof value) {
throw new _yerror.default(E_UNDEFINED_CONSTANT_INITIALIZER, initializer[_util.SPECIAL_PROPS.NAME]);
}
initializer = (0, _util.provider)(async () => ({
service: value
}), initializer[_util.SPECIAL_PROPS.NAME], [], true); // Needed for the build utils to still recognize
// this initializer as a constant value
initializer[_util.SPECIAL_PROPS.VALUE] = value;
initializer[_util.SPECIAL_PROPS.TYPE] = 'constant';
} else if ('undefined' !== typeof initializer[_util.SPECIAL_PROPS.VALUE]) {
throw new _yerror.default(E_BAD_VALUED_NON_CONSTANT_INITIALIZER, initializer[_util.SPECIAL_PROPS.NAME]);
} // Temporary cast service initializers into
// providers. Best would be to threat each differently
// at dependencies initialization level to boost performances
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';
/* 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 litteral values.
- providers: they instead resolve to an object that
contains the service built into the `service` property
but also an optional `dispose` property exposing a
method to properly stop the service and a
`fatalErrorPromise` that will be rejected if an
unrecoverable error happens.
Initializers can be declared as singletons. This means
that they will be instanciated once for all for each
executions silos using them (we will cover this
topic later on).
*/
/**
* Register an initializer
* @param {Function} initializer
* An initializer
* @return {Knifecycle}
* The Knifecycle instance (for chaining)
*/
register(initializer) {
if (this.shutdownPromise) {
throw new YError(E_INSTANCE_DESTROYED);
}
unwrapInitializerProperties(initializer);
// Temporary cast constants into providers
// Best would be to threat each differently
// at dependencies initialization level to boost performances
if (initializer[SPECIAL_PROPS.TYPE] === 'constant') {
const value = initializer[SPECIAL_PROPS.VALUE];
if ('undefined' === typeof value) {
throw new YError(E_UNDEFINED_CONSTANT_INITIALIZER, initializer[SPECIAL_PROPS.NAME]);
}
initializer = provider(async () => ({
service: value,
}), initializer[SPECIAL_PROPS.NAME], [], true);
// Needed for the build utils to still recognize
// this initializer as a constant value
initializer[SPECIAL_PROPS.VALUE] = value;
initializer[SPECIAL_PROPS.TYPE] = 'constant';
}
else if ('undefined' !== typeof initializer[SPECIAL_PROPS.VALUE]) {
throw new YError(E_BAD_VALUED_NON_CONSTANT_INITIALIZER, initializer[SPECIAL_PROPS.NAME]);
}
// 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]) {
initializer = reuseSpecialProps(initializer, serviceAdapter.bind(null, initializer[SPECIAL_PROPS.NAME], initializer));
initializer[SPECIAL_PROPS.TYPE] = 'provider';
}
const initializerDependsOfItself = initializer[SPECIAL_PROPS.INJECT]
.map(_pickServiceNameFromDeclaration)
.includes(initializer[SPECIAL_PROPS.NAME]);
if (initializerDependsOfItself) {
throw new YError(E_CIRCULAR_DEPENDENCY, initializer[SPECIAL_PROPS.NAME]);
}
initializer[SPECIAL_PROPS.INJECT].forEach((dependencyDeclaration) => {
this._lookupCircularDependencies(initializer[SPECIAL_PROPS.NAME], dependencyDeclaration);
});
if (this._initializers.has(initializer[SPECIAL_PROPS.NAME])) {
const initializedAsSingleton = this._singletonsServicesHandles.has(initializer[SPECIAL_PROPS.NAME]) &&
this._singletonsServicesDescriptors.has(initializer[SPECIAL_PROPS.NAME]) &&
!this._singletonsServicesDescriptors.get(initializer[SPECIAL_PROPS.NAME])?.preloaded;
const initializedAsInstance = [...this._silosContexts.values()].some((siloContext) => siloContext.servicesSequence.some((sequence) => sequence.includes(initializer[SPECIAL_PROPS.NAME])));
if (initializedAsSingleton || initializedAsInstance) {
throw new YError('E_INITIALIZER_ALREADY_INSTANCIATED', initializer[SPECIAL_PROPS.NAME]);
}
debug(`'Overridden an initializer: ${initializer[SPECIAL_PROPS.NAME]}`);
}
else {
debug(`Registered an initializer: ${initializer[SPECIAL_PROPS.NAME]}`);
}
// 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], {
preloaded: true,
// 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
promise: initializer({}),
});
}
this._initializers.set(initializer[SPECIAL_PROPS.NAME], initializer);
return this;
}
const initializerDependsOfItself = initializer[_util.SPECIAL_PROPS.INJECT].map(_pickServiceNameFromDeclaration).includes(initializer[_util.SPECIAL_PROPS.NAME]);
if (initializerDependsOfItself) {
throw new _yerror.default(E_CIRCULAR_DEPENDENCY, initializer[_util.SPECIAL_PROPS.NAME]);
_lookupCircularDependencies(rootServiceName, dependencyDeclaration, declarationsStacks = []) {
const serviceName = _pickServiceNameFromDeclaration(dependencyDeclaration);
const dependencyProvider = this._initializers.get(serviceName);
if (!dependencyProvider) {
return;
}
declarationsStacks = declarationsStacks.concat(dependencyDeclaration);
dependencyProvider[SPECIAL_PROPS.INJECT].forEach((childDependencyDeclaration) => {
const childServiceName = _pickServiceNameFromDeclaration(childDependencyDeclaration);
if (rootServiceName === childServiceName) {
throw new YError(E_CIRCULAR_DEPENDENCY, ...[rootServiceName]
.concat(declarationsStacks)
.concat(childDependencyDeclaration));
}
this._lookupCircularDependencies(rootServiceName, childDependencyDeclaration, declarationsStacks);
});
}
initializer[_util.SPECIAL_PROPS.INJECT].forEach(dependencyDeclaration => {
this._lookupCircularDependencies(initializer[_util.SPECIAL_PROPS.NAME], dependencyDeclaration);
});
if (this._initializers.has(initializer[_util.SPECIAL_PROPS.NAME])) {
var _this$_singletonsServ;
const initializedAsSingleton = this._singletonsServicesHandles.has(initializer[_util.SPECIAL_PROPS.NAME]) && this._singletonsServicesDescriptors.has(initializer[_util.SPECIAL_PROPS.NAME]) && !((_this$_singletonsServ = this._singletonsServicesDescriptors.get(initializer[_util.SPECIAL_PROPS.NAME])) !== null && _this$_singletonsServ !== void 0 && _this$_singletonsServ.preloaded);
const initializedAsInstance = [...this._silosContexts.values()].some(siloContext => siloContext.servicesSequence.some(sequence => sequence.includes(initializer[_util.SPECIAL_PROPS.NAME])));
if (initializedAsSingleton || initializedAsInstance) {
throw new _yerror.default('E_INITIALIZER_ALREADY_INSTANCIATED', initializer[_util.SPECIAL_PROPS.NAME]);
}
debug(`'Overridden an initializer: ${initializer[_util.SPECIAL_PROPS.NAME]}`);
} else {
debug(`Registered an initializer: ${initializer[_util.SPECIAL_PROPS.NAME]}`);
} // 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], {
preloaded: true,
// 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
promise: initializer({})
});
/**
* Outputs a Mermaid compatible dependency graph of the declared services.
* See [Mermaid docs](https://github.com/knsv/mermaid)
* @param {Object} options
* Options for generating the graph (destructured)
* @param {Array<Object>} options.shapes
* Various shapes to apply
* @param {Array<Object>} options.styles
* Various styles to apply
* @param {Object} options.classes
* A hash of various classes contents
* @return {String}
* Returns a string containing the Mermaid dependency graph
* @example
*
* import Knifecycle, { inject, constant, service } from 'knifecycle';
* import appInitializer from './app';
*
* const $ = new Knifecycle();
*
* $.register(constant('ENV', process.env));
* $.register(constant('OS', require('os')));
* $.register(service('app', inject(['ENV', 'OS'], appInitializer)));
* $.toMermaidGraph();
*
* // returns
* graph TD
* app-->ENV
* app-->OS
*/
toMermaidGraph({ shapes = [], styles = [], classes = {}, } = {
shapes: [],
styles: [],
classes: {},
}) {
const servicesProviders = this._initializers;
const links = Array.from(servicesProviders.keys())
.filter((provider) => !provider.startsWith('$'))
.reduce((links, serviceName) => {
const serviceProvider = servicesProviders.get(serviceName);
if (!serviceProvider[SPECIAL_PROPS.INJECT].length) {
return links;
}
return links.concat(serviceProvider[SPECIAL_PROPS.INJECT].map((dependencyDeclaration) => {
const dependedServiceName = _pickServiceNameFromDeclaration(dependencyDeclaration);
return { serviceName, dependedServiceName };
}));
}, []);
const classesApplications = _applyClasses(classes, styles, links);
if (!links.length) {
return '';
}
return ['graph TD']
.concat(links.map(({ serviceName, dependedServiceName }) => ` ${_applyShapes(shapes, serviceName) || serviceName}-->${_applyShapes(shapes, dependedServiceName) || dependedServiceName}`))
.concat(Object.keys(classes).map((className) => ` classDef ${className} ${classes[className]}`))
.concat(Object.keys(classesApplications).map((serviceName) => ` class ${serviceName} ${classesApplications[serviceName]};`))
.join('\n');
}
this._initializers.set(initializer[_util.SPECIAL_PROPS.NAME], initializer);
return this;
}
_lookupCircularDependencies(rootServiceName, dependencyDeclaration, declarationsStacks = []) {
const serviceName = _pickServiceNameFromDeclaration(dependencyDeclaration);
const dependencyProvider = this._initializers.get(serviceName);
if (!dependencyProvider) {
return;
}
declarationsStacks = declarationsStacks.concat(dependencyDeclaration);
dependencyProvider[_util.SPECIAL_PROPS.INJECT].forEach(childDependencyDeclaration => {
const childServiceName = _pickServiceNameFromDeclaration(childDependencyDeclaration);
if (rootServiceName === childServiceName) {
throw new _yerror.default(E_CIRCULAR_DEPENDENCY, ...[rootServiceName].concat(declarationsStacks).concat(childDependencyDeclaration));
}
this._lookupCircularDependencies(rootServiceName, childDependencyDeclaration, declarationsStacks);
});
}
/**
* Outputs a Mermaid compatible dependency graph of the declared services.
* See [Mermaid docs](https://github.com/knsv/mermaid)
* @param {Object} options
* Options for generating the graph (destructured)
* @param {Array<Object>} options.shapes
* Various shapes to apply
* @param {Array<Object>} options.styles
* Various styles to apply
* @param {Object} options.classes
* A hash of various classes contents
* @return {String}
* Returns a string containing the Mermaid dependency graph
* @example
*
* import Knifecycle, { inject, constant, service } from 'knifecycle';
* import appInitializer from './app';
*
* const $ = new Knifecycle();
*
* $.register(constant('ENV', process.env));
* $.register(constant('OS', require('os')));
* $.register(service('app', inject(['ENV', 'OS'], appInitializer)));
* $.toMermaidGraph();
*
* // returns
* graph TD
* app-->ENV
* app-->OS
*/
toMermaidGraph({
shapes = [],
styles = [],
classes = {}
} = {
shapes: [],
styles: [],
classes: {}
}) {
const servicesProviders = this._initializers;
const links = Array.from(servicesProviders.keys()).filter(provider => !provider.startsWith('$')).reduce((links, serviceName) => {
const serviceProvider = servicesProviders.get(serviceName);
if (!serviceProvider[_util.SPECIAL_PROPS.INJECT].length) {
return links;
}
return links.concat(serviceProvider[_util.SPECIAL_PROPS.INJECT].map(dependencyDeclaration => {
const dependedServiceName = _pickServiceNameFromDeclaration(dependencyDeclaration);
return {
serviceName,
dependedServiceName
/* Architecture Note #1.4: Execution silos
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 on your application design, you could run it
in only one execution silo or into several ones
according to the isolation level your wish to reach.
*/
/**
* Creates a new execution silo
* @param {String[]} dependenciesDeclarations
* Service name.
* @return {Promise}
* Service descriptor promise
* @example
*
* import Knifecycle, { constant } from 'knifecycle'
*
* const $ = new Knifecycle();
*
* $.register(constant('ENV', process.env));
* $.run(['ENV'])
* .then(({ ENV }) => {
* // Here goes your code
* })
*/
async run(dependenciesDeclarations) {
const _this = this;
const internalDependencies = [
...new Set(dependenciesDeclarations.concat(DISPOSE)),
];
const siloContext = {
name: `silo-${this._silosCounter++}`,
servicesDescriptors: new Map(),
servicesSequence: [],
servicesShutdownsPromises: new Map(),
errorsPromises: [],
};
}));
}, []);
const classesApplications = _applyClasses(classes, styles, links);
if (!links.length) {
return '';
if (this.shutdownPromise) {
throw new YError(E_INSTANCE_DESTROYED);
}
// Create a provider for the special fatal error service
siloContext.servicesDescriptors.set(FATAL_ERROR, Promise.resolve({
service: {
promise: new Promise((_resolve, reject) => {
siloContext.throwFatalError = (err) => {
debug('Handled a fatal error', err);
reject(err);
};
}),
},
}));
// Make the siloContext available for internal injections
siloContext.servicesDescriptors.set(SILO_CONTEXT, Promise.resolve({
service: siloContext,
}));
// Create a provider for the shutdown special dependency
siloContext.servicesDescriptors.set(DISPOSE, Promise.resolve({
service: async () => {
siloContext.shutdownPromise =
siloContext.shutdownPromise ||
_shutdownNextServices(siloContext.servicesSequence);
debug('Shutting down services');
await siloContext.shutdownPromise;
this._silosContexts.delete(siloContext);
// Shutdown services in their instanciation order
async function _shutdownNextServices(reversedServiceSequence) {
if (0 === reversedServiceSequence.length) {
return;
}
await Promise.all(reversedServiceSequence.pop().map(async (serviceName) => {
const singletonServiceDescriptor = await _this._pickupSingletonServiceDescriptorPromise(serviceName);
const serviceDescriptor = singletonServiceDescriptor ||
(await siloContext.servicesDescriptors.get(serviceName));
let serviceShutdownPromise = _this._singletonsServicesShutdownsPromises.get(serviceName) ||
siloContext.servicesShutdownsPromises.get(serviceName);
if (serviceShutdownPromise) {
debug('Reusing a service shutdown promise:', serviceName);
return serviceShutdownPromise;
}
if (reversedServiceSequence.some((servicesDeclarations) => servicesDeclarations.includes(serviceName))) {
debug('Delaying service shutdown:', serviceName);
return Promise.resolve();
}
if (singletonServiceDescriptor) {
const handleSet = _this._singletonsServicesHandles.get(serviceName);
handleSet.delete(siloContext.name);
if (handleSet.size) {
debug('Singleton is used elsewhere:', serviceName, handleSet);
return Promise.resolve();
}
_this._singletonsServicesDescriptors.delete(serviceName);
}
debug('Shutting down a service:', serviceName);
serviceShutdownPromise = serviceDescriptor?.dispose
? serviceDescriptor.dispose()
: Promise.resolve();
if (singletonServiceDescriptor) {
_this._singletonsServicesShutdownsPromises.set(serviceName, serviceShutdownPromise);
}
siloContext.servicesShutdownsPromises.set(serviceName, serviceShutdownPromise);
return serviceShutdownPromise;
}));
await _shutdownNextServices(reversedServiceSequence);
}
},
dispose: Promise.resolve.bind(Promise),
}));
this._silosContexts.add(siloContext);
const servicesHash = await this._initializeDependencies(siloContext, siloContext.name, internalDependencies, { injectorContext: false, autoloading: false });
debug('Handling fatal errors:', siloContext.errorsPromises);
Promise.all(siloContext.errorsPromises).catch(siloContext.throwFatalError);
return _buildFinalHash(servicesHash, dependenciesDeclarations);
}
return ['graph TD'].concat(links.map(({
serviceName,
dependedServiceName
}) => ` ${_applyShapes(shapes, serviceName) || serviceName}-->${_applyShapes(shapes, dependedServiceName) || dependedServiceName}`)).concat(Object.keys(classes).map(className => ` classDef ${className} ${classes[className]}`)).concat(Object.keys(classesApplications).map(serviceName => ` class ${serviceName} ${classesApplications[serviceName]};`)).join('\n');
}
/* Architecture Note #1.4: Execution silos
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 on your application design, you could run it
in only one execution silo or into several ones
according to the isolation level your wish to reach.
*/
/**
* Creates a new execution silo
* @param {String[]} dependenciesDeclarations
* Service name.
* @return {Promise}
* Service descriptor promise
* @example
*
* import Knifecycle, { constant } from 'knifecycle'
*
* const $ = new Knifecycle();
*
* $.register(constant('ENV', process.env));
* $.run(['ENV'])
* .then(({ ENV }) => {
* // Here goes your code
* })
*/
async run(dependenciesDeclarations) {
const _this = this;
const internalDependencies = [...new Set(dependenciesDeclarations.concat(DISPOSE))];
const siloContext = {
name: `silo-${this._silosCounter++}`,
servicesDescriptors: new Map(),
servicesSequence: [],
servicesShutdownsPromises: new Map(),
errorsPromises: []
};
if (this.shutdownPromise) {
throw new _yerror.default(E_INSTANCE_DESTROYED);
} // Create a provider for the special fatal error service
siloContext.servicesDescriptors.set(FATAL_ERROR, Promise.resolve({
service: {
promise: new Promise((_resolve, reject) => {
siloContext.throwFatalError = err => {
debug('Handled a fatal error', err);
reject(err);
};
})
}
})); // Make the siloContext available for internal injections
siloContext.servicesDescriptors.set(SILO_CONTEXT, Promise.resolve({
service: siloContext
})); // Create a provider for the shutdown special dependency
siloContext.servicesDescriptors.set(DISPOSE, Promise.resolve({
service: async () => {
siloContext.shutdownPromise = siloContext.shutdownPromise || _shutdownNextServices(siloContext.servicesSequence);
debug('Shutting down services');
await siloContext.shutdownPromise;
this._silosContexts.delete(siloContext); // Shutdown services in their instanciation order
async function _shutdownNextServices(reversedServiceSequence) {
if (0 === reversedServiceSequence.length) {
return;
}
await Promise.all(reversedServiceSequence.pop().map(async serviceName => {
const singletonServiceDescriptor = await _this._pickupSingletonServiceDescriptorPromise(serviceName);
const serviceDescriptor = singletonServiceDescriptor || (await siloContext.servicesDescriptors.get(serviceName));
let serviceShutdownPromise = _this._singletonsServicesShutdownsPromises.get(serviceName) || siloContext.servicesShutdownsPromises.get(serviceName);
if (serviceShutdownPromise) {
debug('Reusing a service shutdown promise:', serviceName);
return serviceShutdownPromise;
/**
* Destroy the Knifecycle instance
* @return {Promise}
* Full destruction promise
* @example
*
* import Knifecycle, { constant } from 'knifecycle'
*
* const $ = new Knifecycle();
*
* $.register(constant('ENV', process.env));
* $.run(['ENV'])
* .then(({ ENV }) => {
* // Here goes your code
*
* // Finally destroy the instance
* $.destroy()
* })
*/
async destroy() {
this.shutdownPromise =
this.shutdownPromise ||
Promise.all([...this._silosContexts].map(async (siloContext) => {
const $dispose = (await siloContext.servicesDescriptors.get(DISPOSE))
?.service;
return $dispose();
})).then(() => undefined);
debug('Shutting down Knifecycle instance.');
return this.shutdownPromise;
}
/**
* Initialize or return a service descriptor
* @param {Object} siloContext
* Current execution silo context
* @param {String} serviceName
* Service name.
* @param {Object} options
* Options for service retrieval
* @param {Boolean} options.injectorContext
* Flag indicating the injection were initiated by the $injector
* @param {Boolean} options.autoloading
* Flag to indicating $autoload dependencies on the fly loading
* @param {String} serviceProvider
* Service provider.
* @return {Promise}
* Service descriptor promise.
*/
async _getServiceDescriptor(siloContext, serviceName, { injectorContext, autoloading, }) {
// Try to get service descriptior early from the silo context
let serviceDescriptorPromise = siloContext.servicesDescriptors.get(serviceName);
if (serviceDescriptorPromise) {
if (autoloading) {
debug(`⚠️ - Possible dead lock due to reusing "${serviceName}" from the silo context while autoloading.`);
}
if (reversedServiceSequence.some(servicesDeclarations => servicesDeclarations.includes(serviceName))) {
debug('Delaying service shutdown:', serviceName);
return Promise.resolve();
return serviceDescriptorPromise;
}
const initializer = await this._findInitializer(siloContext, serviceName, {
injectorContext,
autoloading,
});
serviceDescriptorPromise = this._pickupSingletonServiceDescriptorPromise(serviceName);
if (serviceDescriptorPromise) {
if (autoloading) {
debug(`⚠️ - Possible dead lock due to reusing the singleton "${serviceName}" while autoloading.`);
}
if (singletonServiceDescriptor) {
const handleSet = _this._singletonsServicesHandles.get(serviceName);
handleSet.delete(siloContext.name);
if (handleSet.size) {
debug('Singleton is used elsewhere:', serviceName, handleSet);
return Promise.resolve();
}
_this._singletonsServicesDescriptors.delete(serviceName);
this._singletonsServicesHandles.get(serviceName).add(siloContext.name);
}
else {
serviceDescriptorPromise =
siloContext.servicesDescriptors.get(serviceName);
}
if (serviceDescriptorPromise) {
return serviceDescriptorPromise;
}
// The $injector service is mainly intended to be used as a workaround
// for unavoidable circular dependencies. It rarely make sense to
// instanciate new services at this level so printing a warning for
// debug purposes
if (injectorContext) {
debug('Warning: Instantiating a new service via the $injector. It may' +
' mean that you no longer need it if your worked around a circular' +
' dependency.');
}
serviceDescriptorPromise = this._initializeServiceDescriptor(siloContext, serviceName, initializer, {
autoloading: autoloading || AUTOLOAD === serviceName,
injectorContext,
});
if (initializer[SPECIAL_PROPS.SINGLETON]) {
const handlesSet = new Set();
handlesSet.add(siloContext.name);
this._singletonsServicesHandles.set(serviceName, handlesSet);
this._singletonsServicesDescriptors.set(serviceName, {
preloaded: false,
promise: serviceDescriptorPromise,
});
}
else {
siloContext.servicesDescriptors.set(serviceName, serviceDescriptorPromise);
}
// Since the autoloader is a bit special it must be pushed here
if (AUTOLOAD === serviceName) {
siloContext.servicesSequence.unshift([AUTOLOAD]);
}
return serviceDescriptorPromise;
}
async _findInitializer(siloContext, serviceName, { injectorContext, autoloading, }) {
const initializer = this._initializers.get(serviceName);
if (initializer) {
return initializer;
}
// The auto loader must only have static dependencies
// and we have to do this check here to avoid caching
// non-autoloading request and then be blocked by an
// autoloader dep that waits for that cached load
if (autoloading) {
throw new YError(E_AUTOLOADER_DYNAMIC_DEPENDENCY, serviceName);
}
debug('No service provider:', serviceName);
let initializerPromise = this._initializerResolvers.get(serviceName);
if (initializerPromise) {
return await initializerPromise;
}
initializerPromise = (async () => {
if (!this._initializers.get(AUTOLOAD)) {
throw new YError(E_UNMATCHED_DEPENDENCY, serviceName);
}
debug('Shutting down a service:', serviceName);
serviceShutdownPromise = serviceDescriptor !== null && serviceDescriptor !== void 0 && serviceDescriptor.dispose ? serviceDescriptor.dispose() : Promise.resolve();
if (singletonServiceDescriptor) {
_this._singletonsServicesShutdownsPromises.set(serviceName, serviceShutdownPromise);
debug(`Loading the $autoload service to lookup for: ${serviceName}.`);
try {
const autoloadingDescriptor = (await this._getServiceDescriptor(siloContext, AUTOLOAD, { injectorContext, autoloading: true }));
const { initializer, path } = await autoloadingDescriptor.service(serviceName);
if (typeof initializer !== 'function' &&
(typeof initializer !== 'object' ||
initializer[SPECIAL_PROPS.TYPE] !== 'constant')) {
throw new YError(E_BAD_AUTOLOADED_INITIALIZER, serviceName, initializer);
}
if (initializer[SPECIAL_PROPS.NAME] !== serviceName) {
throw new YError(E_AUTOLOADED_INITIALIZER_MISMATCH, serviceName, initializer[SPECIAL_PROPS.NAME]);
}
debug(`Loaded the ${serviceName} initializer at path ${path}.`);
this.register(initializer);
this._initializerResolvers.delete(serviceName);
// Here we need to pick-up the registered initializer to
// have a universally usable intitializer
return this._initializers.get(serviceName);
}
siloContext.servicesShutdownsPromises.set(serviceName, serviceShutdownPromise);
return serviceShutdownPromise;
}));
await _shutdownNextServices(reversedServiceSequence);
catch (err) {
debug(`Could not load ${serviceName} via the auto loader.`);
throw err;
}
})();
this._initializerResolvers.set(serviceName, initializerPromise);
return await initializerPromise;
}
_pickupSingletonServiceDescriptorPromise(serviceName) {
const serviceDescriptor = this._singletonsServicesDescriptors.get(serviceName);
if (!serviceDescriptor) {
return;
}
},
dispose: Promise.resolve.bind(Promise)
}));
this._silosContexts.add(siloContext);
const servicesHash = await this._initializeDependencies(siloContext, siloContext.name, internalDependencies, {
injectorContext: false,
autoloading: false
});
debug('Handling fatal errors:', siloContext.errorsPromises);
Promise.all(siloContext.errorsPromises).catch(siloContext.throwFatalError);
return _buildFinalHash(servicesHash, dependenciesDeclarations);
}
/**
* Destroy the Knifecycle instance
* @return {Promise}
* Full destruction promise
* @example
*
* import Knifecycle, { constant } from 'knifecycle'
*
* const $ = new Knifecycle();
*
* $.register(constant('ENV', process.env));
* $.run(['ENV'])
* .then(({ ENV }) => {
* // Here goes your code
*
* // Finally destroy the instance
* $.destroy()
* })
*/
async destroy() {
this.shutdownPromise = this.shutdownPromise || Promise.all([...this._silosContexts].map(async siloContext => {
var _await$siloContext$se;
const $dispose = (_await$siloContext$se = await siloContext.servicesDescriptors.get(DISPOSE)) === null || _await$siloContext$se === void 0 ? void 0 : _await$siloContext$se.service;
return $dispose();
})).then(() => undefined);
debug('Shutting down Knifecycle instance.');
return this.shutdownPromise;
}
/**
* Initialize or return a service descriptor
* @param {Object} siloContext
* Current execution silo context
* @param {String} serviceName
* Service name.
* @param {Object} options
* Options for service retrieval
* @param {Boolean} options.injectorContext
* Flag indicating the injection were initiated by the $injector
* @param {Boolean} options.autoloading
* Flag to indicating $autoload dependencies on the fly loading
* @param {String} serviceProvider
* Service provider.
* @return {Promise}
* Service descriptor promise.
*/
async _getServiceDescriptor(siloContext, serviceName, {
injectorContext,
autoloading
}) {
// Try to get service descriptior early from the silo context
let serviceDescriptorPromise = siloContext.servicesDescriptors.get(serviceName);
if (serviceDescriptorPromise) {
if (autoloading) {
debug(`⚠️ - Possible dead lock due to reusing "${serviceName}" from the silo context while autoloading.`);
}
return serviceDescriptorPromise;
serviceDescriptor.preloaded = false;
return serviceDescriptor.promise;
}
const initializer = await this._findInitializer(siloContext, serviceName, {
injectorContext,
autoloading
});
serviceDescriptorPromise = this._pickupSingletonServiceDescriptorPromise(serviceName);
if (serviceDescriptorPromise) {
if (autoloading) {
debug(`⚠️ - Possible dead lock due to reusing the singleton "${serviceName}" while autoloading.`);
}
this._singletonsServicesHandles.get(serviceName).add(siloContext.name);
} else {
serviceDescriptorPromise = siloContext.servicesDescriptors.get(serviceName);
}
if (serviceDescriptorPromise) {
return serviceDescriptorPromise;
} // The $injector service is mainly intended to be used as a workaround
// for unavoidable circular dependencies. It rarely make sense to
// instanciate new services at this level so printing a warning for
// debug purposes
if (injectorContext) {
debug('Warning: Instantiating a new service via the $injector. It may' + ' mean that you no longer need it if your worked around a circular' + ' dependency.');
}
serviceDescriptorPromise = this._initializeServiceDescriptor(siloContext, serviceName, initializer, {
autoloading: autoloading || AUTOLOAD === serviceName,
injectorContext
});
if (initializer[_util.SPECIAL_PROPS.SINGLETON]) {
const handlesSet = new Set();
handlesSet.add(siloContext.name);
this._singletonsServicesHandles.set(serviceName, handlesSet);
this._singletonsServicesDescriptors.set(serviceName, {
preloaded: false,
promise: serviceDescriptorPromise
});
} else {
siloContext.servicesDescriptors.set(serviceName, serviceDescriptorPromise);
} // Since the autoloader is a bit special it must be pushed here
if (AUTOLOAD === serviceName) {
siloContext.servicesSequence.unshift([AUTOLOAD]);
}
return serviceDescriptorPromise;
}
async _findInitializer(siloContext, serviceName, {
injectorContext,
autoloading
}) {
const initializer = this._initializers.get(serviceName);
if (initializer) {
return initializer;
} // The auto loader must only have static dependencies
// and we have to do this check here to avoid caching
// non-autoloading request and then be blocked by an
// autoloader dep that waits for that cached load
if (autoloading) {
throw new _yerror.default(E_AUTOLOADER_DYNAMIC_DEPENDENCY, serviceName);
}
debug('No service provider:', serviceName);
let initializerPromise = this._initializerResolvers.get(serviceName);
if (initializerPromise) {
return await initializerPromise;
}
initializerPromise = (async () => {
if (!this._initializers.get(AUTOLOAD)) {
throw new _yerror.default(E_UNMATCHED_DEPENDENCY, serviceName);
}
debug(`Loading the $autoload service to lookup for: ${serviceName}.`);
try {
const autoloadingDescriptor = await this._getServiceDescriptor(siloContext, AUTOLOAD, {
injectorContext,
autoloading: true
});
const {
initializer,
path
} = await autoloadingDescriptor.service(serviceName);
if (typeof initializer !== 'function' && (typeof initializer !== 'object' || initializer[_util.SPECIAL_PROPS.TYPE] !== 'constant')) {
throw new _yerror.default(E_BAD_AUTOLOADED_INITIALIZER, serviceName, initializer);
/**
* Initialize a service descriptor
* @param {Object} siloContext
* Current execution silo context
* @param {String} serviceName
* Service name.
* @param {Object} options
* Options for service retrieval
* @param {Boolean} options.injectorContext
* Flag indicating the injection were initiated by the $injector
* @param {Boolean} options.autoloading
* Flag to indicating $autoload dependendencies on the fly loading.
* @return {Promise}
* Service dependencies hash promise.
*/
async _initializeServiceDescriptor(siloContext, serviceName, initializer, { autoloading, injectorContext, }) {
let serviceDescriptor;
debug('Initializing a service descriptor:', serviceName);
try {
// A singleton service may use a reserved resource
// like a TCP socket. This is why we have to be aware
// of singleton services full shutdown before creating
// a new one
await (this._singletonsServicesShutdownsPromises.get(serviceName) ||
Promise.resolve());
// Anyway delete any shutdown promise before instanciating
// a new service
this._singletonsServicesShutdownsPromises.delete(serviceName);
siloContext.servicesShutdownsPromises.delete(serviceName);
const servicesHash = await this._initializeDependencies(siloContext, serviceName, initializer[SPECIAL_PROPS.INJECT], { injectorContext, autoloading });
debug('Successfully gathered service dependencies:', serviceName);
serviceDescriptor = await initializer(initializer[SPECIAL_PROPS.INJECT].reduce((finalHash, dependencyDeclaration) => {
const { serviceName, mappedName } = parseDependencyDeclaration(dependencyDeclaration);
finalHash[serviceName] = servicesHash[mappedName];
return finalHash;
}, {}));
if (!serviceDescriptor) {
debug('Provider did not return a descriptor:', serviceName);
return Promise.reject(new YError(E_BAD_SERVICE_PROVIDER, serviceName));
}
debug('Successfully initialized a service descriptor:', serviceName);
if (serviceDescriptor.fatalErrorPromise) {
debug('Registering service descriptor error promise:', serviceName);
siloContext.errorsPromises.push(serviceDescriptor.fatalErrorPromise);
}
siloContext.servicesDescriptors.set(serviceName, Promise.resolve(serviceDescriptor));
}
if (initializer[_util.SPECIAL_PROPS.NAME] !== serviceName) {
throw new _yerror.default(E_AUTOLOADED_INITIALIZER_MISMATCH, serviceName, initializer[_util.SPECIAL_PROPS.NAME]);
catch (err) {
debug('Error initializing a service descriptor:', serviceName, err.stack || 'no_stack_trace');
if (E_UNMATCHED_DEPENDENCY === err.code) {
throw YError.wrap(err, E_UNMATCHED_DEPENDENCY, ...[serviceName].concat(err.params));
}
throw err;
}
debug(`Loaded the ${serviceName} initializer at path ${path}.`);
this.register(initializer);
this._initializerResolvers.delete(serviceName); // Here we need to pick-up the registered initializer to
// have a universally usable intitializer
return this._initializers.get(serviceName);
} catch (err) {
debug(`Could not load ${serviceName} via the auto loader.`);
throw err;
}
})();
this._initializerResolvers.set(serviceName, initializerPromise);
return await initializerPromise;
}
_pickupSingletonServiceDescriptorPromise(serviceName) {
const serviceDescriptor = this._singletonsServicesDescriptors.get(serviceName);
if (!serviceDescriptor) {
return;
return serviceDescriptor;
}
serviceDescriptor.preloaded = false;
return serviceDescriptor.promise;
}
/**
* Initialize a service descriptor
* @param {Object} siloContext
* Current execution silo context
* @param {String} serviceName
* Service name.
* @param {Object} options
* Options for service retrieval
* @param {Boolean} options.injectorContext
* Flag indicating the injection were initiated by the $injector
* @param {Boolean} options.autoloading
* Flag to indicating $autoload dependendencies on the fly loading.
* @return {Promise}
* Service dependencies hash promise.
*/
async _initializeServiceDescriptor(siloContext, serviceName, initializer, {
autoloading,
injectorContext
}) {
let serviceDescriptor;
debug('Initializing a service descriptor:', serviceName);
try {
// A singleton service may use a reserved resource
// like a TCP socket. This is why we have to be aware
// of singleton services full shutdown before creating
// a new one
await (this._singletonsServicesShutdownsPromises.get(serviceName) || Promise.resolve()); // Anyway delete any shutdown promise before instanciating
// a new service
this._singletonsServicesShutdownsPromises.delete(serviceName);
siloContext.servicesShutdownsPromises.delete(serviceName);
const servicesHash = await this._initializeDependencies(siloContext, serviceName, initializer[_util.SPECIAL_PROPS.INJECT], {
injectorContext,
autoloading
});
debug('Successfully gathered service dependencies:', serviceName);
serviceDescriptor = await initializer(initializer[_util.SPECIAL_PROPS.INJECT].reduce((finalHash, dependencyDeclaration) => {
const {
serviceName,
mappedName
} = (0, _util.parseDependencyDeclaration)(dependencyDeclaration);
finalHash[serviceName] = servicesHash[mappedName];
return finalHash;
}, {}));
if (!serviceDescriptor) {
debug('Provider did not return a descriptor:', serviceName);
return Promise.reject(new _yerror.default(E_BAD_SERVICE_PROVIDER, serviceName));
}
debug('Successfully initialized a service descriptor:', serviceName);
if (serviceDescriptor.fatalErrorPromise) {
debug('Registering service descriptor error promise:', serviceName);
siloContext.errorsPromises.push(serviceDescriptor.fatalErrorPromise);
}
siloContext.servicesDescriptors.set(serviceName, Promise.resolve(serviceDescriptor));
} catch (err) {
debug('Error initializing a service descriptor:', serviceName, err.stack || 'no_stack_trace');
if (E_UNMATCHED_DEPENDENCY === err.code) {
throw _yerror.default.wrap(err, E_UNMATCHED_DEPENDENCY, ...[serviceName].concat(err.params));
}
throw err;
/**
* Initialize a service dependencies
* @param {Object} siloContext
* Current execution silo siloContext
* @param {String} serviceName
* Service name.
* @param {String} servicesDeclarations
* Dependencies declarations.
* @param {Object} options
* Options for service retrieval
* @param {Boolean} options.injectorContext
* Flag indicating the injection were initiated by the $injector
* @param {Boolean} options.autoloading
* Flag to indicating $autoload dependendencies on the fly loading.
* @return {Promise}
* Service dependencies hash promise.
*/
async _initializeDependencies(siloContext, serviceName, servicesDeclarations, { injectorContext = false, autoloading = false, }) {
debug('Initializing dependencies:', serviceName, servicesDeclarations);
const servicesDescriptors = await Promise.all(servicesDeclarations.map(async (serviceDeclaration) => {
const { mappedName, optional } = parseDependencyDeclaration(serviceDeclaration);
try {
const serviceDescriptor = await this._getServiceDescriptor(siloContext, mappedName, {
injectorContext,
autoloading,
});
return serviceDescriptor;
}
catch (err) {
if (optional &&
[
'E_UNMATCHED_DEPENDENCY',
E_AUTOLOADER_DYNAMIC_DEPENDENCY,
].includes(err.code)) {
debug('Optional dependency not found:', serviceDeclaration, err.stack || 'no_stack_trace');
return;
}
throw err;
}
}));
debug('Initialized dependencies descriptors:', serviceName, servicesDeclarations, servicesDescriptors);
siloContext.servicesSequence.push(servicesDeclarations
.filter((_, index) => servicesDescriptors[index])
.map(_pickMappedNameFromDeclaration));
const services = await Promise.all(servicesDescriptors.map(async (serviceDescriptor) => {
if (!serviceDescriptor) {
return undefined;
}
return serviceDescriptor.service;
}));
return services.reduce((hash, service, index) => {
const mappedName = _pickMappedNameFromDeclaration(servicesDeclarations[index]);
hash[mappedName] = service;
return hash;
}, {});
}
return serviceDescriptor;
}
/**
* Initialize a service dependencies
* @param {Object} siloContext
* Current execution silo siloContext
* @param {String} serviceName
* Service name.
* @param {String} servicesDeclarations
* Dependencies declarations.
* @param {Object} options
* Options for service retrieval
* @param {Boolean} options.injectorContext
* Flag indicating the injection were initiated by the $injector
* @param {Boolean} options.autoloading
* Flag to indicating $autoload dependendencies on the fly loading.
* @return {Promise}
* Service dependencies hash promise.
*/
async _initializeDependencies(siloContext, serviceName, servicesDeclarations, {
injectorContext = false,
autoloading = false
}) {
debug('Initializing dependencies:', serviceName, servicesDeclarations);
const servicesDescriptors = await Promise.all(servicesDeclarations.map(async serviceDeclaration => {
const {
mappedName,
optional
} = (0, _util.parseDependencyDeclaration)(serviceDeclaration);
try {
const serviceDescriptor = await this._getServiceDescriptor(siloContext, mappedName, {
injectorContext,
autoloading
});
return serviceDescriptor;
} catch (err) {
if (optional && ['E_UNMATCHED_DEPENDENCY', E_AUTOLOADER_DYNAMIC_DEPENDENCY].includes(err.code)) {
debug('Optional dependency not found:', serviceDeclaration, err.stack || 'no_stack_trace');
return;
}
throw err;
}
}));
debug('Initialized dependencies descriptors:', serviceName, servicesDeclarations, servicesDescriptors);
siloContext.servicesSequence.push(servicesDeclarations.filter((_, index) => servicesDescriptors[index]).map(_pickMappedNameFromDeclaration));
const services = await Promise.all(servicesDescriptors.map(async serviceDescriptor => {
if (!serviceDescriptor) {
return undefined;
}
return serviceDescriptor.service;
}));
return services.reduce((hash, service, index) => {
const mappedName = _pickMappedNameFromDeclaration(servicesDeclarations[index]);
hash[mappedName] = service;
return hash;
}, {});
}
}
exports.Knifecycle = Knifecycle;
var _default = Knifecycle;
exports.default = _default;
export default Knifecycle;
export { SPECIAL_PROPS, SPECIAL_PROPS_PREFIX, DECLARATION_SEPARATOR, OPTIONAL_FLAG, ALLOWED_INITIALIZER_TYPES, ALLOWED_SPECIAL_PROPS, parseInjections, readFunctionName, parseName, Knifecycle, initializer, name, autoName, type, inject, useInject, mergeInject, autoInject, alsoInject, extra, singleton, reuseSpecialProps, wrapInitializer, constant, service, autoService, provider, autoProvider, handler, autoHandler, parseDependencyDeclaration, stringifyDependencyDeclaration, unwrapInitializerProperties, initInitializerBuilder, };
function _pickServiceNameFromDeclaration(dependencyDeclaration) {
const {
serviceName
} = (0, _util.parseDependencyDeclaration)(dependencyDeclaration);
return serviceName;
const { serviceName } = parseDependencyDeclaration(dependencyDeclaration);
return serviceName;
}
function _pickMappedNameFromDeclaration(dependencyDeclaration) {
const {
mappedName
} = (0, _util.parseDependencyDeclaration)(dependencyDeclaration);
return mappedName;
const { mappedName } = parseDependencyDeclaration(dependencyDeclaration);
return mappedName;
}
function _applyShapes(shapes, serviceName) {
return shapes.reduce((shapedService, shape) => {
if (shapedService) {
return shapedService;
}
const matches = shape.pattern.exec(serviceName);
if (!matches) {
return shapedService;
}
return shape.template.replace(/\$([0-9])+/g, ($, $1) => matches[parseInt($1, 10)]);
}, '');
return shapes.reduce((shapedService, shape) => {
if (shapedService) {
return shapedService;
}
const matches = shape.pattern.exec(serviceName);
if (!matches) {
return shapedService;
}
return shape.template.replace(/\$([0-9])+/g, ($, $1) => matches[parseInt($1, 10)]);
}, '');
}
function _applyClasses(classes, styles, links) {
return links.reduce((classesApplications, link) => Object.assign(classesApplications, _applyStyles(classes, styles, link)), {});
return links.reduce((classesApplications, link) => Object.assign(classesApplications, _applyStyles(classes, styles, link)), {});
}
function _applyStyles(classes, styles, {
serviceName,
dependedServiceName
}) {
return styles.reduce((classesApplications, style) => {
if (style.pattern.test(serviceName) && !classesApplications[serviceName]) {
if (!classes[style.className]) {
throw new _yerror.default(E_BAD_CLASS, style.className, serviceName);
}
classesApplications[serviceName] = style.className;
}
if (style.pattern.test(dependedServiceName) && !classesApplications[dependedServiceName]) {
if (!classes[style.className]) {
throw new _yerror.default(E_BAD_CLASS, style.className, dependedServiceName);
}
classesApplications[dependedServiceName] = style.className;
}
return classesApplications;
}, {});
function _applyStyles(classes, styles, { serviceName, dependedServiceName, }) {
return styles.reduce((classesApplications, style) => {
if (style.pattern.test(serviceName) && !classesApplications[serviceName]) {
if (!classes[style.className]) {
throw new YError(E_BAD_CLASS, style.className, serviceName);
}
classesApplications[serviceName] = style.className;
}
if (style.pattern.test(dependedServiceName) &&
!classesApplications[dependedServiceName]) {
if (!classes[style.className]) {
throw new YError(E_BAD_CLASS, style.className, dependedServiceName);
}
classesApplications[dependedServiceName] = style.className;
}
return classesApplications;
}, {});
}
function serviceAdapter(serviceName, initializer, dependenciesHash) {
const servicePromise = initializer(dependenciesHash);
if (!servicePromise || !servicePromise.then) {
throw new _yerror.default(E_BAD_SERVICE_PROMISE, serviceName);
}
return servicePromise.then(_service_ => ({
service: _service_
}));
const servicePromise = initializer(dependenciesHash);
if (!servicePromise || !servicePromise.then) {
throw new YError(E_BAD_SERVICE_PROMISE, serviceName);
}
return servicePromise.then((_service_) => ({
service: _service_,
}));
}
function _buildFinalHash(servicesHash, dependenciesDeclarations) {
return dependenciesDeclarations.reduce((finalHash, dependencyDeclaration) => {
const {
serviceName,
mappedName
} = (0, _util.parseDependencyDeclaration)(dependencyDeclaration);
finalHash[serviceName] = servicesHash[mappedName];
return finalHash;
}, {});
return dependenciesDeclarations.reduce((finalHash, dependencyDeclaration) => {
const { serviceName, mappedName } = parseDependencyDeclaration(dependencyDeclaration);
finalHash[serviceName] = servicesHash[mappedName];
return finalHash;
}, {});
}
//# sourceMappingURL=index.js.map

@@ -1,1229 +0,1222 @@

"use strict";
var _assert = _interopRequireDefault(require("assert"));
var _sinon = _interopRequireDefault(require("sinon"));
var _yerror = _interopRequireDefault(require("yerror"));
var _index = require("./index");
var _util = require("./util");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/* eslint max-nested-callbacks:0 */
import assert from 'assert';
import sinon from 'sinon';
import { YError } from 'yerror';
import { SPECIAL_PROPS, Knifecycle, initializer, inject, constant, service, provider, singleton, } from './index.js';
import { ALLOWED_INITIALIZER_TYPES } from './util.js';
describe('Knifecycle', () => {
let $;
const ENV = {
MY_ENV_VAR: 'plop'
};
const time = Date.now.bind(Date);
async function timeService() {
return time;
}
async function hashProvider(hash) {
return {
service: hash
let $;
const ENV = {
MY_ENV_VAR: 'plop',
};
}
beforeEach(() => {
$ = new _index.Knifecycle();
});
describe('register', () => {
describe('with constants', () => {
it('should work with an object', () => {
$.register((0, _index.constant)('ENV', ENV));
});
it('should work with a function', () => {
$.register((0, _index.constant)('time', time));
});
it('should work when overriding a previously set constant', async () => {
$.register((0, _index.constant)('TEST', 1));
$.register((0, _index.constant)('TEST', 2));
_assert.default.deepEqual(await $.run(['TEST']), {
TEST: 2
const time = Date.now.bind(Date);
async function timeService() {
return time;
}
async function hashProvider(hash) {
return {
service: hash,
};
}
beforeEach(() => {
$ = new Knifecycle();
});
describe('register', () => {
describe('with constants', () => {
it('should work with an object', () => {
$.register(constant('ENV', ENV));
});
it('should work with a function', () => {
$.register(constant('time', time));
});
it('should work when overriding a previously set constant', async () => {
$.register(constant('TEST', 1));
$.register(constant('TEST', 2));
assert.deepEqual(await $.run(['TEST']), {
TEST: 2,
});
});
it('should fail when overriding an initialized constant', async () => {
$.register(constant('TEST', 1));
assert.deepEqual(await $.run(['TEST']), {
TEST: 1,
});
try {
$.register(constant('TEST', 2));
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
assert.equal(err.code, 'E_INITIALIZER_ALREADY_INSTANCIATED');
}
});
});
});
it('should fail when overriding an initialized constant', async () => {
$.register((0, _index.constant)('TEST', 1));
_assert.default.deepEqual(await $.run(['TEST']), {
TEST: 1
describe('with services', () => {
it('should work with a service', () => {
$.register(service(timeService, 'time'));
});
it('should work when overriding a previously set service', async () => {
$.register(service(async () => () => 1, 'test'));
$.register(service(async () => () => 2, 'test'));
const { test } = await $.run(['test']);
assert.deepEqual(test(), 2);
});
it('should fail when overriding an initialized service', async () => {
$.register(service(async () => () => 1, 'test'));
const { test } = await $.run(['test']);
assert.deepEqual(test(), 1);
try {
$.register(service(async () => () => 2, 'test'));
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
assert.equal(err.code, 'E_INITIALIZER_ALREADY_INSTANCIATED');
}
});
});
try {
$.register((0, _index.constant)('TEST', 2));
throw new _yerror.default('E_UNEXPECTED_SUCCESS');
} catch (err) {
_assert.default.equal(err.code, 'E_INITIALIZER_ALREADY_INSTANCIATED');
}
});
describe('with providers', () => {
it('should work with a provider', () => {
$.register(service(hashProvider, 'hash'));
});
it('should work when overriding a previously set provider', async () => {
$.register(initializer({
type: 'provider',
name: 'test',
inject: [],
}, async () => ({
service: 1,
})));
$.register(initializer({
type: 'provider',
name: 'test',
inject: [],
}, async () => ({
service: 2,
})));
const { test } = await $.run(['test']);
assert.deepEqual(test, 2);
});
it('should work when overriding a previously set singleton provider', async () => {
$.register(initializer({
type: 'provider',
name: 'test',
inject: [],
singleton: true,
}, async () => ({
service: 1,
})));
$.register(initializer({
type: 'provider',
name: 'test',
inject: [],
}, async () => ({
service: 2,
})));
const { test } = await $.run(['test']);
assert.deepEqual(test, 2);
});
it('should fail when overriding an initialized provider', async () => {
$.register(initializer({
type: 'provider',
name: 'test',
inject: [],
singleton: true,
}, async () => ({
service: 1,
})));
const { test } = await $.run(['test']);
assert.deepEqual(test, 1);
try {
$.register(initializer({
type: 'provider',
name: 'test',
inject: [],
}, async () => ({
service: 2,
})));
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
assert.equal(err.code, 'E_INITIALIZER_ALREADY_INSTANCIATED');
}
});
});
it('should fail when intitializer is no a function', () => {
assert.throws(() => {
$.register('not_a_function');
}, (err) => {
assert.deepEqual(err.code, 'E_BAD_INITIALIZER');
assert.deepEqual(err.params, ['not_a_function']);
return true;
});
});
it('should fail with no service name', () => {
assert.throws(() => {
$.register(async () => undefined);
}, (err) => {
assert.deepEqual(err.code, 'E_ANONYMOUS_ANALYZER');
assert.deepEqual(err.params, []);
return true;
});
});
it('should fail with a bad service type', () => {
assert.throws(() => {
const fn = async () => undefined;
fn[SPECIAL_PROPS.NAME] = 'test';
fn[SPECIAL_PROPS.TYPE] = 'not_allowed_type';
$.register(fn);
}, (err) => {
assert.deepEqual(err.code, 'E_BAD_INITIALIZER_TYPE');
assert.deepEqual(err.params, [
'test',
'not_allowed_type',
ALLOWED_INITIALIZER_TYPES,
]);
return true;
});
});
it('should fail with an undefined constant', () => {
assert.throws(() => {
const fn = async () => undefined;
fn[SPECIAL_PROPS.NAME] = 'THE_NUMBER';
fn[SPECIAL_PROPS.TYPE] = 'constant';
fn[SPECIAL_PROPS.VALUE] = undefined;
$.register(fn);
}, (err) => {
assert.deepEqual(err.code, 'E_UNDEFINED_CONSTANT_INITIALIZER');
assert.deepEqual(err.params, ['THE_NUMBER']);
return true;
});
});
it('should fail with a non constant that has a value', () => {
assert.throws(() => {
const fn = async () => undefined;
fn[SPECIAL_PROPS.NAME] = 'myService';
fn[SPECIAL_PROPS.TYPE] = 'service';
fn[SPECIAL_PROPS.VALUE] = 42;
$.register(fn);
}, (err) => {
assert.deepEqual(err.code, 'E_BAD_VALUED_NON_CONSTANT_INITIALIZER');
assert.deepEqual(err.params, ['myService']);
return true;
});
});
it('should fail with special autoload intitializer that is not a singleton', () => {
assert.throws(() => {
$.register(initializer({
name: '$autoload',
type: 'provider',
}, async () => ({ service: () => undefined })));
}, (err) => {
assert.deepEqual(err.code, 'E_BAD_AUTOLOADER');
assert.deepEqual(err.params, [false]);
return true;
});
});
});
describe('with services', () => {
it('should work with a service', () => {
$.register((0, _index.service)(timeService, 'time'));
});
it('should work when overriding a previously set service', async () => {
$.register((0, _index.service)(async () => () => 1, 'test'));
$.register((0, _index.service)(async () => () => 2, 'test'));
const {
test
} = await $.run(['test']);
_assert.default.deepEqual(test(), 2);
});
it('should fail when overriding an initialized service', async () => {
$.register((0, _index.service)(async () => () => 1, 'test'));
const {
test
} = await $.run(['test']);
_assert.default.deepEqual(test(), 1);
try {
$.register((0, _index.service)(async () => () => 2, 'test'));
throw new _yerror.default('E_UNEXPECTED_SUCCESS');
} catch (err) {
_assert.default.equal(err.code, 'E_INITIALIZER_ALREADY_INSTANCIATED');
}
});
describe('provider', () => {
it('should register provider', () => {
$.register(provider(hashProvider, 'hash'));
});
it('should fail with direct circular dependencies', () => {
assert.throws(() => {
$.register(provider(hashProvider, 'hash', ['hash']));
}, (err) => {
assert.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY');
assert.deepEqual(err.params, ['hash']);
return true;
});
});
it('should fail with direct circular dependencies on mapped services', () => {
assert.throws(() => {
$.register(provider(hashProvider, 'hash', ['hash>lol']));
}, (err) => {
assert.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY');
assert.deepEqual(err.params, ['hash']);
return true;
});
});
it('should fail with circular dependencies', () => {
assert.throws(() => {
$.register(provider(inject(['hash3'], hashProvider), 'hash'));
$.register(provider(inject(['hash'], hashProvider), 'hash1'));
$.register(provider(inject(['hash1'], hashProvider), 'hash2'));
$.register(provider(inject(['hash'], hashProvider), 'hash3'));
}, (err) => {
assert.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY');
assert.deepEqual(err.params, ['hash3', 'hash', 'hash3']);
return true;
});
});
it('should fail with deeper circular dependencies', () => {
assert.throws(() => {
$.register(provider(inject(['hash1'], hashProvider), 'hash'));
$.register(provider(inject(['hash2'], hashProvider), 'hash1'));
$.register(provider(inject(['hash3'], hashProvider), 'hash2'));
$.register(provider(inject(['hash'], hashProvider), 'hash3'));
}, (err) => {
assert.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY');
assert.deepEqual(err.params, [
'hash3',
'hash',
'hash1',
'hash2',
'hash3',
]);
return true;
});
});
it('should fail with circular dependencies on mapped services', () => {
assert.throws(() => {
$.register(provider(inject(['hash3>aHash3'], hashProvider), 'hash'));
$.register(provider(inject(['hash>aHash'], hashProvider), 'hash1'));
$.register(provider(inject(['hash1>aHash1'], hashProvider), 'hash2'));
$.register(provider(inject(['hash>aHash'], hashProvider), 'hash3'));
}, (err) => {
assert.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY');
assert.deepEqual(err.params, [
'hash3',
'hash>aHash',
'hash3>aHash3',
]);
return true;
});
});
});
describe('with providers', () => {
it('should work with a provider', () => {
$.register((0, _index.service)(hashProvider, 'hash'));
});
it('should work when overriding a previously set provider', async () => {
$.register((0, _index.initializer)({
type: 'provider',
name: 'test',
inject: []
}, async () => ({
service: 1
})));
$.register((0, _index.initializer)({
type: 'provider',
name: 'test',
inject: []
}, async () => ({
service: 2
})));
const {
test
} = await $.run(['test']);
_assert.default.deepEqual(test, 2);
});
it('should work when overriding a previously set singleton provider', async () => {
$.register((0, _index.initializer)({
type: 'provider',
name: 'test',
inject: [],
singleton: true
}, async () => ({
service: 1
})));
$.register((0, _index.initializer)({
type: 'provider',
name: 'test',
inject: []
}, async () => ({
service: 2
})));
const {
test
} = await $.run(['test']);
_assert.default.deepEqual(test, 2);
});
it('should fail when overriding an initialized provider', async () => {
$.register((0, _index.initializer)({
type: 'provider',
name: 'test',
inject: [],
singleton: true
}, async () => ({
service: 1
})));
const {
test
} = await $.run(['test']);
_assert.default.deepEqual(test, 1);
try {
$.register((0, _index.initializer)({
type: 'provider',
name: 'test',
inject: []
}, async () => ({
service: 2
})));
throw new _yerror.default('E_UNEXPECTED_SUCCESS');
} catch (err) {
_assert.default.equal(err.code, 'E_INITIALIZER_ALREADY_INSTANCIATED');
}
});
describe('run', () => {
it('should work with no dependencies', async () => {
const dependencies = await $.run([]);
assert.deepEqual(dependencies, {});
});
it('should work with constant dependencies', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
const dependencies = await $.run(['time', 'ENV']);
assert.deepEqual(Object.keys(dependencies), ['time', 'ENV']);
assert.deepEqual(dependencies, {
ENV,
time,
});
});
it('should work with service dependencies', async () => {
const wrappedSampleService = inject(['time'], async function sampleService({ time }) {
return Promise.resolve(typeof time);
});
$.register(service(wrappedSampleService, 'sample'));
$.register(constant('time', time));
const dependencies = await $.run(['sample']);
assert.deepEqual(Object.keys(dependencies), ['sample']);
assert.deepEqual(dependencies, {
sample: 'function',
});
});
it('should work with simple dependencies', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
const dependencies = await $.run(['time', 'hash']);
assert.deepEqual(Object.keys(dependencies), ['time', 'hash']);
assert.deepEqual(dependencies, {
hash: { ENV },
time,
});
});
it('should work with given optional dependencies', async () => {
$.register(constant('ENV', ENV));
$.register(constant('DEBUG', {}));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV', '?DEBUG']));
const dependencies = await $.run(['time', 'hash']);
assert.deepEqual(Object.keys(dependencies), ['time', 'hash']);
assert.deepEqual(dependencies, {
hash: { ENV, DEBUG: {} },
time,
});
});
it('should work with lacking optional dependencies', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV', '?DEBUG']));
const dependencies = await $.run(['time', 'hash']);
assert.deepEqual(Object.keys(dependencies), ['time', 'hash']);
assert.deepEqual(dependencies, {
hash: { ENV, DEBUG: undefined },
time,
});
});
it('should work with deeper dependencies', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
$.register(provider(hashProvider, 'hash1', ['hash']));
$.register(provider(hashProvider, 'hash2', ['hash1']));
$.register(provider(hashProvider, 'hash3', ['hash2']));
$.register(provider(hashProvider, 'hash4', ['hash3']));
$.register(provider(hashProvider, 'hash5', ['hash4']));
const dependencies = await $.run(['hash5', 'time']);
assert.deepEqual(Object.keys(dependencies), ['hash5', 'time']);
});
it('should instanciate services once', async () => {
const timeServiceStub = sinon.spy(timeService);
$.register(constant('ENV', ENV));
$.register(service(timeServiceStub, 'time'));
$.register(provider(hashProvider, 'hash', ['ENV', 'time']));
$.register(provider(hashProvider, 'hash2', ['ENV', 'time']));
$.register(provider(hashProvider, 'hash3', ['ENV', 'time']));
const dependencies = await $.run([
'hash',
'hash2',
'hash3',
'time',
]);
assert.deepEqual(Object.keys(dependencies), [
'hash',
'hash2',
'hash3',
'time',
]);
assert.deepEqual(timeServiceStub.args, [[{}]]);
});
it('should instanciate a single mapped service', async () => {
const providerStub = sinon.stub().returns(Promise.resolve({
service: 'stub',
}));
const providerStub2 = sinon.stub().returns(Promise.resolve({
service: 'stub2',
}));
$.register(provider(providerStub, 'mappedStub', ['stub2>mappedStub2']));
$.register(provider(providerStub2, 'mappedStub2'));
const dependencies = await $.run([
'stub>mappedStub',
]);
assert.deepEqual(dependencies, {
stub: 'stub',
});
assert.deepEqual(providerStub.args, [
[
{
stub2: 'stub2',
},
],
]);
});
it('should instanciate several services with mappings', async () => {
const timeServiceStub = sinon.spy(timeService);
$.register(constant('ENV', ENV));
$.register(singleton(service(timeServiceStub, 'aTime')));
$.register(provider(hashProvider, 'aHash', ['ENV', 'time>aTime']));
$.register(provider(hashProvider, 'aHash2', ['ENV', 'hash>aHash']));
$.register(provider(hashProvider, 'aHash3', ['ENV', 'hash>aHash']));
const dependencies = await $.run([
'hash2>aHash2',
'hash3>aHash3',
'time>aTime',
]);
assert.deepEqual(Object.keys(dependencies), ['hash2', 'hash3', 'time']);
assert.deepEqual(timeServiceStub.args, [[{}]]);
});
it('should fail with bad service', async () => {
$.register(service((() => undefined), 'lol'));
try {
await $.run(['lol']);
throw new Error('E_UNEXPECTED_SUCCESS');
}
catch (err) {
assert.deepEqual(err.code, 'E_BAD_SERVICE_PROMISE');
assert.deepEqual(err.params, ['lol']);
}
});
it('should fail with bad provider', async () => {
$.register(provider((() => undefined), 'lol'));
try {
await $.run(['lol']);
throw new Error('E_UNEXPECTED_SUCCESS');
}
catch (err) {
assert.deepEqual(err.code, 'E_BAD_SERVICE_PROVIDER');
assert.deepEqual(err.params, ['lol']);
}
});
it('should fail with bad service in a provider', async () => {
$.register(provider(() => Promise.resolve(), 'lol'));
try {
await $.run(['lol']);
throw new Error('E_UNEXPECTED_SUCCESS');
}
catch (err) {
assert.deepEqual(err.code, 'E_BAD_SERVICE_PROVIDER');
assert.deepEqual(err.params, ['lol']);
}
});
it('should fail with undeclared dependencies', async () => {
try {
await $.run(['lol']);
throw new Error('E_UNEXPECTED_SUCCESS');
}
catch (err) {
assert.deepEqual(err.code, 'E_UNMATCHED_DEPENDENCY');
assert.deepEqual(err.params, ['lol']);
}
});
it('should fail with undeclared dependencies upstream', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV', 'hash2']));
$.register(provider(hashProvider, 'hash2', ['ENV', 'lol']));
try {
await $.run(['time', 'hash']);
throw new Error('E_UNEXPECTED_SUCCESS');
}
catch (err) {
assert.deepEqual(err.code, 'E_UNMATCHED_DEPENDENCY');
assert.deepEqual(err.params, ['hash', 'hash2', 'lol']);
}
});
it('should provide a fatal error handler', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
$.register(provider(dbProvider, 'db', ['ENV']));
$.register(provider(processProvider, 'process', ['$fatalError']));
function processProvider({ $fatalError, }) {
return Promise.resolve({
service: {
fatalErrorPromise: $fatalError.promise,
},
});
}
async function dbProvider({ ENV }) {
let service;
const fatalErrorPromise = new Promise((resolve, reject) => {
service = Promise.resolve({
resolve,
reject,
ENV,
});
});
return {
service,
fatalErrorPromise,
};
}
const { process, db } = await $.run([
'time',
'hash',
'db',
'process',
]);
try {
db.reject(new Error('E_DB_ERROR'));
await process.fatalErrorPromise;
throw new Error('E_UNEXPECTED_SUCCESS');
}
catch (err) {
assert.deepEqual(err.message, 'E_DB_ERROR');
}
});
});
it('should fail when intitializer is no a function', () => {
_assert.default.throws(() => {
$.register('not_a_function');
}, err => {
_assert.default.deepEqual(err.code, 'E_BAD_INITIALIZER');
_assert.default.deepEqual(err.params, ['not_a_function']);
return true;
});
describe('autoload', () => {
it('should work with lacking autoloaded dependencies', async () => {
const autoloaderInitializer = initializer({
type: 'service',
name: '$autoload',
inject: [],
singleton: true,
}, async () => async (serviceName) => ({
path: '/path/of/debug',
initializer: initializer({
type: 'service',
name: 'DEBUG',
inject: [],
}, async () => 'THE_DEBUG:' + serviceName),
}));
const wrappedProvider = provider(hashProvider, 'hash', ['ENV', '?DEBUG']);
$.register(autoloaderInitializer);
$.register(wrappedProvider);
$.register(constant('ENV', ENV));
$.register(constant('time', time));
const dependencies = await $.run(['time', 'hash']);
assert.deepEqual(Object.keys(dependencies), ['time', 'hash']);
assert.deepEqual(dependencies, {
hash: { ENV, DEBUG: 'THE_DEBUG:DEBUG' },
time,
});
});
it('should work with deeper several lacking dependencies', async () => {
$.register(initializer({
name: '$autoload',
type: 'service',
singleton: true,
}, async () => async (serviceName) => ({
path: `/path/to/${serviceName}`,
initializer: initializer({
type: 'provider',
name: serviceName,
inject: 'hash2' === serviceName
? ['hash1']
: 'hash4' === serviceName
? ['hash3']
: [],
}, hashProvider),
})));
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
$.register(provider(hashProvider, 'hash1', ['hash']));
$.register(provider(hashProvider, 'hash3', ['hash2']));
$.register(provider(hashProvider, 'hash5', ['hash4']));
const dependencies = await $.run(['hash5', 'time']);
assert.deepEqual(Object.keys(dependencies), ['hash5', 'time']);
});
it('should work with various dependencies', async () => {
$.register(provider(hashProvider, 'hash', ['hash2']));
$.register(provider(hashProvider, 'hash3', ['?ENV']));
$.register(constant('DEBUG', 1));
$.register(initializer({
type: 'service',
name: '$autoload',
inject: ['?ENV', 'DEBUG'],
singleton: true,
}, async () => async (serviceName) => {
if ('ENV' === serviceName) {
throw new YError('E_UNMATCHED_DEPENDENCY');
}
return {
path: '/path/of/debug',
initializer: initializer({
type: 'service',
name: 'hash2',
inject: ['hash3'],
}, async () => 'THE_HASH:' + serviceName),
};
}));
const dependencies = await $.run(['hash', '?ENV']);
assert.deepEqual(Object.keys(dependencies), ['hash', 'ENV']);
});
it('should instanciate services once', async () => {
$.register(initializer({
name: '$autoload',
type: 'service',
singleton: true,
}, async () => async (serviceName) => ({
path: `/path/to/${serviceName}`,
initializer: initializer({
type: 'provider',
name: serviceName,
inject: ['ENV', 'time'],
}, hashProvider),
})));
const timeServiceStub = sinon.spy(timeService);
$.register(constant('ENV', ENV));
$.register(service(timeServiceStub, 'time'));
$.register(provider(hashProvider, 'hash', ['hash1', 'hash2', 'hash3']));
$.register(provider(hashProvider, 'hash_', ['hash1', 'hash2', 'hash3']));
const dependencies = await $.run([
'hash',
'hash_',
'hash3',
]);
assert.deepEqual(timeServiceStub.args, [[{}]]);
assert.deepEqual(Object.keys(dependencies), ['hash', 'hash_', 'hash3']);
});
it('should fail when autoload does not exists', async () => {
try {
await $.run(['test']);
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
assert.equal(err.code, 'E_UNMATCHED_DEPENDENCY');
}
});
it('should fail when autoloaded dependencies are not found', async () => {
$.register(initializer({
type: 'service',
name: '$autoload',
inject: [],
singleton: true,
}, async () => async (serviceName) => {
throw new YError('E_CANNOT_AUTOLOAD', serviceName);
}));
try {
await $.run(['test']);
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
assert.equal(err.code, 'E_CANNOT_AUTOLOAD');
assert.deepEqual(err.params, ['test']);
}
});
it('should fail when autoloaded dependencies are not initializers', async () => {
$.register(initializer({
type: 'service',
name: '$autoload',
inject: [],
singleton: true,
}, async () => async () => 'not_an_initializer'));
try {
await $.run(['test']);
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
assert.equal(err.code, 'E_BAD_AUTOLOADED_INITIALIZER');
assert.deepEqual(err.params, ['test', undefined]);
}
});
it('should fail when autoloaded dependencies are not right initializers', async () => {
$.register(initializer({
type: 'service',
name: '$autoload',
inject: [],
singleton: true,
}, async () => async (serviceName) => ({
path: '/path/of/debug',
initializer: initializer({
type: 'service',
name: 'not-' + serviceName,
inject: [],
}, async () => 'THE_TEST:' + serviceName),
})));
try {
await $.run(['test']);
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
assert.equal(err.code, 'E_AUTOLOADED_INITIALIZER_MISMATCH');
assert.deepEqual(err.params, ['test', 'not-test']);
}
});
it('should fail when autoload depends on existing autoloaded dependencies', async () => {
$.register(initializer({
type: 'service',
name: '$autoload',
inject: ['ENV'],
singleton: true,
}, async () => async (serviceName) => ({
path: '/path/of/debug',
initializer: initializer({
type: 'service',
name: 'DEBUG',
inject: [],
}, async () => 'THE_DEBUG:' + serviceName),
})));
try {
await $.run(['test']);
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
assert.equal(err.code, 'E_AUTOLOADER_DYNAMIC_DEPENDENCY');
assert.deepEqual(err.params, ['ENV']);
}
});
it('should work when autoload depends on optional and unexisting autoloaded dependencies', async () => {
$.register(initializer({
type: 'service',
name: '$autoload',
inject: ['?ENV'],
singleton: true,
}, async () => async (serviceName) => ({
path: `/path/of/${serviceName}`,
initializer: initializer({
type: 'service',
name: serviceName,
inject: [],
}, async () => `THE_${serviceName.toUpperCase()}:` + serviceName),
})));
const dependencies = await $.run(['test']);
assert.deepEqual(Object.keys(dependencies), ['test']);
});
it.skip('should work when autoload depends on deeper optional and unexisting autoloaded dependencies', async () => {
$.register(initializer({
type: 'service',
name: 'log',
inject: ['?LOG_ROUTING', '?LOGGER', '?debug'],
}, async () => {
return () => undefined;
}));
$.register(constant('LOGGER', 'LOGGER_CONSTANT'));
$.register(constant('debug', 'debug_value'));
$.register(initializer({
type: 'service',
name: '$autoload',
inject: ['?ENV', '?log'],
singleton: true,
}, async () => async (serviceName) => ({
path: `/path/of/${serviceName}`,
initializer: initializer({
type: 'service',
name: serviceName,
inject: [],
}, async () => `THE_${serviceName.toUpperCase()}:` + serviceName),
})));
const dependencies = await $.run(['test', 'log']);
assert.deepEqual(Object.keys(dependencies), ['test', 'log']);
});
});
it('should fail with no service name', () => {
_assert.default.throws(() => {
$.register(async () => undefined);
}, err => {
_assert.default.deepEqual(err.code, 'E_ANONYMOUS_ANALYZER');
_assert.default.deepEqual(err.params, []);
return true;
});
describe('$injector', () => {
it('should work with no dependencies', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
const dependencies = await $.run([
'time',
'hash',
'$injector',
]);
assert.deepEqual(Object.keys(dependencies), [
'time',
'hash',
'$injector',
]);
const injectDependencies = await dependencies.$injector([]);
assert.deepEqual(Object.keys(injectDependencies), []);
assert.deepEqual(injectDependencies, {});
});
it('should work with same dependencies then the running silo', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
const dependencies = await $.run([
'time',
'hash',
'$injector',
]);
assert.deepEqual(Object.keys(dependencies), [
'time',
'hash',
'$injector',
]);
const injectDependencies = await dependencies.$injector(['time', 'hash']);
assert.deepEqual(Object.keys(injectDependencies), ['time', 'hash']);
assert.deepEqual(injectDependencies, {
hash: { ENV },
time,
});
});
it('should work with name mapping', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
const dependencies = await $.run([
'time',
'hash',
'$injector',
]);
assert.deepEqual(Object.keys(dependencies), [
'time',
'hash',
'$injector',
]);
const injectDependencies = await dependencies.$injector([
'aTime>time',
'aHash>hash',
]);
assert.deepEqual(Object.keys(injectDependencies), ['aTime', 'aHash']);
assert.deepEqual(injectDependencies, {
aHash: { ENV },
aTime: time,
});
});
it('should work with non instanciated dependencies', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
const dependencies = await $.run([
'time',
'$injector',
]);
assert.deepEqual(Object.keys(dependencies), ['time', '$injector']);
const injectDependencies = await dependencies.$injector(['time', 'hash']);
assert.deepEqual(Object.keys(injectDependencies), ['time', 'hash']);
assert.deepEqual(injectDependencies, {
hash: { ENV },
time,
});
});
it('should create dependencies when not declared as singletons', async () => {
$.register(constant('ENV', ENV));
$.register(provider(hashProvider, 'hash', ['ENV']));
const [{ hash }, { hash: sameHash }] = await Promise.all([
$.run(['hash']),
$.run(['hash']),
]);
assert.notEqual(hash, sameHash);
const { hash: yaSameHash } = await $.run(['hash']);
assert.notEqual(hash, yaSameHash);
});
it('should reuse dependencies when declared as singletons', async () => {
$.register(constant('ENV', ENV));
$.register(provider(hashProvider, 'hash', ['ENV'], true));
$.register(provider(hashProvider, 'hash2', ['ENV'], true));
const [{ hash, hash2 }, { hash: sameHash, hash2: sameHash2 }] = await Promise.all([
$.run(['hash']),
$.run(['hash']),
$.run(['hash2']),
$.run(['hash2']),
]);
assert.equal(hash, sameHash);
assert.equal(hash2, sameHash2);
const { hash: yaSameHash } = await $.run(['hash']);
assert.equal(hash, yaSameHash);
});
});
it('should fail with a bad service type', () => {
_assert.default.throws(() => {
const fn = async () => undefined;
fn[_index.SPECIAL_PROPS.NAME] = 'test';
fn[_index.SPECIAL_PROPS.TYPE] = 'not_allowed_type';
$.register(fn);
}, err => {
_assert.default.deepEqual(err.code, 'E_BAD_INITIALIZER_TYPE');
_assert.default.deepEqual(err.params, ['test', 'not_allowed_type', _util.ALLOWED_INITIALIZER_TYPES]);
return true;
});
});
it('should fail with an undefined constant', () => {
_assert.default.throws(() => {
const fn = async () => undefined;
fn[_index.SPECIAL_PROPS.NAME] = 'THE_NUMBER';
fn[_index.SPECIAL_PROPS.TYPE] = 'constant';
fn[_index.SPECIAL_PROPS.VALUE] = undefined;
$.register(fn);
}, err => {
_assert.default.deepEqual(err.code, 'E_UNDEFINED_CONSTANT_INITIALIZER');
_assert.default.deepEqual(err.params, ['THE_NUMBER']);
return true;
});
});
it('should fail with a non constant that has a value', () => {
_assert.default.throws(() => {
const fn = async () => undefined;
fn[_index.SPECIAL_PROPS.NAME] = 'myService';
fn[_index.SPECIAL_PROPS.TYPE] = 'service';
fn[_index.SPECIAL_PROPS.VALUE] = 42;
$.register(fn);
}, err => {
_assert.default.deepEqual(err.code, 'E_BAD_VALUED_NON_CONSTANT_INITIALIZER');
_assert.default.deepEqual(err.params, ['myService']);
return true;
});
});
it('should fail with special autoload intitializer that is not a singleton', () => {
_assert.default.throws(() => {
$.register((0, _index.initializer)({
name: '$autoload',
type: 'provider'
}, async () => ({
service: () => undefined
})));
}, err => {
_assert.default.deepEqual(err.code, 'E_BAD_AUTOLOADER');
_assert.default.deepEqual(err.params, [false]);
return true;
});
});
});
describe('provider', () => {
it('should register provider', () => {
$.register((0, _index.provider)(hashProvider, 'hash'));
});
it('should fail with direct circular dependencies', () => {
_assert.default.throws(() => {
$.register((0, _index.provider)(hashProvider, 'hash', ['hash']));
}, err => {
_assert.default.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY');
_assert.default.deepEqual(err.params, ['hash']);
return true;
});
});
it('should fail with direct circular dependencies on mapped services', () => {
_assert.default.throws(() => {
$.register((0, _index.provider)(hashProvider, 'hash', ['hash>lol']));
}, err => {
_assert.default.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY');
_assert.default.deepEqual(err.params, ['hash']);
return true;
});
});
it('should fail with circular dependencies', () => {
_assert.default.throws(() => {
$.register((0, _index.provider)((0, _index.inject)(['hash3'], hashProvider), 'hash'));
$.register((0, _index.provider)((0, _index.inject)(['hash'], hashProvider), 'hash1'));
$.register((0, _index.provider)((0, _index.inject)(['hash1'], hashProvider), 'hash2'));
$.register((0, _index.provider)((0, _index.inject)(['hash'], hashProvider), 'hash3'));
}, err => {
_assert.default.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY');
_assert.default.deepEqual(err.params, ['hash3', 'hash', 'hash3']);
return true;
});
});
it('should fail with deeper circular dependencies', () => {
_assert.default.throws(() => {
$.register((0, _index.provider)((0, _index.inject)(['hash1'], hashProvider), 'hash'));
$.register((0, _index.provider)((0, _index.inject)(['hash2'], hashProvider), 'hash1'));
$.register((0, _index.provider)((0, _index.inject)(['hash3'], hashProvider), 'hash2'));
$.register((0, _index.provider)((0, _index.inject)(['hash'], hashProvider), 'hash3'));
}, err => {
_assert.default.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY');
_assert.default.deepEqual(err.params, ['hash3', 'hash', 'hash1', 'hash2', 'hash3']);
return true;
});
});
it('should fail with circular dependencies on mapped services', () => {
_assert.default.throws(() => {
$.register((0, _index.provider)((0, _index.inject)(['hash3>aHash3'], hashProvider), 'hash'));
$.register((0, _index.provider)((0, _index.inject)(['hash>aHash'], hashProvider), 'hash1'));
$.register((0, _index.provider)((0, _index.inject)(['hash1>aHash1'], hashProvider), 'hash2'));
$.register((0, _index.provider)((0, _index.inject)(['hash>aHash'], hashProvider), 'hash3'));
}, err => {
_assert.default.deepEqual(err.code, 'E_CIRCULAR_DEPENDENCY');
_assert.default.deepEqual(err.params, ['hash3', 'hash>aHash', 'hash3>aHash3']);
return true;
});
});
});
describe('run', () => {
it('should work with no dependencies', async () => {
const dependencies = await $.run([]);
_assert.default.deepEqual(dependencies, {});
});
it('should work with constant dependencies', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
const dependencies = await $.run(['time', 'ENV']);
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'ENV']);
_assert.default.deepEqual(dependencies, {
ENV,
time
});
});
it('should work with service dependencies', async () => {
const wrappedSampleService = (0, _index.inject)(['time'], async function sampleService({
time
}) {
return Promise.resolve(typeof time);
});
$.register((0, _index.service)(wrappedSampleService, 'sample'));
$.register((0, _index.constant)('time', time));
const dependencies = await $.run(['sample']);
_assert.default.deepEqual(Object.keys(dependencies), ['sample']);
_assert.default.deepEqual(dependencies, {
sample: 'function'
});
});
it('should work with simple dependencies', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
const dependencies = await $.run(['time', 'hash']);
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash']);
_assert.default.deepEqual(dependencies, {
hash: {
ENV
},
time
});
});
it('should work with given optional dependencies', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('DEBUG', {}));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV', '?DEBUG']));
const dependencies = await $.run(['time', 'hash']);
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash']);
_assert.default.deepEqual(dependencies, {
hash: {
ENV,
DEBUG: {}
},
time
});
});
it('should work with lacking optional dependencies', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV', '?DEBUG']));
const dependencies = await $.run(['time', 'hash']);
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash']);
_assert.default.deepEqual(dependencies, {
hash: {
ENV,
DEBUG: undefined
},
time
});
});
it('should work with deeper dependencies', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
$.register((0, _index.provider)(hashProvider, 'hash1', ['hash']));
$.register((0, _index.provider)(hashProvider, 'hash2', ['hash1']));
$.register((0, _index.provider)(hashProvider, 'hash3', ['hash2']));
$.register((0, _index.provider)(hashProvider, 'hash4', ['hash3']));
$.register((0, _index.provider)(hashProvider, 'hash5', ['hash4']));
const dependencies = await $.run(['hash5', 'time']);
_assert.default.deepEqual(Object.keys(dependencies), ['hash5', 'time']);
});
it('should instanciate services once', async () => {
const timeServiceStub = _sinon.default.spy(timeService);
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.service)(timeServiceStub, 'time'));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV', 'time']));
$.register((0, _index.provider)(hashProvider, 'hash2', ['ENV', 'time']));
$.register((0, _index.provider)(hashProvider, 'hash3', ['ENV', 'time']));
const dependencies = await $.run(['hash', 'hash2', 'hash3', 'time']);
_assert.default.deepEqual(Object.keys(dependencies), ['hash', 'hash2', 'hash3', 'time']);
_assert.default.deepEqual(timeServiceStub.args, [[{}]]);
});
it('should instanciate a single mapped service', async () => {
const providerStub = _sinon.default.stub().returns(Promise.resolve({
service: 'stub'
}));
const providerStub2 = _sinon.default.stub().returns(Promise.resolve({
service: 'stub2'
}));
$.register((0, _index.provider)(providerStub, 'mappedStub', ['stub2>mappedStub2']));
$.register((0, _index.provider)(providerStub2, 'mappedStub2'));
const dependencies = await $.run(['stub>mappedStub']);
_assert.default.deepEqual(dependencies, {
stub: 'stub'
});
_assert.default.deepEqual(providerStub.args, [[{
stub2: 'stub2'
}]]);
});
it('should instanciate several services with mappings', async () => {
const timeServiceStub = _sinon.default.spy(timeService);
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.singleton)((0, _index.service)(timeServiceStub, 'aTime')));
$.register((0, _index.provider)(hashProvider, 'aHash', ['ENV', 'time>aTime']));
$.register((0, _index.provider)(hashProvider, 'aHash2', ['ENV', 'hash>aHash']));
$.register((0, _index.provider)(hashProvider, 'aHash3', ['ENV', 'hash>aHash']));
const dependencies = await $.run(['hash2>aHash2', 'hash3>aHash3', 'time>aTime']);
_assert.default.deepEqual(Object.keys(dependencies), ['hash2', 'hash3', 'time']);
_assert.default.deepEqual(timeServiceStub.args, [[{}]]);
});
it('should fail with bad service', async () => {
$.register((0, _index.service)(() => undefined, 'lol'));
try {
await $.run(['lol']);
throw new Error('E_UNEXPECTED_SUCCESS');
} catch (err) {
_assert.default.deepEqual(err.code, 'E_BAD_SERVICE_PROMISE');
_assert.default.deepEqual(err.params, ['lol']);
}
});
it('should fail with bad provider', async () => {
$.register((0, _index.provider)(() => undefined, 'lol'));
try {
await $.run(['lol']);
throw new Error('E_UNEXPECTED_SUCCESS');
} catch (err) {
_assert.default.deepEqual(err.code, 'E_BAD_SERVICE_PROVIDER');
_assert.default.deepEqual(err.params, ['lol']);
}
});
it('should fail with bad service in a provider', async () => {
$.register((0, _index.provider)(() => Promise.resolve(), 'lol'));
try {
await $.run(['lol']);
throw new Error('E_UNEXPECTED_SUCCESS');
} catch (err) {
_assert.default.deepEqual(err.code, 'E_BAD_SERVICE_PROVIDER');
_assert.default.deepEqual(err.params, ['lol']);
}
});
it('should fail with undeclared dependencies', async () => {
try {
await $.run(['lol']);
throw new Error('E_UNEXPECTED_SUCCESS');
} catch (err) {
_assert.default.deepEqual(err.code, 'E_UNMATCHED_DEPENDENCY');
_assert.default.deepEqual(err.params, ['lol']);
}
});
it('should fail with undeclared dependencies upstream', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV', 'hash2']));
$.register((0, _index.provider)(hashProvider, 'hash2', ['ENV', 'lol']));
try {
await $.run(['time', 'hash']);
throw new Error('E_UNEXPECTED_SUCCESS');
} catch (err) {
_assert.default.deepEqual(err.code, 'E_UNMATCHED_DEPENDENCY');
_assert.default.deepEqual(err.params, ['hash', 'hash2', 'lol']);
}
});
it('should provide a fatal error handler', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
$.register((0, _index.provider)(dbProvider, 'db', ['ENV']));
$.register((0, _index.provider)(processProvider, 'process', ['$fatalError']));
function processProvider({
$fatalError
}) {
return Promise.resolve({
service: {
fatalErrorPromise: $fatalError.promise
}
describe('destroy', () => {
it('should work even with one silo and no dependencies', async () => {
assert.equal(typeof $.destroy, 'function');
const dependencies = await $.run(['$instance']);
await dependencies.$instance.destroy();
});
}
async function dbProvider({
ENV
}) {
let service;
const fatalErrorPromise = new Promise((resolve, reject) => {
service = Promise.resolve({
resolve,
reject,
ENV
});
it('should work with several silos and dependencies', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV'], true));
$.register(provider(hashProvider, 'hash1', ['ENV']));
$.register(provider(hashProvider, 'hash2', ['ENV']));
const [dependencies] = await Promise.all([
$.run(['$instance']),
$.run(['ENV', 'hash', 'hash1', 'time']),
$.run(['ENV', 'hash', 'hash2']),
]);
assert.equal(typeof dependencies.$instance.destroy, 'function');
await $.destroy();
});
return {
service,
fatalErrorPromise
};
}
const {
process,
db
} = await $.run(['time', 'hash', 'db', 'process']);
try {
db.reject(new Error('E_DB_ERROR'));
await process.fatalErrorPromise;
throw new Error('E_UNEXPECTED_SUCCESS');
} catch (err) {
_assert.default.deepEqual(err.message, 'E_DB_ERROR');
}
it('should work when trigered from several silos simultaneously', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
$.register(provider(hashProvider, 'hash1', ['ENV']));
$.register(provider(hashProvider, 'hash2', ['ENV']));
const dependenciesBuckets = await Promise.all([
$.run(['$instance']),
$.run([
'$instance',
'ENV',
'hash',
'hash1',
'time',
]),
$.run(['$instance', 'ENV', 'hash', 'hash2']),
]);
await Promise.all(dependenciesBuckets.map((dependencies) => dependencies.$instance.destroy()));
});
it('should work when a silo shutdown is in progress', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
$.register(provider(hashProvider, 'hash1', ['ENV']));
$.register(provider(hashProvider, 'hash2', ['ENV']));
const [dependencies1, dependencies2] = await Promise.all([
$.run(['$instance']),
$.run([
'$dispose',
'ENV',
'hash',
'hash1',
'time',
]),
$.run(['ENV', 'hash', 'hash2']),
]);
await Promise.all([
dependencies2.$dispose(),
dependencies1.$instance.destroy(),
]);
});
it('should disallow new runs', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
$.register(provider(hashProvider, 'hash1', ['ENV']));
const dependencies = await $.run(['$instance']);
assert.equal(typeof dependencies.$instance.destroy, 'function');
await dependencies.$instance.destroy();
try {
await $.run(['ENV', 'hash', 'hash1']);
throw new YError('E_UNEXPECTED_SUCCES');
}
catch (err) {
assert.equal(err.code, 'E_INSTANCE_DESTROYED');
}
});
});
});
describe('autoload', () => {
it('should work with lacking autoloaded dependencies', async () => {
const autoloaderInitializer = (0, _index.initializer)({
type: 'service',
name: '$autoload',
inject: [],
singleton: true
}, async () => async serviceName => ({
path: '/path/of/debug',
initializer: (0, _index.initializer)({
type: 'service',
name: 'DEBUG',
inject: []
}, async () => 'THE_DEBUG:' + serviceName)
}));
const wrappedProvider = (0, _index.provider)(hashProvider, 'hash', ['ENV', '?DEBUG']);
$.register(autoloaderInitializer);
$.register(wrappedProvider);
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
const dependencies = await $.run(['time', 'hash']);
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash']);
_assert.default.deepEqual(dependencies, {
hash: {
ENV,
DEBUG: 'THE_DEBUG:DEBUG'
},
time
});
});
it('should work with deeper several lacking dependencies', async () => {
$.register((0, _index.initializer)({
name: '$autoload',
type: 'service',
singleton: true
}, async () => async serviceName => ({
path: `/path/to/${serviceName}`,
initializer: (0, _index.initializer)({
type: 'provider',
name: serviceName,
inject: 'hash2' === serviceName ? ['hash1'] : 'hash4' === serviceName ? ['hash3'] : []
}, hashProvider)
})));
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
$.register((0, _index.provider)(hashProvider, 'hash1', ['hash']));
$.register((0, _index.provider)(hashProvider, 'hash3', ['hash2']));
$.register((0, _index.provider)(hashProvider, 'hash5', ['hash4']));
const dependencies = await $.run(['hash5', 'time']);
_assert.default.deepEqual(Object.keys(dependencies), ['hash5', 'time']);
});
it('should work with various dependencies', async () => {
$.register((0, _index.provider)(hashProvider, 'hash', ['hash2']));
$.register((0, _index.provider)(hashProvider, 'hash3', ['?ENV']));
$.register((0, _index.constant)('DEBUG', 1));
$.register((0, _index.initializer)({
type: 'service',
name: '$autoload',
inject: ['?ENV', 'DEBUG'],
singleton: true
}, async () => async serviceName => {
if ('ENV' === serviceName) {
throw new _yerror.default('E_UNMATCHED_DEPENDENCY');
}
return {
path: '/path/of/debug',
initializer: (0, _index.initializer)({
type: 'service',
name: 'hash2',
inject: ['hash3']
}, async () => 'THE_HASH:' + serviceName)
};
}));
const dependencies = await $.run(['hash', '?ENV']);
_assert.default.deepEqual(Object.keys(dependencies), ['hash', 'ENV']);
});
it('should instanciate services once', async () => {
$.register((0, _index.initializer)({
name: '$autoload',
type: 'service',
singleton: true
}, async () => async serviceName => ({
path: `/path/to/${serviceName}`,
initializer: (0, _index.initializer)({
type: 'provider',
name: serviceName,
inject: ['ENV', 'time']
}, hashProvider)
})));
const timeServiceStub = _sinon.default.spy(timeService);
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.service)(timeServiceStub, 'time'));
$.register((0, _index.provider)(hashProvider, 'hash', ['hash1', 'hash2', 'hash3']));
$.register((0, _index.provider)(hashProvider, 'hash_', ['hash1', 'hash2', 'hash3']));
const dependencies = await $.run(['hash', 'hash_', 'hash3']);
_assert.default.deepEqual(timeServiceStub.args, [[{}]]);
_assert.default.deepEqual(Object.keys(dependencies), ['hash', 'hash_', 'hash3']);
});
it('should fail when autoload does not exists', async () => {
try {
await $.run(['test']);
throw new _yerror.default('E_UNEXPECTED_SUCCESS');
} catch (err) {
_assert.default.equal(err.code, 'E_UNMATCHED_DEPENDENCY');
}
});
it('should fail when autoloaded dependencies are not found', async () => {
$.register((0, _index.initializer)({
type: 'service',
name: '$autoload',
inject: [],
singleton: true
}, async () => async serviceName => {
throw new _yerror.default('E_CANNOT_AUTOLOAD', serviceName);
}));
try {
await $.run(['test']);
throw new _yerror.default('E_UNEXPECTED_SUCCESS');
} catch (err) {
_assert.default.equal(err.code, 'E_CANNOT_AUTOLOAD');
_assert.default.deepEqual(err.params, ['test']);
}
});
it('should fail when autoloaded dependencies are not initializers', async () => {
$.register((0, _index.initializer)({
type: 'service',
name: '$autoload',
inject: [],
singleton: true
}, async () => async () => 'not_an_initializer'));
try {
await $.run(['test']);
throw new _yerror.default('E_UNEXPECTED_SUCCESS');
} catch (err) {
_assert.default.equal(err.code, 'E_BAD_AUTOLOADED_INITIALIZER');
_assert.default.deepEqual(err.params, ['test', undefined]);
}
});
it('should fail when autoloaded dependencies are not right initializers', async () => {
$.register((0, _index.initializer)({
type: 'service',
name: '$autoload',
inject: [],
singleton: true
}, async () => async serviceName => ({
path: '/path/of/debug',
initializer: (0, _index.initializer)({
type: 'service',
name: 'not-' + serviceName,
inject: []
}, async () => 'THE_TEST:' + serviceName)
})));
try {
await $.run(['test']);
throw new _yerror.default('E_UNEXPECTED_SUCCESS');
} catch (err) {
_assert.default.equal(err.code, 'E_AUTOLOADED_INITIALIZER_MISMATCH');
_assert.default.deepEqual(err.params, ['test', 'not-test']);
}
});
it('should fail when autoload depends on existing autoloaded dependencies', async () => {
$.register((0, _index.initializer)({
type: 'service',
name: '$autoload',
inject: ['ENV'],
singleton: true
}, async () => async serviceName => ({
path: '/path/of/debug',
initializer: (0, _index.initializer)({
type: 'service',
name: 'DEBUG',
inject: []
}, async () => 'THE_DEBUG:' + serviceName)
})));
try {
await $.run(['test']);
throw new _yerror.default('E_UNEXPECTED_SUCCESS');
} catch (err) {
_assert.default.equal(err.code, 'E_AUTOLOADER_DYNAMIC_DEPENDENCY');
_assert.default.deepEqual(err.params, ['ENV']);
}
});
it('should work when autoload depends on optional and unexisting autoloaded dependencies', async () => {
$.register((0, _index.initializer)({
type: 'service',
name: '$autoload',
inject: ['?ENV'],
singleton: true
}, async () => async serviceName => ({
path: `/path/of/${serviceName}`,
initializer: (0, _index.initializer)({
type: 'service',
name: serviceName,
inject: []
}, async () => `THE_${serviceName.toUpperCase()}:` + serviceName)
})));
const dependencies = await $.run(['test']);
_assert.default.deepEqual(Object.keys(dependencies), ['test']);
});
it.skip('should work when autoload depends on deeper optional and unexisting autoloaded dependencies', async () => {
$.register((0, _index.initializer)({
type: 'service',
name: 'log',
inject: ['?LOG_ROUTING', '?LOGGER', '?debug']
}, async () => {
return () => undefined;
}));
$.register((0, _index.constant)('LOGGER', 'LOGGER_CONSTANT'));
$.register((0, _index.constant)('debug', 'debug_value'));
$.register((0, _index.initializer)({
type: 'service',
name: '$autoload',
inject: ['?ENV', '?log'],
singleton: true
}, async () => async serviceName => ({
path: `/path/of/${serviceName}`,
initializer: (0, _index.initializer)({
type: 'service',
name: serviceName,
inject: []
}, async () => `THE_${serviceName.toUpperCase()}:` + serviceName)
})));
const dependencies = await $.run(['test', 'log']);
_assert.default.deepEqual(Object.keys(dependencies), ['test', 'log']);
});
});
describe('$injector', () => {
it('should work with no dependencies', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
const dependencies = await $.run(['time', 'hash', '$injector']);
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash', '$injector']);
const injectDependencies = await dependencies.$injector([]);
_assert.default.deepEqual(Object.keys(injectDependencies), []);
_assert.default.deepEqual(injectDependencies, {});
});
it('should work with same dependencies then the running silo', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
const dependencies = await $.run(['time', 'hash', '$injector']);
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash', '$injector']);
const injectDependencies = await dependencies.$injector(['time', 'hash']);
_assert.default.deepEqual(Object.keys(injectDependencies), ['time', 'hash']);
_assert.default.deepEqual(injectDependencies, {
hash: {
ENV
},
time
});
});
it('should work with name mapping', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
const dependencies = await $.run(['time', 'hash', '$injector']);
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash', '$injector']);
const injectDependencies = await dependencies.$injector(['aTime>time', 'aHash>hash']);
_assert.default.deepEqual(Object.keys(injectDependencies), ['aTime', 'aHash']);
_assert.default.deepEqual(injectDependencies, {
aHash: {
ENV
},
aTime: time
});
});
it('should work with non instanciated dependencies', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
const dependencies = await $.run(['time', '$injector']);
_assert.default.deepEqual(Object.keys(dependencies), ['time', '$injector']);
const injectDependencies = await dependencies.$injector(['time', 'hash']);
_assert.default.deepEqual(Object.keys(injectDependencies), ['time', 'hash']);
_assert.default.deepEqual(injectDependencies, {
hash: {
ENV
},
time
});
});
it('should create dependencies when not declared as singletons', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
const [{
hash
}, {
hash: sameHash
}] = await Promise.all([$.run(['hash']), $.run(['hash'])]);
_assert.default.notEqual(hash, sameHash);
const {
hash: yaSameHash
} = await $.run(['hash']);
_assert.default.notEqual(hash, yaSameHash);
});
it('should reuse dependencies when declared as singletons', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV'], true));
$.register((0, _index.provider)(hashProvider, 'hash2', ['ENV'], true));
const [{
hash,
hash2
}, {
hash: sameHash,
hash2: sameHash2
}] = await Promise.all([$.run(['hash']), $.run(['hash']), $.run(['hash2']), $.run(['hash2'])]);
_assert.default.equal(hash, sameHash);
_assert.default.equal(hash2, sameHash2);
const {
hash: yaSameHash
} = await $.run(['hash']);
_assert.default.equal(hash, yaSameHash);
});
});
describe('destroy', () => {
it('should work even with one silo and no dependencies', async () => {
_assert.default.equal(typeof $.destroy, 'function');
const dependencies = await $.run(['$instance']);
await dependencies.$instance.destroy();
});
it('should work with several silos and dependencies', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV'], true));
$.register((0, _index.provider)(hashProvider, 'hash1', ['ENV']));
$.register((0, _index.provider)(hashProvider, 'hash2', ['ENV']));
const [dependencies] = await Promise.all([$.run(['$instance']), $.run(['ENV', 'hash', 'hash1', 'time']), $.run(['ENV', 'hash', 'hash2'])]);
_assert.default.equal(typeof dependencies.$instance.destroy, 'function');
await $.destroy();
});
it('should work when trigered from several silos simultaneously', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
$.register((0, _index.provider)(hashProvider, 'hash1', ['ENV']));
$.register((0, _index.provider)(hashProvider, 'hash2', ['ENV']));
const dependenciesBuckets = await Promise.all([$.run(['$instance']), $.run(['$instance', 'ENV', 'hash', 'hash1', 'time']), $.run(['$instance', 'ENV', 'hash', 'hash2'])]);
await Promise.all(dependenciesBuckets.map(dependencies => dependencies.$instance.destroy()));
});
it('should work when a silo shutdown is in progress', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
$.register((0, _index.provider)(hashProvider, 'hash1', ['ENV']));
$.register((0, _index.provider)(hashProvider, 'hash2', ['ENV']));
const [dependencies1, dependencies2] = await Promise.all([$.run(['$instance']), $.run(['$dispose', 'ENV', 'hash', 'hash1', 'time']), $.run(['ENV', 'hash', 'hash2'])]);
await Promise.all([dependencies2.$dispose(), dependencies1.$instance.destroy()]);
});
it('should disallow new runs', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
$.register((0, _index.provider)(hashProvider, 'hash1', ['ENV']));
const dependencies = await $.run(['$instance']);
_assert.default.equal(typeof dependencies.$instance.destroy, 'function');
await dependencies.$instance.destroy();
try {
await $.run(['ENV', 'hash', 'hash1']);
throw new _yerror.default('E_UNEXPECTED_SUCCES');
} catch (err) {
_assert.default.equal(err.code, 'E_INSTANCE_DESTROYED');
}
});
});
describe('$dispose', () => {
it('should work with no dependencies', async () => {
const dependencies = await $.run(['$dispose']);
_assert.default.equal(typeof dependencies.$dispose, 'function');
return dependencies.$dispose();
});
it('should work with constant dependencies', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
const dependencies = await $.run(['time', 'ENV', '$dispose']);
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'ENV', '$dispose']);
await dependencies.$dispose();
});
it('should work with simple dependencies', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
const dependencies = await $.run(['time', 'hash', '$dispose']);
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash', '$dispose']);
await dependencies.$dispose();
});
it('should work with deeper dependencies', async () => {
let shutdownCallResolve;
let shutdownResolve;
const shutdownCallPromise = new Promise(resolve => {
shutdownCallResolve = resolve;
});
const shutdownStub = _sinon.default.spy(() => {
shutdownCallResolve();
return new Promise(resolve => {
shutdownResolve = resolve;
describe('$dispose', () => {
it('should work with no dependencies', async () => {
const dependencies = await $.run(['$dispose']);
assert.equal(typeof dependencies.$dispose, 'function');
return dependencies.$dispose();
});
});
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
$.register((0, _index.provider)(hashProvider, 'hash1', ['hash']));
$.register((0, _index.provider)(hashProvider, 'hash2', ['hash1']));
$.register((0, _index.provider)(hashProvider, 'hash3', ['hash2']));
$.register((0, _index.provider)(hashProvider, 'hash4', ['hash3']));
$.register((0, _index.provider)(hashProvider, 'hash5', ['hash4']));
$.register((0, _index.provider)(() => Promise.resolve({
service: {
shutdownStub,
shutdownResolve
},
dispose: shutdownStub
}), 'shutdownChecker', ['hash4']));
const dependencies = await $.run(['hash5', 'time', '$dispose', 'shutdownChecker']);
_assert.default.deepEqual(Object.keys(dependencies), ['hash5', 'time', '$dispose', 'shutdownChecker']);
const finalPromise = shutdownCallPromise.then(() => {
_assert.default.deepEqual(shutdownStub.args, [[]]);
shutdownResolve();
});
await dependencies.$dispose();
await finalPromise;
it('should work with constant dependencies', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
const dependencies = await $.run([
'time',
'ENV',
'$dispose',
]);
assert.deepEqual(Object.keys(dependencies), ['time', 'ENV', '$dispose']);
await dependencies.$dispose();
});
it('should work with simple dependencies', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
const dependencies = await $.run([
'time',
'hash',
'$dispose',
]);
assert.deepEqual(Object.keys(dependencies), ['time', 'hash', '$dispose']);
await dependencies.$dispose();
});
it('should work with deeper dependencies', async () => {
let shutdownCallResolve;
let shutdownResolve;
const shutdownCallPromise = new Promise((resolve) => {
shutdownCallResolve = resolve;
});
const shutdownStub = sinon.spy(() => {
shutdownCallResolve();
return new Promise((resolve) => {
shutdownResolve = resolve;
});
});
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
$.register(provider(hashProvider, 'hash1', ['hash']));
$.register(provider(hashProvider, 'hash2', ['hash1']));
$.register(provider(hashProvider, 'hash3', ['hash2']));
$.register(provider(hashProvider, 'hash4', ['hash3']));
$.register(provider(hashProvider, 'hash5', ['hash4']));
$.register(provider(() => Promise.resolve({
service: {
shutdownStub,
shutdownResolve,
},
dispose: shutdownStub,
}), 'shutdownChecker', ['hash4']));
const dependencies = await $.run([
'hash5',
'time',
'$dispose',
'shutdownChecker',
]);
assert.deepEqual(Object.keys(dependencies), [
'hash5',
'time',
'$dispose',
'shutdownChecker',
]);
const finalPromise = shutdownCallPromise.then(() => {
assert.deepEqual(shutdownStub.args, [[]]);
shutdownResolve();
});
await dependencies.$dispose();
await finalPromise;
});
it('should work with deeper multi used dependencies', async () => {
let shutdownCallResolve;
let shutdownResolve;
const shutdownCallPromise = new Promise((resolve) => {
shutdownCallResolve = resolve;
});
const shutdownStub = sinon.spy(() => {
shutdownCallResolve();
return new Promise((resolve) => {
shutdownResolve = resolve;
});
});
$.register(constant('ENV', ENV));
$.register(provider(hashProvider, 'hash', ['ENV']));
$.register(provider(() => Promise.resolve({
service: {
shutdownStub,
shutdownResolve,
},
dispose: shutdownStub,
}), 'shutdownChecker', ['hash']));
$.register(provider(hashProvider, 'hash1', ['shutdownChecker']));
$.register(provider(hashProvider, 'hash2', ['shutdownChecker']));
const dependencies = await $.run([
'hash1',
'hash2',
'$dispose',
'shutdownChecker',
]);
assert.deepEqual(Object.keys(dependencies), [
'hash1',
'hash2',
'$dispose',
'shutdownChecker',
]);
const finalPromise = shutdownCallPromise.then(() => {
assert.deepEqual(shutdownStub.args, [[]]);
shutdownResolve();
});
await dependencies.$dispose();
await finalPromise;
});
it('should delay service shutdown to their deeper dependencies', async () => {
const servicesShutdownCalls = sinon.spy(() => Promise.resolve());
$.register(provider(() => Promise.resolve({
service: {},
dispose: servicesShutdownCalls.bind(null, 'hash'),
}), 'hash'));
$.register(provider(() => Promise.resolve({
service: {},
dispose: servicesShutdownCalls.bind(null, 'hash1'),
}), 'hash1', ['hash']));
$.register(provider(() => Promise.resolve({
service: {},
dispose: servicesShutdownCalls.bind(null, 'hash2'),
}), 'hash2', ['hash1', 'hash']));
const dependencies = await $.run([
'hash2',
'$dispose',
]);
assert.deepEqual(Object.keys(dependencies), ['hash2', '$dispose']);
await dependencies.$dispose();
assert.deepEqual(servicesShutdownCalls.args, [
['hash2'],
['hash1'],
['hash'],
]);
});
it('should not shutdown singleton dependencies if used elsewhere', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV'], true));
const { hash } = await $.run(['time', 'hash']);
const dependencies = await $.run([
'time',
'hash',
'$dispose',
]);
assert.equal(dependencies.hash, hash);
await dependencies.$dispose();
const newDependencies = await $.run([
'time',
'hash',
]);
assert.equal(newDependencies.hash, hash);
});
it('should shutdown singleton dependencies if not used elsewhere', async () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV'], true));
const { hash, $dispose } = await $.run([
'time',
'hash',
'$dispose',
]);
await $dispose();
const dependencies = await $.run(['time', 'hash']);
assert.notEqual(dependencies.hash, hash);
});
});
it('should work with deeper multi used dependencies', async () => {
let shutdownCallResolve;
let shutdownResolve;
const shutdownCallPromise = new Promise(resolve => {
shutdownCallResolve = resolve;
});
const shutdownStub = _sinon.default.spy(() => {
shutdownCallResolve();
return new Promise(resolve => {
shutdownResolve = resolve;
describe('toMermaidGraph', () => {
it('should print nothing when no dependency', () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
assert.equal($.toMermaidGraph(), '');
});
});
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
$.register((0, _index.provider)(() => Promise.resolve({
service: {
shutdownStub,
shutdownResolve
},
dispose: shutdownStub
}), 'shutdownChecker', ['hash']));
$.register((0, _index.provider)(hashProvider, 'hash1', ['shutdownChecker']));
$.register((0, _index.provider)(hashProvider, 'hash2', ['shutdownChecker']));
const dependencies = await $.run(['hash1', 'hash2', '$dispose', 'shutdownChecker']);
_assert.default.deepEqual(Object.keys(dependencies), ['hash1', 'hash2', '$dispose', 'shutdownChecker']);
const finalPromise = shutdownCallPromise.then(() => {
_assert.default.deepEqual(shutdownStub.args, [[]]);
shutdownResolve();
});
await dependencies.$dispose();
await finalPromise;
it('should print a dependency graph', () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
$.register(provider(hashProvider, 'hash1', ['hash']));
$.register(provider(hashProvider, 'hash2', ['hash1']));
$.register(provider(hashProvider, 'hash3', ['hash2']));
$.register(provider(hashProvider, 'hash4', ['hash3']));
$.register(provider(hashProvider, 'hash5', ['hash4']));
assert.equal($.toMermaidGraph(), 'graph TD\n' +
' hash-->ENV\n' +
' hash1-->hash\n' +
' hash2-->hash1\n' +
' hash3-->hash2\n' +
' hash4-->hash3\n' +
' hash5-->hash4');
});
it('should allow custom shapes', () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
$.register(provider(hashProvider, 'hash1', ['hash']));
$.register(provider(hashProvider, 'hash2', ['hash1']));
$.register(provider(hashProvider, 'hash3', ['hash2']));
$.register(provider(hashProvider, 'hash4', ['hash3']));
$.register(provider(hashProvider, 'hash5', ['hash4']));
assert.equal($.toMermaidGraph({
shapes: [
{
pattern: /^hash([0-9]+)$/,
template: '$0(($1))',
},
{
pattern: /^[A-Z_]+$/,
template: '$0{$0}',
},
{
pattern: /^.+$/,
template: '$0[$0]',
},
],
}), 'graph TD\n' +
' hash[hash]-->ENV{ENV}\n' +
' hash1((1))-->hash[hash]\n' +
' hash2((2))-->hash1((1))\n' +
' hash3((3))-->hash2((2))\n' +
' hash4((4))-->hash3((3))\n' +
' hash5((5))-->hash4((4))');
});
it('should allow custom styles', () => {
$.register(constant('ENV', ENV));
$.register(constant('time', time));
$.register(provider(hashProvider, 'hash', ['ENV']));
$.register(provider(hashProvider, 'hash1', ['hash']));
$.register(provider(hashProvider, 'hash2', ['hash1']));
$.register(provider(hashProvider, 'hash3', ['hash2']));
$.register(provider(hashProvider, 'hash4', ['hash3']));
$.register(provider(hashProvider, 'hash5', ['hash4']));
assert.equal($.toMermaidGraph({
classes: {
exotic: 'fill:#f9f,stroke:#333,stroke-width:4px;',
},
styles: [
{
pattern: /^hash([0-9]+)$/,
className: 'exotic',
},
{
pattern: /^hash([0-9]+)$/,
className: 'notapplied',
},
],
shapes: [
{
pattern: /^hash([0-9]+)$/,
template: '$0(($1))',
},
{
pattern: /^[A-Z_]+$/,
template: '$0{$0}',
},
{
pattern: /^.+$/,
template: '$0[$0]',
},
],
}), 'graph TD\n' +
' hash[hash]-->ENV{ENV}\n' +
' hash1((1))-->hash[hash]\n' +
' hash2((2))-->hash1((1))\n' +
' hash3((3))-->hash2((2))\n' +
' hash4((4))-->hash3((3))\n' +
' hash5((5))-->hash4((4))\n' +
' classDef exotic fill:#f9f,stroke:#333,stroke-width:4px;\n' +
' class hash1 exotic;\n' +
' class hash2 exotic;\n' +
' class hash3 exotic;\n' +
' class hash4 exotic;\n' +
' class hash5 exotic;');
});
});
it('should delay service shutdown to their deeper dependencies', async () => {
const servicesShutdownCalls = _sinon.default.spy(() => Promise.resolve());
$.register((0, _index.provider)(() => Promise.resolve({
service: {},
dispose: servicesShutdownCalls.bind(null, 'hash')
}), 'hash'));
$.register((0, _index.provider)(() => Promise.resolve({
service: {},
dispose: servicesShutdownCalls.bind(null, 'hash1')
}), 'hash1', ['hash']));
$.register((0, _index.provider)(() => Promise.resolve({
service: {},
dispose: servicesShutdownCalls.bind(null, 'hash2')
}), 'hash2', ['hash1', 'hash']));
const dependencies = await $.run(['hash2', '$dispose']);
_assert.default.deepEqual(Object.keys(dependencies), ['hash2', '$dispose']);
await dependencies.$dispose();
_assert.default.deepEqual(servicesShutdownCalls.args, [['hash2'], ['hash1'], ['hash']]);
});
it('should not shutdown singleton dependencies if used elsewhere', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV'], true));
const {
hash
} = await $.run(['time', 'hash']);
const dependencies = await $.run(['time', 'hash', '$dispose']);
_assert.default.equal(dependencies.hash, hash);
await dependencies.$dispose();
const newDependencies = await $.run(['time', 'hash']);
_assert.default.equal(newDependencies.hash, hash);
});
it('should shutdown singleton dependencies if not used elsewhere', async () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV'], true));
const {
hash,
$dispose
} = await $.run(['time', 'hash', '$dispose']);
await $dispose();
const dependencies = await $.run(['time', 'hash']);
_assert.default.notEqual(dependencies.hash, hash);
});
});
describe('toMermaidGraph', () => {
it('should print nothing when no dependency', () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
_assert.default.equal($.toMermaidGraph(), '');
});
it('should print a dependency graph', () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
$.register((0, _index.provider)(hashProvider, 'hash1', ['hash']));
$.register((0, _index.provider)(hashProvider, 'hash2', ['hash1']));
$.register((0, _index.provider)(hashProvider, 'hash3', ['hash2']));
$.register((0, _index.provider)(hashProvider, 'hash4', ['hash3']));
$.register((0, _index.provider)(hashProvider, 'hash5', ['hash4']));
_assert.default.equal($.toMermaidGraph(), 'graph TD\n' + ' hash-->ENV\n' + ' hash1-->hash\n' + ' hash2-->hash1\n' + ' hash3-->hash2\n' + ' hash4-->hash3\n' + ' hash5-->hash4');
});
it('should allow custom shapes', () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
$.register((0, _index.provider)(hashProvider, 'hash1', ['hash']));
$.register((0, _index.provider)(hashProvider, 'hash2', ['hash1']));
$.register((0, _index.provider)(hashProvider, 'hash3', ['hash2']));
$.register((0, _index.provider)(hashProvider, 'hash4', ['hash3']));
$.register((0, _index.provider)(hashProvider, 'hash5', ['hash4']));
_assert.default.equal($.toMermaidGraph({
shapes: [{
pattern: /^hash([0-9]+)$/,
template: '$0(($1))'
}, {
pattern: /^[A-Z_]+$/,
template: '$0{$0}'
}, {
pattern: /^.+$/,
template: '$0[$0]'
}]
}), 'graph TD\n' + ' hash[hash]-->ENV{ENV}\n' + ' hash1((1))-->hash[hash]\n' + ' hash2((2))-->hash1((1))\n' + ' hash3((3))-->hash2((2))\n' + ' hash4((4))-->hash3((3))\n' + ' hash5((5))-->hash4((4))');
});
it('should allow custom styles', () => {
$.register((0, _index.constant)('ENV', ENV));
$.register((0, _index.constant)('time', time));
$.register((0, _index.provider)(hashProvider, 'hash', ['ENV']));
$.register((0, _index.provider)(hashProvider, 'hash1', ['hash']));
$.register((0, _index.provider)(hashProvider, 'hash2', ['hash1']));
$.register((0, _index.provider)(hashProvider, 'hash3', ['hash2']));
$.register((0, _index.provider)(hashProvider, 'hash4', ['hash3']));
$.register((0, _index.provider)(hashProvider, 'hash5', ['hash4']));
_assert.default.equal($.toMermaidGraph({
classes: {
exotic: 'fill:#f9f,stroke:#333,stroke-width:4px;'
},
styles: [{
pattern: /^hash([0-9]+)$/,
className: 'exotic'
}, {
pattern: /^hash([0-9]+)$/,
className: 'notapplied'
}],
shapes: [{
pattern: /^hash([0-9]+)$/,
template: '$0(($1))'
}, {
pattern: /^[A-Z_]+$/,
template: '$0{$0}'
}, {
pattern: /^.+$/,
template: '$0[$0]'
}]
}), 'graph TD\n' + ' hash[hash]-->ENV{ENV}\n' + ' hash1((1))-->hash[hash]\n' + ' hash2((2))-->hash1((1))\n' + ' hash3((3))-->hash2((2))\n' + ' hash4((4))-->hash3((3))\n' + ' hash5((5))-->hash4((4))\n' + ' classDef exotic fill:#f9f,stroke:#333,stroke-width:4px;\n' + ' class hash1 exotic;\n' + ' class hash2 exotic;\n' + ' class hash3 exotic;\n' + ' class hash4 exotic;\n' + ' class hash5 exotic;');
});
});
});
//# sourceMappingURL=index.test.js.map

@@ -1,53 +0,35 @@

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.buildInitializationSequence = buildInitializationSequence;
var _yerror = _interopRequireDefault(require("yerror"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
import { YError } from 'yerror';
const MAX_ITERATIONS = 99;
function buildInitializationSequence(rootNode) {
const batches = [];
let i = 0;
while (i < MAX_ITERATIONS) {
const batch = recursivelyGetNextSequenceBatch(rootNode, batches);
if (0 === batch.length) {
break;
export function buildInitializationSequence(rootNode) {
const batches = [];
let i = 0;
while (i < MAX_ITERATIONS) {
const batch = recursivelyGetNextSequenceBatch(rootNode, batches);
if (0 === batch.length) {
break;
}
batches.push(batch);
i++;
}
batches.push(batch);
i++;
}
if (i === MAX_ITERATIONS) {
throw new _yerror.default('E_PROBABLE_CIRCULAR_DEPENDENCY');
}
return batches;
if (i === MAX_ITERATIONS) {
throw new YError('E_PROBABLE_CIRCULAR_DEPENDENCY');
}
return batches;
}
function recursivelyGetNextSequenceBatch(node, batches, batch = []) {
const nodeIsALeaf = !(node.__childNodes && node.__childNodes.length);
if (nodeIsInBatches(batches, node)) {
return batch;
}
if (nodeIsALeaf || node.__childNodes.every(childNode => nodeIsInBatches(batches, childNode))) {
return batch.concat(node.__name);
}
return node.__childNodes.reduce((batch, childNode) => [...new Set(recursivelyGetNextSequenceBatch(childNode, batches, batch))], batch);
const nodeIsALeaf = !(node.__childNodes && node.__childNodes.length);
if (nodeIsInBatches(batches, node)) {
return batch;
}
if (nodeIsALeaf ||
node.__childNodes.every((childNode) => nodeIsInBatches(batches, childNode))) {
return batch.concat(node.__name);
}
return node.__childNodes.reduce((batch, childNode) => [
...new Set(recursivelyGetNextSequenceBatch(childNode, batches, batch)),
], batch);
}
function nodeIsInBatches(batches, node) {
return batches.some(batch => batch.includes(node.__name));
return batches.some((batch) => batch.includes(node.__name));
}
//# sourceMappingURL=sequence.js.map

@@ -1,104 +0,149 @@

"use strict";
var _assert = _interopRequireDefault(require("assert"));
var _sequence = require("./sequence");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
import assert from 'assert';
import { buildInitializationSequence } from './sequence.js';
describe('buildInitializationSequence()', () => {
it('should work with one level trees', () => {
const tree = {
__name: 'lol'
};
_assert.default.deepEqual((0, _sequence.buildInitializationSequence)(tree), [['lol']]);
});
it('should work with multi-level trees', () => {
const tree = {
__name: 'lol',
__childNodes: [{
__name: 'lol 1',
__childNodes: [{
__name: 'lol 1.1',
__childNodes: [{
__name: 'lol 1.1.1',
__childNodes: []
}]
}, {
__name: 'lol 1.2'
}]
}, {
__name: 'lol 2',
__childNodes: [{
__name: 'lol 2.1',
__childNodes: []
}]
}, {
__name: 'lol 3',
__childNodes: [{
__name: 'lol 3.1',
__childNodes: [{
__name: 'lol 3.1.1',
__childNodes: []
}]
}]
}]
};
_assert.default.deepEqual((0, _sequence.buildInitializationSequence)(tree), [['lol 1.1.1', 'lol 1.2', 'lol 2.1', 'lol 3.1.1'], ['lol 1.1', 'lol 2', 'lol 3.1'], ['lol 1', 'lol 3'], ['lol']]);
});
it('should work with multi-level trees and cross dependencies', () => {
const tree = {
__name: 'lol',
__childNodes: [{
__name: 'lol 1',
__childNodes: [{
__name: 'lol 1.1',
__childNodes: [{
__name: 'lol 1.1.1',
__childNodes: []
}]
}, {
__name: 'lol 1.2'
}, {
__name: 'lol 1.3',
__childNodes: [{
__name: 'lol 3',
__childNodes: [{
__name: 'lol 3.1',
__childNodes: [{
__name: 'lol 2',
__childNodes: [{
__name: 'lol 2.1',
__childNodes: []
}]
}]
}]
}]
}]
}, {
__name: 'lol 2',
__childNodes: [{
__name: 'lol 2.1',
__childNodes: []
}]
}, {
__name: 'lol 3',
__childNodes: [{
__name: 'lol 3.1',
__childNodes: [{
__name: 'lol 2',
__childNodes: [{
__name: 'lol 2.1',
__childNodes: []
}]
}]
}]
}]
};
_assert.default.deepEqual((0, _sequence.buildInitializationSequence)(tree), [['lol 1.1.1', 'lol 1.2', 'lol 2.1'], ['lol 1.1', 'lol 2'], ['lol 3.1'], ['lol 3'], ['lol 1.3'], ['lol 1'], ['lol']]);
});
it('should work with one level trees', () => {
const tree = {
__name: 'lol',
};
assert.deepEqual(buildInitializationSequence(tree), [['lol']]);
});
it('should work with multi-level trees', () => {
const tree = {
__name: 'lol',
__childNodes: [
{
__name: 'lol 1',
__childNodes: [
{
__name: 'lol 1.1',
__childNodes: [
{
__name: 'lol 1.1.1',
__childNodes: [],
},
],
},
{
__name: 'lol 1.2',
},
],
},
{
__name: 'lol 2',
__childNodes: [
{
__name: 'lol 2.1',
__childNodes: [],
},
],
},
{
__name: 'lol 3',
__childNodes: [
{
__name: 'lol 3.1',
__childNodes: [
{
__name: 'lol 3.1.1',
__childNodes: [],
},
],
},
],
},
],
};
assert.deepEqual(buildInitializationSequence(tree), [
['lol 1.1.1', 'lol 1.2', 'lol 2.1', 'lol 3.1.1'],
['lol 1.1', 'lol 2', 'lol 3.1'],
['lol 1', 'lol 3'],
['lol'],
]);
});
it('should work with multi-level trees and cross dependencies', () => {
const tree = {
__name: 'lol',
__childNodes: [
{
__name: 'lol 1',
__childNodes: [
{
__name: 'lol 1.1',
__childNodes: [
{
__name: 'lol 1.1.1',
__childNodes: [],
},
],
},
{
__name: 'lol 1.2',
},
{
__name: 'lol 1.3',
__childNodes: [
{
__name: 'lol 3',
__childNodes: [
{
__name: 'lol 3.1',
__childNodes: [
{
__name: 'lol 2',
__childNodes: [
{
__name: 'lol 2.1',
__childNodes: [],
},
],
},
],
},
],
},
],
},
],
},
{
__name: 'lol 2',
__childNodes: [
{
__name: 'lol 2.1',
__childNodes: [],
},
],
},
{
__name: 'lol 3',
__childNodes: [
{
__name: 'lol 3.1',
__childNodes: [
{
__name: 'lol 2',
__childNodes: [
{
__name: 'lol 2.1',
__childNodes: [],
},
],
},
],
},
],
},
],
};
assert.deepEqual(buildInitializationSequence(tree), [
['lol 1.1.1', 'lol 1.2', 'lol 2.1'],
['lol 1.1', 'lol 2'],
['lol 3.1'],
['lol 3'],
['lol 1.3'],
['lol 1'],
['lol'],
]);
});
});
//# sourceMappingURL=sequence.test.js.map

@@ -1,42 +0,5 @@

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SPECIAL_PROPS_PREFIX = exports.SPECIAL_PROPS = exports.OPTIONAL_FLAG = exports.DECLARATION_SEPARATOR = exports.ALLOWED_SPECIAL_PROPS = exports.ALLOWED_INITIALIZER_TYPES = void 0;
exports.alsoInject = alsoInject;
exports.autoHandler = autoHandler;
exports.autoInject = autoInject;
exports.autoName = autoName;
exports.autoProvider = autoProvider;
exports.autoService = autoService;
exports.constant = constant;
exports.extra = extra;
exports.handler = handler;
exports.initializer = initializer;
exports.inject = inject;
exports.mergeInject = mergeInject;
exports.name = name;
exports.parseDependencyDeclaration = parseDependencyDeclaration;
exports.parseInjections = parseInjections;
exports.parseName = parseName;
exports.provider = provider;
exports.readFunctionName = readFunctionName;
exports.reuseSpecialProps = reuseSpecialProps;
exports.service = service;
exports.singleton = singleton;
exports.stringifyDependencyDeclaration = stringifyDependencyDeclaration;
exports.type = type;
exports.unwrapInitializerProperties = unwrapInitializerProperties;
exports.useInject = useInject;
exports.wrapInitializer = wrapInitializer;
var _yerror = _interopRequireDefault(require("yerror"));
var _debug = _interopRequireDefault(require("debug"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/* eslint @typescript-eslint/ban-types:0 */
const debug = (0, _debug.default)('knifecycle');
import { YError } from 'yerror';
import initDebug from 'debug';
const debug = initDebug('knifecycle');
/* Architecture Note #1.2: Creating initializers

@@ -64,87 +27,76 @@

*/
const DECLARATION_SEPARATOR = '>';
exports.DECLARATION_SEPARATOR = DECLARATION_SEPARATOR;
const OPTIONAL_FLAG = '?';
exports.OPTIONAL_FLAG = OPTIONAL_FLAG;
const ALLOWED_INITIALIZER_TYPES = ['provider', 'service', 'constant'];
exports.ALLOWED_INITIALIZER_TYPES = ALLOWED_INITIALIZER_TYPES;
const SPECIAL_PROPS_PREFIX = '$';
exports.SPECIAL_PROPS_PREFIX = SPECIAL_PROPS_PREFIX;
const SPECIAL_PROPS = {
TYPE: `${SPECIAL_PROPS_PREFIX}type`,
NAME: `${SPECIAL_PROPS_PREFIX}name`,
INJECT: `${SPECIAL_PROPS_PREFIX}inject`,
SINGLETON: `${SPECIAL_PROPS_PREFIX}singleton`,
EXTRA: `${SPECIAL_PROPS_PREFIX}extra`,
VALUE: `${SPECIAL_PROPS_PREFIX}value`
export const DECLARATION_SEPARATOR = '>';
export const OPTIONAL_FLAG = '?';
export const ALLOWED_INITIALIZER_TYPES = [
'provider',
'service',
'constant',
];
export const SPECIAL_PROPS_PREFIX = '$';
export const SPECIAL_PROPS = {
TYPE: `${SPECIAL_PROPS_PREFIX}type`,
NAME: `${SPECIAL_PROPS_PREFIX}name`,
INJECT: `${SPECIAL_PROPS_PREFIX}inject`,
SINGLETON: `${SPECIAL_PROPS_PREFIX}singleton`,
EXTRA: `${SPECIAL_PROPS_PREFIX}extra`,
VALUE: `${SPECIAL_PROPS_PREFIX}value`,
};
exports.SPECIAL_PROPS = SPECIAL_PROPS;
const ALLOWED_SPECIAL_PROPS = Object.keys(SPECIAL_PROPS).map(key => SPECIAL_PROPS[key]);
exports.ALLOWED_SPECIAL_PROPS = ALLOWED_SPECIAL_PROPS;
export const ALLOWED_SPECIAL_PROPS = Object.keys(SPECIAL_PROPS).map((key) => SPECIAL_PROPS[key]);
const E_BAD_INJECT_IN_CONSTANT = 'E_BAD_INJECT_IN_CONSTANT';
const E_CONSTANT_INJECTION = 'E_CONSTANT_INJECTION';
function parseInjections(source, options) {
const matches = source.match(/^\s*(?:async\s+function(?:\s+\w+)?|async)\s*\(\s*\{\s*([^{}]+)(\s*\.\.\.[^{}]+|)\s*\}/);
if (!matches) {
if (!source.match(/^\s*async/)) {
throw new _yerror.default('E_NON_ASYNC_INITIALIZER', source);
export function parseInjections(source, options) {
const matches = source.match(/^\s*(?:async\s+function(?:\s+\w+)?|async)\s*\(\s*\{\s*([^{}]+)(\s*\.\.\.[^{}]+|)\s*\}/);
if (!matches) {
if (!source.match(/^\s*async/)) {
throw new YError('E_NON_ASYNC_INITIALIZER', source);
}
if (options &&
options.allowEmpty &&
source.match(/^\s*(?:async\s+function(?:\s+\w+)?|async)\s*\(\s*\)/)) {
return [];
}
throw new YError('E_AUTO_INJECTION_FAILURE', source);
}
if (options && options.allowEmpty && source.match(/^\s*(?:async\s+function(?:\s+\w+)?|async)\s*\(\s*\)/)) {
return [];
return matches[1]
.trim()
.replace(/,$/, '')
.split(/\s*,\s*/)
.map((s) => s.trim())
.filter((s) => !s.startsWith('...'))
.map((injection) => (injection.includes('=') ? '?' : '') +
injection.split(/\s*=\s*/).shift().split(/\s*:\s*/).shift())
.filter((injection) => !/[)(\][]/.test(injection));
}
export function readFunctionName(aFunction) {
if (typeof aFunction !== 'function') {
throw new YError('E_AUTO_NAMING_FAILURE', typeof aFunction);
}
throw new _yerror.default('E_AUTO_INJECTION_FAILURE', source);
}
return matches[1].trim().replace(/,$/, '').split(/\s*,\s*/).map(s => s.trim()).filter(s => !s.startsWith('...')).map(injection => (injection.includes('=') ? '?' : '') + injection.split(/\s*=\s*/).shift().split(/\s*:\s*/).shift()).filter(injection => !/[)(\][]/.test(injection));
const functionName = parseName(aFunction.name || '');
if (!functionName) {
throw new YError('E_AUTO_NAMING_FAILURE', aFunction.name);
}
return functionName;
}
function readFunctionName(aFunction) {
if (typeof aFunction !== 'function') {
throw new _yerror.default('E_AUTO_NAMING_FAILURE', typeof aFunction);
}
const functionName = parseName(aFunction.name || '');
if (!functionName) {
throw new _yerror.default('E_AUTO_NAMING_FAILURE', aFunction.name);
}
return functionName;
export function parseName(functionName) {
return functionName.split(' ').pop().replace(/^init(?:ialize)?([A-Z])/, (_, $1) => $1.toLowerCase());
}
function parseName(functionName) {
return functionName.split(' ').pop().replace(/^init(?:ialize)?([A-Z])/, (_, $1) => $1.toLowerCase());
export function reuseSpecialProps(from, to, amend = {}) {
const uniqueInitializer = to.bind(null);
return [...new Set(Object.keys(from).concat(Object.keys(amend)))]
.filter((prop) => prop.startsWith(SPECIAL_PROPS_PREFIX))
.reduce((fn, prop) => {
const value = 'undefined' !== typeof amend[prop] ? amend[prop] : from[prop];
if (value instanceof Array) {
fn[prop] = value.concat();
}
else if (value instanceof Object) {
fn[prop] = Object.assign({}, value);
}
else {
fn[prop] = value;
}
return fn;
}, uniqueInitializer);
}
/**
* Apply special props to the given initializer from another one
* and optionally amend with new special props
* @param {Function} from The initializer in which to pick the props
* @param {Function} to The initializer from which to build the new one
* @param {Object} [amend={}] Some properties to override
* @return {Function} The newly built initializer
*/
function reuseSpecialProps(from, to, amend = {}) {
const uniqueInitializer = to.bind(null);
return [...new Set(Object.keys(from).concat(Object.keys(amend)))].filter(prop => prop.startsWith(SPECIAL_PROPS_PREFIX)).reduce((fn, prop) => {
const value = 'undefined' !== typeof amend[prop] ? amend[prop] : from[prop];
if (value instanceof Array) {
fn[prop] = value.concat();
} else if (value instanceof Object) {
fn[prop] = Object.assign({}, value);
} else {
fn[prop] = value;
}
return fn;
}, uniqueInitializer);
}
/**
* Decorator that creates an initializer for a constant value

@@ -172,18 +124,14 @@ * @param {String} name

*/
function constant(name, value) {
const contantLooksLikeAnInitializer = value instanceof Function && value[SPECIAL_PROPS.INJECT];
if (contantLooksLikeAnInitializer) {
throw new _yerror.default(E_CONSTANT_INJECTION, value[SPECIAL_PROPS.INJECT]);
}
debug(`Created an initializer from a constant: ${name}.`);
return {
$type: 'constant',
$name: name,
$singleton: true,
$value: value
};
export function constant(name, value) {
const contantLooksLikeAnInitializer = value instanceof Function && value[SPECIAL_PROPS.INJECT];
if (contantLooksLikeAnInitializer) {
throw new YError(E_CONSTANT_INJECTION, value[SPECIAL_PROPS.INJECT]);
}
debug(`Created an initializer from a constant: ${name}.`);
return {
$type: 'constant',
$name: name,
$singleton: true,
$value: value,
};
}

@@ -220,22 +168,22 @@ /**

*/
function service(serviceBuilder, name, dependencies, singleton, extra) {
if (!serviceBuilder) {
throw new _yerror.default('E_NO_SERVICE_BUILDER');
}
name = name || serviceBuilder[SPECIAL_PROPS.NAME] || 'anonymous';
dependencies = dependencies || serviceBuilder[SPECIAL_PROPS.INJECT] || [];
singleton = typeof singleton === 'undefined' ? serviceBuilder[SPECIAL_PROPS.SINGLETON] || false : singleton;
extra = extra || serviceBuilder[SPECIAL_PROPS.EXTRA] || [];
debug(`Created an initializer from a service builder: ${name}.`);
const uniqueInitializer = reuseSpecialProps(serviceBuilder, serviceBuilder, {
[SPECIAL_PROPS.TYPE]: 'service',
[SPECIAL_PROPS.NAME]: name,
[SPECIAL_PROPS.INJECT]: dependencies,
[SPECIAL_PROPS.SINGLETON]: singleton,
[SPECIAL_PROPS.EXTRA]: extra
});
return uniqueInitializer;
export function service(serviceBuilder, name, dependencies, singleton, extra) {
if (!serviceBuilder) {
throw new YError('E_NO_SERVICE_BUILDER');
}
name = name || serviceBuilder[SPECIAL_PROPS.NAME] || 'anonymous';
dependencies = dependencies || serviceBuilder[SPECIAL_PROPS.INJECT] || [];
singleton =
typeof singleton === 'undefined'
? serviceBuilder[SPECIAL_PROPS.SINGLETON] || false
: singleton;
extra = extra || serviceBuilder[SPECIAL_PROPS.EXTRA] || [];
debug(`Created an initializer from a service builder: ${name}.`);
const uniqueInitializer = reuseSpecialProps(serviceBuilder, serviceBuilder, {
[SPECIAL_PROPS.TYPE]: 'service',
[SPECIAL_PROPS.NAME]: name,
[SPECIAL_PROPS.INJECT]: dependencies,
[SPECIAL_PROPS.SINGLETON]: singleton,
[SPECIAL_PROPS.EXTRA]: extra,
});
return uniqueInitializer;
}

@@ -251,11 +199,7 @@ /**

*/
function autoService(serviceBuilder) {
const name = readFunctionName(serviceBuilder);
const source = serviceBuilder.toString();
const dependencies = parseInjections(source, {
allowEmpty: true
});
return service(serviceBuilder, name, dependencies);
export function autoService(serviceBuilder) {
const name = readFunctionName(serviceBuilder);
const source = serviceBuilder.toString();
const dependencies = parseInjections(source, { allowEmpty: true });
return service(serviceBuilder, name, dependencies);
}

@@ -310,22 +254,22 @@ /**

*/
function provider(providerBuilder, name, dependencies, singleton, extra) {
if (!providerBuilder) {
throw new _yerror.default('E_NO_PROVIDER_BUILDER');
}
name = name || providerBuilder[SPECIAL_PROPS.NAME] || 'anonymous';
dependencies = dependencies || providerBuilder[SPECIAL_PROPS.INJECT] || [];
singleton = typeof singleton === 'undefined' ? providerBuilder[SPECIAL_PROPS.SINGLETON] || false : singleton;
extra = extra || providerBuilder[SPECIAL_PROPS.EXTRA] || [];
debug(`Created an initializer from a provider builder: ${name || 'anonymous'}.`);
const uniqueInitializer = reuseSpecialProps(providerBuilder, providerBuilder, {
[SPECIAL_PROPS.TYPE]: 'provider',
[SPECIAL_PROPS.NAME]: name,
[SPECIAL_PROPS.INJECT]: dependencies,
[SPECIAL_PROPS.SINGLETON]: singleton,
[SPECIAL_PROPS.EXTRA]: extra
});
return uniqueInitializer;
export function provider(providerBuilder, name, dependencies, singleton, extra) {
if (!providerBuilder) {
throw new YError('E_NO_PROVIDER_BUILDER');
}
name = name || providerBuilder[SPECIAL_PROPS.NAME] || 'anonymous';
dependencies = dependencies || providerBuilder[SPECIAL_PROPS.INJECT] || [];
singleton =
typeof singleton === 'undefined'
? providerBuilder[SPECIAL_PROPS.SINGLETON] || false
: singleton;
extra = extra || providerBuilder[SPECIAL_PROPS.EXTRA] || [];
debug(`Created an initializer from a provider builder: ${name || 'anonymous'}.`);
const uniqueInitializer = reuseSpecialProps(providerBuilder, providerBuilder, {
[SPECIAL_PROPS.TYPE]: 'provider',
[SPECIAL_PROPS.NAME]: name,
[SPECIAL_PROPS.INJECT]: dependencies,
[SPECIAL_PROPS.SINGLETON]: singleton,
[SPECIAL_PROPS.EXTRA]: extra,
});
return uniqueInitializer;
}

@@ -341,353 +285,105 @@ /**

*/
function autoProvider(providerBuilder) {
const name = readFunctionName(providerBuilder);
const source = providerBuilder.toString();
const dependencies = parseInjections(source, {
allowEmpty: true
});
return provider(providerBuilder, name, dependencies);
export function autoProvider(providerBuilder) {
const name = readFunctionName(providerBuilder);
const source = providerBuilder.toString();
const dependencies = parseInjections(source, { allowEmpty: true });
return provider(providerBuilder, name, dependencies);
}
/**
* Allows to wrap an initializer to add extra initialization steps
* @param {Function} wrapper
* A function taking dependencies and the base
* service in arguments
* @param {Function} baseInitializer
* The initializer to decorate
* @return {Function}
* The new initializer
*/
function wrapInitializer(wrapper, baseInitializer) {
return reuseSpecialProps(baseInitializer, async services => {
const baseInstance = await baseInitializer(services);
return wrapper(services, baseInstance);
});
export function wrapInitializer(wrapper, baseInitializer) {
return reuseSpecialProps(baseInitializer, async (services) => {
const baseInstance = await baseInitializer(services);
return wrapper(services, baseInstance);
});
}
/**
* Decorator creating a new initializer with different
* dependencies declarations set to it.
* @param {Array<String>} dependencies
* List of dependencies declarations to declare which
* services the initializer needs to provide its
* own service
* @param {Function} initializer
* The initializer to tweak
* @return {Function}
* Returns a new initializer
* @example
*
* import Knifecycle, { inject } from 'knifecycle'
* import myServiceInitializer from './service';
*
* new Knifecycle()
* .register(
* service(
* inject(['ENV'], myServiceInitializer)
* 'myService',
* )
* )
* );
*/
function inject(dependencies, initializer) {
if ('constant' === initializer[SPECIAL_PROPS.TYPE]) {
throw new _yerror.default(E_BAD_INJECT_IN_CONSTANT, initializer[SPECIAL_PROPS.NAME], dependencies);
}
const uniqueInitializer = reuseSpecialProps(initializer, initializer, {
[SPECIAL_PROPS.INJECT]: dependencies
});
debug('Wrapped an initializer with dependencies:', dependencies);
return uniqueInitializer;
export function inject(dependencies, initializer) {
if ('constant' === initializer[SPECIAL_PROPS.TYPE]) {
throw new YError(E_BAD_INJECT_IN_CONSTANT, initializer[SPECIAL_PROPS.NAME], dependencies);
}
const uniqueInitializer = reuseSpecialProps(initializer, initializer, {
[SPECIAL_PROPS.INJECT]: dependencies,
});
debug('Wrapped an initializer with dependencies:', dependencies);
return uniqueInitializer;
}
/**
* Apply injected dependencies from the given initializer to another one
* @param {Function} from The initialization function in which to pick the dependencies
* @param {Function} to The destination initialization function
* @return {Function} The newly built initialization function
*/
function useInject(from, to) {
return inject(from[SPECIAL_PROPS.INJECT] || [], to);
export function useInject(from, to) {
return inject(from[SPECIAL_PROPS.INJECT] || [], to);
}
/**
* Merge injected dependencies of the given initializer with another one
* @param {Function} from The initialization function in which to pick the dependencies
* @param {Function} to The destination initialization function
* @return {Function} The newly built initialization function
*/
function mergeInject(from, to) {
return alsoInject(from[SPECIAL_PROPS.INJECT] || [], to);
export function mergeInject(from, to) {
return alsoInject(from[SPECIAL_PROPS.INJECT] || [], to);
}
/**
* Decorator creating a new initializer with different
* dependencies declarations set to it according to the
* given function signature.
* @param {Function} initializer
* The original initializer
* @return {Function}
* Returns a new initializer
* @example
*
* import Knifecycle, { autoInject, name } from 'knifecycle'
*
* new Knifecycle()
* .register(
* name(
* 'application',
* autoInject(
* async ({ NODE_ENV, mysql: db }) =>
* async () => db.query('SELECT applicationId FROM applications WHERE environment=?', [NODE_ENV])
* )
* )
* )
* )
* );
*/
function autoInject(initializer) {
const source = initializer.toString();
const dependencies = parseInjections(source);
return inject(dependencies, initializer);
export function autoInject(initializer) {
const source = initializer.toString();
const dependencies = parseInjections(source);
return inject(dependencies, initializer);
}
/**
* Decorator creating a new initializer with some
* more dependencies declarations appended to it.
* @param {Array<String>} dependencies
* List of dependencies declarations to append
* @param {Function} initializer
* The initializer to tweak
* @return {Function}
* Returns a new initializer
* @example
*
* import Knifecycle, { alsoInject } from 'knifecycle'
* import myServiceInitializer from './service';
*
* new Knifecycle()
* .register(service(
* alsoInject(['ENV'], myServiceInitializer),
* 'myService',
* ));
*/
function alsoInject(dependencies, initializer) {
const currentDependencies = (initializer[SPECIAL_PROPS.INJECT] || []).map(parseDependencyDeclaration);
const addedDependencies = dependencies.map(parseDependencyDeclaration);
const dedupedDependencies = currentDependencies.filter(({
serviceName
}) => {
const declarationIsOverridden = addedDependencies.some(({
serviceName: addedServiceName
}) => {
return addedServiceName === serviceName;
export function alsoInject(dependencies, initializer) {
const currentDependencies = (initializer[SPECIAL_PROPS.INJECT] || []).map(parseDependencyDeclaration);
const addedDependencies = dependencies.map(parseDependencyDeclaration);
const dedupedDependencies = currentDependencies
.filter(({ serviceName }) => {
const declarationIsOverridden = addedDependencies.some(({ serviceName: addedServiceName }) => {
return addedServiceName === serviceName;
});
return !declarationIsOverridden;
})
.concat(addedDependencies.map(({ serviceName, mappedName, optional }) => {
const isOptionalEverywhere = optional &&
currentDependencies.every(({ optional, mappedName: addedMappedName }) => {
return addedMappedName !== mappedName || optional;
});
return {
serviceName,
mappedName,
optional: isOptionalEverywhere,
};
}))
.map(stringifyDependencyDeclaration);
return inject(dedupedDependencies, initializer);
}
export function extra(extraInformations, initializer, merge = false) {
const uniqueInitializer = reuseSpecialProps(initializer, initializer, {
[SPECIAL_PROPS.EXTRA]: merge
? Object.assign(initializer[SPECIAL_PROPS.EXTRA] || {}, extraInformations)
: extraInformations,
});
return !declarationIsOverridden;
}).concat(addedDependencies.map(({
serviceName,
mappedName,
optional
}) => {
const isOptionalEverywhere = optional && currentDependencies.every(({
optional,
mappedName: addedMappedName
}) => {
return addedMappedName !== mappedName || optional;
debug('Wrapped an initializer with extra informations:', extraInformations);
return uniqueInitializer;
}
export function singleton(initializer, isSingleton = true) {
const uniqueInitializer = reuseSpecialProps(initializer, initializer, {
[SPECIAL_PROPS.SINGLETON]: isSingleton,
});
return {
serviceName,
mappedName,
optional: isOptionalEverywhere
};
})).map(stringifyDependencyDeclaration);
return inject(dedupedDependencies, initializer);
debug('Marked an initializer as singleton:', isSingleton);
return uniqueInitializer;
}
/**
* Decorator creating a new initializer with some
* extra informations appended to it. It is just
* a way for user to store some additional
* informations but has no interaction with the
* Knifecycle internals.
* @param {Object} extraInformations
* An object containing those extra informations.
* @param {Function} initializer
* The initializer to tweak
* @param {Boolean} [merge=false]
* Whether the extra object should be merged
* with the existing one or not
* @return {Function}
* Returns a new initializer
* @example
*
* import Knifecycle, { extra } from 'knifecycle'
* import myServiceInitializer from './service';
*
* new Knifecycle()
* .register(service(
* extra({ httpHandler: true }, myServiceInitializer),
* 'myService',
* ));
*/
function extra(extraInformations, initializer, merge = false) {
const uniqueInitializer = reuseSpecialProps(initializer, initializer, {
[SPECIAL_PROPS.EXTRA]: merge ? Object.assign(initializer[SPECIAL_PROPS.EXTRA] || {}, extraInformations) : extraInformations
});
debug('Wrapped an initializer with extra informations:', extraInformations);
return uniqueInitializer;
export function name(name, initializer) {
const uniqueInitializer = reuseSpecialProps(initializer, initializer, {
[SPECIAL_PROPS.NAME]: name,
});
debug('Wrapped an initializer with a name:', name);
return uniqueInitializer;
}
/**
* Decorator to set an initializer singleton option.
* @param {Function} initializer
* The initializer to tweak
* @param {boolean} [isSingleton=true]
* Define the initializer singleton option
* (one instance for several runs if true)
* @return {Function}
* Returns a new initializer
* @example
*
* import Knifecycle, { inject, singleton } from 'knifecycle';
* import myServiceInitializer from './service';
*
* new Knifecycle()
* .register(service(
* inject(['ENV'],
* singleton(myServiceInitializer)
* ),
* 'myService',
* ));
*/
function singleton(initializer, isSingleton = true) {
const uniqueInitializer = reuseSpecialProps(initializer, initializer, {
[SPECIAL_PROPS.SINGLETON]: isSingleton
});
debug('Marked an initializer as singleton:', isSingleton);
return uniqueInitializer;
export function autoName(initializer) {
return name(readFunctionName(initializer), initializer);
}
/**
* Decorator to set an initializer name.
* @param {String} name
* The name of the service the initializer resolves to.
* @param {Function} initializer
* The initializer to tweak
* @return {Function}
* Returns a new initializer with that name set
* @example
*
* import Knifecycle, { name } from 'knifecycle';
* import myServiceInitializer from './service';
*
* new Knifecycle()
* .register(name('myService', myServiceInitializer));
*/
function name(name, initializer) {
const uniqueInitializer = reuseSpecialProps(initializer, initializer, {
[SPECIAL_PROPS.NAME]: name
});
debug('Wrapped an initializer with a name:', name);
return uniqueInitializer;
export function type(type, initializer) {
const uniqueInitializer = reuseSpecialProps(initializer, initializer, {
[SPECIAL_PROPS.TYPE]: type,
});
debug('Wrapped an initializer with a type:', type);
return uniqueInitializer;
}
/**
* Decorator to set an initializer name from its function name.
* @param {Function} initializer
* The initializer to name
* @return {Function}
* Returns a new initializer with that name set
* @example
*
* import Knifecycle, { autoName } from 'knifecycle';
*
* new Knifecycle()
* .register(autoName(async function myService() {}));
*/
function autoName(initializer) {
return name(readFunctionName(initializer), initializer);
export function initializer(properties, initializer) {
const uniqueInitializer = reuseSpecialProps(initializer, initializer, Object.keys(properties).reduce((finalProperties, property) => {
const finalProperty = SPECIAL_PROPS_PREFIX + property;
if (!ALLOWED_SPECIAL_PROPS.includes(finalProperty)) {
throw new YError('E_BAD_PROPERTY', property);
}
finalProperties[finalProperty] = properties[property];
return finalProperties;
}, {}));
debug('Wrapped an initializer with properties:', properties);
return uniqueInitializer;
}
/**
* Decorator to set an initializer type.
* @param {String} type
* The type to set to the initializer.
* @param {Function} initializer
* The initializer to tweak
* @return {Function}
* Returns a new initializer
* @example
*
* import Knifecycle, { name, type } from 'knifecycle';
* import myServiceInitializer from './service';
*
* new Knifecycle()
* .register(
* type('service',
* name('myService',
* myServiceInitializer
* )
* )
* );
*/
function type(type, initializer) {
const uniqueInitializer = reuseSpecialProps(initializer, initializer, {
[SPECIAL_PROPS.TYPE]: type
});
debug('Wrapped an initializer with a type:', type);
return uniqueInitializer;
}
/**
* Decorator to set an initializer properties.
* @param {Object} properties
* Properties to set to the service.
* @param {Function} initializer
* The initializer to tweak
* @return {Function}
* Returns a new initializer
* @example
*
* import Knifecycle, { initializer } from 'knifecycle';
* import myServiceInitializer from './service';
*
* new Knifecycle()
* .register(initializer({
* name: 'myService',
* type: 'service',
* inject: ['ENV'],
* singleton: true,
* }, myServiceInitializer));
*/
function initializer(properties, initializer) {
const uniqueInitializer = reuseSpecialProps(initializer, initializer, Object.keys(properties).reduce((finalProperties, property) => {
const finalProperty = SPECIAL_PROPS_PREFIX + property;
if (!ALLOWED_SPECIAL_PROPS.includes(finalProperty)) {
throw new _yerror.default('E_BAD_PROPERTY', property);
}
finalProperties[finalProperty] = properties[property];
return finalProperties;
}, {}));
debug('Wrapped an initializer with properties:', properties);
return uniqueInitializer;
}
/**
* Shortcut to create an initializer with a simple handler

@@ -717,19 +413,15 @@ * @param {Function} handlerFunction

*/
function handler(handlerFunction, name, dependencies, singleton, extra) {
name = name || handlerFunction[SPECIAL_PROPS.NAME];
dependencies = dependencies || handlerFunction[SPECIAL_PROPS.INJECT] || [];
if (!name) {
throw new _yerror.default('E_NO_HANDLER_NAME', handlerFunction);
}
return initializer({
name,
type: 'service',
inject: dependencies,
singleton,
extra
}, async dependencies => handlerFunction.bind(null, dependencies));
export function handler(handlerFunction, name, dependencies, singleton, extra) {
name = name || handlerFunction[SPECIAL_PROPS.NAME];
dependencies = dependencies || handlerFunction[SPECIAL_PROPS.INJECT] || [];
if (!name) {
throw new YError('E_NO_HANDLER_NAME', handlerFunction);
}
return initializer({
name,
type: 'service',
inject: dependencies,
singleton,
extra,
}, async (dependencies) => handlerFunction.bind(null, dependencies));
}

@@ -755,13 +447,11 @@ /**

*/
function autoHandler(handlerFunction) {
const name = readFunctionName(handlerFunction);
const source = handlerFunction.toString();
const dependencies = parseInjections(source);
return initializer({
name,
type: 'service',
inject: dependencies
}, async dependencies => handlerFunction.bind(null, dependencies));
export function autoHandler(handlerFunction) {
const name = readFunctionName(handlerFunction);
const source = handlerFunction.toString();
const dependencies = parseInjections(source);
return initializer({
name,
type: 'service',
inject: dependencies,
}, async (dependencies) => handlerFunction.bind(null, dependencies));
}

@@ -778,3 +468,2 @@ /* Architecture Note #1.2.1: Dependencies declaration syntax

*/
/**

@@ -795,12 +484,10 @@ * Explode a dependency declaration an returns its parts.

*/
function parseDependencyDeclaration(dependencyDeclaration) {
const optional = dependencyDeclaration.startsWith(OPTIONAL_FLAG);
const [serviceName, mappedName] = (optional ? dependencyDeclaration.slice(1) : dependencyDeclaration).split(DECLARATION_SEPARATOR);
return {
serviceName,
mappedName: mappedName || serviceName,
optional
};
export function parseDependencyDeclaration(dependencyDeclaration) {
const optional = dependencyDeclaration.startsWith(OPTIONAL_FLAG);
const [serviceName, mappedName] = (optional ? dependencyDeclaration.slice(1) : dependencyDeclaration).split(DECLARATION_SEPARATOR);
return {
serviceName,
mappedName: mappedName || serviceName,
optional,
};
}

@@ -823,66 +510,43 @@ /**

*/
function stringifyDependencyDeclaration(dependencyDeclarationParts) {
return `${dependencyDeclarationParts.optional ? '?' : ''}${dependencyDeclarationParts.serviceName}${dependencyDeclarationParts.mappedName !== dependencyDeclarationParts.serviceName ? '>' + dependencyDeclarationParts.mappedName : ''}`;
export function stringifyDependencyDeclaration(dependencyDeclarationParts) {
return `${dependencyDeclarationParts.optional ? '?' : ''}${dependencyDeclarationParts.serviceName}${dependencyDeclarationParts.mappedName !==
dependencyDeclarationParts.serviceName
? '>' + dependencyDeclarationParts.mappedName
: ''}`;
}
/* Architecture Note #3: TypeScript tweaks
Sadly TypeScript does not allow to add generic types
in all cases. This is why `(Service|Provider)Initializer`
types do not embed the `(Service|Provider)Properties`
direclty. Instead, we use this utility function to
reveal it to TypeScript and, by the way, check their
completeness at execution time.
For more details, see:
https://stackoverflow.com/questions/64948037/generics-type-loss-while-infering/64950184#64950184
*/
/**
* Utility function to check and reveal initializer properties.
* @param {Function} initializer
* The initializer to tweak
* @return {Function}
* Returns revealed initializer (with TypeScript types for properties)
*/
function unwrapInitializerProperties(initializer) {
if (typeof initializer !== 'function' && typeof initializer !== 'object') {
throw new _yerror.default('E_BAD_INITIALIZER', initializer);
}
const properties = initializer;
if (typeof properties[SPECIAL_PROPS.NAME] !== 'string' || properties[SPECIAL_PROPS.NAME] === '') {
throw new _yerror.default('E_ANONYMOUS_ANALYZER', properties[SPECIAL_PROPS.NAME]);
}
if (!ALLOWED_INITIALIZER_TYPES.includes(properties[SPECIAL_PROPS.TYPE])) {
throw new _yerror.default('E_BAD_INITIALIZER_TYPE', initializer[SPECIAL_PROPS.NAME], initializer[SPECIAL_PROPS.TYPE], ALLOWED_INITIALIZER_TYPES);
}
if (initializer[SPECIAL_PROPS.NAME] === '$autoload' && !initializer[SPECIAL_PROPS.SINGLETON]) {
throw new _yerror.default('E_BAD_AUTOLOADER', initializer[SPECIAL_PROPS.SINGLETON] || false);
}
if (properties[SPECIAL_PROPS.TYPE] === 'constant') {
if ('undefined' === typeof initializer[SPECIAL_PROPS.VALUE]) {
throw new _yerror.default('E_UNDEFINED_CONSTANT_INITIALIZER', properties[SPECIAL_PROPS.NAME]);
export function unwrapInitializerProperties(initializer) {
if (typeof initializer !== 'function' && typeof initializer !== 'object') {
throw new YError('E_BAD_INITIALIZER', initializer);
}
properties[SPECIAL_PROPS.SINGLETON] = true;
} else {
if ('undefined' !== typeof initializer[SPECIAL_PROPS.VALUE]) {
throw new _yerror.default('E_BAD_VALUED_NON_CONSTANT_INITIALIZER', initializer[SPECIAL_PROPS.NAME]);
const properties = initializer;
if (typeof properties[SPECIAL_PROPS.NAME] !== 'string' ||
properties[SPECIAL_PROPS.NAME] === '') {
throw new YError('E_ANONYMOUS_ANALYZER', properties[SPECIAL_PROPS.NAME]);
}
properties[SPECIAL_PROPS.INJECT] = properties[SPECIAL_PROPS.INJECT] || [];
properties[SPECIAL_PROPS.SINGLETON] = properties[SPECIAL_PROPS.SINGLETON] || false;
properties[SPECIAL_PROPS.EXTRA] = properties[SPECIAL_PROPS.EXTRA] || undefined;
}
return initializer;
if (!ALLOWED_INITIALIZER_TYPES.includes(properties[SPECIAL_PROPS.TYPE])) {
throw new YError('E_BAD_INITIALIZER_TYPE', initializer[SPECIAL_PROPS.NAME], initializer[SPECIAL_PROPS.TYPE], ALLOWED_INITIALIZER_TYPES);
}
if (initializer[SPECIAL_PROPS.NAME] === '$autoload' &&
!initializer[SPECIAL_PROPS.SINGLETON]) {
throw new YError('E_BAD_AUTOLOADER', initializer[SPECIAL_PROPS.SINGLETON] || false);
}
if (properties[SPECIAL_PROPS.TYPE] === 'constant') {
if ('undefined' ===
typeof initializer[SPECIAL_PROPS.VALUE]) {
throw new YError('E_UNDEFINED_CONSTANT_INITIALIZER', properties[SPECIAL_PROPS.NAME]);
}
properties[SPECIAL_PROPS.SINGLETON] = true;
}
else {
if ('undefined' !== typeof initializer[SPECIAL_PROPS.VALUE]) {
throw new YError('E_BAD_VALUED_NON_CONSTANT_INITIALIZER', initializer[SPECIAL_PROPS.NAME]);
}
properties[SPECIAL_PROPS.INJECT] = properties[SPECIAL_PROPS.INJECT] || [];
properties[SPECIAL_PROPS.SINGLETON] =
properties[SPECIAL_PROPS.SINGLETON] || false;
properties[SPECIAL_PROPS.EXTRA] =
properties[SPECIAL_PROPS.EXTRA] || undefined;
}
return initializer;
}
//# sourceMappingURL=util.js.map

@@ -1,423 +0,317 @@

"use strict";
var _assert = _interopRequireDefault(require("assert"));
var _sinon = _interopRequireDefault(require("sinon"));
var _util = require("./util");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
import assert from 'assert';
import sinon from 'sinon';
import { reuseSpecialProps, wrapInitializer, parseDependencyDeclaration, name, autoName, type, inject, autoInject, alsoInject, useInject, mergeInject, parseInjections, singleton, extra, initializer, constant, service, autoService, provider, autoProvider, handler, autoHandler, SPECIAL_PROPS, } from './util.js';
async function aProviderInitializer() {
return {
service: 'A_PROVIDER_SERVICE'
};
return {
service: 'A_PROVIDER_SERVICE',
};
}
async function aServiceInitializer() {
return 'A_PROVIDER_SERVICE';
return 'A_PROVIDER_SERVICE';
}
describe('reuseSpecialProps', () => {
it('should work', () => {
// We can safely ignore coverage here since the
// function are here just as placeholders
/* istanbul ignore next */
async function from() {
return 'from';
}
/* istanbul ignore next */
async function to() {
return 'to';
}
from.$name = 'from';
from.$type = 'service';
from.$inject = ['ki', 'kooo', 'lol'];
from.$singleton = false;
from.$extra = {
httpHandler: true
};
const newFn = (0, _util.reuseSpecialProps)(from, to);
_assert.default.notEqual(newFn, to);
_assert.default.equal(newFn.$name, from.$name);
_assert.default.equal(newFn.$type, from.$type);
_assert.default.notEqual(newFn.$inject, from.$inject);
_assert.default.deepEqual(newFn.$inject, from.$inject);
_assert.default.equal(newFn.$singleton, from.$singleton);
_assert.default.notEqual(newFn.$extra, from.$extra);
_assert.default.deepEqual(newFn.$extra, from.$extra);
const newFn2 = (0, _util.reuseSpecialProps)(from, to, {
$name: 'yolo'
it('should work', () => {
// We can safely ignore coverage here since the
// function are here just as placeholders
/* istanbul ignore next */
async function from() {
return 'from';
}
/* istanbul ignore next */
async function to() {
return 'to';
}
from.$name = 'from';
from.$type = 'service';
from.$inject = ['ki', 'kooo', 'lol'];
from.$singleton = false;
from.$extra = { httpHandler: true };
const newFn = reuseSpecialProps(from, to);
assert.notEqual(newFn, to);
assert.equal(newFn.$name, from.$name);
assert.equal(newFn.$type, from.$type);
assert.notEqual(newFn.$inject, from.$inject);
assert.deepEqual(newFn.$inject, from.$inject);
assert.equal(newFn.$singleton, from.$singleton);
assert.notEqual(newFn.$extra, from.$extra);
assert.deepEqual(newFn.$extra, from.$extra);
const newFn2 = reuseSpecialProps(from, to, {
$name: 'yolo',
});
assert.notEqual(newFn2, to);
assert.equal(newFn2.$name, 'yolo');
assert.equal(newFn2.$type, from.$type);
assert.notEqual(newFn2.$inject, from.$inject);
assert.deepEqual(newFn2.$inject, from.$inject);
assert.equal(newFn2.$singleton, from.$singleton);
assert.notEqual(newFn.$extra, from.$extra);
assert.deepEqual(newFn.$extra, from.$extra);
});
_assert.default.notEqual(newFn2, to);
_assert.default.equal(newFn2.$name, 'yolo');
_assert.default.equal(newFn2.$type, from.$type);
_assert.default.notEqual(newFn2.$inject, from.$inject);
_assert.default.deepEqual(newFn2.$inject, from.$inject);
_assert.default.equal(newFn2.$singleton, from.$singleton);
_assert.default.notEqual(newFn.$extra, from.$extra);
_assert.default.deepEqual(newFn.$extra, from.$extra);
});
});
describe('wrapInitializer', () => {
it('should work with a service initializer', async () => {
async function baseServiceInitializer() {
return () => 'test';
}
const log = _sinon.default.stub();
const newInitializer = (0, _util.wrapInitializer)(async ({
log
}, service) => {
log('Wrapping...');
return () => service() + '-wrapped';
}, (0, _util.service)(baseServiceInitializer, 'baseServiceInitializer', ['log', '?test'], false, {
httpHandler: false
}));
const newService = await newInitializer({
log
it('should work with a service initializer', async () => {
async function baseServiceInitializer() {
return () => 'test';
}
const log = sinon.stub();
const newInitializer = wrapInitializer(async ({ log }, service) => {
log('Wrapping...');
return () => service() + '-wrapped';
}, service(baseServiceInitializer, 'baseServiceInitializer', ['log', '?test'], false, {
httpHandler: false,
}));
const newService = await newInitializer({ log });
assert.equal(newService(), 'test-wrapped');
assert.deepEqual(log.args, [['Wrapping...']]);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], ['log', '?test']);
});
_assert.default.equal(newService(), 'test-wrapped');
_assert.default.deepEqual(log.args, [['Wrapping...']]);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], ['log', '?test']);
});
it('should work with a provider initialzer', async () => {
async function baseInitializer() {
return {
service: () => 'test'
};
}
const log = _sinon.default.stub();
const baseProviderInitializer = (0, _util.provider)(baseInitializer, 'baseInitializer', ['log', '?test'], false, {
httpHandler: false
it('should work with a provider initialzer', async () => {
async function baseInitializer() {
return { service: () => 'test' };
}
const log = sinon.stub();
const baseProviderInitializer = provider(baseInitializer, 'baseInitializer', ['log', '?test'], false, {
httpHandler: false,
});
const newInitializer = wrapInitializer(async ({ log }, service) => {
log('Wrapping...');
return { service: () => service.service() + '-wrapped' };
}, baseProviderInitializer);
const newService = await newInitializer({ log });
assert.equal(newService.service(), 'test-wrapped');
assert.deepEqual(log.args, [['Wrapping...']]);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], ['log', '?test']);
});
const newInitializer = (0, _util.wrapInitializer)(async ({
log
}, service) => {
log('Wrapping...');
return {
service: () => service.service() + '-wrapped'
};
}, baseProviderInitializer);
const newService = await newInitializer({
log
});
_assert.default.equal(newService.service(), 'test-wrapped');
_assert.default.deepEqual(log.args, [['Wrapping...']]);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], ['log', '?test']);
});
});
describe('inject', () => {
it('should allow to decorate an initializer with dependencies', () => {
const dependencies = ['ENV'];
const newInitializer = (0, _util.inject)(dependencies, (0, _util.provider)(aProviderInitializer, 'aProvider'));
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
});
it('should allow to decorate an initializer builder with dependencies', () => {
const dependencies = ['ENV'];
const newInitializer = (0, _util.inject)(dependencies, aProviderInitializer);
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
});
it('should allow to decorate an initializer with dependencies', () => {
const dependencies = ['ENV'];
const newInitializer = (0, _util.inject)(dependencies, (0, _util.service)(aServiceInitializer, 'aService'));
_assert.default.notEqual(newInitializer, aServiceInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
});
it('should allow to decorate an initializer builder with dependencies', () => {
const dependencies = ['ENV'];
const newInitializer = (0, _util.inject)(dependencies, aServiceInitializer);
_assert.default.notEqual(newInitializer, aServiceInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
});
it('should allow to decorate an initializer with mapped dependencies', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const newInitializer = (0, _util.inject)(dependencies, aProviderInitializer);
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
});
it('should fail with a constant', () => {
_assert.default.throws(() => {
(0, _util.inject)(['test'], (0, _util.constant)('test', 'test'));
}, /E_BAD_INJECT_IN_CONSTANT/);
});
it('should allow to decorate an initializer with dependencies', () => {
const dependencies = ['ENV'];
const newInitializer = inject(dependencies, provider(aProviderInitializer, 'aProvider'));
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
});
it('should allow to decorate an initializer builder with dependencies', () => {
const dependencies = ['ENV'];
const newInitializer = inject(dependencies, aProviderInitializer);
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
});
it('should allow to decorate an initializer with dependencies', () => {
const dependencies = ['ENV'];
const newInitializer = inject(dependencies, service(aServiceInitializer, 'aService'));
assert.notEqual(newInitializer, aServiceInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
});
it('should allow to decorate an initializer builder with dependencies', () => {
const dependencies = ['ENV'];
const newInitializer = inject(dependencies, aServiceInitializer);
assert.notEqual(newInitializer, aServiceInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
});
it('should allow to decorate an initializer with mapped dependencies', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const newInitializer = inject(dependencies, aProviderInitializer);
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
});
it('should fail with a constant', () => {
assert.throws(() => {
inject(['test'], constant('test', 'test'));
}, /E_BAD_INJECT_IN_CONSTANT/);
});
});
describe('useInject', () => {
it('should set the right dependencies', () => {
const fromDependencies = ['ENV', 'CORS'];
const fromInitializer = (0, _util.inject)(fromDependencies, aProviderInitializer);
const toDependencies = ['db', 'log'];
const toInitializer = (0, _util.inject)(toDependencies, aProviderInitializer);
const newInitializer = (0, _util.useInject)(fromInitializer, toInitializer);
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], fromDependencies);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], toDependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], [...fromDependencies]);
});
it('should set the right dependencies', () => {
const fromDependencies = ['ENV', 'CORS'];
const fromInitializer = inject(fromDependencies, aProviderInitializer);
const toDependencies = ['db', 'log'];
const toInitializer = inject(toDependencies, aProviderInitializer);
const newInitializer = useInject(fromInitializer, toInitializer);
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], fromDependencies);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], toDependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], [
...fromDependencies,
]);
});
});
describe('mergeInject', () => {
it('should amend dependencies', () => {
const fromDependencies = ['ENV', 'CORS'];
const fromInitializer = (0, _util.inject)(fromDependencies, aProviderInitializer);
const toDependencies = ['db', 'log'];
const toInitializer = (0, _util.inject)(toDependencies, aProviderInitializer);
const newInitializer = (0, _util.mergeInject)(fromInitializer, toInitializer);
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], fromDependencies);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], toDependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], [...toDependencies, ...fromDependencies]);
});
it('should amend dependencies', () => {
const fromDependencies = ['ENV', 'CORS'];
const fromInitializer = inject(fromDependencies, aProviderInitializer);
const toDependencies = ['db', 'log'];
const toInitializer = inject(toDependencies, aProviderInitializer);
const newInitializer = mergeInject(fromInitializer, toInitializer);
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], fromDependencies);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], toDependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], [
...toDependencies,
...fromDependencies,
]);
});
});
describe('autoInject', () => {
it('should allow to decorate an initializer with dependencies', () => {
const baseProvider = async ({
ENV,
mysql: db
}) => async () => ({
ENV,
db
it('should allow to decorate an initializer with dependencies', () => {
const baseProvider = async ({ ENV, mysql: db }) => async () => ({
ENV,
db,
});
const dependencies = ['ENV', 'mysql'];
const newInitializer = autoInject(baseProvider);
assert.notEqual(newInitializer, baseProvider);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
});
const dependencies = ['ENV', 'mysql'];
const newInitializer = (0, _util.autoInject)(baseProvider);
_assert.default.notEqual(newInitializer, baseProvider);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
});
it('should allow to decorate an initializer with a function name', () => {
async function baseProvider({
ENV,
mysql: db
}) {
async () => ({
ENV,
db
});
}
const dependencies = ['ENV', 'mysql'];
const newInitializer = (0, _util.autoInject)(baseProvider);
_assert.default.notEqual(newInitializer, baseProvider);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
});
it('should allow to decorate an initializer with optional dependencies', () => {
const noop = () => undefined;
const baseProvider = async ({
ENV,
log = noop,
debug: aDebug = noop
}) => async () => ({
ENV,
log,
aDebug
it('should allow to decorate an initializer with a function name', () => {
async function baseProvider({ ENV, mysql: db }) {
async () => ({
ENV,
db,
});
}
const dependencies = ['ENV', 'mysql'];
const newInitializer = autoInject(baseProvider);
assert.notEqual(newInitializer, baseProvider);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
});
const dependencies = ['ENV', '?log', '?debug'];
const newInitializer = (0, _util.autoInject)(baseProvider);
_assert.default.notEqual(newInitializer, baseProvider);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
});
it('should allow to decorate an initializer with several arguments', () => {
const noop = () => undefined;
const baseProvider = async ({
ENV,
log = noop,
debug: aDebug = noop
}) => async () => ({
ENV,
log,
aDebug
it('should allow to decorate an initializer with optional dependencies', () => {
const noop = () => undefined;
const baseProvider = async ({ ENV, log = noop, debug: aDebug = noop }) => async () => ({
ENV,
log,
aDebug,
});
const dependencies = ['ENV', '?log', '?debug'];
const newInitializer = autoInject(baseProvider);
assert.notEqual(newInitializer, baseProvider);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
});
const dependencies = ['ENV', '?log', '?debug'];
const newInitializer = (0, _util.autoInject)(baseProvider);
_assert.default.notEqual(newInitializer, baseProvider);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
});
it('should allow to decorate an initializer with complex arguments', () => {
const noop = () => undefined;
const baseProvider = async ({
ENV,
log = noop,
debug: aDebug = noop
}) => async () => ({
ENV,
log,
aDebug
it('should allow to decorate an initializer with several arguments', () => {
const noop = () => undefined;
const baseProvider = async ({ ENV, log = noop, debug: aDebug = noop }) => async () => ({
ENV,
log,
aDebug,
});
const dependencies = ['ENV', '?log', '?debug'];
const newInitializer = autoInject(baseProvider);
assert.notEqual(newInitializer, baseProvider);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
});
const dependencies = ['ENV', '?log', '?debug'];
const newInitializer = (0, _util.autoInject)(baseProvider);
_assert.default.notEqual(newInitializer, baseProvider);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
});
it('should fail with non async initializers', () => {
_assert.default.throws(() => {
(0, _util.autoInject)(({
foo: bar = {
bar: 'foo'
}
}) => {
return bar;
});
}, /E_NON_ASYNC_INITIALIZER/);
});
it('should fail with too complex injections', () => {
_assert.default.throws(() => {
(0, _util.autoInject)(async ({
foo: bar = {
bar: 'foo'
}
}) => {
return bar;
});
}, /E_AUTO_INJECTION_FAILURE/);
});
it('should fail with no injections', () => {
_assert.default.throws(() => {
(0, _util.autoInject)(async () => undefined);
}, /E_AUTO_INJECTION_FAILURE/);
});
it('should allow to decorate an initializer with complex arguments', () => {
const noop = () => undefined;
const baseProvider = async ({ ENV, log = noop, debug: aDebug = noop }) => async () => ({
ENV,
log,
aDebug,
});
const dependencies = ['ENV', '?log', '?debug'];
const newInitializer = autoInject(baseProvider);
assert.notEqual(newInitializer, baseProvider);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
});
it('should fail with non async initializers', () => {
assert.throws(() => {
autoInject((({ foo: bar = { bar: 'foo' } }) => {
return bar;
}));
}, /E_NON_ASYNC_INITIALIZER/);
});
it('should fail with too complex injections', () => {
assert.throws(() => {
autoInject(async ({ foo: bar = { bar: 'foo' } }) => {
return bar;
});
}, /E_AUTO_INJECTION_FAILURE/);
});
it('should fail with no injections', () => {
assert.throws(() => {
autoInject(async () => undefined);
}, /E_AUTO_INJECTION_FAILURE/);
});
});
describe('alsoInject', () => {
it('should allow to decorate an initializer with dependencies', () => {
const newInitializer = (0, _util.alsoInject)(['ENV'], (0, _util.inject)(['TEST'], aProviderInitializer));
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], ['TEST', 'ENV']);
});
it('should allow to decorate an initializer with dependencies', () => {
const newInitializer = (0, _util.alsoInject)(['ENV'], aProviderInitializer);
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], ['ENV']);
});
it('should dedupe dependencies', () => {
const baseProvider = (0, _util.inject)(['?TEST'], aProviderInitializer);
const newInitializer = (0, _util.alsoInject)(['ENV', '?NODE_ENV', '?TEST', 'TEST2', 'db>mysql'], (0, _util.alsoInject)(['ENV', 'NODE_ENV', '?TEST', '?TEST2', 'mysql'], baseProvider));
_assert.default.notEqual(newInitializer, baseProvider);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], ['mysql', 'ENV', 'NODE_ENV', '?TEST', 'TEST2', 'db>mysql']);
});
it('should preserve single optional dependencies', () => {
const baseProvider = (0, _util.inject)(['ENV', '?TEST'], aProviderInitializer);
const newInitializer = (0, _util.alsoInject)(['ENV', '?TEST2'], (0, _util.alsoInject)(['ENV', '?TEST3'], baseProvider));
_assert.default.notEqual(newInitializer, baseProvider);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], ['?TEST', '?TEST3', 'ENV', '?TEST2']);
});
it('should preserve mapped dependencies', () => {
const baseProvider = (0, _util.inject)(['mysql', '?sftp'], aProviderInitializer);
const newInitializer = (0, _util.alsoInject)(['db>mysql', '?ftp>sftp'], baseProvider);
_assert.default.notEqual(newInitializer, baseProvider);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], ['mysql', '?sftp', 'db>mysql', '?ftp>sftp']);
});
it('should solve dependencies alias name clash', () => {
const baseProvider = (0, _util.inject)(['?TEST'], aProviderInitializer);
const newInitializer = (0, _util.alsoInject)(['ENV', '?NODE_ENV', '?TEST', 'db>mysql', '?log>logly'], (0, _util.alsoInject)(['ENV', 'NODE_ENV', '?TEST', 'db>pg', '?log>logger'], baseProvider));
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], ['ENV', 'NODE_ENV', '?TEST', 'db>mysql', '?log>logly']);
});
it('should solve dependencies alias name clash', () => {
const baseProvider = (0, _util.inject)(['?TEST'], aProviderInitializer);
const newInitializer = (0, _util.alsoInject)(['ENV', '?NODE_ENV', '?TEST', 'db>mysql', '?log>logly'], (0, _util.alsoInject)(['ENV', 'NODE_ENV', '?TEST', 'db>pg', '?log>logger'], baseProvider));
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], ['ENV', 'NODE_ENV', '?TEST', 'db>mysql', '?log>logly']);
});
it('should allow to decorate an initializer with dependencies', () => {
const newInitializer = alsoInject(['ENV'], inject(['TEST'], aProviderInitializer));
assert.notEqual(newInitializer, aProviderInitializer);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], ['TEST', 'ENV']);
});
it('should allow to decorate an initializer with dependencies', () => {
const newInitializer = alsoInject(['ENV'], aProviderInitializer);
assert.notEqual(newInitializer, aProviderInitializer);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], ['ENV']);
});
it('should dedupe dependencies', () => {
const baseProvider = inject(['?TEST'], aProviderInitializer);
const newInitializer = alsoInject(['ENV', '?NODE_ENV', '?TEST', 'TEST2', 'db>mysql'], alsoInject(['ENV', 'NODE_ENV', '?TEST', '?TEST2', 'mysql'], baseProvider));
assert.notEqual(newInitializer, baseProvider);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], [
'mysql',
'ENV',
'NODE_ENV',
'?TEST',
'TEST2',
'db>mysql',
]);
});
it('should preserve single optional dependencies', () => {
const baseProvider = inject(['ENV', '?TEST'], aProviderInitializer);
const newInitializer = alsoInject(['ENV', '?TEST2'], alsoInject(['ENV', '?TEST3'], baseProvider));
assert.notEqual(newInitializer, baseProvider);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], [
'?TEST',
'?TEST3',
'ENV',
'?TEST2',
]);
});
it('should preserve mapped dependencies', () => {
const baseProvider = inject(['mysql', '?sftp'], aProviderInitializer);
const newInitializer = alsoInject(['db>mysql', '?ftp>sftp'], baseProvider);
assert.notEqual(newInitializer, baseProvider);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], [
'mysql',
'?sftp',
'db>mysql',
'?ftp>sftp',
]);
});
it('should solve dependencies alias name clash', () => {
const baseProvider = inject(['?TEST'], aProviderInitializer);
const newInitializer = alsoInject(['ENV', '?NODE_ENV', '?TEST', 'db>mysql', '?log>logly'], alsoInject(['ENV', 'NODE_ENV', '?TEST', 'db>pg', '?log>logger'], baseProvider));
assert.notEqual(newInitializer, aProviderInitializer);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], [
'ENV',
'NODE_ENV',
'?TEST',
'db>mysql',
'?log>logly',
]);
});
it('should solve dependencies alias name clash', () => {
const baseProvider = inject(['?TEST'], aProviderInitializer);
const newInitializer = alsoInject(['ENV', '?NODE_ENV', '?TEST', 'db>mysql', '?log>logly'], alsoInject(['ENV', 'NODE_ENV', '?TEST', 'db>pg', '?log>logger'], baseProvider));
assert.notEqual(newInitializer, aProviderInitializer);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], [
'ENV',
'NODE_ENV',
'?TEST',
'db>mysql',
'?log>logly',
]);
});
});
describe('parseInjections', () => {
it('should work with TypeScript dependencies', () => {
_assert.default.deepEqual((0, _util.parseInjections)(`async function initNexmo({
it('should work with TypeScript dependencies', () => {
assert.deepEqual(parseInjections(`async function initNexmo({
ENV,

@@ -431,550 +325,379 @@ NEXMO,

}): Promise<SMSService> {}`), ['ENV', 'NEXMO', '?log']);
});
it('should allow to decorate an initializer with dependencies', () => {
const newInitializer = (0, _util.alsoInject)(['ENV'], aProviderInitializer);
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], ['ENV']);
});
});
it('should allow to decorate an initializer with dependencies', () => {
const newInitializer = alsoInject(['ENV'], aProviderInitializer);
assert.notEqual(newInitializer, aProviderInitializer);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], ['ENV']);
});
});
describe('singleton', () => {
it('should allow to decorate an initializer with singleton option', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const newInitializer = (0, _util.inject)(dependencies, (0, _util.singleton)(aProviderInitializer, true));
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.SINGLETON], true);
});
it('should allow to be used several times', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const newInitializer = (0, _util.inject)(dependencies, (0, _util.singleton)((0, _util.singleton)(aProviderInitializer), false));
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.SINGLETON], false);
});
it('should allow to decorate an initializer with singleton option', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const newInitializer = inject(dependencies, singleton(aProviderInitializer, true));
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.equal(newInitializer[SPECIAL_PROPS.SINGLETON], true);
});
it('should allow to be used several times', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const newInitializer = inject(dependencies, singleton(singleton(aProviderInitializer), false));
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.equal(newInitializer[SPECIAL_PROPS.SINGLETON], false);
});
});
describe('name', () => {
it('should allow to decorate an initializer with a name', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const baseName = 'hash';
const newInitializer = (0, _util.inject)(dependencies, (0, _util.name)(baseName, aProviderInitializer));
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], baseName);
});
it('should allow to decorate an initializer with a name', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const baseName = 'hash';
const newInitializer = inject(dependencies, name(baseName, aProviderInitializer));
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], baseName);
});
});
describe('autoName', () => {
it('should allow to decorate an initializer with its function name', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const baseName = 'hash';
const newInitializer = (0, _util.inject)(dependencies, (0, _util.autoName)(async function hash() {
return undefined;
}));
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], baseName);
});
it('should allow to decorate an initializer with its init like function name', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const baseName = 'hash';
const newInitializer = (0, _util.inject)(dependencies, (0, _util.autoName)(async function initHash() {
return undefined;
}));
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], baseName);
});
it('should allow to decorate an initializer with its initialize like function name', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const baseName = 'hash';
const newInitializer = (0, _util.inject)(dependencies, (0, _util.autoName)(async function initializeHash() {
return undefined;
}));
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], baseName);
});
it('should allow to decorate a bounded initializer', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const baseName = 'hash';
const newInitializer = (0, _util.autoName)((0, _util.inject)(dependencies, (0, _util.singleton)(async function initializeHash() {
return undefined;
})));
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.SINGLETON], true);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], baseName);
});
it('should fail with anonymous functions', () => {
_assert.default.throws(() => {
(0, _util.autoName)(async () => undefined);
}, /E_AUTO_NAMING_FAILURE/);
});
it('should allow to decorate an initializer with its function name', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const baseName = 'hash';
const newInitializer = inject(dependencies, autoName(async function hash() {
return undefined;
}));
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], baseName);
});
it('should allow to decorate an initializer with its init like function name', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const baseName = 'hash';
const newInitializer = inject(dependencies, autoName(async function initHash() {
return undefined;
}));
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], baseName);
});
it('should allow to decorate an initializer with its initialize like function name', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const baseName = 'hash';
const newInitializer = inject(dependencies, autoName(async function initializeHash() {
return undefined;
}));
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], baseName);
});
it('should allow to decorate a bounded initializer', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const baseName = 'hash';
const newInitializer = autoName(inject(dependencies, singleton(async function initializeHash() {
return undefined;
})));
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.equal(newInitializer[SPECIAL_PROPS.SINGLETON], true);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], baseName);
});
it('should fail with anonymous functions', () => {
assert.throws(() => {
autoName(async () => undefined);
}, /E_AUTO_NAMING_FAILURE/);
});
});
describe('extra', () => {
it('should allow to decorate an initializer with extra infos', () => {
const extraInformations = {
httpHandler: true
};
const newInitializer = (0, _util.extra)(extraInformations, aProviderInitializer);
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.EXTRA], extraInformations);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.EXTRA], extraInformations);
});
it('should allow to decorate an initializer with extra infos', () => {
const extraInformations = {
httpHandler: true
};
const newInitializer = (0, _util.extra)(extraInformations, aProviderInitializer, true);
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.EXTRA], extraInformations);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.EXTRA], extraInformations);
});
it('should allow to decorate an initializer with additional extra infos', () => {
const baseExtraInformations = {
yolo: true,
httpHandler: false
};
const additionalExtraInformations = {
httpHandler: true
};
const newInitializer = (0, _util.extra)(baseExtraInformations, (0, _util.extra)(additionalExtraInformations, aProviderInitializer), true);
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.EXTRA], baseExtraInformations);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.EXTRA], baseExtraInformations);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.EXTRA], { ...baseExtraInformations,
...baseExtraInformations
it('should allow to decorate an initializer with extra infos', () => {
const extraInformations = { httpHandler: true };
const newInitializer = extra(extraInformations, aProviderInitializer);
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.EXTRA], extraInformations);
assert.deepEqual(newInitializer[SPECIAL_PROPS.EXTRA], extraInformations);
});
});
it('should allow to decorate an initializer with extra infos', () => {
const extraInformations = { httpHandler: true };
const newInitializer = extra(extraInformations, aProviderInitializer, true);
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.EXTRA], extraInformations);
assert.deepEqual(newInitializer[SPECIAL_PROPS.EXTRA], extraInformations);
});
it('should allow to decorate an initializer with additional extra infos', () => {
const baseExtraInformations = { yolo: true, httpHandler: false };
const additionalExtraInformations = { httpHandler: true };
const newInitializer = extra(baseExtraInformations, extra(additionalExtraInformations, aProviderInitializer), true);
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.EXTRA], baseExtraInformations);
assert.notEqual(newInitializer[SPECIAL_PROPS.EXTRA], baseExtraInformations);
assert.deepEqual(newInitializer[SPECIAL_PROPS.EXTRA], {
...baseExtraInformations,
...baseExtraInformations,
});
});
});
describe('type', () => {
it('should allow to decorate an initializer with a type', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const baseName = 'hash';
const baseType = 'service';
const newInitializer = (0, _util.inject)(dependencies, (0, _util.name)(baseName, (0, _util.type)(baseType, aProviderInitializer)));
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], baseName);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.TYPE], baseType);
});
it('should allow to decorate an initializer with a type', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const baseName = 'hash';
const baseType = 'service';
const newInitializer = inject(dependencies, name(baseName, type(baseType, aProviderInitializer)));
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], baseName);
assert.equal(newInitializer[SPECIAL_PROPS.TYPE], baseType);
});
});
describe('initializer', () => {
it('should allow to decorate an initializer with every properties', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const baseName = 'hash';
const baseType = 'service';
const newInitializer = (0, _util.initializer)({
type: baseType,
inject: dependencies,
singleton: true,
name: baseName
}, aServiceInitializer);
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.SINGLETON], true);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], baseName);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.TYPE], baseType);
});
it('should fail with bad properties', () => {
_assert.default.throws(() => {
(0, _util.initializer)({
name: 'yolo',
yolo: ''
}, async () => undefined);
}, /E_BAD_PROPERTY/);
});
it('should allow to decorate an initializer with every properties', () => {
const dependencies = ['ANOTHER_ENV>ENV'];
const baseName = 'hash';
const baseType = 'service';
const newInitializer = initializer({
type: baseType,
inject: dependencies,
singleton: true,
name: baseName,
}, aServiceInitializer);
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.equal(newInitializer[SPECIAL_PROPS.SINGLETON], true);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], baseName);
assert.equal(newInitializer[SPECIAL_PROPS.TYPE], baseType);
});
it('should fail with bad properties', () => {
assert.throws(() => {
initializer({
name: 'yolo',
yolo: '',
}, async () => undefined);
}, /E_BAD_PROPERTY/);
});
});
describe('constant', () => {
it('should allow to create an initializer from a constant', async () => {
const baseName = 'THE_VALUE';
const baseValue = 42;
const constantInitializer = (0, _util.constant)(baseName, baseValue);
_assert.default.equal(constantInitializer[_util.SPECIAL_PROPS.NAME], baseName);
_assert.default.equal(constantInitializer[_util.SPECIAL_PROPS.TYPE], 'constant');
_assert.default.equal(constantInitializer[_util.SPECIAL_PROPS.VALUE], baseValue);
});
it('should fail with dependencies since it makes no sense', () => {
_assert.default.throws(() => {
(0, _util.constant)('time', (0, _util.inject)(['hash3'], async () => undefined));
}, /E_CONSTANT_INJECTION/);
});
it('should allow to create an initializer from a constant', async () => {
const baseName = 'THE_VALUE';
const baseValue = 42;
const constantInitializer = constant(baseName, baseValue);
assert.equal(constantInitializer[SPECIAL_PROPS.NAME], baseName);
assert.equal(constantInitializer[SPECIAL_PROPS.TYPE], 'constant');
assert.equal(constantInitializer[SPECIAL_PROPS.VALUE], baseValue);
});
it('should fail with dependencies since it makes no sense', () => {
assert.throws(() => {
constant('time', inject(['hash3'], async () => undefined));
}, /E_CONSTANT_INJECTION/);
});
});
describe('service', () => {
it('should allow to create an initializer from a service builder', async () => {
const aServiceBuilder = async _services => 'A_SERVICE';
const dependencies = ['ANOTHER_ENV>ENV'];
const extraData = {
cool: true
};
const baseName = 'hash';
const baseType = 'service';
const newInitializer = (0, _util.service)(aServiceBuilder, baseName, dependencies, true, extraData);
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.SINGLETON], true);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.EXTRA], extraData);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], baseName);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.TYPE], baseType);
});
it('should allow to create an initializer from a generic service builder', async () => {
const aServiceBuilder = async _services => '';
const dependencies = ['ANOTHER_ENV>ENV'];
const extraData = {
nice: true
};
const baseName = 'hash';
const baseType = 'service';
const newInitializer = (0, _util.service)(aServiceBuilder, baseName, dependencies, true, extraData);
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.SINGLETON], true);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.EXTRA], extraData);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], baseName);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.TYPE], baseType);
});
it('should fail with no service builder', () => {
_assert.default.throws(() => {
(0, _util.service)(undefined);
}, /E_NO_SERVICE_BUILDER/);
});
it('should allow to create an initializer from a service builder', async () => {
const aServiceBuilder = async (_services) => 'A_SERVICE';
const dependencies = ['ANOTHER_ENV>ENV'];
const extraData = { cool: true };
const baseName = 'hash';
const baseType = 'service';
const newInitializer = service(aServiceBuilder, baseName, dependencies, true, extraData);
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.equal(newInitializer[SPECIAL_PROPS.SINGLETON], true);
assert.deepEqual(newInitializer[SPECIAL_PROPS.EXTRA], extraData);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], baseName);
assert.equal(newInitializer[SPECIAL_PROPS.TYPE], baseType);
});
it('should allow to create an initializer from a generic service builder', async () => {
const aServiceBuilder = async (_services) => '';
const dependencies = ['ANOTHER_ENV>ENV'];
const extraData = { nice: true };
const baseName = 'hash';
const baseType = 'service';
const newInitializer = service(aServiceBuilder, baseName, dependencies, true, extraData);
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.equal(newInitializer[SPECIAL_PROPS.SINGLETON], true);
assert.deepEqual(newInitializer[SPECIAL_PROPS.EXTRA], extraData);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], baseName);
assert.equal(newInitializer[SPECIAL_PROPS.TYPE], baseType);
});
it('should fail with no service builder', () => {
assert.throws(() => {
service(undefined);
}, /E_NO_SERVICE_BUILDER/);
});
});
describe('autoService', () => {
it('should detect the service details', () => {
const baseServiceBuilder = async function initializeMySQL({
ENV
}) {
return ENV;
};
const newInitializer = (0, _util.autoService)(baseServiceBuilder);
_assert.default.notEqual(newInitializer, baseServiceBuilder);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], ['ENV']);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], 'mySQL');
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.TYPE], 'service');
});
it('should detect the service details even with no dependencies', () => {
const baseServiceBuilder = async function initializeMySQL() {
return;
};
const newInitializer = (0, _util.autoService)(baseServiceBuilder);
_assert.default.notEqual(newInitializer, baseServiceBuilder);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], []);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], 'mySQL');
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.TYPE], 'service');
});
it('should detect the service details', () => {
const baseServiceBuilder = async function initializeMySQL({ ENV }) {
return ENV;
};
const newInitializer = autoService(baseServiceBuilder);
assert.notEqual(newInitializer, baseServiceBuilder);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], ['ENV']);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], 'mySQL');
assert.equal(newInitializer[SPECIAL_PROPS.TYPE], 'service');
});
it('should detect the service details even with no dependencies', () => {
const baseServiceBuilder = async function initializeMySQL() {
return;
};
const newInitializer = autoService(baseServiceBuilder);
assert.notEqual(newInitializer, baseServiceBuilder);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], []);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], 'mySQL');
assert.equal(newInitializer[SPECIAL_PROPS.TYPE], 'service');
});
});
describe('provider', () => {
it('should allow to create an initializer from a provider builder', async () => {
const aProviderInitializerBuilder = async () => ({
service: 'A_SERVICE'
it('should allow to create an initializer from a provider builder', async () => {
const aProviderInitializerBuilder = async () => ({ service: 'A_SERVICE' });
const dependencies = ['ANOTHER_ENV>ENV'];
const extraData = { singleton: true };
const baseName = 'hash';
const baseType = 'provider';
const newInitializer = provider(aProviderInitializerBuilder, baseName, dependencies, true, extraData);
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.equal(newInitializer[SPECIAL_PROPS.SINGLETON], true);
assert.deepEqual(newInitializer[SPECIAL_PROPS.EXTRA], extraData);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], baseName);
assert.equal(newInitializer[SPECIAL_PROPS.TYPE], baseType);
});
const dependencies = ['ANOTHER_ENV>ENV'];
const extraData = {
singleton: true
};
const baseName = 'hash';
const baseType = 'provider';
const newInitializer = (0, _util.provider)(aProviderInitializerBuilder, baseName, dependencies, true, extraData);
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.SINGLETON], true);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.EXTRA], extraData);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], baseName);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.TYPE], baseType);
});
it('should allow to create an initializer from a provider builder', async () => {
const aServiceBuilder = async _services => ({
service: 'A_SERVICE'
it('should allow to create an initializer from a provider builder', async () => {
const aServiceBuilder = async (_services) => ({
service: 'A_SERVICE',
});
const dependencies = ['ANOTHER_ENV>ENV'];
const extraData = { extra: true };
const baseName = 'hash';
const baseType = 'provider';
const newInitializer = provider(name(baseName, inject(dependencies, singleton(extra(extraData, aServiceBuilder)))));
assert.notEqual(newInitializer, aProviderInitializer);
assert.notEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], dependencies);
assert.equal(newInitializer[SPECIAL_PROPS.SINGLETON], true);
assert.deepEqual(newInitializer[SPECIAL_PROPS.EXTRA], extraData);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], baseName);
assert.equal(newInitializer[SPECIAL_PROPS.TYPE], baseType);
});
const dependencies = ['ANOTHER_ENV>ENV'];
const extraData = {
extra: true
};
const baseName = 'hash';
const baseType = 'provider';
const newInitializer = (0, _util.provider)((0, _util.name)(baseName, (0, _util.inject)(dependencies, (0, _util.singleton)((0, _util.extra)(extraData, aServiceBuilder)))));
_assert.default.notEqual(newInitializer, aProviderInitializer);
_assert.default.notEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], dependencies);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.SINGLETON], true);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.EXTRA], extraData);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], baseName);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.TYPE], baseType);
});
it('should fail with no provider builder', () => {
_assert.default.throws(() => {
(0, _util.provider)(undefined);
}, /E_NO_PROVIDER_BUILDER/);
});
it('should fail with no provider builder', () => {
assert.throws(() => {
provider(undefined);
}, /E_NO_PROVIDER_BUILDER/);
});
});
describe('autoProvider', () => {
it('should detect the provider details', () => {
const baseInitializer = async function initializeMySQL({
ENV
}) {
return {
service: ENV
};
};
const newInitializer = (0, _util.autoProvider)(baseInitializer);
_assert.default.notEqual(newInitializer, baseInitializer);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], ['ENV']);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], 'mySQL');
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.TYPE], 'provider');
});
it('should detect the provider details even with no dependencies', () => {
const baseInitializer = async function initializeMySQL() {
return {
service: 'A_SERVICE'
};
};
const newInitializer = (0, _util.autoProvider)(baseInitializer);
_assert.default.notEqual(newInitializer, baseInitializer);
_assert.default.deepEqual(newInitializer[_util.SPECIAL_PROPS.INJECT], []);
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.NAME], 'mySQL');
_assert.default.equal(newInitializer[_util.SPECIAL_PROPS.TYPE], 'provider');
});
it('should detect the provider details', () => {
const baseInitializer = async function initializeMySQL({ ENV, }) {
return { service: ENV };
};
const newInitializer = autoProvider(baseInitializer);
assert.notEqual(newInitializer, baseInitializer);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], ['ENV']);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], 'mySQL');
assert.equal(newInitializer[SPECIAL_PROPS.TYPE], 'provider');
});
it('should detect the provider details even with no dependencies', () => {
const baseInitializer = async function initializeMySQL() {
return { service: 'A_SERVICE' };
};
const newInitializer = autoProvider(baseInitializer);
assert.notEqual(newInitializer, baseInitializer);
assert.deepEqual(newInitializer[SPECIAL_PROPS.INJECT], []);
assert.equal(newInitializer[SPECIAL_PROPS.NAME], 'mySQL');
assert.equal(newInitializer[SPECIAL_PROPS.TYPE], 'provider');
});
});
describe('handler', () => {
it('should work', async () => {
const baseName = 'sampleHandler';
const injectedServices = ['kikooo', 'lol'];
const services = {
kikooo: 'kikooo',
lol: 'lol'
};
const theInitializer = (0, _util.handler)(sampleHandler, baseName, injectedServices);
_assert.default.deepEqual(theInitializer.$name, baseName);
_assert.default.deepEqual(theInitializer.$inject, injectedServices);
const theHandler = await theInitializer(services);
const result = await theHandler('test');
_assert.default.deepEqual(result, {
deps: services,
args: ['test']
it('should work', async () => {
const baseName = 'sampleHandler';
const injectedServices = ['kikooo', 'lol'];
const services = {
kikooo: 'kikooo',
lol: 'lol',
};
const theInitializer = handler(sampleHandler, baseName, injectedServices);
assert.deepEqual(theInitializer.$name, baseName);
assert.deepEqual(theInitializer.$inject, injectedServices);
const theHandler = await theInitializer(services);
const result = await theHandler('test');
assert.deepEqual(result, {
deps: services,
args: ['test'],
});
async function sampleHandler(deps, ...args) {
return { deps, args };
}
});
async function sampleHandler(deps, ...args) {
return {
deps,
args
};
}
});
it('should fail with no name', () => {
_assert.default.throws(() => {
(0, _util.handler)(async () => undefined);
}, /E_NO_HANDLER_NAME/);
});
it('should fail with no name', () => {
assert.throws(() => {
handler(async () => undefined);
}, /E_NO_HANDLER_NAME/);
});
});
describe('autoHandler', () => {
it('should work', async () => {
const services = {
kikooo: 'kikooo',
lol: 'lol'
};
const theInitializer = (0, _util.autoHandler)(sampleHandler);
_assert.default.deepEqual(theInitializer.$name, sampleHandler.name);
_assert.default.deepEqual(theInitializer.$inject, ['kikooo', 'lol']);
const theHandler = await theInitializer(services);
const result = await theHandler('test');
_assert.default.deepEqual(result, {
deps: services,
args: ['test']
it('should work', async () => {
const services = {
kikooo: 'kikooo',
lol: 'lol',
};
const theInitializer = autoHandler(sampleHandler);
assert.deepEqual(theInitializer.$name, sampleHandler.name);
assert.deepEqual(theInitializer.$inject, ['kikooo', 'lol']);
const theHandler = await theInitializer(services);
const result = await theHandler('test');
assert.deepEqual(result, {
deps: services,
args: ['test'],
});
async function sampleHandler({ kikooo, lol }, ...args) {
return { deps: { kikooo, lol }, args };
}
});
async function sampleHandler({
kikooo,
lol
}, ...args) {
return {
deps: {
kikooo,
lol
},
args
};
}
});
it('should work with spread services', async () => {
const services = {
kikooo: 'kikooo',
lol: 'lol'
};
const theInitializer = (0, _util.autoHandler)(sampleHandler);
_assert.default.deepEqual(theInitializer.$name, sampleHandler.name);
_assert.default.deepEqual(theInitializer.$inject, ['kikooo', 'lol']);
const theHandler = await theInitializer(services);
const result = await theHandler('test');
_assert.default.deepEqual(result, {
deps: services,
args: ['test']
it('should work with spread services', async () => {
const services = {
kikooo: 'kikooo',
lol: 'lol',
};
const theInitializer = autoHandler(sampleHandler);
assert.deepEqual(theInitializer.$name, sampleHandler.name);
assert.deepEqual(theInitializer.$inject, ['kikooo', 'lol']);
const theHandler = await theInitializer(services);
const result = await theHandler('test');
assert.deepEqual(result, {
deps: services,
args: ['test'],
});
async function sampleHandler({ kikooo, lol, ...services }, ...args) {
return { deps: { kikooo, lol, ...services }, args };
}
});
async function sampleHandler({
kikooo,
lol,
...services
}, ...args) {
return {
deps: {
kikooo,
lol,
...services
},
args
};
}
});
it('should fail for anonymous functions', () => {
_assert.default.throws(() => {
(0, _util.autoHandler)(async () => undefined);
}, /E_AUTO_NAMING_FAILURE/);
});
it('should fail for anonymous functions', () => {
assert.throws(() => {
autoHandler(async () => undefined);
}, /E_AUTO_NAMING_FAILURE/);
});
});
describe('parseDependencyDeclaration', () => {
it('should work', () => {
_assert.default.deepEqual((0, _util.parseDependencyDeclaration)('db>pgsql'), {
serviceName: 'db',
mappedName: 'pgsql',
optional: false
it('should work', () => {
assert.deepEqual(parseDependencyDeclaration('db>pgsql'), {
serviceName: 'db',
mappedName: 'pgsql',
optional: false,
});
});
});
it('should work with unmapped names', () => {
_assert.default.deepEqual((0, _util.parseDependencyDeclaration)('?pgsql'), {
serviceName: 'pgsql',
mappedName: 'pgsql',
optional: true
it('should work with unmapped names', () => {
assert.deepEqual(parseDependencyDeclaration('?pgsql'), {
serviceName: 'pgsql',
mappedName: 'pgsql',
optional: true,
});
});
});
});
//# sourceMappingURL=util.test.js.map
{
"name": "knifecycle",
"version": "12.0.4",
"version": "13.0.0",
"description": "Manage your NodeJS processes's lifecycle automatically with an unobtrusive dependency injection implementation.",
"main": "dist/index",
"module": "dist/index.mjs",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"repository": {

@@ -13,3 +13,3 @@ "type": "git",

"engines": {
"node": ">=12.19.0"
"node": ">=16.15.0"
},

@@ -20,8 +20,6 @@ "metapak": {

"readme",
"tsesm",
"eslint",
"babel",
"typescript",
"jest",
"travis",
"karma",
"ghactions",
"jsdocs",

@@ -53,7 +51,5 @@ "jsarch",

"architecture": "jsarch 'src/**/*.ts' > ARCHITECTURE.md && git add ARCHITECTURE.md",
"build": "rimraf -f 'dist' && tsc --outDir dist",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md",
"cli": "env NODE_ENV=${NODE_ENV:-cli}",
"compile": "rimraf -f 'dist' && npm run compile:cjs && npm run compile:mjs",
"compile:cjs": "babel --env-name=cjs --out-dir=dist --extensions '.ts,.js' --source-maps=true src",
"compile:mjs": "babel --env-name=mjs --out-file-extension=.mjs --out-dir=dist --extensions '.ts,.js' --source-maps=true src",
"cover": "npm run jest -- --coverage",

@@ -63,12 +59,10 @@ "coveralls": "npm run cover && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage",

"doc": "echo \"# API\" > API.md; jsdoc2md 'dist/**/*.js' >> API.md && git add API.md",
"jest": "NODE_ENV=test jest",
"karma": "karma start karma.conf.js",
"jest": "NODE_OPTIONS=--experimental-vm-modules NODE_ENV=test jest",
"lint": "eslint 'src/**/*.ts'",
"metapak": "metapak",
"precz": "npm run compile && npm run types && npm run lint && npm t && npm run doc && npm run architecture && npm run metapak -- -s",
"prepublish": "npm run compile && npm run types",
"precz": "npm run build && npm run lint && npm t && npm run doc && npm run architecture && npm run metapak -- -s",
"prepublish": "npm run build",
"prettier": "prettier --write 'src/**/*.ts'",
"preversion": "npm run compile && npm run types && npm run lint && npm t && npm run doc && npm run architecture && npm run metapak -- -s",
"test": "npm run jest && npm run karma",
"types": "rimraf -f 'dist/**/*.d.ts' && tsc --project . --declaration --emitDeclarationOnly --outDir dist",
"preversion": "npm run build && npm run lint && npm t && npm run doc && npm run architecture && npm run metapak -- -s",
"test": "npm run jest",
"version": "npm run changelog"

@@ -90,14 +84,5 @@ },

"devDependencies": {
"@babel/cli": "^7.17.10",
"@babel/core": "^7.18.2",
"@babel/eslint-parser": "^7.18.2",
"@babel/plugin-proposal-class-properties": "^7.17.12",
"@babel/plugin-proposal-object-rest-spread": "^7.18.0",
"@babel/preset-env": "^7.18.2",
"@babel/preset-typescript": "^7.17.12",
"@babel/register": "^7.17.7",
"@types/jest": "^27.0.2",
"@typescript-eslint/eslint-plugin": "^5.26.0",
"@typescript-eslint/parser": "^5.26.0",
"browserify": "^17.0.0",
"@types/jest": "^28.1.1",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"commitizen": "^4.2.4",

@@ -107,3 +92,6 @@ "conventional-changelog-cli": "^2.2.2",

"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.16.0",
"esbuild": "^0.14.43",
"esbuild-jest": "^0.5.0",
"eslint": "^8.17.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",

@@ -113,10 +101,4 @@ "jest": "^28.1.0",

"jsdoc-to-markdown": "^7.1.1",
"karma": "^6.3.20",
"karma-browserify": "^8.0.0",
"karma-chrome-launcher": "^3.1.1",
"karma-firefox-launcher": "^2.1.2",
"karma-mocha": "^2.0.1",
"karma-sauce-launcher": "^4.3.6",
"metapak": "^4.0.3",
"metapak-nfroidure": "11.2.1",
"metapak": "^4.0.4",
"metapak-nfroidure": "12.0.6",
"mocha": "8.2.1",

@@ -126,8 +108,7 @@ "prettier": "^2.6.2",

"sinon": "^14.0.0",
"typescript": "^4.7.2"
"typescript": "^4.7.3"
},
"dependencies": {
"debug": "^4.3.4",
"type-fest": "^2.13.0",
"yerror": "^6.0.2"
"yerror": "^6.1.1"
},

@@ -144,21 +125,12 @@ "config": {

"conventional-changelog-cli",
"typescript",
"rimraf",
"eslint",
"eslint-config-prettier",
"eslint-plugin-prettier",
"prettier",
"@typescript-eslint/eslint-plugin",
"@typescript-eslint/parser",
"@babel/cli",
"@babel/core",
"@babel/register",
"@babel/preset-env",
"@babel/plugin-proposal-object-rest-spread",
"@babel/preset-typescript",
"@babel/plugin-proposal-class-properties",
"babel-eslint",
"babel-core",
"typescript",
"jest",
"coveralls",
"karma",
"karma-chrome-launcher",
"karma-firefox-launcher",
"jsdoc-to-markdown",

@@ -176,2 +148,3 @@ "jsarch"

"eslint:recommended",
"plugin:prettier/recommended",
"plugin:@typescript-eslint/eslint-recommended",

@@ -182,3 +155,3 @@ "plugin:@typescript-eslint/recommended"

"ecmaVersion": 2018,
"sourceType": "module",
"sourceType": "script",
"modules": true

@@ -210,49 +183,2 @@ },

},
"babel": {
"plugins": [
"@babel/proposal-class-properties"
],
"presets": [
"@babel/typescript",
[
"@babel/env",
{
"targets": {
"node": "12.19.0"
}
}
]
],
"env": {
"cjs": {
"presets": [
[
"@babel/env",
{
"targets": {
"node": "10"
},
"modules": "commonjs"
}
]
],
"comments": true
},
"mjs": {
"presets": [
[
"@babel/env",
{
"targets": {
"node": "12"
},
"modules": false
}
]
],
"comments": false
}
},
"sourceMaps": true
},
"jest": {

@@ -267,2 +193,18 @@ "coverageReporters": [

"<rootDir>/src"
],
"transform": {
"^.+\\.tsx?$": [
"esbuild-jest",
{
"sourcemap": true,
"format": "esm"
}
]
},
"testEnvironment": "node",
"moduleNameMapper": {
"(.+)\\.js": "$1"
},
"extensionsToTreatAsEsm": [
".ts"
]

@@ -269,0 +211,0 @@ },

@@ -11,3 +11,2 @@ [//]: # ( )

[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/nfroidure/knifecycle/blob/master/LICENSE)
[![Build status](https://travis-ci.com/nfroidure/knifecycle.svg?branch=master)](https://travis-ci.com/github/nfroidure/knifecycle)
[![Coverage Status](https://coveralls.io/repos/github/nfroidure/knifecycle/badge.svg?branch=master)](https://coveralls.io/github/nfroidure/knifecycle?branch=master)

@@ -76,3 +75,3 @@

import fs from 'fs';
import YError from 'YError';
import { YError } from 'YError';
import Knifecycle, { initializer, constant, inject, name } from 'knifecycle';

@@ -409,17 +408,8 @@

## Members
## Functions
<dl>
<dt><a href="#default">default</a> ⇒ <code>Promise.&lt;function()&gt;</code></dt>
<dt><a href="#initInitializerBuilder">initInitializerBuilder(services)</a> ⇒ <code>Promise.&lt;function()&gt;</code></dt>
<dd><p>Instantiate the initializer builder service</p>
</dd>
</dl>
## Functions
<dl>
<dt><a href="#reuseSpecialProps">reuseSpecialProps(from, to, [amend])</a> ⇒ <code>function</code></dt>
<dd><p>Apply special props to the given initializer from another one
and optionally amend with new special props</p>
</dd>
<dt><a href="#constant">constant(name, value)</a> ⇒ <code>function</code></dt>

@@ -445,46 +435,2 @@ <dd><p>Decorator that creates an initializer for a constant value</p>

</dd>
<dt><a href="#wrapInitializer">wrapInitializer(wrapper, baseInitializer)</a> ⇒ <code>function</code></dt>
<dd><p>Allows to wrap an initializer to add extra initialization steps</p>
</dd>
<dt><a href="#inject">inject(dependencies, 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="#useInject">useInject(from, to)</a> ⇒ <code>function</code></dt>
<dd><p>Apply injected dependencies from the given initializer to another one</p>
</dd>
<dt><a href="#mergeInject">mergeInject(from, to)</a> ⇒ <code>function</code></dt>
<dd><p>Merge injected dependencies of the given initializer with another one</p>
</dd>
<dt><a href="#autoInject">autoInject(initializer)</a> ⇒ <code>function</code></dt>
<dd><p>Decorator creating a new initializer with different
dependencies declarations set to it according to the
given function signature.</p>
</dd>
<dt><a href="#alsoInject">alsoInject(dependencies, initializer)</a> ⇒ <code>function</code></dt>
<dd><p>Decorator creating a new initializer with some
more dependencies declarations appended to it.</p>
</dd>
<dt><a href="#extra">extra(extraInformations, initializer, [merge])</a> ⇒ <code>function</code></dt>
<dd><p>Decorator creating a new initializer with some
extra informations appended to it. It is just
a way for user to store some additional
informations but has no interaction with the
Knifecycle internals.</p>
</dd>
<dt><a href="#singleton">singleton(initializer, [isSingleton])</a> ⇒ <code>function</code></dt>
<dd><p>Decorator to set an initializer singleton option.</p>
</dd>
<dt><a href="#name">name(name, initializer)</a> ⇒ <code>function</code></dt>
<dd><p>Decorator to set an initializer name.</p>
</dd>
<dt><a href="#autoName">autoName(initializer)</a> ⇒ <code>function</code></dt>
<dd><p>Decorator to set an initializer name from its function name.</p>
</dd>
<dt><a href="#type">type(type, initializer)</a> ⇒ <code>function</code></dt>
<dd><p>Decorator to set an initializer type.</p>
</dd>
<dt><a href="#initializer">initializer(properties, initializer)</a> ⇒ <code>function</code></dt>
<dd><p>Decorator to set an initializer properties.</p>
</dd>
<dt><a href="#handler">handler(handlerFunction, [name], [dependencies], [options])</a> ⇒ <code>function</code></dt>

@@ -502,5 +448,2 @@ <dd><p>Shortcut to create an initializer with a simple handler</p>

</dd>
<dt><a href="#unwrapInitializerProperties">unwrapInitializerProperties(initializer)</a> ⇒ <code>function</code></dt>
<dd><p>Utility function to check and reveal initializer properties.</p>
</dd>
</dl>

@@ -676,8 +619,8 @@

<a name="default"></a>
<a name="initInitializerBuilder"></a>
## default ⇒ <code>Promise.&lt;function()&gt;</code>
## initInitializerBuilder(services) ⇒ <code>Promise.&lt;function()&gt;</code>
Instantiate the initializer builder service
**Kind**: global variable
**Kind**: global function
**Returns**: <code>Promise.&lt;function()&gt;</code> - A promise of the buildInitializer function

@@ -698,17 +641,26 @@

```
<a name="reuseSpecialProps"></a>
<a name="initInitializerBuilder..buildInitializer"></a>
## reuseSpecialProps(from, to, [amend]) ⇒ <code>function</code>
Apply special props to the given initializer from another one
and optionally amend with new special props
### initInitializerBuilder~buildInitializer(dependencies) ⇒ <code>Promise.&lt;String&gt;</code>
Create a JavaScript module that initialize
a set of dependencies with hardcoded
import/awaits.
**Kind**: global function
**Returns**: <code>function</code> - The newly built initializer
**Kind**: inner method of [<code>initInitializerBuilder</code>](#initInitializerBuilder)
**Returns**: <code>Promise.&lt;String&gt;</code> - The JavaScript module content
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| from | <code>function</code> | | The initializer in which to pick the props |
| to | <code>function</code> | | The initializer from which to build the new one |
| [amend] | <code>Object</code> | <code>{}</code> | Some properties to override |
| Param | Type | Description |
| --- | --- | --- |
| dependencies | <code>Array.&lt;String&gt;</code> | The main dependencies |
**Example**
```js
import initInitializerBuilder from 'knifecycle/dist/build';
const buildInitializer = await initInitializerBuilder({
$autoload: async () => {},
});
const content = await buildInitializer(['entryPoint']);
```
<a name="constant"></a>

@@ -854,273 +806,2 @@

<a name="wrapInitializer"></a>
## wrapInitializer(wrapper, baseInitializer) ⇒ <code>function</code>
Allows to wrap an initializer to add extra initialization steps
**Kind**: global function
**Returns**: <code>function</code> - The new initializer
| Param | Type | Description |
| --- | --- | --- |
| wrapper | <code>function</code> | A function taking dependencies and the base service in arguments |
| baseInitializer | <code>function</code> | The initializer to decorate |
<a name="inject"></a>
## inject(dependencies, initializer) ⇒ <code>function</code>
Decorator creating a new initializer with different
dependencies declarations set to it.
**Kind**: global function
**Returns**: <code>function</code> - Returns a new initializer
| Param | Type | Description |
| --- | --- | --- |
| dependencies | <code>Array.&lt;String&gt;</code> | List of dependencies declarations to declare which services the initializer needs to provide its own service |
| initializer | <code>function</code> | The initializer to tweak |
**Example**
```js
import Knifecycle, { inject } from 'knifecycle'
import myServiceInitializer from './service';
new Knifecycle()
.register(
service(
inject(['ENV'], myServiceInitializer)
'myService',
)
)
);
```
<a name="useInject"></a>
## useInject(from, to) ⇒ <code>function</code>
Apply injected dependencies from the given initializer to another one
**Kind**: global function
**Returns**: <code>function</code> - The newly built initialization function
| Param | Type | Description |
| --- | --- | --- |
| from | <code>function</code> | The initialization function in which to pick the dependencies |
| to | <code>function</code> | The destination initialization function |
<a name="mergeInject"></a>
## mergeInject(from, to) ⇒ <code>function</code>
Merge injected dependencies of the given initializer with another one
**Kind**: global function
**Returns**: <code>function</code> - The newly built initialization function
| Param | Type | Description |
| --- | --- | --- |
| from | <code>function</code> | The initialization function in which to pick the dependencies |
| to | <code>function</code> | The destination initialization function |
<a name="autoInject"></a>
## autoInject(initializer) ⇒ <code>function</code>
Decorator creating a new initializer with different
dependencies declarations set to it according to the
given function signature.
**Kind**: global function
**Returns**: <code>function</code> - Returns a new initializer
| Param | Type | Description |
| --- | --- | --- |
| initializer | <code>function</code> | The original initializer |
**Example**
```js
import Knifecycle, { autoInject, name } from 'knifecycle'
new Knifecycle()
.register(
name(
'application',
autoInject(
async ({ NODE_ENV, mysql: db }) =>
async () => db.query('SELECT applicationId FROM applications WHERE environment=?', [NODE_ENV])
)
)
)
)
);
```
<a name="alsoInject"></a>
## alsoInject(dependencies, 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 |
| --- | --- | --- |
| dependencies | <code>Array.&lt;String&gt;</code> | List of dependencies declarations to append |
| initializer | <code>function</code> | The initializer to tweak |
**Example**
```js
import Knifecycle, { alsoInject } from 'knifecycle'
import myServiceInitializer from './service';
new Knifecycle()
.register(service(
alsoInject(['ENV'], myServiceInitializer),
'myService',
));
```
<a name="extra"></a>
## extra(extraInformations, initializer, [merge]) ⇒ <code>function</code>
Decorator creating a new initializer with some
extra informations appended to it. It is just
a way for user to store some additional
informations but has no interaction with the
Knifecycle internals.
**Kind**: global function
**Returns**: <code>function</code> - Returns a new initializer
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| extraInformations | <code>Object</code> | | An object containing those extra informations. |
| initializer | <code>function</code> | | The initializer to tweak |
| [merge] | <code>Boolean</code> | <code>false</code> | Whether the extra object should be merged with the existing one or not |
**Example**
```js
import Knifecycle, { extra } from 'knifecycle'
import myServiceInitializer from './service';
new Knifecycle()
.register(service(
extra({ httpHandler: true }, myServiceInitializer),
'myService',
));
```
<a name="singleton"></a>
## singleton(initializer, [isSingleton]) ⇒ <code>function</code>
Decorator to set an initializer singleton option.
**Kind**: global function
**Returns**: <code>function</code> - Returns a new initializer
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| initializer | <code>function</code> | | The initializer to tweak |
| [isSingleton] | <code>boolean</code> | <code>true</code> | Define the initializer singleton option (one instance for several runs if true) |
**Example**
```js
import Knifecycle, { inject, singleton } from 'knifecycle';
import myServiceInitializer from './service';
new Knifecycle()
.register(service(
inject(['ENV'],
singleton(myServiceInitializer)
),
'myService',
));
```
<a name="name"></a>
## name(name, initializer) ⇒ <code>function</code>
Decorator to set an initializer name.
**Kind**: global function
**Returns**: <code>function</code> - Returns a new initializer with that name set
| Param | Type | Description |
| --- | --- | --- |
| name | <code>String</code> | The name of the service the initializer resolves to. |
| initializer | <code>function</code> | The initializer to tweak |
**Example**
```js
import Knifecycle, { name } from 'knifecycle';
import myServiceInitializer from './service';
new Knifecycle()
.register(name('myService', myServiceInitializer));
```
<a name="autoName"></a>
## autoName(initializer) ⇒ <code>function</code>
Decorator to set an initializer name from its function name.
**Kind**: global function
**Returns**: <code>function</code> - Returns a new initializer with that name set
| Param | Type | Description |
| --- | --- | --- |
| initializer | <code>function</code> | The initializer to name |
**Example**
```js
import Knifecycle, { autoName } from 'knifecycle';
new Knifecycle()
.register(autoName(async function myService() {}));
```
<a name="type"></a>
## type(type, initializer) ⇒ <code>function</code>
Decorator to set an initializer type.
**Kind**: global function
**Returns**: <code>function</code> - Returns a new initializer
| Param | Type | Description |
| --- | --- | --- |
| type | <code>String</code> | The type to set to the initializer. |
| initializer | <code>function</code> | The initializer to tweak |
**Example**
```js
import Knifecycle, { name, type } from 'knifecycle';
import myServiceInitializer from './service';
new Knifecycle()
.register(
type('service',
name('myService',
myServiceInitializer
)
)
);
```
<a name="initializer"></a>
## initializer(properties, initializer) ⇒ <code>function</code>
Decorator to set an initializer properties.
**Kind**: global function
**Returns**: <code>function</code> - Returns a new initializer
| Param | Type | Description |
| --- | --- | --- |
| properties | <code>Object</code> | Properties to set to the service. |
| initializer | <code>function</code> | The initializer to tweak |
**Example**
```js
import Knifecycle, { initializer } from 'knifecycle';
import myServiceInitializer from './service';
new Knifecycle()
.register(initializer({
name: 'myService',
type: 'service',
inject: ['ENV'],
singleton: true,
}, myServiceInitializer));
```
<a name="handler"></a>

@@ -1226,15 +907,3 @@

```
<a name="unwrapInitializerProperties"></a>
## unwrapInitializerProperties(initializer) ⇒ <code>function</code>
Utility function to check and reveal initializer properties.
**Kind**: global function
**Returns**: <code>function</code> - Returns revealed initializer (with TypeScript types for properties)
| Param | Type | Description |
| --- | --- | --- |
| initializer | <code>function</code> | The initializer to tweak |
# Authors

@@ -1241,0 +910,0 @@ - [Nicolas Froidure](http://insertafter.com/en/index.html)

import assert from 'assert';
import YError from 'yerror';
import initInitializerBuilder from './build';
import Knifecycle, { initializer, constant } from '.';
import type { BuildInitializer } from './build';
import { YError } from 'yerror';
import initInitializerBuilder from './build.js';
import Knifecycle, { initializer, constant } from './index.js';
import type { BuildInitializer } from './build.js';

@@ -7,0 +7,0 @@ describe('buildInitializer', () => {

@@ -1,5 +0,9 @@

import { SPECIAL_PROPS, parseDependencyDeclaration, initializer } from './util';
import { buildInitializationSequence } from './sequence';
import type { DependencyDeclaration, Initializer } from './util';
import type { Autoloader } from '.';
import {
SPECIAL_PROPS,
parseDependencyDeclaration,
initializer,
} from './util.js';
import { buildInitializationSequence } from './sequence.js';
import type { DependencyDeclaration, Initializer } from './util.js';
import type { Autoloader } from './index.js';

@@ -6,0 +10,0 @@ export type BuildOptions = { modules?: 'commonjs' | true };

/* eslint max-nested-callbacks:0 */
import assert from 'assert';
import sinon from 'sinon';
import YError from 'yerror';
import { YError } from 'yerror';

@@ -17,5 +16,4 @@ import {

FatalErrorService,
ProviderInitializer,
} from './index';
import { ALLOWED_INITIALIZER_TYPES } from './util';
} from './index.js';
import { ALLOWED_INITIALIZER_TYPES } from './util.js';

@@ -22,0 +20,0 @@ describe('Knifecycle', () => {

@@ -35,5 +35,5 @@ /* eslint max-len: ["warn", { "ignoreComments": true }] @typescript-eslint/no-this-alias: "warn" */

unwrapInitializerProperties,
} from './util';
import initInitializerBuilder from './build';
import YError from 'yerror';
} from './util.js';
import initInitializerBuilder from './build.js';
import { YError } from 'yerror';
import initDebug from 'debug';

@@ -68,4 +68,4 @@ import type {

Parameters,
} from './util';
import type { BuildInitializer } from './build';
} from './util.js';
import type { BuildInitializer } from './build.js';
export type {

@@ -72,0 +72,0 @@ ServiceName,

import assert from 'assert';
import { buildInitializationSequence } from './sequence';
import { buildInitializationSequence } from './sequence.js';

@@ -4,0 +4,0 @@ describe('buildInitializationSequence()', () => {

@@ -1,2 +0,2 @@

import YError from 'yerror';
import { YError } from 'yerror';

@@ -3,0 +3,0 @@ export type Node = {

@@ -27,6 +27,5 @@ import assert from 'assert';

SPECIAL_PROPS,
} from './util';
import type { PromiseValue } from 'type-fest';
import type { Provider } from './util';
import type { Dependencies, ServiceInitializer } from '.';
} from './util.js';
import type { Provider } from './util.js';
import type { Dependencies, ServiceInitializer } from './index.js';

@@ -244,3 +243,3 @@ async function aProviderInitializer() {

},
PromiseValue<ReturnType<typeof aProviderInitializer>>
Awaited<ReturnType<typeof aProviderInitializer>>
>(fromDependencies, aProviderInitializer);

@@ -247,0 +246,0 @@ const toDependencies = ['db', 'log'];

/* eslint @typescript-eslint/ban-types:0 */
import YError from 'yerror';
import { YError } from 'yerror';
import initDebug from 'debug';

@@ -5,0 +5,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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