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

@loopback/context

Package Overview
Dependencies
Maintainers
18
Versions
196
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@loopback/context - npm Package Compare versions

Comparing version 4.0.0-alpha.30 to 4.0.0-alpha.31

36

CHANGELOG.md

@@ -6,2 +6,38 @@ # Change Log

<a name="4.0.0-alpha.31"></a>
# [4.0.0-alpha.31](https://github.com/strongloop/loopback-next/compare/@loopback/context@4.0.0-alpha.30...@loopback/context@4.0.0-alpha.31) (2018-02-07)
### Bug Fixes
* **build:** fix tslint config and slipped violations ([22f8e05](https://github.com/strongloop/loopback-next/commit/22f8e05))
* **context:** address review comments ([3925296](https://github.com/strongloop/loopback-next/commit/3925296))
* **context:** pass metadata to `[@inject](https://github.com/inject).tag` ([27e26e9](https://github.com/strongloop/loopback-next/commit/27e26e9))
### build
* drop dist6 related targets ([#945](https://github.com/strongloop/loopback-next/issues/945)) ([a2368ce](https://github.com/strongloop/loopback-next/commit/a2368ce))
### Features
* **context:** add [@inject](https://github.com/inject).context for context injection ([6e0deaf](https://github.com/strongloop/loopback-next/commit/6e0deaf))
* **context:** add decorator & optional attrs to injection metadata ([3a1c7de](https://github.com/strongloop/loopback-next/commit/3a1c7de))
* **context:** add name to context ([21e1daf](https://github.com/strongloop/loopback-next/commit/21e1daf))
* **context:** add unbind() to allow remove bindings by key ([b9c3893](https://github.com/strongloop/loopback-next/commit/b9c3893))
* **context:** enhance binding caching to be context aware ([7b7eb30](https://github.com/strongloop/loopback-next/commit/7b7eb30))
* **context:** reports the resolution path for circular deps ([bc4ce20](https://github.com/strongloop/loopback-next/commit/bc4ce20))
### BREAKING CHANGES
* Support for Node.js version lower than 8.0 has been dropped.
Please upgrade to the latest Node.js 8.x LTS version.
Co-Authored-by: Taranveer Virk <taranveer@virk.cc>
<a name="4.0.0-alpha.30"></a>

@@ -8,0 +44,0 @@ # [4.0.0-alpha.30](https://github.com/strongloop/loopback-next/compare/@loopback/context@4.0.0-alpha.29...@loopback/context@4.0.0-alpha.30) (2018-02-04)

7

dist/src/binding.d.ts
import { Context } from './context';
import { ResolutionSession } from './resolution-session';
import { Constructor } from './resolver';
import { BoundValue } from './value-promise';
import { Constructor, BoundValue, ValueOrPromise } from './value-promise';
import { Provider } from './provider';

@@ -148,3 +147,3 @@ /**

*/
getValue(ctx: Context, session?: ResolutionSession): BoundValue | Promise<BoundValue>;
getValue(ctx: Context, session?: ResolutionSession): ValueOrPromise<BoundValue>;
lock(): this;

@@ -184,3 +183,3 @@ tag(tagName: string | string[]): this;

*/
toDynamicValue(factoryFn: () => BoundValue | Promise<BoundValue>): this;
toDynamicValue(factoryFn: () => ValueOrPromise<BoundValue>): this;
/**

@@ -187,0 +186,0 @@ * Bind the key to a value computed by a Provider.

@@ -144,7 +144,10 @@ "use strict";

_cacheValue(ctx, result) {
// Initialize the cache as a weakmap keyed by context
if (!this._cache)
this._cache = new WeakMap();
if (value_promise_1.isPromise(result)) {
if (this.scope === BindingScope.SINGLETON) {
// Cache the value
// Cache the value at owning context level
result = result.then(val => {
this._cache = val;
this._cache.set(ctx.getOwnerContext(this.key), val);
return val;

@@ -154,12 +157,5 @@ });

else if (this.scope === BindingScope.CONTEXT) {
// Cache the value
// Cache the value at the current context
result = result.then(val => {
if (ctx.contains(this.key)) {
// The ctx owns the binding
this._cache = val;
}
else {
// Create a binding of the cached value for the current context
ctx.bind(this.key).to(val);
}
this._cache.set(ctx, val);
return val;

@@ -172,13 +168,7 @@ });

// Cache the value
this._cache = result;
this._cache.set(ctx.getOwnerContext(this.key), result);
}
else if (this.scope === BindingScope.CONTEXT) {
if (ctx.contains(this.key)) {
// The ctx owns the binding
this._cache = result;
}
else {
// Create a binding of the cached value for the current context
ctx.bind(this.key).to(result);
}
// Cache the value at the current context
this._cache.set(ctx, result);
}

@@ -218,9 +208,12 @@ }

// First check cached value for non-transient
if (this._cache !== undefined) {
if (this._cache) {
if (this.scope === BindingScope.SINGLETON) {
return this._cache;
const ownerCtx = ctx.getOwnerContext(this.key);
if (ownerCtx && this._cache.has(ownerCtx)) {
return this._cache.get(ownerCtx);
}
}
else if (this.scope === BindingScope.CONTEXT) {
if (ctx.contains(this.key)) {
return this._cache;
if (this._cache.has(ctx)) {
return this._cache.get(ctx);
}

@@ -227,0 +220,0 @@ }

import { Binding } from './binding';
import { BoundValue, ValueOrPromise } from './value-promise';
import { ResolutionSession } from './resolution-session';
import { ResolutionOptions, ResolutionSession } from './resolution-session';
/**

@@ -8,9 +8,13 @@ * Context provides an implementation of Inversion of Control (IoC) container

export declare class Context {
private _parent;
private registry;
/**
* Name of the context
*/
readonly name: string;
protected readonly registry: Map<string, Binding>;
protected _parent?: Context;
/**
* Create a new context
* @param _parent The optional parent context
*/
constructor(_parent?: Context | undefined);
constructor(_parent?: Context | string, name?: string);
/**

@@ -24,2 +28,13 @@ * Create a binding with the given key in the context. If a locked binding

/**
* Unbind a binding from the context. No parent contexts will be checked. If
* you need to unbind a binding owned by a parent context, use the code below:
* ```ts
* const ownerCtx = ctx.getOwnerContext(key);
* return ownerCtx != null && ownerCtx.unbind(key);
* ```
* @param key Binding key
* @returns true if the binding key is found and removed from this context
*/
unbind(key: string): boolean;
/**
* Check if a binding exists with the given key in the local context without

@@ -36,2 +51,7 @@ * delegating to the parent context

/**
* Get the owning context for a binding key
* @param key Binding key
*/
getOwnerContext(key: string): Context | undefined;
/**
* Find bindings using the key pattern

@@ -67,5 +87,7 @@ * @param pattern Key regexp or pattern with optional `*` wildcards

* (deeply) nested property to retrieve.
* @param optionsOrSession Options or session for resolution. An instance of
* `ResolutionSession` is accepted for backward compatibility.
* @returns A promise of the bound value.
*/
get(key: string, session?: ResolutionSession): Promise<BoundValue>;
get(keyWithPath: string, optionsOrSession?: ResolutionOptions | ResolutionSession): Promise<BoundValue>;
/**

@@ -91,5 +113,7 @@ * Get the synchronous value bound to the given key, optionally

* (deeply) nested property to retrieve.
* * @param optionsOrSession Options or session for resolution. An instance of
* `ResolutionSession` is accepted for backward compatibility.
* @returns A promise of the bound value.
*/
getSync(key: string, session?: ResolutionSession): BoundValue;
getSync(keyWithPath: string, optionsOrSession?: ResolutionOptions | ResolutionSession): BoundValue;
/**

@@ -103,2 +127,15 @@ * Look up a binding by key in the context and its ancestors. If no matching

/**
* Look up a binding by key in the context and its ancestors. If no matching
* binding is found and `options.optional` is not set to true, an error will
* be thrown.
*
* @param key Binding key
* @param options Options to control if the binding is optional. If
* `options.optional` is set to true, the method will return `undefined`
* instead of throwing an error if the binding key is not found.
*/
getBinding(key: string, options?: {
optional?: boolean;
}): Binding | undefined;
/**
* Get the value bound to the given key.

@@ -125,3 +162,3 @@ *

* (deeply) nested property to retrieve.
* @param session An object to keep states of the resolution
* @param optionsOrSession Options for resolution or a session
* @returns The bound value or a promise of the bound value, depending

@@ -131,3 +168,3 @@ * on how the binding was configured.

*/
getValueOrPromise(keyWithPath: string, session?: ResolutionSession): ValueOrPromise<BoundValue>;
getValueOrPromise(keyWithPath: string, optionsOrSession?: ResolutionOptions | ResolutionSession): ValueOrPromise<BoundValue>;
/**

@@ -134,0 +171,0 @@ * Create a plain JSON object for the context

@@ -9,2 +9,4 @@ "use strict";

const value_promise_1 = require("./value-promise");
const resolution_session_1 = require("./resolution-session");
const uuid_1 = require("uuid");
const debugModule = require("debug");

@@ -20,5 +22,10 @@ const debug = debugModule('loopback:context');

*/
constructor(_parent) {
constructor(_parent, name) {
this.registry = new Map();
if (typeof _parent === 'string') {
name = _parent;
_parent = undefined;
}
this._parent = _parent;
this.registry = new Map();
this.name = name || uuid_1.v1();
}

@@ -49,2 +56,21 @@ /**

/**
* Unbind a binding from the context. No parent contexts will be checked. If
* you need to unbind a binding owned by a parent context, use the code below:
* ```ts
* const ownerCtx = ctx.getOwnerContext(key);
* return ownerCtx != null && ownerCtx.unbind(key);
* ```
* @param key Binding key
* @returns true if the binding key is found and removed from this context
*/
unbind(key) {
binding_1.Binding.validateKey(key);
const binding = this.registry.get(key);
if (binding == null)
return false;
if (binding && binding.isLocked)
throw new Error(`Cannot unbind key "${key}" of a locked binding`);
return this.registry.delete(key);
}
/**
* Check if a binding exists with the given key in the local context without

@@ -71,2 +97,14 @@ * delegating to the parent context

/**
* Get the owning context for a binding key
* @param key Binding key
*/
getOwnerContext(key) {
if (this.contains(key))
return this;
if (this._parent) {
return this._parent.getOwnerContext(key);
}
return undefined;
}
/**
* Find bindings using the key pattern

@@ -146,11 +184,13 @@ * @param pattern Key regexp or pattern with optional `*` wildcards

* (deeply) nested property to retrieve.
* @param optionsOrSession Options or session for resolution. An instance of
* `ResolutionSession` is accepted for backward compatibility.
* @returns A promise of the bound value.
*/
get(key, session) {
get(keyWithPath, optionsOrSession) {
/* istanbul ignore if */
if (debug.enabled) {
debug('Resolving binding: %s', key);
debug('Resolving binding: %s', keyWithPath);
}
try {
return Promise.resolve(this.getValueOrPromise(key, session));
return Promise.resolve(this.getValueOrPromise(keyWithPath, optionsOrSession));
}

@@ -181,22 +221,18 @@ catch (err) {

* (deeply) nested property to retrieve.
* * @param optionsOrSession Options or session for resolution. An instance of
* `ResolutionSession` is accepted for backward compatibility.
* @returns A promise of the bound value.
*/
getSync(key, session) {
getSync(keyWithPath, optionsOrSession) {
/* istanbul ignore if */
if (debug.enabled) {
debug('Resolving binding synchronously: %s', key);
debug('Resolving binding synchronously: %s', keyWithPath);
}
const valueOrPromise = this.getValueOrPromise(key, session);
const valueOrPromise = this.getValueOrPromise(keyWithPath, optionsOrSession);
if (value_promise_1.isPromise(valueOrPromise)) {
throw new Error(`Cannot get ${key} synchronously: the value is a promise`);
throw new Error(`Cannot get ${keyWithPath} synchronously: the value is a promise`);
}
return valueOrPromise;
}
/**
* Look up a binding by key in the context and its ancestors. If no matching
* binding is found, an error will be thrown.
*
* @param key Binding key
*/
getBinding(key) {
getBinding(key, options) {
binding_1.Binding.validateKey(key);

@@ -208,4 +244,6 @@ const binding = this.registry.get(key);

if (this._parent) {
return this._parent.getBinding(key);
return this._parent.getBinding(key, options);
}
if (options && options.optional)
return undefined;
throw new Error(`The key ${key} was not bound to any value.`);

@@ -235,3 +273,3 @@ }

* (deeply) nested property to retrieve.
* @param session An object to keep states of the resolution
* @param optionsOrSession Options for resolution or a session
* @returns The bound value or a promise of the bound value, depending

@@ -241,5 +279,11 @@ * on how the binding was configured.

*/
getValueOrPromise(keyWithPath, session) {
getValueOrPromise(keyWithPath, optionsOrSession) {
const { key, path } = binding_1.Binding.parseKeyWithPath(keyWithPath);
const boundValue = this.getBinding(key).getValue(this, session);
if (optionsOrSession instanceof resolution_session_1.ResolutionSession) {
optionsOrSession = { session: optionsOrSession };
}
const binding = this.getBinding(key, optionsOrSession);
if (binding == null)
return undefined;
const boundValue = binding.getValue(this, optionsOrSession && optionsOrSession.session);
if (path === undefined || path === '') {

@@ -246,0 +290,0 @@ return boundValue;

@@ -0,10 +1,9 @@

export * from '@loopback/metadata';
export { isPromise, BoundValue, Constructor, ValueOrPromise, MapObject, resolveList, resolveMap, tryWithFinally, getDeepProperty } from './value-promise';
export { Binding, BindingScope, BindingType } from './binding';
export { Context } from './context';
export { Constructor } from './resolver';
export { ResolutionSession } from './resolution-session';
export { inject, Setter, Getter } from './inject';
export { inject, Setter, Getter, Injection } from './inject';
export { Provider } from './provider';
export { isPromise, BoundValue, ValueOrPromise, MapObject, resolveList, resolveMap, tryWithFinally, getDeepProperty } from './value-promise';
export { instantiateClass, invokeMethod } from './resolver';
export { describeInjectedArguments, describeInjectedProperties, Injection } from './inject';
export * from '@loopback/metadata';
export { describeInjectedArguments, describeInjectedProperties } from './inject';

@@ -10,2 +10,9 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
__export(require("@loopback/metadata"));
var value_promise_1 = require("./value-promise");
exports.isPromise = value_promise_1.isPromise;
exports.resolveList = value_promise_1.resolveList;
exports.resolveMap = value_promise_1.resolveMap;
exports.tryWithFinally = value_promise_1.tryWithFinally;
exports.getDeepProperty = value_promise_1.getDeepProperty;
var binding_1 = require("./binding");

@@ -21,16 +28,9 @@ exports.Binding = binding_1.Binding;

exports.inject = inject_1.inject;
var value_promise_1 = require("./value-promise");
exports.isPromise = value_promise_1.isPromise;
exports.resolveList = value_promise_1.resolveList;
exports.resolveMap = value_promise_1.resolveMap;
exports.tryWithFinally = value_promise_1.tryWithFinally;
exports.getDeepProperty = value_promise_1.getDeepProperty;
// internals for testing
var resolver_1 = require("./resolver");
exports.instantiateClass = resolver_1.instantiateClass;
exports.invokeMethod = resolver_1.invokeMethod;
// internals for testing
var inject_2 = require("./inject");
exports.describeInjectedArguments = inject_2.describeInjectedArguments;
exports.describeInjectedProperties = inject_2.describeInjectedProperties;
__export(require("@loopback/metadata"));
//# sourceMappingURL=index.js.map

@@ -89,4 +89,4 @@ import { MetadataMap } from '@loopback/metadata';

/**
* Inject an array of values by a tag
* @param bindingTag Tag name or regex
* Inject an array of values by a tag pattern string or regexp
*
* @example

@@ -97,7 +97,20 @@ * ```ts

* @inject.tag('authentication.strategy') public strategies: Strategy[],
* ) { }
* ) {}
* }
* ```
* @param bindingTag Tag name or regex
* @param metadata Optional metadata to help the injection
*/
const tag: (bindingTag: string | RegExp) => (target: Object, member: string | symbol, methodDescriptorOrParameterIndex?: number | TypedPropertyDescriptor<any> | undefined) => void;
const tag: (bindingTag: string | RegExp, metadata?: Object | undefined) => (target: Object, member: string | symbol, methodDescriptorOrParameterIndex?: number | TypedPropertyDescriptor<any> | undefined) => void;
/**
* Inject the context object.
*
* @example
* ```ts
* class MyProvider {
* constructor(@inject.context() private ctx: Context) {}
* }
* ```
*/
const context: () => (target: Object, member: string | symbol, methodDescriptorOrParameterIndex?: number | TypedPropertyDescriptor<any> | undefined) => void;
}

@@ -104,0 +117,0 @@ /**

@@ -39,2 +39,3 @@ "use strict";

function inject(bindingKey, metadata, resolve) {
metadata = Object.assign({ decorator: '@inject' }, metadata);
return function markParameterOrPropertyAsInjected(target, member, methodDescriptorOrParameterIndex) {

@@ -98,2 +99,3 @@ if (typeof methodDescriptorOrParameterIndex === 'number') {

inject.getter = function injectGetter(bindingKey, metadata) {
metadata = Object.assign({ decorator: '@inject.getter' }, metadata);
return inject(bindingKey, metadata, resolveAsGetter);

@@ -115,7 +117,8 @@ };

inject.setter = function injectSetter(bindingKey, metadata) {
metadata = Object.assign({ decorator: '@inject.setter' }, metadata);
return inject(bindingKey, metadata, resolveAsSetter);
};
/**
* Inject an array of values by a tag
* @param bindingTag Tag name or regex
* Inject an array of values by a tag pattern string or regexp
*
* @example

@@ -126,9 +129,25 @@ * ```ts

* @inject.tag('authentication.strategy') public strategies: Strategy[],
* ) { }
* ) {}
* }
* ```
* @param bindingTag Tag name or regex
* @param metadata Optional metadata to help the injection
*/
inject.tag = function injectTag(bindingTag) {
return inject('', { tag: bindingTag }, resolveByTag);
inject.tag = function injectTag(bindingTag, metadata) {
metadata = Object.assign({ decorator: '@inject.tag', tag: bindingTag }, metadata);
return inject('', metadata, resolveByTag);
};
/**
* Inject the context object.
*
* @example
* ```ts
* class MyProvider {
* constructor(@inject.context() private ctx: Context) {}
* }
* ```
*/
inject.context = function injectContext() {
return inject('', { decorator: '@inject.context' }, ctx => ctx);
};
})(inject = exports.inject || (exports.inject = {}));

@@ -139,3 +158,6 @@ function resolveAsGetter(ctx, injection, session) {

return function getter() {
return ctx.get(injection.bindingKey, session);
return ctx.get(injection.bindingKey, {
session,
optional: injection.metadata && injection.metadata.optional,
});
};

@@ -142,0 +164,0 @@ }

@@ -107,2 +107,10 @@ import { Binding } from './binding';

/**
* Getter for bindings on the stack
*/
readonly bindingStack: Binding[];
/**
* Getter for injections on the stack
*/
readonly injectionStack: Injection[];
/**
* Get the binding path as `bindingA --> bindingB --> bindingC`.

@@ -123,1 +131,16 @@ */

}
/**
* Options for binding/dependency resolution
*/
export interface ResolutionOptions {
/**
* A session to track bindings and injections
*/
session?: ResolutionSession;
/**
* A boolean flag to indicate if the dependency is optional. If it's set to
* `true` and the binding is not bound in a context, the resolution
* will return `undefined` instead of throwing an error.
*/
optional?: boolean;
}

@@ -13,2 +13,16 @@ "use strict";

/**
* Type guard for binding elements
* @param element A resolution element
*/
function isBinding(element) {
return element != null && element.type === 'binding';
}
/**
* Type guard for injection elements
* @param element A resolution element
*/
function isInjection(element) {
return element != null && element.type === 'injection';
}
/**
* Object to keep states for a session to resolve bindings and their

@@ -113,3 +127,3 @@ * dependencies within a context

const top = this.stack.pop();
if (top === undefined || top.type !== 'injection') {
if (!isInjection(top)) {
throw new Error('The top element must be an injection');

@@ -131,6 +145,4 @@ }

const element = this.stack[i];
switch (element.type) {
case 'injection':
return element.value;
}
if (isInjection(element))
return element.value;
}

@@ -145,6 +157,4 @@ return undefined;

const element = this.stack[i];
switch (element.type) {
case 'binding':
return element.value;
}
if (isBinding(element))
return element.value;
}

@@ -162,4 +172,7 @@ return undefined;

}
if (this.stack.find(i => i.type === 'binding' && i.value === binding)) {
throw new Error(`Circular dependency detected on path '${this.getBindingPath()} --> ${binding.key}'`);
if (this.stack.find(i => isBinding(i) && i.value === binding)) {
const msg = `Circular dependency detected: ` +
`${this.getResolutionPath()} --> ${binding.key}`;
debugSession(msg);
throw new Error(msg);
}

@@ -177,3 +190,3 @@ this.stack.push({ type: 'binding', value: binding });

const top = this.stack.pop();
if (top === undefined || top.type !== 'binding') {
if (!isBinding(top)) {
throw new Error('The top element must be a binding');

@@ -190,9 +203,18 @@ }

/**
* Getter for bindings on the stack
*/
get bindingStack() {
return this.stack.filter(isBinding).map(e => e.value);
}
/**
* Getter for injections on the stack
*/
get injectionStack() {
return this.stack.filter(isInjection).map(e => e.value);
}
/**
* Get the binding path as `bindingA --> bindingB --> bindingC`.
*/
getBindingPath() {
return this.stack
.filter(i => i.type === 'binding')
.map(b => b.value.key)
.join(' --> ');
return this.bindingStack.map(b => b.key).join(' --> ');
}

@@ -203,5 +225,4 @@ /**

getInjectionPath() {
return this.stack
.filter(i => i.type === 'injection')
.map(i => ResolutionSession.describeInjection(i.value).targetName)
return this.injectionStack
.map(i => ResolutionSession.describeInjection(i).targetName)
.join(' --> ');

@@ -208,0 +229,0 @@ }

import { Context } from './context';
import { BoundValue, ValueOrPromise, MapObject } from './value-promise';
import { BoundValue, Constructor, ValueOrPromise, MapObject } from './value-promise';
import { ResolutionSession } from './resolution-session';
/**
* A class constructor accepting arbitrary arguments.
*/
export declare type Constructor<T> = new (...args: any[]) => T;
/**
* Create an instance of a class which constructor has arguments

@@ -10,0 +6,0 @@ * decorated with `@inject`.

@@ -108,3 +108,8 @@ "use strict";

// Default to resolve the value from the context by binding key
return ctx.getValueOrPromise(injection.bindingKey, s);
return ctx.getValueOrPromise(injection.bindingKey, {
session: s,
// If the `optional` flag is set for the injection, the resolution
// will return `undefined` instead of throwing an error
optional: injection.metadata && injection.metadata.optional,
});
}

@@ -111,0 +116,0 @@ }, injection, session);

@@ -5,2 +5,6 @@ /**

*/
/**
* A class constructor accepting arbitrary arguments.
*/
export declare type Constructor<T> = new (...args: any[]) => T;
export declare type BoundValue = any;

@@ -7,0 +11,0 @@ /**

@@ -67,3 +67,7 @@ "use strict";

const setter = (key) => (val) => {
result[key] = val;
if (val !== undefined) {
// Only set the value if it's not undefined so that the default value
// for a key will be honored
result[key] = val;
}
};

@@ -78,3 +82,7 @@ for (const key in map) {

else {
result[key] = valueOrPromise;
if (valueOrPromise !== undefined) {
// Only set the value if it's not undefined so that the default value
// for a key will be honored
result[key] = valueOrPromise;
}
}

@@ -81,0 +89,0 @@ }

@@ -6,3 +6,2 @@ // Copyright IBM Corp. 2017. All Rights Reserved.

const nodeMajorVersion = +process.versions.node.split('.')[0];
module.exports = nodeMajorVersion >= 7 ? require('./dist') : require('./dist6');
module.exports = require('./dist');
{
"name": "@loopback/context",
"version": "4.0.0-alpha.30",
"version": "4.0.0-alpha.31",
"description": "LoopBack's container for Inversion of Control",

@@ -10,10 +10,7 @@ "engines": {

"acceptance": "lb-mocha \"DIST/test/acceptance/**/*.js\"",
"build": "npm run build:dist && npm run build:dist6",
"build:current": "lb-tsc",
"build:dist": "lb-tsc es2017",
"build:dist6": "lb-tsc es2015",
"build": "lb-tsc es2017",
"build:apidocs": "lb-apidocs",
"clean": "lb-clean loopback-context*.tgz dist dist6 package api-docs",
"clean": "lb-clean loopback-context*.tgz dist package api-docs",
"prepublishOnly": "npm run build && npm run build:apidocs",
"pretest": "npm run build:current",
"pretest": "npm run build",
"test": "lb-mocha \"DIST/test/unit/**/*.js\" \"DIST/test/acceptance/**/*.js\"",

@@ -27,10 +24,12 @@ "unit": "lb-mocha \"DIST/test/unit/**/*.js\"",

"dependencies": {
"@loopback/metadata": "^4.0.0-alpha.9",
"debug": "^3.1.0"
"@loopback/metadata": "^4.0.0-alpha.10",
"debug": "^3.1.0",
"uuid": "^3.2.1"
},
"devDependencies": {
"@loopback/build": "^4.0.0-alpha.13",
"@loopback/testlab": "^4.0.0-alpha.23",
"@loopback/testlab": "^4.0.0-alpha.24",
"@types/bluebird": "^3.5.18",
"@types/debug": "^0.0.30",
"@types/uuid": "^3.4.3",
"bluebird": "^3.5.0"

@@ -52,3 +51,2 @@ },

"dist/src",
"dist6/src",
"api-docs",

@@ -55,0 +53,0 @@ "src"

@@ -8,4 +8,9 @@ // Copyright IBM Corp. 2017,2018. All Rights Reserved.

import {ResolutionSession} from './resolution-session';
import {Constructor, instantiateClass} from './resolver';
import {isPromise, BoundValue, ValueOrPromise} from './value-promise';
import {instantiateClass} from './resolver';
import {
Constructor,
isPromise,
BoundValue,
ValueOrPromise,
} from './value-promise';
import {Provider} from './provider';

@@ -149,7 +154,7 @@

private _cache: BoundValue;
private _cache: WeakMap<Context, BoundValue>;
private _getValue: (
ctx?: Context,
session?: ResolutionSession,
) => BoundValue | Promise<BoundValue>;
) => ValueOrPromise<BoundValue>;

@@ -172,21 +177,17 @@ // For bindings bound via toClass, this property contains the constructor

ctx: Context,
result: BoundValue | Promise<BoundValue>,
): BoundValue | Promise<BoundValue> {
result: ValueOrPromise<BoundValue>,
): ValueOrPromise<BoundValue> {
// Initialize the cache as a weakmap keyed by context
if (!this._cache) this._cache = new WeakMap<Context, BoundValue>();
if (isPromise(result)) {
if (this.scope === BindingScope.SINGLETON) {
// Cache the value
// Cache the value at owning context level
result = result.then(val => {
this._cache = val;
this._cache.set(ctx.getOwnerContext(this.key)!, val);
return val;
});
} else if (this.scope === BindingScope.CONTEXT) {
// Cache the value
// Cache the value at the current context
result = result.then(val => {
if (ctx.contains(this.key)) {
// The ctx owns the binding
this._cache = val;
} else {
// Create a binding of the cached value for the current context
ctx.bind(this.key).to(val);
}
this._cache.set(ctx, val);
return val;

@@ -198,11 +199,6 @@ });

// Cache the value
this._cache = result;
this._cache.set(ctx.getOwnerContext(this.key)!, result);
} else if (this.scope === BindingScope.CONTEXT) {
if (ctx.contains(this.key)) {
// The ctx owns the binding
this._cache = result;
} else {
// Create a binding of the cached value for the current context
ctx.bind(this.key).to(result);
}
// Cache the value at the current context
this._cache.set(ctx, result);
}

@@ -240,3 +236,3 @@ }

session?: ResolutionSession,
): BoundValue | Promise<BoundValue> {
): ValueOrPromise<BoundValue> {
/* istanbul ignore if */

@@ -247,8 +243,11 @@ if (debug.enabled) {

// First check cached value for non-transient
if (this._cache !== undefined) {
if (this._cache) {
if (this.scope === BindingScope.SINGLETON) {
return this._cache;
const ownerCtx = ctx.getOwnerContext(this.key);
if (ownerCtx && this._cache.has(ownerCtx)) {
return this._cache.get(ownerCtx);
}
} else if (this.scope === BindingScope.CONTEXT) {
if (ctx.contains(this.key)) {
return this._cache;
if (this._cache.has(ctx)) {
return this._cache.get(ctx);
}

@@ -352,3 +351,3 @@ }

*/
toDynamicValue(factoryFn: () => BoundValue | Promise<BoundValue>): this {
toDynamicValue(factoryFn: () => ValueOrPromise<BoundValue>): this {
/* istanbul ignore if */

@@ -355,0 +354,0 @@ if (debug.enabled) {

@@ -13,4 +13,6 @@ // Copyright IBM Corp. 2017,2018. All Rights Reserved.

} from './value-promise';
import {ResolutionSession} from './resolution-session';
import {ResolutionOptions, ResolutionSession} from './resolution-session';
import {v1 as uuidv1} from 'uuid';
import * as debugModule from 'debug';

@@ -23,3 +25,8 @@ const debug = debugModule('loopback:context');

export class Context {
private registry: Map<string, Binding>;
/**
* Name of the context
*/
readonly name: string;
protected readonly registry: Map<string, Binding> = new Map();
protected _parent?: Context;

@@ -30,4 +37,9 @@ /**

*/
constructor(private _parent?: Context) {
this.registry = new Map();
constructor(_parent?: Context | string, name?: string) {
if (typeof _parent === 'string') {
name = _parent;
_parent = undefined;
}
this._parent = _parent;
this.name = name || uuidv1();
}

@@ -61,2 +73,21 @@

/**
* Unbind a binding from the context. No parent contexts will be checked. If
* you need to unbind a binding owned by a parent context, use the code below:
* ```ts
* const ownerCtx = ctx.getOwnerContext(key);
* return ownerCtx != null && ownerCtx.unbind(key);
* ```
* @param key Binding key
* @returns true if the binding key is found and removed from this context
*/
unbind(key: string): boolean {
Binding.validateKey(key);
const binding = this.registry.get(key);
if (binding == null) return false;
if (binding && binding.isLocked)
throw new Error(`Cannot unbind key "${key}" of a locked binding`);
return this.registry.delete(key);
}
/**
* Check if a binding exists with the given key in the local context without

@@ -84,2 +115,14 @@ * delegating to the parent context

/**
* Get the owning context for a binding key
* @param key Binding key
*/
getOwnerContext(key: string): Context | undefined {
if (this.contains(key)) return this;
if (this._parent) {
return this._parent.getOwnerContext(key);
}
return undefined;
}
/**
* Find bindings using the key pattern

@@ -162,11 +205,18 @@ * @param pattern Key regexp or pattern with optional `*` wildcards

* (deeply) nested property to retrieve.
* @param optionsOrSession Options or session for resolution. An instance of
* `ResolutionSession` is accepted for backward compatibility.
* @returns A promise of the bound value.
*/
get(key: string, session?: ResolutionSession): Promise<BoundValue> {
get(
keyWithPath: string,
optionsOrSession?: ResolutionOptions | ResolutionSession,
): Promise<BoundValue> {
/* istanbul ignore if */
if (debug.enabled) {
debug('Resolving binding: %s', key);
debug('Resolving binding: %s', keyWithPath);
}
try {
return Promise.resolve(this.getValueOrPromise(key, session));
return Promise.resolve(
this.getValueOrPromise(keyWithPath, optionsOrSession),
);
} catch (err) {

@@ -197,14 +247,22 @@ return Promise.reject(err);

* (deeply) nested property to retrieve.
* * @param optionsOrSession Options or session for resolution. An instance of
* `ResolutionSession` is accepted for backward compatibility.
* @returns A promise of the bound value.
*/
getSync(key: string, session?: ResolutionSession): BoundValue {
getSync(
keyWithPath: string,
optionsOrSession?: ResolutionOptions | ResolutionSession,
): BoundValue {
/* istanbul ignore if */
if (debug.enabled) {
debug('Resolving binding synchronously: %s', key);
debug('Resolving binding synchronously: %s', keyWithPath);
}
const valueOrPromise = this.getValueOrPromise(key, session);
const valueOrPromise = this.getValueOrPromise(
keyWithPath,
optionsOrSession,
);
if (isPromise(valueOrPromise)) {
throw new Error(
`Cannot get ${key} synchronously: the value is a promise`,
`Cannot get ${keyWithPath} synchronously: the value is a promise`,
);

@@ -222,3 +280,17 @@ }

*/
getBinding(key: string): Binding {
getBinding(key: string): Binding;
/**
* Look up a binding by key in the context and its ancestors. If no matching
* binding is found and `options.optional` is not set to true, an error will
* be thrown.
*
* @param key Binding key
* @param options Options to control if the binding is optional. If
* `options.optional` is set to true, the method will return `undefined`
* instead of throwing an error if the binding key is not found.
*/
getBinding(key: string, options?: {optional?: boolean}): Binding | undefined;
getBinding(key: string, options?: {optional?: boolean}): Binding | undefined {
Binding.validateKey(key);

@@ -231,5 +303,6 @@ const binding = this.registry.get(key);

if (this._parent) {
return this._parent.getBinding(key);
return this._parent.getBinding(key, options);
}
if (options && options.optional) return undefined;
throw new Error(`The key ${key} was not bound to any value.`);

@@ -260,3 +333,3 @@ }

* (deeply) nested property to retrieve.
* @param session An object to keep states of the resolution
* @param optionsOrSession Options for resolution or a session
* @returns The bound value or a promise of the bound value, depending

@@ -268,6 +341,14 @@ * on how the binding was configured.

keyWithPath: string,
session?: ResolutionSession,
optionsOrSession?: ResolutionOptions | ResolutionSession,
): ValueOrPromise<BoundValue> {
const {key, path} = Binding.parseKeyWithPath(keyWithPath);
const boundValue = this.getBinding(key).getValue(this, session);
if (optionsOrSession instanceof ResolutionSession) {
optionsOrSession = {session: optionsOrSession};
}
const binding = this.getBinding(key, optionsOrSession);
if (binding == null) return undefined;
const boundValue = binding.getValue(
this,
optionsOrSession && optionsOrSession.session,
);
if (path === undefined || path === '') {

@@ -274,0 +355,0 @@ return boundValue;

@@ -6,12 +6,8 @@ // Copyright IBM Corp. 2017,2018. All Rights Reserved.

export {Binding, BindingScope, BindingType} from './binding';
export * from '@loopback/metadata';
export {Context} from './context';
export {Constructor} from './resolver';
export {ResolutionSession} from './resolution-session';
export {inject, Setter, Getter} from './inject';
export {Provider} from './provider';
export {
isPromise,
BoundValue,
Constructor,
ValueOrPromise,

@@ -25,10 +21,11 @@ MapObject,

export {Binding, BindingScope, BindingType} from './binding';
export {Context} from './context';
export {ResolutionSession} from './resolution-session';
export {inject, Setter, Getter, Injection} from './inject';
export {Provider} from './provider';
export {instantiateClass, invokeMethod} from './resolver';
// internals for testing
export {instantiateClass, invokeMethod} from './resolver';
export {
describeInjectedArguments,
describeInjectedProperties,
Injection,
} from './inject';
export * from '@loopback/metadata';
export {describeInjectedArguments, describeInjectedProperties} from './inject';

@@ -13,8 +13,3 @@ // Copyright IBM Corp. 2017,2018. All Rights Reserved.

} from '@loopback/metadata';
import {
BoundValue,
ValueOrPromise,
isPromise,
resolveList,
} from './value-promise';
import {BoundValue, ValueOrPromise, resolveList} from './value-promise';
import {Context} from './context';

@@ -83,2 +78,3 @@ import {ResolutionSession} from './resolution-session';

) {
metadata = Object.assign({decorator: '@inject'}, metadata);
return function markParameterOrPropertyAsInjected(

@@ -173,2 +169,3 @@ target: Object,

) {
metadata = Object.assign({decorator: '@inject.getter'}, metadata);
return inject(bindingKey, metadata, resolveAsGetter);

@@ -194,2 +191,3 @@ };

) {
metadata = Object.assign({decorator: '@inject.setter'}, metadata);
return inject(bindingKey, metadata, resolveAsSetter);

@@ -199,4 +197,4 @@ };

/**
* Inject an array of values by a tag
* @param bindingTag Tag name or regex
* Inject an array of values by a tag pattern string or regexp
*
* @example

@@ -207,9 +205,32 @@ * ```ts

* @inject.tag('authentication.strategy') public strategies: Strategy[],
* ) { }
* ) {}
* }
* ```
* @param bindingTag Tag name or regex
* @param metadata Optional metadata to help the injection
*/
export const tag = function injectTag(bindingTag: string | RegExp) {
return inject('', {tag: bindingTag}, resolveByTag);
export const tag = function injectTag(
bindingTag: string | RegExp,
metadata?: Object,
) {
metadata = Object.assign(
{decorator: '@inject.tag', tag: bindingTag},
metadata,
);
return inject('', metadata, resolveByTag);
};
/**
* Inject the context object.
*
* @example
* ```ts
* class MyProvider {
* constructor(@inject.context() private ctx: Context) {}
* }
* ```
*/
export const context = function injectContext() {
return inject('', {decorator: '@inject.context'}, ctx => ctx);
};
}

@@ -225,3 +246,6 @@

return function getter() {
return ctx.get(injection.bindingKey, session);
return ctx.get(injection.bindingKey, {
session,
optional: injection.metadata && injection.metadata.optional,
});
};

@@ -228,0 +252,0 @@ }

@@ -8,8 +8,3 @@ // Copyright IBM Corp. 2018. All Rights Reserved.

import {Injection} from './inject';
import {
isPromise,
ValueOrPromise,
BoundValue,
tryWithFinally,
} from './value-promise';
import {ValueOrPromise, BoundValue, tryWithFinally} from './value-promise';
import * as debugModule from 'debug';

@@ -50,2 +45,22 @@ import {DecoratorFactory} from '@loopback/metadata';

/**
* Type guard for binding elements
* @param element A resolution element
*/
function isBinding(
element: ResolutionElement | undefined,
): element is BindingElement {
return element != null && element.type === 'binding';
}
/**
* Type guard for injection elements
* @param element A resolution element
*/
function isInjection(
element: ResolutionElement | undefined,
): element is InjectionElement {
return element != null && element.type === 'injection';
}
/**
* Object to keep states for a session to resolve bindings and their

@@ -184,3 +199,3 @@ * dependencies within a context

const top = this.stack.pop();
if (top === undefined || top.type !== 'injection') {
if (!isInjection(top)) {
throw new Error('The top element must be an injection');

@@ -207,6 +222,3 @@ }

const element = this.stack[i];
switch (element.type) {
case 'injection':
return element.value;
}
if (isInjection(element)) return element.value;
}

@@ -222,6 +234,3 @@ return undefined;

const element = this.stack[i];
switch (element.type) {
case 'binding':
return element.value;
}
if (isBinding(element)) return element.value;
}

@@ -240,8 +249,9 @@ return undefined;

}
if (this.stack.find(i => i.type === 'binding' && i.value === binding)) {
throw new Error(
`Circular dependency detected on path '${this.getBindingPath()} --> ${
binding.key
}'`,
);
if (this.stack.find(i => isBinding(i) && i.value === binding)) {
const msg =
`Circular dependency detected: ` +
`${this.getResolutionPath()} --> ${binding.key}`;
debugSession(msg);
throw new Error(msg);
}

@@ -260,3 +270,3 @@ this.stack.push({type: 'binding', value: binding});

const top = this.stack.pop();
if (top === undefined || top.type !== 'binding') {
if (!isBinding(top)) {
throw new Error('The top element must be a binding');

@@ -274,9 +284,20 @@ }

/**
* Getter for bindings on the stack
*/
get bindingStack(): Binding[] {
return this.stack.filter(isBinding).map(e => e.value);
}
/**
* Getter for injections on the stack
*/
get injectionStack(): Injection[] {
return this.stack.filter(isInjection).map(e => e.value);
}
/**
* Get the binding path as `bindingA --> bindingB --> bindingC`.
*/
getBindingPath() {
return this.stack
.filter(i => i.type === 'binding')
.map(b => (<Binding>b.value).key)
.join(' --> ');
return this.bindingStack.map(b => b.key).join(' --> ');
}

@@ -288,8 +309,4 @@

getInjectionPath() {
return this.stack
.filter(i => i.type === 'injection')
.map(
i =>
ResolutionSession.describeInjection(<Injection>i.value)!.targetName,
)
return this.injectionStack
.map(i => ResolutionSession.describeInjection(i)!.targetName)
.join(' --> ');

@@ -316,1 +333,18 @@ }

}
/**
* Options for binding/dependency resolution
*/
export interface ResolutionOptions {
/**
* A session to track bindings and injections
*/
session?: ResolutionSession;
/**
* A boolean flag to indicate if the dependency is optional. If it's set to
* `true` and the binding is not bound in a context, the resolution
* will return `undefined` instead of throwing an error.
*/
optional?: boolean;
}

@@ -10,2 +10,3 @@ // Copyright IBM Corp. 2017,2018. All Rights Reserved.

BoundValue,
Constructor,
ValueOrPromise,

@@ -32,9 +33,2 @@ MapObject,

/**
* A class constructor accepting arbitrary arguments.
*/
export type Constructor<T> =
// tslint:disable-next-line:no-any
new (...args: any[]) => T;
/**
* Create an instance of a class which constructor has arguments

@@ -139,3 +133,8 @@ * decorated with `@inject`.

// Default to resolve the value from the context by binding key
return ctx.getValueOrPromise(injection.bindingKey, s);
return ctx.getValueOrPromise(injection.bindingKey, {
session: s,
// If the `optional` flag is set for the injection, the resolution
// will return `undefined` instead of throwing an error
optional: injection.metadata && injection.metadata.optional,
});
}

@@ -142,0 +141,0 @@ },

@@ -11,2 +11,9 @@ // Copyright IBM Corp. 2017,2018. All Rights Reserved.

/**
* A class constructor accepting arbitrary arguments.
*/
export type Constructor<T> =
// tslint:disable-next-line:no-any
new (...args: any[]) => T;
// tslint:disable-next-line:no-any

@@ -87,3 +94,7 @@ export type BoundValue = any;

const setter = (key: string) => (val: V) => {
result[key] = val;
if (val !== undefined) {
// Only set the value if it's not undefined so that the default value
// for a key will be honored
result[key] = val;
}
};

@@ -97,3 +108,7 @@

} else {
result[key] = valueOrPromise;
if (valueOrPromise !== undefined) {
// Only set the value if it's not undefined so that the default value
// for a key will be honored
result[key] = valueOrPromise;
}
}

@@ -100,0 +115,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

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