///
import { Debugger } from 'debug';
import { EventEmitter } from 'events';
import { Binding, BindingInspectOptions, BindingScope, BindingTag } from './binding';
import { ConfigurationResolver } from './binding-config';
import { BindingFilter } from './binding-filter';
import { BindingAddress } from './binding-key';
import { BindingComparator } from './binding-sorter';
import { ContextEvent, ContextEventListener } from './context-event';
import { ContextEventObserver, ContextObserver } from './context-observer';
import { ContextSubscriptionManager, Subscription } from './context-subscription';
import { ContextTagIndexer } from './context-tag-indexer';
import { ContextView } from './context-view';
import { JSONObject } from './json-types';
import { ResolutionOptions, ResolutionOptionsOrSession, ResolutionSession } from './resolution-session';
import { BoundValue, ValueOrPromise } from './value-promise';
/**
* Context provides an implementation of Inversion of Control (IoC) container
*/
export declare class Context extends EventEmitter {
/**
* Name of the context
*/
readonly name: string;
/**
* Key to binding map as the internal registry
*/
protected readonly registry: Map;
/**
* Indexer for bindings by tag
*/
protected readonly tagIndexer: ContextTagIndexer;
/**
* Manager for observer subscriptions
*/
readonly subscriptionManager: ContextSubscriptionManager;
/**
* Parent context
*/
protected _parent?: Context;
/**
* Configuration resolver
*/
protected configResolver: ConfigurationResolver;
/**
* A debug function which can be overridden by subclasses.
*
* @example
* ```ts
* import debugFactory from 'debug';
* const debug = debugFactory('loopback:context:application');
* export class Application extends Context {
* super('application');
* this._debug = debug;
* }
* ```
*/
protected _debug: Debugger;
/**
* Scope for binding resolution
*/
scope: BindingScope;
/**
* Create a new context.
*
* @example
* ```ts
* // Create a new root context, let the framework to create a unique name
* const rootCtx = new Context();
*
* // Create a new child context inheriting bindings from `rootCtx`
* const childCtx = new Context(rootCtx);
*
* // Create another root context called "application"
* const appCtx = new Context('application');
*
* // Create a new child context called "request" and inheriting bindings
* // from `appCtx`
* const reqCtx = new Context(appCtx, 'request');
* ```
* @param _parent - The optional parent context
* @param name - Name of the context. If not provided, a unique identifier
* will be generated as the name.
*/
constructor(_parent?: Context | string, name?: string);
/**
* Get the debug namespace for the context class. Subclasses can override
* this method to supply its own namespace.
*
* @example
* ```ts
* export class Application extends Context {
* super('application');
* }
*
* protected getDebugNamespace() {
* return 'loopback:context:application';
* }
* ```
*/
protected getDebugNamespace(): string;
private generateName;
/**
* @internal
* Getter for ContextSubscriptionManager
*/
get parent(): Context | undefined;
/**
* Wrap the debug statement so that it always print out the context name
* as the prefix
* @param args - Arguments for the debug
*/
protected debug(...args: unknown[]): void;
/**
* A strongly-typed method to emit context events
* @param type Event type
* @param event Context event
*/
emitEvent(type: string, event: T): void;
/**
* Emit an `error` event
* @param err Error
*/
emitError(err: unknown): void;
/**
* Create a binding with the given key in the context. If a locked binding
* already exists with the same key, an error will be thrown.
*
* @param key - Binding key
*/
bind(key: BindingAddress): Binding;
/**
* Add a binding to the context. If a locked binding already exists with the
* same key, an error will be thrown.
* @param binding - The configured binding to be added
*/
add(binding: Binding): this;
/**
* Create a corresponding binding for configuration of the target bound by
* the given key in the context.
*
* For example, `ctx.configure('controllers.MyController').to({x: 1})` will
* create binding `controllers.MyController:$config` with value `{x: 1}`.
*
* @param key - The key for the binding to be configured
*/
configure(key?: BindingAddress): Binding;
/**
* Get the value or promise of configuration for a given binding by key
*
* @param key - Binding key
* @param propertyPath - Property path for the option. For example, `x.y`
* requests for `.x.y`. If not set, the `` object will be
* returned.
* @param resolutionOptions - Options for the resolution.
* - optional: if not set or set to `true`, `undefined` will be returned if
* no corresponding value is found. Otherwise, an error will be thrown.
*/
getConfigAsValueOrPromise(key: BindingAddress, propertyPath?: string, resolutionOptions?: ResolutionOptions): ValueOrPromise;
/**
* Set up the configuration resolver if needed
*/
protected setupConfigurationResolverIfNeeded(): ConfigurationResolver;
/**
* Resolve configuration for the binding by key
*
* @param key - Binding key
* @param propertyPath - Property path for the option. For example, `x.y`
* requests for `.x.y`. If not set, the `` object will be
* returned.
* @param resolutionOptions - Options for the resolution.
*/
getConfig(key: BindingAddress, propertyPath?: string, resolutionOptions?: ResolutionOptions): Promise;
/**
* Resolve configuration synchronously for the binding by key
*
* @param key - Binding key
* @param propertyPath - Property path for the option. For example, `x.y`
* requests for `config.x.y`. If not set, the `config` object will be
* returned.
* @param resolutionOptions - Options for the resolution.
*/
getConfigSync(key: BindingAddress, propertyPath?: string, resolutionOptions?: ResolutionOptions): ConfigValueType | undefined;
/**
* Unbind a binding from the context. No parent contexts will be checked.
*
* @remarks
* 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: BindingAddress): boolean;
/**
* Add a context event observer to the context
* @param observer - Context observer instance or function
*/
subscribe(observer: ContextEventObserver): Subscription;
/**
* Remove the context event observer from the context
* @param observer - Context event observer
*/
unsubscribe(observer: ContextEventObserver): boolean;
/**
* Close the context: clear observers, stop notifications, and remove event
* listeners from its parent context.
*
* @remarks
* This method MUST be called to avoid memory leaks once a context object is
* no longer needed and should be recycled. An example is the `RequestContext`,
* which is created per request.
*/
close(): void;
/**
* Check if an observer is subscribed to this context
* @param observer - Context observer
*/
isSubscribed(observer: ContextObserver): boolean;
/**
* Create a view of the context chain with the given binding filter
* @param filter - A function to match bindings
* @param comparator - A function to sort matched bindings
* @param options - Resolution options
*/
createView(filter: BindingFilter, comparator?: BindingComparator, options?: Omit): ContextView;
/**
* Check if a binding exists with the given key in the local context without
* delegating to the parent context
* @param key - Binding key
*/
contains(key: BindingAddress): boolean;
/**
* Check if a key is bound in the context or its ancestors
* @param key - Binding key
*/
isBound(key: BindingAddress): boolean;
/**
* Get the owning context for a binding or its key
* @param keyOrBinding - Binding object or key
*/
getOwnerContext(keyOrBinding: BindingAddress | Readonly>): Context | undefined;
/**
* Get the context matching the scope
* @param scope - Binding scope
*/
getScopedContext(scope: BindingScope.APPLICATION | BindingScope.SERVER | BindingScope.REQUEST): Context | undefined;
/**
* Locate the resolution context for the given binding. Only bindings in the
* resolution context and its ancestors are visible as dependencies to resolve
* the given binding
* @param binding - Binding object
*/
getResolutionContext(binding: Readonly>): Context | undefined;
/**
* Check if this context is visible (same or ancestor) to the given one
* @param ctx - Another context object
*/
isVisibleTo(ctx: Context): boolean;
/**
* Find bindings using a key pattern or filter function
* @param pattern - A filter function, a regexp or a wildcard pattern with
* optional `*` and `?`. Find returns such bindings where the key matches
* the provided pattern.
*
* For a wildcard:
* - `*` matches zero or more characters except `.` and `:`
* - `?` matches exactly one character except `.` and `:`
*
* For a filter function:
* - return `true` to include the binding in the results
* - return `false` to exclude it.
*/
find(pattern?: string | RegExp | BindingFilter): Readonly>[];
/**
* Find bindings using the tag filter. If the filter matches one of the
* binding tags, the binding is included.
*
* @param tagFilter - A filter for tags. It can be in one of the following
* forms:
* - A regular expression, such as `/controller/`
* - A wildcard pattern string with optional `*` and `?`, such as `'con*'`
* For a wildcard:
* - `*` matches zero or more characters except `.` and `:`
* - `?` matches exactly one character except `.` and `:`
* - An object containing tag name/value pairs, such as
* `{name: 'my-controller'}`
*/
findByTag(tagFilter: BindingTag | RegExp): Readonly>[];
/**
* Find bindings by tag leveraging indexes
* @param tag - Tag name pattern or name/value pairs
*/
protected _findByTagIndex(tag: BindingTag | RegExp): Readonly>[];
protected _mergeWithParent(childList: Readonly>[], parentList?: Readonly>[]): Readonly>[];
/**
* Get the value bound to the given key, throw an error when no value is
* bound for the given key.
*
* @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.get('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 session - Optional session for resolution (accepted for backward
* compatibility)
* @returns A promise of the bound value.
*/
get(keyWithPath: BindingAddress, session?: ResolutionSession): Promise;
/**
* 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 no config is provided
* const config = await ctx.get('config#rest', {
* optional: true
* });
* ```
*
* @param keyWithPath - The binding key, optionally suffixed with a path to the
* (deeply) nested property to retrieve.
* @param options - Options for resolution.
* @returns A promise of the bound value, or a promise of undefined when
* the optional binding is not found.
*/
get(keyWithPath: BindingAddress, options: ResolutionOptions): Promise;
/**
* 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.getSync('application.instance');
*
* // get "rest" property from the value bound to "config"
* const config = await ctx.getSync('config#rest');
* ```
*
* @param keyWithPath - The binding key, optionally suffixed with a path to the
* (deeply) nested property to retrieve.
* @param session - Session for resolution (accepted for backward compatibility)
* @returns A promise of the bound value.
*/
getSync(keyWithPath: BindingAddress, session?: ResolutionSession): ValueType;
/**
* 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 is provided
* const config = await ctx.getSync('config#rest', {
* optional: true
* });
* ```
*
* @param keyWithPath - The binding key, optionally suffixed with a path to the
* (deeply) nested property to retrieve.
* @param options - Options for resolution.
* @returns The bound value, or undefined when an optional binding is not found.
*/
getSync(keyWithPath: BindingAddress, options?: ResolutionOptions): ValueType | undefined;
/**
* 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: BindingAddress): 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: BindingAddress, options?: {
optional?: boolean;
}): Binding | undefined;
/**
* Find or create a binding for the given key
* @param key - Binding address
* @param policy - Binding creation policy
*/
findOrCreateBinding(key: BindingAddress, policy?: BindingCreationPolicy): Binding;
/**
* Get the value bound to the given key.
*
* This is an internal version that preserves the dual sync/async result
* of `Binding#getValue()`. Users should use `get()` or `getSync()` instead.
*
* @example
*
* ```ts
* // get the value bound to "application.instance"
* ctx.getValueOrPromise('application.instance');
*
* // get "rest" property from the value bound to "config"
* 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});
* ctx.getValueOrPromise('data#numbers.a');
* ```
*
* @param keyWithPath - The binding key, optionally suffixed with a path to the
* (deeply) nested property to retrieve.
* @param optionsOrSession - Options for resolution or a session
* @returns The bound value or a promise of the bound value, depending
* on how the binding is configured.
* @internal
*/
getValueOrPromise(keyWithPath: BindingAddress, optionsOrSession?: ResolutionOptionsOrSession): ValueOrPromise;
/**
* Create a plain JSON object for the context
*/
toJSON(): JSONObject;
/**
* Inspect the context and dump out a JSON object representing the context
* hierarchy
* @param options - Options for inspect
*/
inspect(options?: ContextInspectOptions): JSONObject;
/**
* Inspect the context hierarchy
* @param options - Options for inspect
* @param visitedClasses - A map to keep class to name so that we can have
* different names for classes with colliding names. The situation can happen
* when two classes with the same name are bound in different modules.
*/
private _inspect;
/**
* The "bind" event is emitted when a new binding is added to the context.
* The "unbind" event is emitted when an existing binding is removed.
*
* @param eventName The name of the event - always `bind` or `unbind`.
* @param listener The listener function to call when the event is emitted.
*/
on(eventName: 'bind' | 'unbind', listener: ContextEventListener): this;
on(event: string | symbol, listener: (...args: any[]) => void): this;
/**
* The "bind" event is emitted when a new binding is added to the context.
* The "unbind" event is emitted when an existing binding is removed.
*
* @param eventName The name of the event - always `bind` or `unbind`.
* @param listener The listener function to call when the event is emitted.
*/
once(eventName: 'bind' | 'unbind', listener: ContextEventListener): this;
once(event: string | symbol, listener: (...args: any[]) => void): this;
}
/**
* Options for context.inspect()
*/
export interface ContextInspectOptions extends BindingInspectOptions {
/**
* The flag to control if parent context should be inspected
*/
includeParent?: boolean;
}
/**
* Policy to control if a binding should be created for the context
*/
export declare enum BindingCreationPolicy {
/**
* Always create a binding with the key for the context
*/
ALWAYS_CREATE = "Always",
/**
* Never create a binding for the context. If the key is not bound in the
* context, throw an error.
*/
NEVER_CREATE = "Never",
/**
* Create a binding if the key is not bound in the context. Otherwise, return
* the existing binding.
*/
CREATE_IF_NOT_BOUND = "IfNotBound"
}