From 7252452edd7d1b5c5d64e8ec16d6ade33504ed18 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 25 Mar 2019 10:37:37 -0700 Subject: [PATCH] feat(context): pass resolution options into binding.getValue() --- packages/context/src/binding.ts | 61 ++++++++++++++++------ packages/context/src/context.ts | 33 ++++++------ packages/context/src/resolution-session.ts | 31 ++++++++--- 3 files changed, 85 insertions(+), 40 deletions(-) diff --git a/packages/context/src/binding.ts b/packages/context/src/binding.ts index 3045bdee3f7c..210ab7fcf6a5 100644 --- a/packages/context/src/binding.ts +++ b/packages/context/src/binding.ts @@ -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, @@ -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 @@ -128,6 +133,11 @@ export type BindingTag = TagMap | string; */ export type BindingTemplate = (binding: Binding) => void; +type ValueGetter = ( + ctx: Context, + options: ResolutionOptions, +) => ValueOrPromise; + /** * Binding represents an entry in the `Context`. Each binding has a key and a * corresponding value getter. @@ -161,10 +171,7 @@ export class Binding { } private _cache: WeakMap; - private _getValue: ( - ctx?: Context, - session?: ResolutionSession, - ) => ValueOrPromise; + private _getValue: ValueGetter; private _valueConstructor?: Constructor; /** @@ -228,7 +235,10 @@ export class Binding { * @param ctx Context for the resolution * @param session Optional session for binding and dependency resolution */ - getValue(ctx: Context, session?: ResolutionSession): ValueOrPromise { + getValue( + ctx: Context, + optionsOrSession?: ResolutionOptionsOrSession, + ): ValueOrPromise { /* istanbul ignore if */ if (debug.enabled) { debug('Get value for binding %s', this.key); @@ -246,11 +256,15 @@ export class Binding { } } } + 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); } @@ -333,6 +347,14 @@ export class Binding { return this; } + /** + * Set the `_getValue` function + * @param getValue getValue function + */ + private _setValueGetter(getValue: ValueGetter) { + 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. @@ -372,7 +394,7 @@ export class Binding { debug('Bind %s to constant:', this.key, value); } this._type = BindingType.CONSTANT; - this._getValue = () => value; + this._setValueGetter(() => value); return this; } @@ -400,7 +422,7 @@ export class Binding { debug('Bind %s to dynamic value:', this.key, factoryFn); } this._type = BindingType.DYNAMIC_VALUE; - this._getValue = ctx => factoryFn(); + this._setValueGetter(ctx => factoryFn()); return this; } @@ -426,14 +448,14 @@ export class Binding { 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>( providerClass, - ctx!, - session, + ctx, + options.session, ); return transformValueOrPromise(providerOrPromise, p => p.value()); - }; + }); return this; } @@ -450,11 +472,16 @@ export class Binding { 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; diff --git a/packages/context/src/context.ts b/packages/context/src/context.ts index 3d32c221e321..7f9e578a76c0 100644 --- a/packages/context/src/context.ts +++ b/packages/context/src/context.ts @@ -3,14 +3,12 @@ // 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, @@ -18,8 +16,19 @@ import { 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. @@ -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 @@ -766,22 +775,16 @@ export class Context extends EventEmitter { */ getValueOrPromise( keyWithPath: BindingAddress, - optionsOrSession?: ResolutionOptions | ResolutionSession, + optionsOrSession?: ResolutionOptionsOrSession, ): ValueOrPromise { const {key, propertyPath} = BindingKey.parseKeyWithPath(keyWithPath); - // backwards compatibility - if (optionsOrSession instanceof ResolutionSession) { - optionsOrSession = {session: optionsOrSession}; - } + optionsOrSession = asResolutionOptions(optionsOrSession); const binding = this.getBinding(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; } diff --git a/packages/context/src/resolution-session.ts b/packages/context/src/resolution-session.ts index 4bfc53cfa19b..39ebeaf74cc9 100644 --- a/packages/context/src/resolution-session.ts +++ b/packages/context/src/resolution-session.ts @@ -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 */ @@ -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, }; @@ -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 || {}; +}