Skip to content

Commit

Permalink
feat(context): pass resolution options into binding.getValue()
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondfeng committed Mar 29, 2019
1 parent 124c078 commit 7252452
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 40 deletions.
61 changes: 44 additions & 17 deletions packages/context/src/binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import * as debugModule from 'debug';
import * as debugFactory from 'debug';
import {BindingAddress, BindingKey} from './binding-key';
import {Context} from './context';
import {Provider} from './provider';
import {ResolutionSession} from './resolution-session';
import {
asResolutionOptions,
ResolutionOptions,
ResolutionOptionsOrSession,
ResolutionSession,
} from './resolution-session';
import {instantiateClass} from './resolver';
import {
BoundValue,
Expand All @@ -18,7 +23,7 @@ import {
ValueOrPromise,
} from './value-promise';

const debug = debugModule('loopback:context:binding');
const debug = debugFactory('loopback:context:binding');

/**
* Scope for binding values
Expand Down Expand Up @@ -128,6 +133,11 @@ export type BindingTag = TagMap | string;
*/
export type BindingTemplate<T = unknown> = (binding: Binding<T>) => void;

type ValueGetter<T> = (
ctx: Context,
options: ResolutionOptions,
) => ValueOrPromise<T | undefined>;

/**
* Binding represents an entry in the `Context`. Each binding has a key and a
* corresponding value getter.
Expand Down Expand Up @@ -161,10 +171,7 @@ export class Binding<T = BoundValue> {
}

private _cache: WeakMap<Context, T>;
private _getValue: (
ctx?: Context,
session?: ResolutionSession,
) => ValueOrPromise<T>;
private _getValue: ValueGetter<T>;

private _valueConstructor?: Constructor<T>;
/**
Expand Down Expand Up @@ -228,7 +235,10 @@ export class Binding<T = BoundValue> {
* @param ctx Context for the resolution
* @param session Optional session for binding and dependency resolution
*/
getValue(ctx: Context, session?: ResolutionSession): ValueOrPromise<T> {
getValue(
ctx: Context,
optionsOrSession?: ResolutionOptionsOrSession,
): ValueOrPromise<T> {
/* istanbul ignore if */
if (debug.enabled) {
debug('Get value for binding %s', this.key);
Expand All @@ -246,11 +256,15 @@ export class Binding<T = BoundValue> {
}
}
}
const options = asResolutionOptions(optionsOrSession);
if (this._getValue) {
let result = ResolutionSession.runWithBinding(
s => this._getValue(ctx, s),
s => {
const optionsWithSession = Object.assign({}, options, {session: s});
return this._getValue(ctx, optionsWithSession);
},
this,
session,
options.session,
);
return this._cacheValue(ctx, result);
}
Expand Down Expand Up @@ -333,6 +347,14 @@ export class Binding<T = BoundValue> {
return this;
}

/**
* Set the `_getValue` function
* @param getValue getValue function
*/
private _setValueGetter(getValue: ValueGetter<T>) {
this._getValue = getValue;
}

/**
* Bind the key to a constant value. The value must be already available
* at binding time, it is not allowed to pass a Promise instance.
Expand Down Expand Up @@ -372,7 +394,7 @@ export class Binding<T = BoundValue> {
debug('Bind %s to constant:', this.key, value);
}
this._type = BindingType.CONSTANT;
this._getValue = () => value;
this._setValueGetter(() => value);
return this;
}

Expand Down Expand Up @@ -400,7 +422,7 @@ export class Binding<T = BoundValue> {
debug('Bind %s to dynamic value:', this.key, factoryFn);
}
this._type = BindingType.DYNAMIC_VALUE;
this._getValue = ctx => factoryFn();
this._setValueGetter(ctx => factoryFn());
return this;
}

Expand All @@ -426,14 +448,14 @@ export class Binding<T = BoundValue> {
debug('Bind %s to provider %s', this.key, providerClass.name);
}
this._type = BindingType.PROVIDER;
this._getValue = (ctx, session) => {
this._setValueGetter((ctx, options) => {
const providerOrPromise = instantiateClass<Provider<T>>(
providerClass,
ctx!,
session,
ctx,
options.session,
);
return transformValueOrPromise(providerOrPromise, p => p.value());
};
});
return this;
}

Expand All @@ -450,11 +472,16 @@ export class Binding<T = BoundValue> {
debug('Bind %s to class %s', this.key, ctor.name);
}
this._type = BindingType.CLASS;
this._getValue = (ctx, session) => instantiateClass(ctor, ctx!, session);
this._setValueGetter((ctx, options) =>
instantiateClass(ctor, ctx, options.session),
);
this._valueConstructor = ctor;
return this;
}

/**
* Unlock the binding
*/
unlock(): this {
this.isLocked = false;
return this;
Expand Down
33 changes: 18 additions & 15 deletions packages/context/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,32 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import * as debugModule from 'debug';
import * as debugFactory from 'debug';
import {EventEmitter} from 'events';
import {v1 as uuidv1} from 'uuid';
import {ValueOrPromise} from '.';
import {Binding, BindingTag} from './binding';
import {BindingFilter, filterByKey, filterByTag} from './binding-filter';
import {BindingAddress, BindingKey} from './binding-key';
import {ContextView} from './context-view';
import {
ContextEventObserver,
ContextEventType,
ContextObserver,
Notification,
Subscription,
} from './context-observer';
import {ResolutionOptions, ResolutionSession} from './resolution-session';
import {BoundValue, getDeepProperty, isPromiseLike} from './value-promise';
import {ContextView} from './context-view';
import {
asResolutionOptions,
ResolutionOptions,
ResolutionOptionsOrSession,
ResolutionSession,
} from './resolution-session';
import {
BoundValue,
getDeepProperty,
isPromiseLike,
ValueOrPromise,
} from './value-promise';

/**
* Polyfill Symbol.asyncIterator as required by TypeScript for Node 8.x.
Expand All @@ -32,7 +41,7 @@ if (!Symbol.asyncIterator) {
// This import must happen after the polyfill
import {iterator, multiple} from 'p-event';

const debug = debugModule('loopback:context');
const debug = debugFactory('loopback:context');

/**
* Context provides an implementation of Inversion of Control (IoC) container
Expand Down Expand Up @@ -766,22 +775,16 @@ export class Context extends EventEmitter {
*/
getValueOrPromise<ValueType>(
keyWithPath: BindingAddress<ValueType>,
optionsOrSession?: ResolutionOptions | ResolutionSession,
optionsOrSession?: ResolutionOptionsOrSession,
): ValueOrPromise<ValueType | undefined> {
const {key, propertyPath} = BindingKey.parseKeyWithPath(keyWithPath);

// backwards compatibility
if (optionsOrSession instanceof ResolutionSession) {
optionsOrSession = {session: optionsOrSession};
}
optionsOrSession = asResolutionOptions(optionsOrSession);

const binding = this.getBinding<ValueType>(key, optionsOrSession);
if (binding == null) return undefined;

const boundValue = binding.getValue(
this,
optionsOrSession && optionsOrSession.session,
);
const boundValue = binding.getValue(this, optionsOrSession);
if (propertyPath === undefined || propertyPath === '') {
return boundValue;
}
Expand Down
31 changes: 23 additions & 8 deletions packages/context/src/resolution-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,15 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {DecoratorFactory} from '@loopback/metadata';
import * as debugModule from 'debug';
import {Binding} from './binding';
import {Injection} from './inject';
import {ValueOrPromise, BoundValue, tryWithFinally} from './value-promise';
import * as debugModule from 'debug';
import {DecoratorFactory} from '@loopback/metadata';
import {BoundValue, tryWithFinally, ValueOrPromise} from './value-promise';

const debugSession = debugModule('loopback:context:resolver:session');
const getTargetName = DecoratorFactory.getTargetName;

// NOTE(bajtos) The following import is required to satisfy TypeScript compiler
// tslint:disable-next-line:no-unused
import {BindingKey} from './binding-key';

/**
* A function to be executed with the resolution session
*/
Expand Down Expand Up @@ -169,7 +165,7 @@ export class ResolutionSession {
);
return {
targetName: name,
bindingKey: injection.bindingSelector,
bindingSelector: injection.bindingSelector,
// Cast to Object so that we don't have to expose InjectionMetadata
metadata: injection.metadata as Object,
};
Expand Down Expand Up @@ -343,3 +339,22 @@ export interface ResolutionOptions {
*/
optional?: boolean;
}

/**
* Resolution options or session
*/
export type ResolutionOptionsOrSession = ResolutionOptions | ResolutionSession;

/**
* Normalize ResolutionOptionsOrSession to ResolutionOptions
* @param optionsOrSession resolution options or session
*/
export function asResolutionOptions(
optionsOrSession?: ResolutionOptionsOrSession,
): ResolutionOptions {
// backwards compatibility
if (optionsOrSession instanceof ResolutionSession) {
return {session: optionsOrSession};
}
return optionsOrSession || {};
}

0 comments on commit 7252452

Please sign in to comment.