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 0.1.1 to 0.2.0

41

CHANGELOG.md

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

<a name="0.2.0"></a>
# [0.2.0](https://github.com/strongloop/loopback-next/compare/@loopback/context@0.1.2...@loopback/context@0.2.0) (2018-03-01)
**Note:** Version bump only for package @loopback/context
<a name="0.1.2"></a>
## [0.1.2](https://github.com/strongloop/loopback-next/compare/@loopback/context@0.1.1...@loopback/context@0.1.2) (2018-03-01)
### Features
* **context:** add type as a generic parameter to `ctx.get()` and friends ([24b217d](https://github.com/strongloop/loopback-next/commit/24b217d))
* **context:** allow context.find by a filter function ([9b1e26c](https://github.com/strongloop/loopback-next/commit/9b1e26c))
* **context:** use Readonly to guard immutable values ([871ddef](https://github.com/strongloop/loopback-next/commit/871ddef))
### BREAKING CHANGES
* **context:** `ctx.get()` and `ctx.getSync()` require a type now.
See the example below for upgrade instructions:
```diff
- const c: MyController = await ctx.get('MyController');
+ const c = await ctx.get<MyController>('MyController');
```
`isPromise` was renamed to `isPromiseLike` and acts as a type guard
for `PromiseLike`, not `Promise`. When upgrading affected code, you
need to determine whether the code was accepting any Promise
implementation (i.e. `PromiseLike`) or only native Promises. In the
former case, you should use `isPromiseLike` and potentially convert the
userland Promise instance to a native Promise via
`Promise.resolve(promiseLike)`. In the latter case, you can replace
`isPromise(p)` with `p instanceof Promise`.
<a name="0.1.1"></a>

@@ -8,0 +49,0 @@ ## [0.1.1](https://github.com/strongloop/loopback-next/compare/@loopback/context@0.1.0...@loopback/context@0.1.1) (2018-02-23)

6

dist/src/binding.js

@@ -147,3 +147,3 @@ "use strict";

this._cache = new WeakMap();
if (value_promise_1.isPromise(result)) {
if (value_promise_1.isPromiseLike(result)) {
if (this.scope === BindingScope.SINGLETON) {

@@ -257,3 +257,3 @@ // Cache the value at owning context level

to(value) {
if (value_promise_1.isPromise(value)) {
if (value_promise_1.isPromiseLike(value)) {
// Promises are a construct primarily intended for flow control:

@@ -336,3 +336,3 @@ // In an algorithm with steps 1 and 2, we want to wait for the outcome

const providerOrPromise = resolver_1.instantiateClass(providerClass, ctx, session);
if (value_promise_1.isPromise(providerOrPromise)) {
if (value_promise_1.isPromiseLike(providerOrPromise)) {
return providerOrPromise.then(p => p.value());

@@ -339,0 +339,0 @@ }

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

@@ -54,15 +54,35 @@ * Context provides an implementation of Inversion of Control (IoC) container

/**
* Convert a wildcard pattern to RegExp
* @param pattern A wildcard string with `*` and `?` as special characters.
* - `*` matches zero or more characters except `.` and `:`
* - `?` matches exactly one character except `.` and `:`
*/
private wildcardToRegExp(pattern);
/**
* Find bindings using the key pattern
* @param pattern Key regexp or pattern with optional `*` wildcards
* @param pattern A regexp or wildcard pattern with optional `*` and `?`. If
* it matches the binding key, the binding is included. For a wildcard:
* - `*` matches zero or more characters except `.` and `:`
* - `?` matches exactly one character except `.` and `:`
*/
find(pattern?: string | RegExp): Binding[];
find(pattern?: string | RegExp): Readonly<Binding>[];
/**
* Find bindings using a filter function
* @param filter A function to test on the binding. It returns `true` to
* include the binding or `false` to exclude the binding.
*/
find(filter: (binding: Readonly<Binding>) => boolean): Readonly<Binding>[];
/**
* Find bindings using the tag pattern
* @param pattern Tag name regexp or pattern with optional `*` wildcards
* @param pattern A regexp or wildcard pattern with optional `*` and `?`. If
* it matches one of the binding tags, the binding is included. For a
* wildcard:
* - `*` matches zero or more characters except `.` and `:`
* - `?` matches exactly one character except `.` and `:`
*/
findByTag(pattern: string | RegExp): Binding[];
protected _mergeWithParent(childList: Binding[], parentList?: Binding[]): Binding[];
findByTag(pattern: string | RegExp): Readonly<Binding>[];
protected _mergeWithParent(childList: Readonly<Binding>[], parentList?: Readonly<Binding>[]): Readonly<Binding>[];
/**
* Get the value bound to the given key, optionally return a (deep) property
* of the bound value.
* Get the value bound to the given key, throw an error when no value was
* bound for the given key.
*

@@ -73,10 +93,10 @@ * @example

* // get the value bound to "application.instance"
* const app = await ctx.get('application.instance');
* const app = await ctx.get<Application>('application.instance');
*
* // get "rest" property from the value bound to "config"
* const config = await ctx.getValueOrPromise('config#rest');
* const config = await ctx.get<RestComponentConfig>('config#rest');
*
* // get "a" property of "numbers" property from the value bound to "data"
* ctx.bind('data').to({numbers: {a: 1, b: 2}, port: 3000});
* const a = await ctx.get('data#numbers.a');
* const a = await ctx.get<number>('data#numbers.a');
* ```

@@ -86,7 +106,27 @@ *

* (deeply) nested property to retrieve.
* @returns A promise of the bound value.
*/
get<T>(keyWithPath: string): Promise<T>;
/**
* Get the value bound to the given key, optionally return a (deep) property
* of the bound value.
*
* @example
*
* ```ts
* // get "rest" property from the value bound to "config"
* // use "undefined" when not config was provided
* const config = await ctx.get<RestComponentConfig>('config#rest', {
* optional: true
* });
* ```
*
* @param keyWithPath The binding key, optionally suffixed with a path to the
* (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.
* @returns A promise of the bound value, or a promise of undefined when
* the optional binding was not found.
*/
get(keyWithPath: string, optionsOrSession?: ResolutionOptions | ResolutionSession): Promise<BoundValue>;
get<T>(keyWithPath: string, optionsOrSession?: ResolutionOptions | ResolutionSession): Promise<T | undefined>;
/**

@@ -104,6 +144,6 @@ * Get the synchronous value bound to the given key, optionally

* // get the value bound to "application.instance"
* const app = ctx.get('application.instance');
* const app = ctx.getSync<Application>('application.instance');
*
* // get "rest" property from the value bound to "config"
* const config = ctx.getValueOrPromise('config#rest');
* const config = await ctx.getSync<RestComponentConfig>('config#rest');
* ```

@@ -117,4 +157,29 @@ *

*/
getSync(keyWithPath: string, optionsOrSession?: ResolutionOptions | ResolutionSession): BoundValue;
getSync<T>(keyWithPath: string): T;
/**
* Get the synchronous value bound to the given key, optionally
* return a (deep) property of the bound value.
*
* This method throws an error if the bound value requires async computation
* (returns a promise). You should never rely on sync bindings in production
* code.
*
* @example
*
* ```ts
* // get "rest" property from the value bound to "config"
* // use "undefined" when no config was provided
* const config = await ctx.getSync<RestComponentConfig>('config#rest', {
* optional: true
* });
* ```
*
* @param keyWithPath The binding key, optionally suffixed with a path to the
* (deeply) nested property to retrieve.
* * @param optionsOrSession Options or session for resolution. An instance of
* `ResolutionSession` is accepted for backward compatibility.
* @returns The bound value, or undefined when an optional binding was not found.
*/
getSync<T>(keyWithPath: string, optionsOrSession?: ResolutionOptions | ResolutionSession): T | undefined;
/**
* Look up a binding by key in the context and its ancestors. If no matching

@@ -149,10 +214,10 @@ * binding is found, an error will be thrown.

* // get the value bound to "application.instance"
* ctx.getValueOrPromise('application.instance');
* ctx.getValueOrPromise<Application>('application.instance');
*
* // get "rest" property from the value bound to "config"
* ctx.getValueOrPromise('config#rest');
* ctx.getValueOrPromise<RestComponentConfig>('config#rest');
*
* // get "a" property of "numbers" property from the value bound to "data"
* ctx.bind('data').to({numbers: {a: 1, b: 2}, port: 3000});
* ctx.getValueOrPromise('data#numbers.a');
* ctx.getValueOrPromise<number>('data#numbers.a');
* ```

@@ -167,3 +232,3 @@ *

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

@@ -170,0 +235,0 @@ * Create a plain JSON object for the context

@@ -106,27 +106,38 @@ "use strict";

/**
* Find bindings using the key pattern
* @param pattern Key regexp or pattern with optional `*` wildcards
* Convert a wildcard pattern to RegExp
* @param pattern A wildcard string with `*` and `?` as special characters.
* - `*` matches zero or more characters except `.` and `:`
* - `?` matches exactly one character except `.` and `:`
*/
wildcardToRegExp(pattern) {
// Escape reserved chars for RegExp:
// `- \ ^ $ + . ( ) | { } [ ] :`
let regexp = pattern.replace(/[\-\[\]\/\{\}\(\)\+\.\\\^\$\|\:]/g, '\\$&');
// Replace wildcard chars `*` and `?`
// `*` matches zero or more characters except `.` and `:`
// `?` matches one character except `.` and `:`
regexp = regexp.replace(/\*/g, '[^.:]*').replace(/\?/g, '[^.:]');
return new RegExp(`^${regexp}$`);
}
find(pattern) {
let bindings = [];
let glob = undefined;
if (typeof pattern === 'string') {
// TODO(@superkhau): swap with production grade glob to regex lib
binding_1.Binding.validateKey(pattern);
glob = new RegExp('^' + pattern.split('*').join('.*') + '$');
let filter;
if (!pattern) {
filter = binding => true;
}
else if (typeof pattern === 'string') {
const regex = this.wildcardToRegExp(pattern);
filter = binding => regex.test(binding.key);
}
else if (pattern instanceof RegExp) {
glob = pattern;
filter = binding => pattern.test(binding.key);
}
if (glob) {
this.registry.forEach(binding => {
const isMatch = glob.test(binding.key);
if (isMatch)
bindings.push(binding);
});
}
else {
bindings = Array.from(this.registry.values());
filter = pattern;
}
const parentBindings = this._parent && this._parent.find(pattern);
for (const b of this.registry.values()) {
if (filter(b))
bindings.push(b);
}
const parentBindings = this._parent && this._parent.find(filter);
return this._mergeWithParent(bindings, parentBindings);

@@ -136,17 +147,11 @@ }

* Find bindings using the tag pattern
* @param pattern Tag name regexp or pattern with optional `*` wildcards
* @param pattern A regexp or wildcard pattern with optional `*` and `?`. If
* it matches one of the binding tags, the binding is included. For a
* wildcard:
* - `*` matches zero or more characters except `.` and `:`
* - `?` matches exactly one character except `.` and `:`
*/
findByTag(pattern) {
const bindings = [];
// TODO(@superkhau): swap with production grade glob to regex lib
const glob = typeof pattern === 'string'
? new RegExp('^' + pattern.split('*').join('.*') + '$')
: pattern;
this.registry.forEach(binding => {
const isMatch = Array.from(binding.tags).some(tag => glob.test(tag));
if (isMatch)
bindings.push(binding);
});
const parentBindings = this._parent && this._parent.findByTag(pattern);
return this._mergeWithParent(bindings, parentBindings);
const regexp = typeof pattern === 'string' ? this.wildcardToRegExp(pattern) : pattern;
return this.find(b => Array.from(b.tags).some(t => regexp.test(t)));
}

@@ -162,27 +167,4 @@ _mergeWithParent(childList, parentList) {

}
/**
* Get the value bound to the given key, optionally return a (deep) property
* of the bound value.
*
* @example
*
* ```ts
* // get the value bound to "application.instance"
* const app = await ctx.get('application.instance');
*
* // get "rest" property from the value bound to "config"
* const config = await ctx.getValueOrPromise('config#rest');
*
* // get "a" property of "numbers" property from the value bound to "data"
* ctx.bind('data').to({numbers: {a: 1, b: 2}, port: 3000});
* const a = await ctx.get('data#numbers.a');
* ```
*
* @param keyWithPath The binding key, optionally suffixed with a path to the
* (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(keyWithPath, optionsOrSession) {
// Implementation
async get(keyWithPath, optionsOrSession) {
/* istanbul ignore if */

@@ -192,33 +174,5 @@ if (debug.enabled) {

}
try {
return Promise.resolve(this.getValueOrPromise(keyWithPath, optionsOrSession));
}
catch (err) {
return Promise.reject(err);
}
return await this.getValueOrPromise(keyWithPath, optionsOrSession);
}
/**
* Get the synchronous value bound to the given key, optionally
* return a (deep) property of the bound value.
*
* This method throws an error if the bound value requires async computation
* (returns a promise). You should never rely on sync bindings in production
* code.
*
* @example
*
* ```ts
* // get the value bound to "application.instance"
* const app = ctx.get('application.instance');
*
* // get "rest" property from the value bound to "config"
* const config = ctx.getValueOrPromise('config#rest');
* ```
*
* @param keyWithPath The binding key, optionally suffixed with a path to the
* (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.
*/
// Implementation
getSync(keyWithPath, optionsOrSession) {

@@ -230,3 +184,3 @@ /* istanbul ignore if */

const valueOrPromise = this.getValueOrPromise(keyWithPath, optionsOrSession);
if (value_promise_1.isPromise(valueOrPromise)) {
if (value_promise_1.isPromiseLike(valueOrPromise)) {
throw new Error(`Cannot get ${keyWithPath} synchronously: the value is a promise`);

@@ -259,10 +213,10 @@ }

* // get the value bound to "application.instance"
* ctx.getValueOrPromise('application.instance');
* ctx.getValueOrPromise<Application>('application.instance');
*
* // get "rest" property from the value bound to "config"
* ctx.getValueOrPromise('config#rest');
* ctx.getValueOrPromise<RestComponentConfig>('config#rest');
*
* // get "a" property of "numbers" property from the value bound to "data"
* ctx.bind('data').to({numbers: {a: 1, b: 2}, port: 3000});
* ctx.getValueOrPromise('data#numbers.a');
* ctx.getValueOrPromise<number>('data#numbers.a');
* ```

@@ -279,2 +233,3 @@ *

const { key, path } = binding_1.Binding.parseKeyWithPath(keyWithPath);
// backwards compatibility
if (optionsOrSession instanceof resolution_session_1.ResolutionSession) {

@@ -290,3 +245,3 @@ optionsOrSession = { session: optionsOrSession };

}
if (value_promise_1.isPromise(boundValue)) {
if (value_promise_1.isPromiseLike(boundValue)) {
return boundValue.then(v => value_promise_1.getDeepProperty(v, path));

@@ -293,0 +248,0 @@ }

export * from '@loopback/metadata';
export { isPromise, BoundValue, Constructor, ValueOrPromise, MapObject, resolveList, resolveMap, tryWithFinally, getDeepProperty } from './value-promise';
export { isPromiseLike, BoundValue, Constructor, ValueOrPromise, MapObject, resolveList, resolveMap, tryWithFinally, getDeepProperty } from './value-promise';
export { Binding, BindingScope, BindingType } from './binding';

@@ -4,0 +4,0 @@ export { Context } from './context';

@@ -12,3 +12,3 @@ "use strict";

var value_promise_1 = require("./value-promise");
exports.isPromise = value_promise_1.isPromise;
exports.isPromiseLike = value_promise_1.isPromiseLike;
exports.resolveList = value_promise_1.resolveList;

@@ -15,0 +15,0 @@ exports.resolveMap = value_promise_1.resolveMap;

@@ -9,3 +9,3 @@ import { MetadataMap } from '@loopback/metadata';

export interface ResolverFunction {
(ctx: Context, injection: Injection, session?: ResolutionSession): ValueOrPromise<BoundValue>;
(ctx: Context, injection: Readonly<Injection>, session?: ResolutionSession): ValueOrPromise<BoundValue>;
}

@@ -138,3 +138,3 @@ /**

*/
export declare function describeInjectedArguments(target: Object, method?: string | symbol): Injection[];
export declare function describeInjectedArguments(target: Object, method?: string | symbol): Readonly<Injection>[];
/**

@@ -145,2 +145,2 @@ * Return a map of injection objects for properties

*/
export declare function describeInjectedProperties(target: Object): MetadataMap<Injection>;
export declare function describeInjectedProperties(target: Object): MetadataMap<Readonly<Injection>>;

@@ -13,3 +13,3 @@ import { Binding } from './binding';

type: 'binding';
value: Binding;
value: Readonly<Binding>;
}

@@ -21,3 +21,3 @@ /**

type: 'injection';
value: Injection;
value: Readonly<Injection>;
}

@@ -57,3 +57,3 @@ /**

*/
static runWithBinding(action: ResolutionAction, binding: Binding, session?: ResolutionSession): any;
static runWithBinding(action: ResolutionAction, binding: Readonly<Binding>, session?: ResolutionSession): any;
/**

@@ -71,3 +71,3 @@ * Push an injection into the session

*/
static runWithInjection(action: ResolutionAction, injection: Injection, session?: ResolutionSession): any;
static runWithInjection(action: ResolutionAction, injection: Readonly<Injection>, session?: ResolutionSession): any;
/**

@@ -77,3 +77,3 @@ * Describe the injection for debugging purpose

*/
static describeInjection(injection?: Injection): {
static describeInjection(injection?: Readonly<Injection>): {
targetName: string;

@@ -87,15 +87,15 @@ bindingKey: string;

*/
pushInjection(injection: Injection): void;
pushInjection(injection: Readonly<Injection>): void;
/**
* Pop the last injection
*/
popInjection(): Injection;
popInjection(): Readonly<Injection>;
/**
* Getter for the current injection
*/
readonly currentInjection: Injection | undefined;
readonly currentInjection: Readonly<Injection> | undefined;
/**
* Getter for the current binding
*/
readonly currentBinding: Binding | undefined;
readonly currentBinding: Readonly<Binding> | undefined;
/**

@@ -105,15 +105,15 @@ * Enter the resolution of the given binding. If

*/
pushBinding(binding: Binding): void;
pushBinding(binding: Readonly<Binding>): void;
/**
* Exit the resolution of a binding
*/
popBinding(): Binding;
popBinding(): Readonly<Binding>;
/**
* Getter for bindings on the stack
*/
readonly bindingStack: Binding[];
readonly bindingStack: Readonly<Binding>[];
/**
* Getter for injections on the stack
*/
readonly injectionStack: Injection[];
readonly injectionStack: Readonly<Injection>[];
/**

@@ -120,0 +120,0 @@ * Get the binding path as `bindingA --> bindingB --> bindingC`.

@@ -40,3 +40,3 @@ "use strict";

let inst;
if (value_promise_1.isPromise(argsOrPromise)) {
if (value_promise_1.isPromiseLike(argsOrPromise)) {
// Instantiate the class asynchronously

@@ -59,3 +59,3 @@ inst = argsOrPromise.then(args => {

}
if (value_promise_1.isPromise(propertiesOrPromise)) {
if (value_promise_1.isPromiseLike(propertiesOrPromise)) {
return propertiesOrPromise.then(props => {

@@ -66,3 +66,3 @@ /* istanbul ignore if */

}
if (value_promise_1.isPromise(inst)) {
if (value_promise_1.isPromiseLike(inst)) {
// Inject the properties asynchronously

@@ -78,3 +78,3 @@ return inst.then(obj => Object.assign(obj, props));

else {
if (value_promise_1.isPromise(inst)) {
if (value_promise_1.isPromiseLike(inst)) {
/* istanbul ignore if */

@@ -209,3 +209,3 @@ if (debug.enabled) {

assert(typeof targetWithMethods[method] === 'function', `Method ${method} not found`);
if (value_promise_1.isPromise(argsOrPromise)) {
if (value_promise_1.isPromiseLike(argsOrPromise)) {
// Invoke the target method asynchronously

@@ -212,0 +212,0 @@ return argsOrPromise.then(args => {

@@ -13,4 +13,9 @@ /**

* synchronous/asynchronous resolution of values.
*
* Note that we are using PromiseLike instead of native Promise to describe
* the asynchronous variant. This allows producers of async values to use
* any Promise implementation (e.g. Bluebird) instead of native Promises
* provided by JavaScript runtime.
*/
export declare type ValueOrPromise<T> = T | Promise<T>;
export declare type ValueOrPromise<T> = T | PromiseLike<T>;
export declare type MapObject<T> = {

@@ -25,3 +30,3 @@ [name: string]: T;

*/
export declare function isPromise<T>(value: T | PromiseLike<T>): value is PromiseLike<T>;
export declare function isPromiseLike<T>(value: T | PromiseLike<T> | undefined): value is PromiseLike<T>;
/**

@@ -32,3 +37,3 @@ * Get nested properties by path

*/
export declare function getDeepProperty(value: BoundValue, path: string): any;
export declare function getDeepProperty(value: BoundValue, path: string): BoundValue;
/**

@@ -35,0 +40,0 @@ * Resolve entries of an object into a new object with the same keys. If one or

@@ -13,3 +13,3 @@ "use strict";

*/
function isPromise(value) {
function isPromiseLike(value) {
if (!value)

@@ -21,3 +21,3 @@ return false;

}
exports.isPromise = isPromise;
exports.isPromiseLike = isPromiseLike;
/**

@@ -77,3 +77,3 @@ * Get nested properties by path

const valueOrPromise = resolver(map[key], key, map);
if (isPromise(valueOrPromise)) {
if (isPromiseLike(valueOrPromise)) {
if (!asyncResolvers)

@@ -134,3 +134,3 @@ asyncResolvers = [];

const valueOrPromise = resolver(list[ix], ix, list);
if (isPromise(valueOrPromise)) {
if (isPromiseLike(valueOrPromise)) {
if (!asyncResolvers)

@@ -167,3 +167,3 @@ asyncResolvers = [];

}
if (isPromise(result)) {
if (isPromiseLike(result)) {
// Once (promise.finally)[https://github.com/tc39/proposal-promise-finally

@@ -170,0 +170,0 @@ // is supported, the following can be simplifed as

{
"name": "@loopback/context",
"version": "0.1.1",
"version": "0.2.0",
"description": "LoopBack's container for Inversion of Control",

@@ -23,3 +23,3 @@ "engines": {

"dependencies": {
"@loopback/metadata": "^0.1.1",
"@loopback/metadata": "^0.2.0",
"debug": "^3.1.0",

@@ -29,4 +29,4 @@ "uuid": "^3.2.1"

"devDependencies": {
"@loopback/build": "^0.1.1",
"@loopback/testlab": "^0.1.1",
"@loopback/build": "^0.2.0",
"@loopback/testlab": "^0.2.0",
"@types/bluebird": "^3.5.18",

@@ -33,0 +33,0 @@ "@types/debug": "^0.0.30",

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

Constructor,
isPromise,
isPromiseLike,
BoundValue,

@@ -180,3 +180,3 @@ ValueOrPromise,

if (!this._cache) this._cache = new WeakMap<Context, BoundValue>();
if (isPromise(result)) {
if (isPromiseLike(result)) {
if (this.scope === BindingScope.SINGLETON) {

@@ -299,3 +299,3 @@ // Cache the value at owning context level

to(value: BoundValue): this {
if (isPromise(value)) {
if (isPromiseLike(value)) {
// Promises are a construct primarily intended for flow control:

@@ -386,3 +386,3 @@ // In an algorithm with steps 1 and 2, we want to wait for the outcome

);
if (isPromise(providerOrPromise)) {
if (isPromiseLike(providerOrPromise)) {
return providerOrPromise.then(p => p.value());

@@ -389,0 +389,0 @@ } else {

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

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

@@ -19,2 +14,3 @@

import * as debugModule from 'debug';
import {ValueOrPromise} from '.';
const debug = debugModule('loopback:context');

@@ -125,25 +121,55 @@

/**
* Convert a wildcard pattern to RegExp
* @param pattern A wildcard string with `*` and `?` as special characters.
* - `*` matches zero or more characters except `.` and `:`
* - `?` matches exactly one character except `.` and `:`
*/
private wildcardToRegExp(pattern: string): RegExp {
// Escape reserved chars for RegExp:
// `- \ ^ $ + . ( ) | { } [ ] :`
let regexp = pattern.replace(/[\-\[\]\/\{\}\(\)\+\.\\\^\$\|\:]/g, '\\$&');
// Replace wildcard chars `*` and `?`
// `*` matches zero or more characters except `.` and `:`
// `?` matches one character except `.` and `:`
regexp = regexp.replace(/\*/g, '[^.:]*').replace(/\?/g, '[^.:]');
return new RegExp(`^${regexp}$`);
}
/**
* Find bindings using the key pattern
* @param pattern Key regexp or pattern with optional `*` wildcards
* @param pattern A regexp or wildcard pattern with optional `*` and `?`. If
* it matches the binding key, the binding is included. For a wildcard:
* - `*` matches zero or more characters except `.` and `:`
* - `?` matches exactly one character except `.` and `:`
*/
find(pattern?: string | RegExp): Binding[] {
let bindings: Binding[] = [];
let glob: RegExp | undefined = undefined;
if (typeof pattern === 'string') {
// TODO(@superkhau): swap with production grade glob to regex lib
Binding.validateKey(pattern);
glob = new RegExp('^' + pattern.split('*').join('.*') + '$');
find(pattern?: string | RegExp): Readonly<Binding>[];
/**
* Find bindings using a filter function
* @param filter A function to test on the binding. It returns `true` to
* include the binding or `false` to exclude the binding.
*/
find(filter: (binding: Readonly<Binding>) => boolean): Readonly<Binding>[];
find(
pattern?: string | RegExp | ((binding: Binding) => boolean),
): Readonly<Binding>[] {
let bindings: Readonly<Binding>[] = [];
let filter: (binding: Readonly<Binding>) => boolean;
if (!pattern) {
filter = binding => true;
} else if (typeof pattern === 'string') {
const regex = this.wildcardToRegExp(pattern);
filter = binding => regex.test(binding.key);
} else if (pattern instanceof RegExp) {
glob = pattern;
}
if (glob) {
this.registry.forEach(binding => {
const isMatch = glob!.test(binding.key);
if (isMatch) bindings.push(binding);
});
filter = binding => pattern.test(binding.key);
} else {
bindings = Array.from(this.registry.values());
filter = pattern;
}
const parentBindings = this._parent && this._parent.find(pattern);
for (const b of this.registry.values()) {
if (filter(b)) bindings.push(b);
}
const parentBindings = this._parent && this._parent.find(filter);
return this._mergeWithParent(bindings, parentBindings);

@@ -154,21 +180,18 @@ }

* Find bindings using the tag pattern
* @param pattern Tag name regexp or pattern with optional `*` wildcards
* @param pattern A regexp or wildcard pattern with optional `*` and `?`. If
* it matches one of the binding tags, the binding is included. For a
* wildcard:
* - `*` matches zero or more characters except `.` and `:`
* - `?` matches exactly one character except `.` and `:`
*/
findByTag(pattern: string | RegExp): Binding[] {
const bindings: Binding[] = [];
// TODO(@superkhau): swap with production grade glob to regex lib
const glob =
typeof pattern === 'string'
? new RegExp('^' + pattern.split('*').join('.*') + '$')
: pattern;
this.registry.forEach(binding => {
const isMatch = Array.from(binding.tags).some(tag => glob.test(tag));
if (isMatch) bindings.push(binding);
});
const parentBindings = this._parent && this._parent.findByTag(pattern);
return this._mergeWithParent(bindings, parentBindings);
findByTag(pattern: string | RegExp): Readonly<Binding>[] {
const regexp =
typeof pattern === 'string' ? this.wildcardToRegExp(pattern) : pattern;
return this.find(b => Array.from(b.tags).some(t => regexp.test(t)));
}
protected _mergeWithParent(childList: Binding[], parentList?: Binding[]) {
protected _mergeWithParent(
childList: Readonly<Binding>[],
parentList?: Readonly<Binding>[],
) {
if (!parentList) return childList;

@@ -185,4 +208,4 @@ const additions = parentList.filter(parentBinding => {

/**
* Get the value bound to the given key, optionally return a (deep) property
* of the bound value.
* Get the value bound to the given key, throw an error when no value was
* bound for the given key.
*

@@ -193,10 +216,10 @@ * @example

* // get the value bound to "application.instance"
* const app = await ctx.get('application.instance');
* const app = await ctx.get<Application>('application.instance');
*
* // get "rest" property from the value bound to "config"
* const config = await ctx.getValueOrPromise('config#rest');
* const config = await ctx.get<RestComponentConfig>('config#rest');
*
* // get "a" property of "numbers" property from the value bound to "data"
* ctx.bind('data').to({numbers: {a: 1, b: 2}, port: 3000});
* const a = await ctx.get('data#numbers.a');
* const a = await ctx.get<number>('data#numbers.a');
* ```

@@ -206,10 +229,37 @@ *

* (deeply) nested property to retrieve.
* @returns A promise of the bound value.
*/
get<T>(keyWithPath: string): Promise<T>;
/**
* Get the value bound to the given key, optionally return a (deep) property
* of the bound value.
*
* @example
*
* ```ts
* // get "rest" property from the value bound to "config"
* // use "undefined" when not config was provided
* const config = await ctx.get<RestComponentConfig>('config#rest', {
* optional: true
* });
* ```
*
* @param keyWithPath The binding key, optionally suffixed with a path to the
* (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.
* @returns A promise of the bound value, or a promise of undefined when
* the optional binding was not found.
*/
get(
get<T>(
keyWithPath: string,
optionsOrSession?: ResolutionOptions | ResolutionSession,
): Promise<BoundValue> {
): Promise<T | undefined>;
// Implementation
async get<T>(
keyWithPath: string,
optionsOrSession?: ResolutionOptions | ResolutionSession,
): Promise<T | undefined> {
/* istanbul ignore if */

@@ -219,9 +269,7 @@ if (debug.enabled) {

}
try {
return Promise.resolve(
this.getValueOrPromise(keyWithPath, optionsOrSession),
);
} catch (err) {
return Promise.reject(err);
}
return await this.getValueOrPromise<T | undefined>(
keyWithPath,
optionsOrSession,
);
}

@@ -241,6 +289,6 @@

* // get the value bound to "application.instance"
* const app = ctx.get('application.instance');
* const app = ctx.getSync<Application>('application.instance');
*
* // get "rest" property from the value bound to "config"
* const config = ctx.getValueOrPromise('config#rest');
* const config = await ctx.getSync<RestComponentConfig>('config#rest');
* ```

@@ -254,6 +302,38 @@ *

*/
getSync(
getSync<T>(keyWithPath: string): T;
/**
* Get the synchronous value bound to the given key, optionally
* return a (deep) property of the bound value.
*
* This method throws an error if the bound value requires async computation
* (returns a promise). You should never rely on sync bindings in production
* code.
*
* @example
*
* ```ts
* // get "rest" property from the value bound to "config"
* // use "undefined" when no config was provided
* const config = await ctx.getSync<RestComponentConfig>('config#rest', {
* optional: true
* });
* ```
*
* @param keyWithPath The binding key, optionally suffixed with a path to the
* (deeply) nested property to retrieve.
* * @param optionsOrSession Options or session for resolution. An instance of
* `ResolutionSession` is accepted for backward compatibility.
* @returns The bound value, or undefined when an optional binding was not found.
*/
getSync<T>(
keyWithPath: string,
optionsOrSession?: ResolutionOptions | ResolutionSession,
): BoundValue {
): T | undefined;
// Implementation
getSync<T>(
keyWithPath: string,
optionsOrSession?: ResolutionOptions | ResolutionSession,
): T | undefined {
/* istanbul ignore if */

@@ -263,3 +343,3 @@ if (debug.enabled) {

}
const valueOrPromise = this.getValueOrPromise(
const valueOrPromise = this.getValueOrPromise<T>(
keyWithPath,

@@ -269,3 +349,3 @@ optionsOrSession,

if (isPromise(valueOrPromise)) {
if (isPromiseLike(valueOrPromise)) {
throw new Error(

@@ -324,10 +404,10 @@ `Cannot get ${keyWithPath} synchronously: the value is a promise`,

* // get the value bound to "application.instance"
* ctx.getValueOrPromise('application.instance');
* ctx.getValueOrPromise<Application>('application.instance');
*
* // get "rest" property from the value bound to "config"
* ctx.getValueOrPromise('config#rest');
* ctx.getValueOrPromise<RestComponentConfig>('config#rest');
*
* // get "a" property of "numbers" property from the value bound to "data"
* ctx.bind('data').to({numbers: {a: 1, b: 2}, port: 3000});
* ctx.getValueOrPromise('data#numbers.a');
* ctx.getValueOrPromise<number>('data#numbers.a');
* ```

@@ -342,12 +422,16 @@ *

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

@@ -361,7 +445,7 @@ this,

if (isPromise(boundValue)) {
return boundValue.then(v => getDeepProperty(v, path));
if (isPromiseLike(boundValue)) {
return boundValue.then(v => getDeepProperty(v, path) as T);
}
return getDeepProperty(boundValue, path);
return getDeepProperty(boundValue, path) as T;
}

@@ -368,0 +452,0 @@

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

export {
isPromise,
isPromiseLike,
BoundValue,

@@ -12,0 +12,0 @@ Constructor,

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

ctx: Context,
injection: Injection,
injection: Readonly<Injection>,
session?: ResolutionSession,

@@ -254,3 +254,3 @@ ): ValueOrPromise<BoundValue>;

ctx: Context,
injection: Injection,
injection: Readonly<Injection>,
session?: ResolutionSession,

@@ -284,5 +284,5 @@ ) {

method?: string | symbol,
): Injection[] {
): Readonly<Injection>[] {
method = method || '';
const meta = MetadataInspector.getAllParameterMetadata<Injection>(
const meta = MetadataInspector.getAllParameterMetadata<Readonly<Injection>>(
PARAMETERS_KEY,

@@ -297,3 +297,3 @@ target,

ctx: Context,
injection: Injection,
injection: Readonly<Injection>,
session?: ResolutionSession,

@@ -318,5 +318,5 @@ ) {

target: Object,
): MetadataMap<Injection> {
): MetadataMap<Readonly<Injection>> {
const metadata =
MetadataInspector.getAllPropertyMetadata<Injection>(
MetadataInspector.getAllPropertyMetadata<Readonly<Injection>>(
PROPERTIES_KEY,

@@ -323,0 +323,0 @@ target,

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

type: 'binding';
value: Binding;
value: Readonly<Binding>;
}

@@ -36,3 +36,3 @@

type: 'injection';
value: Injection;
value: Readonly<Injection>;
}

@@ -95,3 +95,3 @@

private static enterBinding(
binding: Binding,
binding: Readonly<Binding>,
session?: ResolutionSession,

@@ -112,3 +112,3 @@ ): ResolutionSession {

action: ResolutionAction,
binding: Binding,
binding: Readonly<Binding>,
session?: ResolutionSession,

@@ -129,3 +129,3 @@ ) {

private static enterInjection(
injection: Injection,
injection: Readonly<Injection>,
session?: ResolutionSession,

@@ -146,3 +146,3 @@ ): ResolutionSession {

action: ResolutionAction,
injection: Injection,
injection: Readonly<Injection>,
session?: ResolutionSession,

@@ -164,3 +164,3 @@ ) {

*/
static describeInjection(injection?: Injection) {
static describeInjection(injection?: Readonly<Injection>) {
/* istanbul ignore if */

@@ -185,3 +185,3 @@ if (injection == null) return undefined;

*/
pushInjection(injection: Injection) {
pushInjection(injection: Readonly<Injection>) {
/* istanbul ignore if */

@@ -225,3 +225,3 @@ if (debugSession.enabled) {

*/
get currentInjection(): Injection | undefined {
get currentInjection(): Readonly<Injection> | undefined {
for (let i = this.stack.length - 1; i >= 0; i--) {

@@ -237,3 +237,3 @@ const element = this.stack[i];

*/
get currentBinding(): Binding | undefined {
get currentBinding(): Readonly<Binding> | undefined {
for (let i = this.stack.length - 1; i >= 0; i--) {

@@ -250,3 +250,3 @@ const element = this.stack[i];

*/
pushBinding(binding: Binding) {
pushBinding(binding: Readonly<Binding>) {
/* istanbul ignore if */

@@ -274,3 +274,3 @@ if (debugSession.enabled) {

*/
popBinding() {
popBinding(): Readonly<Binding> {
const top = this.stack.pop();

@@ -292,3 +292,3 @@ if (!isBinding(top)) {

*/
get bindingStack(): Binding[] {
get bindingStack(): Readonly<Binding>[] {
return this.stack.filter(isBinding).map(e => e.value);

@@ -300,3 +300,3 @@ }

*/
get injectionStack(): Injection[] {
get injectionStack(): Readonly<Injection>[] {
return this.stack.filter(isInjection).map(e => e.value);

@@ -303,0 +303,0 @@ }

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

MapObject,
isPromise,
isPromiseLike,
resolveList,

@@ -61,3 +61,3 @@ resolveMap,

let inst: ValueOrPromise<T>;
if (isPromise(argsOrPromise)) {
if (isPromiseLike(argsOrPromise)) {
// Instantiate the class asynchronously

@@ -79,3 +79,3 @@ inst = argsOrPromise.then(args => {

}
if (isPromise(propertiesOrPromise)) {
if (isPromiseLike(propertiesOrPromise)) {
return propertiesOrPromise.then(props => {

@@ -86,3 +86,3 @@ /* istanbul ignore if */

}
if (isPromise(inst)) {
if (isPromiseLike(inst)) {
// Inject the properties asynchronously

@@ -96,3 +96,3 @@ return inst.then(obj => Object.assign(obj, props));

} else {
if (isPromise(inst)) {
if (isPromiseLike(inst)) {
/* istanbul ignore if */

@@ -119,3 +119,3 @@ if (debug.enabled) {

ctx: Context,
injection: Injection,
injection: Readonly<Injection>,
session?: ResolutionSession,

@@ -266,3 +266,3 @@ ): ValueOrPromise<T> {

);
if (isPromise(argsOrPromise)) {
if (isPromiseLike(argsOrPromise)) {
// Invoke the target method asynchronously

@@ -269,0 +269,0 @@ return argsOrPromise.then(args => {

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

* synchronous/asynchronous resolution of values.
*
* Note that we are using PromiseLike instead of native Promise to describe
* the asynchronous variant. This allows producers of async values to use
* any Promise implementation (e.g. Bluebird) instead of native Promises
* provided by JavaScript runtime.
*/
export type ValueOrPromise<T> = T | Promise<T>;
export type ValueOrPromise<T> = T | PromiseLike<T>;

@@ -36,4 +41,4 @@ export type MapObject<T> = {[name: string]: T};

*/
export function isPromise<T>(
value: T | PromiseLike<T>,
export function isPromiseLike<T>(
value: T | PromiseLike<T> | undefined,
): value is PromiseLike<T> {

@@ -50,3 +55,3 @@ if (!value) return false;

*/
export function getDeepProperty(value: BoundValue, path: string) {
export function getDeepProperty(value: BoundValue, path: string): BoundValue {
const props = path.split('.').filter(Boolean);

@@ -105,3 +110,3 @@ for (const p of props) {

const valueOrPromise = resolver(map[key], key, map);
if (isPromise(valueOrPromise)) {
if (isPromiseLike(valueOrPromise)) {
if (!asyncResolvers) asyncResolvers = [];

@@ -165,3 +170,3 @@ asyncResolvers.push(valueOrPromise.then(setter(key)));

const valueOrPromise = resolver(list[ix], ix, list);
if (isPromise(valueOrPromise)) {
if (isPromiseLike(valueOrPromise)) {
if (!asyncResolvers) asyncResolvers = [];

@@ -198,3 +203,3 @@ asyncResolvers.push(valueOrPromise.then(setter(ix)));

}
if (isPromise(result)) {
if (isPromiseLike(result)) {
// Once (promise.finally)[https://github.com/tc39/proposal-promise-finally

@@ -201,0 +206,0 @@ // is supported, the following can be simplifed as

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