Socket
Socket
Sign inDemoInstall

@loopback/context

Package Overview
Dependencies
9
Maintainers
7
Versions
192
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.25.1 to 2.0.0

dist/context-event.d.ts

48

CHANGELOG.md

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

# [2.0.0](https://github.com/strongloop/loopback-next/compare/@loopback/context@1.25.1...@loopback/context@2.0.0) (2020-01-27)
### Features
* **context:** add ContextEventListener and tidy up parent event handling ([beb41a7](https://github.com/strongloop/loopback-next/commit/beb41a7b105cf1aea64982e3f43f4d5a8128581f))
* **context:** index bindings by tag to speed up matching by tag ([566b9d9](https://github.com/strongloop/loopback-next/commit/566b9d9a35ce52d9aeefe17e36f91c9714616b21))
* **context:** keep binding tag pattern for BindingTagFilter ([856b62d](https://github.com/strongloop/loopback-next/commit/856b62d7053c22ebe0f6acf6a1904e524175429c))
* **context:** make bindings as event emitters to report changes ([dddddb9](https://github.com/strongloop/loopback-next/commit/dddddb96fd6908a8d4caad8868e43d3d0bb742f6))
* **context:** refactor context observer subscription into a new class ([31ad9a5](https://github.com/strongloop/loopback-next/commit/31ad9a55bbd068cd8e41347fca5caaf0ae5eb6e7))
* **context:** set max listeners to Infinity by default ([0741e3b](https://github.com/strongloop/loopback-next/commit/0741e3b1293065a04f1ecd9dbda09df074a5dd34))
* **context:** use BindingEvent for binding event listeners ([ae5febc](https://github.com/strongloop/loopback-next/commit/ae5febc35679f4d77b9970ecc26a71938a1c972e))
### BREAKING CHANGES
* **context:** Context events are now emitted as `ContextEvent` objects
instead of positional arguments. Context listener functions must switch from
the old style to new style as follows:
1. Old style
```ts
ctx.on('bind', (binding, context) => {
// ...
});
```
2. New style
```ts
ctx.on('bind', (event: ContextEvent) => {
// ...
});
```
Or:
```ts
ctx.on('bind', ({binding, context, type}) => {
// ...
});
```
## [1.25.1](https://github.com/strongloop/loopback-next/compare/@loopback/context@1.25.0...@loopback/context@1.25.1) (2020-01-07)

@@ -8,0 +56,0 @@

20

dist/binding-filter.d.ts

@@ -46,6 +46,24 @@ import { Binding, BindingTag } from './binding';

/**
* Binding filter function that holds a binding tag pattern. `Context.find()`
* uses the `bindingTagPattern` to optimize the matching of bindings by tag to
* avoid expensive check for all bindings.
*/
export interface BindingTagFilter extends BindingFilter<unknown> {
/**
* A special property on the filter function to provide access to the binding
* tag pattern which can be utilized to optimize the matching of bindings by
* tag in a context.
*/
bindingTagPattern: BindingTag | RegExp;
}
/**
* Type guard for BindingTagFilter
* @param filter - A BindingFilter function
*/
export declare function isBindingTagFilter(filter?: BindingFilter): filter is BindingTagFilter;
/**
* Create a binding filter for the tag pattern
* @param tagPattern - Binding tag name, regexp, or object
*/
export declare function filterByTag(tagPattern: BindingTag | RegExp): BindingFilter;
export declare function filterByTag(tagPattern: BindingTag | RegExp): BindingTagFilter;
/**

@@ -52,0 +70,0 @@ * Create a binding filter from key pattern

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

/**
* Type guard for BindingTagFilter
* @param filter - A BindingFilter function
*/
function isBindingTagFilter(filter) {
if (filter == null || !('bindingTagPattern' in filter))
return false;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const tagPattern = filter.bindingTagPattern;
return (tagPattern instanceof RegExp ||
typeof tagPattern === 'string' ||
typeof tagPattern === 'object');
}
exports.isBindingTagFilter = isBindingTagFilter;
/**
* Create a binding filter for the tag pattern

@@ -21,13 +35,28 @@ * @param tagPattern - Binding tag name, regexp, or object

function filterByTag(tagPattern) {
if (typeof tagPattern === 'string' || tagPattern instanceof RegExp) {
const regexp = typeof tagPattern === 'string'
? wildcardToRegExp(tagPattern)
: tagPattern;
return b => Array.from(b.tagNames).some(t => regexp.test(t));
let filter;
let regex = undefined;
if (tagPattern instanceof RegExp) {
// RegExp for tag names
regex = tagPattern;
}
if (typeof tagPattern === 'string' &&
(tagPattern.includes('*') || tagPattern.includes('?'))) {
// Wildcard tag name
regex = wildcardToRegExp(tagPattern);
}
if (regex != null) {
// RegExp or wildcard match
filter = b => b.tagNames.some(t => regex.test(t));
}
else if (typeof tagPattern === 'string') {
// Plain tag string match
filter = b => b.tagNames.includes(tagPattern);
}
else {
return b => {
// Match tag name/value pairs
const tagMap = tagPattern;
filter = b => {
for (const t in tagPattern) {
// One tag name/value does not match
if (b.tagMap[t] !== tagPattern[t])
if (b.tagMap[t] !== tagMap[t])
return false;

@@ -39,2 +68,6 @@ }

}
// Set up binding tag for the filter
const tagFilter = filter;
tagFilter.bindingTagPattern = (regex !== null && regex !== void 0 ? regex : tagPattern);
return tagFilter;
}

@@ -41,0 +74,0 @@ exports.filterByTag = filterByTag;

@@ -0,1 +1,3 @@

/// <reference types="node" />
import { EventEmitter } from 'events';
import { BindingAddress } from './binding-key';

@@ -111,6 +113,31 @@ import { Context } from './context';

/**
* Information for a binding event
*/
export declare type BindingEvent = {
/**
* Event type
*/
type: string;
/**
* Source binding that emits the event
*/
binding: Readonly<Binding<unknown>>;
/**
* Operation that triggers the event
*/
operation: string;
};
/**
* Event listeners for binding events
*/
export declare type BindingEventListener = (
/**
* Binding event
*/
event: BindingEvent) => void;
/**
* Binding represents an entry in the `Context`. Each binding has a key and a
* corresponding value getter.
*/
export declare class Binding<T = BoundValue> {
export declare class Binding<T = BoundValue> extends EventEmitter {
isLocked: boolean;

@@ -199,2 +226,7 @@ /**

/**
* Emit a `changed` event
* @param operation - Operation that makes changes
*/
private emitChangedEvent;
/**
* Tag the binding with names or name/value objects. A tag has a name and

@@ -201,0 +233,0 @@ * an optional value. If not supplied, the tag name is used as the value.

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

const debug_1 = __importDefault(require("debug"));
const events_1 = require("events");
const binding_key_1 = require("./binding-key");

@@ -120,4 +121,5 @@ const interception_proxy_1 = require("./interception-proxy");

*/
class Binding {
class Binding extends events_1.EventEmitter {
constructor(key, isLocked = false) {
super();
this.isLocked = isLocked;

@@ -231,2 +233,10 @@ /**

/**
* Emit a `changed` event
* @param operation - Operation that makes changes
*/
emitChangedEvent(operation) {
const event = { binding: this, operation, type: 'changed' };
this.emit('changed', event);
}
/**
* Tag the binding with names or name/value objects. A tag has a name and

@@ -269,2 +279,3 @@ * an optional value. If not supplied, the tag name is used as the value.

}
this.emitChangedEvent('tag');
return this;

@@ -286,2 +297,3 @@ }

this._scope = scope;
this.emitChangedEvent('scope');
return this;

@@ -313,2 +325,3 @@ }

};
this.emitChangedEvent('value');
}

@@ -315,0 +328,0 @@ /**

37

dist/context-observer.d.ts
import { Binding } from './binding';
import { BindingFilter } from './binding-filter';
import { Context } from './context';
import { ValueOrPromise } from './value-promise';
import { Context } from './context';
/**

@@ -37,36 +37,1 @@ * Context event types. We support `bind` and `unbind` for now but

export declare type ContextEventObserver = ContextObserver | ContextObserverFn;
/**
* Subscription of context events. It's modeled after
* https://github.com/tc39/proposal-observable.
*/
export interface Subscription {
/**
* unsubscribe
*/
unsubscribe(): void;
/**
* Is the subscription closed?
*/
closed: boolean;
}
/**
* Event data for observer notifications
*/
export declare type Notification = {
/**
* Context event type - bind/unbind
*/
eventType: ContextEventType;
/**
* Binding added/removed
*/
binding: Readonly<Binding<unknown>>;
/**
* Owner context for the binding
*/
context: Context;
/**
* A snapshot of observers when the original event is emitted
*/
observers: Set<ContextEventObserver>;
};

@@ -7,3 +7,4 @@ /// <reference types="node" />

import { Context } from './context';
import { ContextEventType, ContextObserver, Subscription } from './context-observer';
import { ContextEventType, ContextObserver } from './context-observer';
import { Subscription } from './context-subscription';
import { Getter } from './inject';

@@ -10,0 +11,0 @@ import { ResolutionSession } from './resolution-session';

@@ -8,3 +8,6 @@ /// <reference types="node" />

import { BindingComparator } from './binding-sorter';
import { ContextEventObserver, ContextEventType, ContextObserver, Subscription } from './context-observer';
import { ContextEvent } 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';

@@ -26,27 +29,18 @@ import { ResolutionOptions, ResolutionOptionsOrSession, ResolutionSession } from './resolution-session';

/**
* Parent context
* Indexer for bindings by tag
*/
protected _parent?: Context;
protected configResolver: ConfigurationResolver;
protected readonly tagIndexer: ContextTagIndexer;
/**
* Event listeners for parent context keyed by event names. It keeps track
* of listeners from this context against its parent so that we can remove
* these listeners when this context is closed.
* Manager for observer subscriptions
*/
protected _parentEventListeners: Map<string, (...args: any[]) => void> | undefined;
readonly subscriptionManager: ContextSubscriptionManager;
/**
* A list of registered context observers. The Set will be created when the
* first observer is added.
* Parent context
*/
protected observers: Set<ContextEventObserver> | undefined;
protected _parent?: Context;
/**
* Internal counter for pending notification events which are yet to be
* processed by observers.
* Configuration resolver
*/
private pendingNotifications;
protected configResolver: ConfigurationResolver;
/**
* Queue for background notifications for observers
*/
private notificationQueue;
/**
* Create a new context.

@@ -75,2 +69,7 @@ *

/**
* @internal
* Getter for ContextSubscriptionManager
*/
get parent(): Context | undefined;
/**
* Wrap the debug statement so that it always print out the context name

@@ -82,41 +81,13 @@ * as the prefix

/**
* Set up an internal listener to notify registered observers asynchronously
* upon `bind` and `unbind` events. This method will be called lazily when
* the first observer is added.
* A strongly-typed method to emit context events
* @param type Event type
* @param event Context event
*/
private setupEventHandlersIfNeeded;
emitEvent<T extends ContextEvent>(type: string, event: T): void;
/**
* Add an event listener to its parent context so that this context will
* be notified of parent events, such as `bind` or `unbind`.
* @param event - Event name
* Emit an `error` event
* @param err Error
*/
private addParentEventListener;
emitError(err: unknown): void;
/**
* Handle errors caught during the notification of observers
* @param err - Error
*/
private handleNotificationError;
/**
* Start a background task to listen on context events and notify observers
*/
private startNotificationTask;
/**
* Process notification events as they arrive on the queue
*/
private processNotifications;
/**
* Listen on given event types and emit `notification` event. This method
* merge multiple event types into one for notification.
* @param eventTypes - Context event types
*/
private setupNotification;
/**
* Wait until observers are notified for all of currently pending notification
* events.
*
* This method is for test only to perform assertions after observers are
* notified for relevant events.
*/
protected waitUntilPendingNotificationsDone(timeout?: number): Promise<void>;
/**
* Create a binding with the given key in the context. If a locked binding

@@ -228,14 +199,2 @@ * already exists with the same key, an error will be thrown.

/**
* Publish an event to the registered observers. Please note the
* notification is queued and performed asynchronously so that we allow fluent
* APIs such as `ctx.bind('key').to(...).tag(...);` and give observers the
* fully populated binding.
*
* @param eventType - Event names: `bind` or `unbind`
* @param binding - Binding bound or unbound
* @param context - Owner context
* @param observers - Current set of context observers
*/
protected notifyObservers(eventType: ContextEventType, binding: Readonly<Binding<unknown>>, context: Context, observers?: Set<ContextEventObserver> | undefined): Promise<void>;
/**
* Check if a binding exists with the given key in the local context without

@@ -286,2 +245,7 @@ * delegating to the parent context

findByTag<ValueType = BoundValue>(tagFilter: BindingTag | RegExp): Readonly<Binding<ValueType>>[];
/**
* Find bindings by tag leveraging indexes
* @param tag - Tag name pattern or name/value pairs
*/
protected _findByTagIndex<ValueType = BoundValue>(tag: BindingTag | RegExp): Readonly<Binding<ValueType>>[];
protected _mergeWithParent<ValueType>(childList: Readonly<Binding<ValueType>>[], parentList?: Readonly<Binding<ValueType>>[]): Readonly<Binding<ValueType>>[];

@@ -288,0 +252,0 @@ /**

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

// License text available at https://opensource.org/licenses/MIT
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {

@@ -25,2 +18,4 @@ return (mod && mod.__esModule) ? mod : { "default": mod };

const binding_key_1 = require("./binding-key");
const context_subscription_1 = require("./context-subscription");
const context_tag_indexer_1 = require("./context-tag-indexer");
const context_view_1 = require("./context-view");

@@ -30,16 +25,2 @@ const keys_1 = require("./keys");

const value_promise_1 = require("./value-promise");
/**
* Polyfill Symbol.asyncIterator as required by TypeScript for Node 8.x.
* See https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html
*/
if (!Symbol.asyncIterator) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Symbol.asyncIterator = Symbol.for('Symbol.asyncIterator');
}
/**
* WARNING: This following import must happen after the polyfill. The
* `auto-import` by an IDE such as VSCode may move the import before the
* polyfill. It must be then fixed manually.
*/
const p_event_1 = require("p-event");
const debug = debug_1.default('loopback:context');

@@ -78,7 +59,7 @@ /**

this.registry = new Map();
/**
* Internal counter for pending notification events which are yet to be
* processed by observers.
*/
this.pendingNotifications = 0;
// The number of listeners can grow with the number of child contexts
// For example, each request can add a listener to the RestServer and the
// listener is removed when the request processing is finished.
// See https://github.com/strongloop/loopback-next/issues/4363
this.setMaxListeners(Infinity);
if (typeof _parent === 'string') {

@@ -90,4 +71,13 @@ name = _parent;

this.name = (name !== null && name !== void 0 ? name : uuid_1.v1());
this.tagIndexer = new context_tag_indexer_1.ContextTagIndexer(this);
this.subscriptionManager = new context_subscription_1.ContextSubscriptionManager(this);
}
/**
* @internal
* Getter for ContextSubscriptionManager
*/
get parent() {
return this._parent;
}
/**
* Wrap the debug statement so that it always print out the context name

@@ -97,3 +87,2 @@ * as the prefix

*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_debug(...args) {

@@ -112,159 +101,17 @@ /* istanbul ignore if */

/**
* Set up an internal listener to notify registered observers asynchronously
* upon `bind` and `unbind` events. This method will be called lazily when
* the first observer is added.
* A strongly-typed method to emit context events
* @param type Event type
* @param event Context event
*/
setupEventHandlersIfNeeded() {
if (this.notificationQueue != null)
return;
this.addParentEventListener('bind');
this.addParentEventListener('unbind');
// The following are two async functions. Returned promises are ignored as
// they are long-running background tasks.
this.startNotificationTask().catch(err => {
this.handleNotificationError(err);
});
let ctx = this._parent;
while (ctx) {
ctx.setupEventHandlersIfNeeded();
ctx = ctx._parent;
}
emitEvent(type, event) {
this.emit(type, event);
}
/**
* Add an event listener to its parent context so that this context will
* be notified of parent events, such as `bind` or `unbind`.
* @param event - Event name
* Emit an `error` event
* @param err Error
*/
addParentEventListener(event) {
var _a;
if (this._parent == null)
return;
// Keep track of parent event listeners so that we can remove them
this._parentEventListeners = (_a = this._parentEventListeners, (_a !== null && _a !== void 0 ? _a : new Map()));
if (this._parentEventListeners.has(event))
return;
const parentEventListener = (binding, context) => {
// Propagate the event to this context only if the binding key does not
// exist in this context. The parent binding is shadowed if there is a
// binding with the same key in this one.
if (this.contains(binding.key)) {
this._debug('Event %s %s is not re-emitted from %s to %s', event, binding.key, context.name, this.name);
return;
}
this._debug('Re-emitting %s %s from %s to %s', event, binding.key, context.name, this.name);
this.emit(event, binding, context);
};
this._parentEventListeners.set(event, parentEventListener);
// Listen on the parent context events
this._parent.on(event, parentEventListener);
}
/**
* Handle errors caught during the notification of observers
* @param err - Error
*/
handleNotificationError(err) {
// Bubbling up the error event over the context chain
// until we find an error listener
// eslint-disable-next-line @typescript-eslint/no-this-alias
let ctx = this;
while (ctx) {
if (ctx.listenerCount('error') === 0) {
// No error listener found, try its parent
ctx = ctx._parent;
continue;
}
this._debug('Emitting error to context %s', ctx.name, err);
ctx.emit('error', err);
return;
}
// No context with error listeners found
this._debug('No error handler is configured for the context chain', err);
// Let it crash now by emitting an error event
emitError(err) {
this.emit('error', err);
}
/**
* Start a background task to listen on context events and notify observers
*/
startNotificationTask() {
// Set up listeners on `bind` and `unbind` for notifications
this.setupNotification('bind', 'unbind');
// Create an async iterator for the `notification` event as a queue
this.notificationQueue = p_event_1.iterator(this, 'notification');
return this.processNotifications();
}
/**
* Process notification events as they arrive on the queue
*/
async processNotifications() {
var e_1, _a;
const events = this.notificationQueue;
if (events == null)
return;
try {
for (var events_2 = __asyncValues(events), events_2_1; events_2_1 = await events_2.next(), !events_2_1.done;) {
const { eventType, binding, context, observers } = events_2_1.value;
// The loop will happen asynchronously upon events
try {
// The execution of observers happen in the Promise micro-task queue
await this.notifyObservers(eventType, binding, context, observers);
this.pendingNotifications--;
this._debug('Observers notified for %s of binding %s', eventType, binding.key);
this.emit('observersNotified', { eventType, binding });
}
catch (err) {
this.pendingNotifications--;
this._debug('Error caught from observers', err);
// Errors caught from observers. Emit it to the current context.
// If no error listeners are registered, crash the process.
this.emit('error', err);
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (events_2_1 && !events_2_1.done && (_a = events_2.return)) await _a.call(events_2);
}
finally { if (e_1) throw e_1.error; }
}
}
/**
* Listen on given event types and emit `notification` event. This method
* merge multiple event types into one for notification.
* @param eventTypes - Context event types
*/
setupNotification(...eventTypes) {
for (const eventType of eventTypes) {
this.on(eventType, (binding, context) => {
// No need to schedule notifications if no observers are present
if (!this.observers || this.observers.size === 0)
return;
// Track pending events
this.pendingNotifications++;
// Take a snapshot of current observers to ensure notifications of this
// event will only be sent to current ones. Emit a new event to notify
// current context observers.
this.emit('notification', {
eventType,
binding,
context,
observers: new Set(this.observers),
});
});
}
}
/**
* Wait until observers are notified for all of currently pending notification
* events.
*
* This method is for test only to perform assertions after observers are
* notified for relevant events.
*/
async waitUntilPendingNotificationsDone(timeout) {
const count = this.pendingNotifications;
if (count === 0)
return;
await p_event_1.multiple(this, 'observersNotified', { count, timeout });
}
/**
* Create a binding with the given key in the context. If a locked binding

@@ -300,5 +147,9 @@ * already exists with the same key, an error will be thrown.

if (existingBinding != null) {
this.emit('unbind', existingBinding, this);
this.emitEvent('unbind', {
binding: existingBinding,
context: this,
type: 'unbind',
});
}
this.emit('bind', binding, this);
this.emitEvent('bind', { binding, context: this, type: 'bind' });
}

@@ -412,3 +263,3 @@ return this;

this.registry.delete(key);
this.emit('unbind', binding, this);
this.emitEvent('unbind', { binding, context: this, type: 'unbind' });
return true;

@@ -421,7 +272,3 @@ }

subscribe(observer) {
var _a;
this.observers = (_a = this.observers, (_a !== null && _a !== void 0 ? _a : new Set()));
this.setupEventHandlersIfNeeded();
this.observers.add(observer);
return new ContextSubscription(this, observer);
return this.subscriptionManager.subscribe(observer);
}

@@ -433,5 +280,3 @@ /**

unsubscribe(observer) {
if (!this.observers)
return false;
return this.observers.delete(observer);
return this.subscriptionManager.unsubscribe(observer);
}

@@ -449,16 +294,4 @@ /**

this._debug('Closing context...');
this.observers = undefined;
if (this.notificationQueue != null) {
// Cancel the notification iterator
this.notificationQueue.return(undefined).catch(err => {
this.handleNotificationError(err);
});
this.notificationQueue = undefined;
}
if (this._parent && this._parentEventListeners) {
for (const [event, listener] of this._parentEventListeners) {
this._parent.removeListener(event, listener);
}
this._parentEventListeners = undefined;
}
this.subscriptionManager.close();
this.tagIndexer.close();
}

@@ -470,5 +303,3 @@ /**

isSubscribed(observer) {
if (!this.observers)
return false;
return this.observers.has(observer);
return this.subscriptionManager.isSubscribed(observer);
}

@@ -486,25 +317,2 @@ /**

/**
* Publish an event to the registered observers. Please note the
* notification is queued and performed asynchronously so that we allow fluent
* APIs such as `ctx.bind('key').to(...).tag(...);` and give observers the
* fully populated binding.
*
* @param eventType - Event names: `bind` or `unbind`
* @param binding - Binding bound or unbound
* @param context - Owner context
* @param observers - Current set of context observers
*/
async notifyObservers(eventType, binding, context, observers = this.observers) {
if (!observers || observers.size === 0)
return;
for (const observer of observers) {
if (typeof observer === 'function') {
await observer(eventType, binding, context);
}
else if (!observer.filter || observer.filter(binding)) {
await observer.observe(eventType, binding, context);
}
}
}
/**
* Check if a binding exists with the given key in the local context without

@@ -557,2 +365,6 @@ * delegating to the parent context

find(pattern) {
// Optimize if the binding filter is for tags
if (typeof pattern === 'function' && binding_filter_1.isBindingTagFilter(pattern)) {
return this._findByTagIndex(pattern.bindingTagPattern);
}
const bindings = [];

@@ -584,2 +396,12 @@ const filter = binding_filter_1.filterByKey(pattern);

}
/**
* Find bindings by tag leveraging indexes
* @param tag - Tag name pattern or name/value pairs
*/
_findByTagIndex(tag) {
var _a;
const currentBindings = this.tagIndexer.findByTagIndex(tag);
const parentBindings = this._parent && ((_a = this._parent) === null || _a === void 0 ? void 0 : _a._findByTagIndex(tag));
return this._mergeWithParent(currentBindings, parentBindings);
}
_mergeWithParent(childList, parentList) {

@@ -715,19 +537,2 @@ if (!parentList)

/**
* An implementation of `Subscription` interface for context events
*/
class ContextSubscription {
constructor(context, observer) {
this.context = context;
this.observer = observer;
this._closed = false;
}
unsubscribe() {
this.context.unsubscribe(this.observer);
this._closed = true;
}
get closed() {
return this._closed;
}
}
/**
* Policy to control if a binding should be created for the context

@@ -734,0 +539,0 @@ */

@@ -10,3 +10,5 @@ export * from '@loopback/metadata';

export * from './context';
export * from './context-event';
export * from './context-observer';
export * from './context-subscription';
export * from './context-view';

@@ -13,0 +15,0 @@ export * from './inject';

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

__export(require("./context"));
__export(require("./context-subscription"));
__export(require("./context-view"));

@@ -21,0 +22,0 @@ __export(require("./inject"));

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

*/
const tag: (bindingTag: string | RegExp | import("./value-promise").MapObject<any>, metadata?: InjectionMetadata | undefined) => (target: Object, member: string | undefined, methodDescriptorOrParameterIndex?: number | TypedPropertyDescriptor<any> | undefined) => void;
const tag: (bindingTag: string | RegExp | Record<string, any>, metadata?: InjectionMetadata | undefined) => (target: Object, member: string | undefined, methodDescriptorOrParameterIndex?: number | TypedPropertyDescriptor<any> | undefined) => void;
/**

@@ -178,0 +178,0 @@ * Inject matching bound values by the filter function

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

const binding_decorator_1 = require("./binding-decorator");
const binding_filter_1 = require("./binding-filter");
const binding_sorter_1 = require("./binding-sorter");

@@ -31,5 +30,6 @@ const interceptor_chain_1 = require("./interceptor-chain");

getGlobalInterceptorBindingKeys() {
const bindings = this.find(binding => binding_filter_1.filterByTag(keys_1.ContextTags.GLOBAL_INTERCEPTOR)(binding) &&
// Only include interceptors that match the source type of the invocation
this.applicableTo(binding));
let bindings = this.findByTag(keys_1.ContextTags.GLOBAL_INTERCEPTOR);
bindings = bindings.filter(binding =>
// Only include interceptors that match the source type of the invocation
this.applicableTo(binding));
this.sortGlobalInterceptorBindings(bindings);

@@ -36,0 +36,0 @@ const keys = bindings.map(b => b.key);

@@ -30,3 +30,2 @@ import { Context } from './context';

export declare class InvocationContext extends Context {
readonly parent: Context;
readonly target: object;

@@ -33,0 +32,0 @@ readonly methodName: string;

@@ -33,8 +33,4 @@ "use strict";

*/
constructor(
// Make `parent` public so that the interceptor can add bindings to
// the request context, for example, tracing id
parent, target, methodName, args, source) {
constructor(parent, target, methodName, args, source) {
super(parent);
this.parent = parent;
this.target = target;

@@ -41,0 +37,0 @@ this.methodName = methodName;

@@ -20,5 +20,3 @@ /**

export declare type ValueOrPromise<T> = T | PromiseLike<T>;
export declare type MapObject<T> = {
[name: string]: T;
};
export declare type MapObject<T> = Record<string, T>;
/**

@@ -25,0 +23,0 @@ * Check whether a value is a Promise-like instance.

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

@@ -21,11 +21,11 @@ "engines": {

"dependencies": {
"@loopback/metadata": "^1.3.10",
"@loopback/metadata": "^1.4.0",
"debug": "^4.1.1",
"p-event": "^4.1.0",
"uuid": "^3.3.3"
"uuid": "^3.4.0"
},
"devDependencies": {
"@loopback/build": "^3.0.1",
"@loopback/eslint-config": "^5.0.1",
"@loopback/testlab": "^1.10.1",
"@loopback/build": "^3.1.0",
"@loopback/eslint-config": "^5.0.2",
"@loopback/testlab": "^1.10.2",
"@types/bluebird": "^3.5.29",

@@ -59,3 +59,3 @@ "@types/debug": "^4.1.5",

},
"gitHead": "598baf6e84de3917bb67aff47a1ab1cb1111e3d2"
"gitHead": "d08f135a0d1040edc61497739a8d86a866e4e29a"
}

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

import {BindingAddress} from './binding-key';
import {MapObject} from './value-promise';

@@ -64,17 +65,64 @@ /**

/**
* Binding filter function that holds a binding tag pattern. `Context.find()`
* uses the `bindingTagPattern` to optimize the matching of bindings by tag to
* avoid expensive check for all bindings.
*/
export interface BindingTagFilter extends BindingFilter<unknown> {
/**
* A special property on the filter function to provide access to the binding
* tag pattern which can be utilized to optimize the matching of bindings by
* tag in a context.
*/
bindingTagPattern: BindingTag | RegExp;
}
/**
* Type guard for BindingTagFilter
* @param filter - A BindingFilter function
*/
export function isBindingTagFilter(
filter?: BindingFilter,
): filter is BindingTagFilter {
if (filter == null || !('bindingTagPattern' in filter)) return false;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const tagPattern = (filter as any).bindingTagPattern;
return (
tagPattern instanceof RegExp ||
typeof tagPattern === 'string' ||
typeof tagPattern === 'object'
);
}
/**
* Create a binding filter for the tag pattern
* @param tagPattern - Binding tag name, regexp, or object
*/
export function filterByTag(tagPattern: BindingTag | RegExp): BindingFilter {
if (typeof tagPattern === 'string' || tagPattern instanceof RegExp) {
const regexp =
typeof tagPattern === 'string'
? wildcardToRegExp(tagPattern)
: tagPattern;
return b => Array.from(b.tagNames).some(t => regexp!.test(t));
export function filterByTag(tagPattern: BindingTag | RegExp): BindingTagFilter {
let filter: BindingFilter;
let regex: RegExp | undefined = undefined;
if (tagPattern instanceof RegExp) {
// RegExp for tag names
regex = tagPattern;
}
if (
typeof tagPattern === 'string' &&
(tagPattern.includes('*') || tagPattern.includes('?'))
) {
// Wildcard tag name
regex = wildcardToRegExp(tagPattern);
}
if (regex != null) {
// RegExp or wildcard match
filter = b => b.tagNames.some(t => regex!.test(t));
} else if (typeof tagPattern === 'string') {
// Plain tag string match
filter = b => b.tagNames.includes(tagPattern);
} else {
return b => {
// Match tag name/value pairs
const tagMap = tagPattern as MapObject<unknown>;
filter = b => {
for (const t in tagPattern) {
// One tag name/value does not match
if (b.tagMap[t] !== tagPattern[t]) return false;
if (b.tagMap[t] !== tagMap[t]) return false;
}

@@ -85,2 +133,6 @@ // All tag name/value pairs match

}
// Set up binding tag for the filter
const tagFilter = filter as BindingTagFilter;
tagFilter.bindingTagPattern = regex ?? tagPattern;
return tagFilter;
}

@@ -87,0 +139,0 @@

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

import debugFactory from 'debug';
import {EventEmitter} from 'events';
import {BindingAddress, BindingKey} from './binding-key';

@@ -143,2 +144,30 @@ import {Context} from './context';

/**
* Information for a binding event
*/
export type BindingEvent = {
/**
* Event type
*/
type: string;
/**
* Source binding that emits the event
*/
binding: Readonly<Binding<unknown>>;
/**
* Operation that triggers the event
*/
operation: string;
};
/**
* Event listeners for binding events
*/
export type BindingEventListener = (
/**
* Binding event
*/
event: BindingEvent,
) => void;
type ValueGetter<T> = (

@@ -153,3 +182,3 @@ ctx: Context,

*/
export class Binding<T = BoundValue> {
export class Binding<T = BoundValue> extends EventEmitter {
/**

@@ -205,2 +234,3 @@ * Key of the binding

constructor(key: BindingAddress<T>, public isLocked: boolean = false) {
super();
BindingKey.validate(key);

@@ -332,2 +362,11 @@ this.key = key.toString();

/**
* Emit a `changed` event
* @param operation - Operation that makes changes
*/
private emitChangedEvent(operation: string) {
const event: BindingEvent = {binding: this, operation, type: 'changed'};
this.emit('changed', event);
}
/**
* Tag the binding with names or name/value objects. A tag has a name and

@@ -370,2 +409,3 @@ * an optional value. If not supplied, the tag name is used as the value.

}
this.emitChangedEvent('tag');
return this;

@@ -388,2 +428,3 @@ }

this._scope = scope;
this.emitChangedEvent('scope');
return this;

@@ -419,2 +460,3 @@ }

};
this.emitChangedEvent('value');
}

@@ -421,0 +463,0 @@

@@ -8,4 +8,4 @@ // Copyright IBM Corp. 2019. All Rights Reserved.

import {BindingFilter} from './binding-filter';
import {Context} from './context';
import {ValueOrPromise} from './value-promise';
import {Context} from './context';

@@ -52,38 +52,1 @@ /**

export type ContextEventObserver = ContextObserver | ContextObserverFn;
/**
* Subscription of context events. It's modeled after
* https://github.com/tc39/proposal-observable.
*/
export interface Subscription {
/**
* unsubscribe
*/
unsubscribe(): void;
/**
* Is the subscription closed?
*/
closed: boolean;
}
/**
* Event data for observer notifications
*/
export type Notification = {
/**
* Context event type - bind/unbind
*/
eventType: ContextEventType;
/**
* Binding added/removed
*/
binding: Readonly<Binding<unknown>>;
/**
* Owner context for the binding
*/
context: Context;
/**
* A snapshot of observers when the original event is emitted
*/
observers: Set<ContextEventObserver>;
};

@@ -13,7 +13,4 @@ // Copyright IBM Corp. 2019. All Rights Reserved.

import {Context} from './context';
import {
ContextEventType,
ContextObserver,
Subscription,
} from './context-observer';
import {ContextEventType, ContextObserver} from './context-observer';
import {Subscription} from './context-subscription';
import {Getter} from './inject';

@@ -20,0 +17,0 @@ import {ResolutionSession} from './resolution-session';

@@ -14,12 +14,14 @@ // Copyright IBM Corp. 2017,2019. All Rights Reserved.

} from './binding-config';
import {BindingFilter, filterByKey, filterByTag} from './binding-filter';
import {
BindingFilter,
filterByKey,
filterByTag,
isBindingTagFilter,
} from './binding-filter';
import {BindingAddress, BindingKey} from './binding-key';
import {BindingComparator} from './binding-sorter';
import {
ContextEventObserver,
ContextEventType,
ContextObserver,
Notification,
Subscription,
} from './context-observer';
import {ContextEvent} 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';

@@ -40,17 +42,2 @@ import {ContextBindings} from './keys';

/**
* Polyfill Symbol.asyncIterator as required by TypeScript for Node 8.x.
* See https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html
*/
if (!Symbol.asyncIterator) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(Symbol as any).asyncIterator = Symbol.for('Symbol.asyncIterator');
}
/**
* WARNING: This following import must happen after the polyfill. The
* `auto-import` by an IDE such as VSCode may move the import before the
* polyfill. It must be then fixed manually.
*/
import {iterator, multiple} from 'p-event';
const debug = debugFactory('loopback:context');

@@ -73,39 +60,22 @@

/**
* Parent context
* Indexer for bindings by tag
*/
protected _parent?: Context;
protected readonly tagIndexer: ContextTagIndexer;
protected configResolver: ConfigurationResolver;
/**
* Event listeners for parent context keyed by event names. It keeps track
* of listeners from this context against its parent so that we can remove
* these listeners when this context is closed.
* Manager for observer subscriptions
*/
protected _parentEventListeners:
| Map<
string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(...args: any[]) => void
>
| undefined;
readonly subscriptionManager: ContextSubscriptionManager;
/**
* A list of registered context observers. The Set will be created when the
* first observer is added.
* Parent context
*/
protected observers: Set<ContextEventObserver> | undefined;
protected _parent?: Context;
/**
* Internal counter for pending notification events which are yet to be
* processed by observers.
* Configuration resolver
*/
private pendingNotifications = 0;
protected configResolver: ConfigurationResolver;
/**
* Queue for background notifications for observers
*/
private notificationQueue: AsyncIterableIterator<Notification> | undefined;
/**
* Create a new context.

@@ -134,2 +104,7 @@ *

super();
// The number of listeners can grow with the number of child contexts
// For example, each request can add a listener to the RestServer and the
// listener is removed when the request processing is finished.
// See https://github.com/strongloop/loopback-next/issues/4363
this.setMaxListeners(Infinity);
if (typeof _parent === 'string') {

@@ -141,5 +116,15 @@ name = _parent;

this.name = name ?? uuidv1();
this.tagIndexer = new ContextTagIndexer(this);
this.subscriptionManager = new ContextSubscriptionManager(this);
}
/**
* @internal
* Getter for ContextSubscriptionManager
*/
get parent() {
return this._parent;
}
/**
* Wrap the debug statement so that it always print out the context name

@@ -149,4 +134,3 @@ * as the prefix

*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _debug(...args: any[]) {
private _debug(...args: unknown[]) {
/* istanbul ignore if */

@@ -163,90 +147,15 @@ if (!debug.enabled) return;

/**
* Set up an internal listener to notify registered observers asynchronously
* upon `bind` and `unbind` events. This method will be called lazily when
* the first observer is added.
* A strongly-typed method to emit context events
* @param type Event type
* @param event Context event
*/
private setupEventHandlersIfNeeded() {
if (this.notificationQueue != null) return;
this.addParentEventListener('bind');
this.addParentEventListener('unbind');
// The following are two async functions. Returned promises are ignored as
// they are long-running background tasks.
this.startNotificationTask().catch(err => {
this.handleNotificationError(err);
});
let ctx = this._parent;
while (ctx) {
ctx.setupEventHandlersIfNeeded();
ctx = ctx._parent;
}
emitEvent<T extends ContextEvent>(type: string, event: T) {
this.emit(type, event);
}
/**
* Add an event listener to its parent context so that this context will
* be notified of parent events, such as `bind` or `unbind`.
* @param event - Event name
* Emit an `error` event
* @param err Error
*/
private addParentEventListener(event: string) {
if (this._parent == null) return;
// Keep track of parent event listeners so that we can remove them
this._parentEventListeners = this._parentEventListeners ?? new Map();
if (this._parentEventListeners.has(event)) return;
const parentEventListener = (
binding: Readonly<Binding<unknown>>,
context: Context,
) => {
// Propagate the event to this context only if the binding key does not
// exist in this context. The parent binding is shadowed if there is a
// binding with the same key in this one.
if (this.contains(binding.key)) {
this._debug(
'Event %s %s is not re-emitted from %s to %s',
event,
binding.key,
context.name,
this.name,
);
return;
}
this._debug(
'Re-emitting %s %s from %s to %s',
event,
binding.key,
context.name,
this.name,
);
this.emit(event, binding, context);
};
this._parentEventListeners.set(event, parentEventListener);
// Listen on the parent context events
this._parent.on(event, parentEventListener);
}
/**
* Handle errors caught during the notification of observers
* @param err - Error
*/
private handleNotificationError(err: unknown) {
// Bubbling up the error event over the context chain
// until we find an error listener
// eslint-disable-next-line @typescript-eslint/no-this-alias
let ctx: Context | undefined = this;
while (ctx) {
if (ctx.listenerCount('error') === 0) {
// No error listener found, try its parent
ctx = ctx._parent;
continue;
}
this._debug('Emitting error to context %s', ctx.name, err);
ctx.emit('error', err);
return;
}
// No context with error listeners found
this._debug('No error handler is configured for the context chain', err);
// Let it crash now by emitting an error event
emitError(err: unknown) {
this.emit('error', err);

@@ -256,81 +165,2 @@ }

/**
* Start a background task to listen on context events and notify observers
*/
private startNotificationTask() {
// Set up listeners on `bind` and `unbind` for notifications
this.setupNotification('bind', 'unbind');
// Create an async iterator for the `notification` event as a queue
this.notificationQueue = iterator(this, 'notification');
return this.processNotifications();
}
/**
* Process notification events as they arrive on the queue
*/
private async processNotifications() {
const events = this.notificationQueue;
if (events == null) return;
for await (const {eventType, binding, context, observers} of events) {
// The loop will happen asynchronously upon events
try {
// The execution of observers happen in the Promise micro-task queue
await this.notifyObservers(eventType, binding, context, observers);
this.pendingNotifications--;
this._debug(
'Observers notified for %s of binding %s',
eventType,
binding.key,
);
this.emit('observersNotified', {eventType, binding});
} catch (err) {
this.pendingNotifications--;
this._debug('Error caught from observers', err);
// Errors caught from observers. Emit it to the current context.
// If no error listeners are registered, crash the process.
this.emit('error', err);
}
}
}
/**
* Listen on given event types and emit `notification` event. This method
* merge multiple event types into one for notification.
* @param eventTypes - Context event types
*/
private setupNotification(...eventTypes: ContextEventType[]) {
for (const eventType of eventTypes) {
this.on(eventType, (binding, context) => {
// No need to schedule notifications if no observers are present
if (!this.observers || this.observers.size === 0) return;
// Track pending events
this.pendingNotifications++;
// Take a snapshot of current observers to ensure notifications of this
// event will only be sent to current ones. Emit a new event to notify
// current context observers.
this.emit('notification', {
eventType,
binding,
context,
observers: new Set(this.observers),
});
});
}
}
/**
* Wait until observers are notified for all of currently pending notification
* events.
*
* This method is for test only to perform assertions after observers are
* notified for relevant events.
*/
protected async waitUntilPendingNotificationsDone(timeout?: number) {
const count = this.pendingNotifications;
if (count === 0) return;
await multiple(this, 'observersNotified', {count, timeout});
}
/**
* Create a binding with the given key in the context. If a locked binding

@@ -368,5 +198,9 @@ * already exists with the same key, an error will be thrown.

if (existingBinding != null) {
this.emit('unbind', existingBinding, this);
this.emitEvent('unbind', {
binding: existingBinding,
context: this,
type: 'unbind',
});
}
this.emit('bind', binding, this);
this.emitEvent('bind', {binding, context: this, type: 'bind'});
}

@@ -517,3 +351,3 @@ return this;

this.registry.delete(key);
this.emit('unbind', binding, this);
this.emitEvent('unbind', {binding, context: this, type: 'unbind'});
return true;

@@ -527,6 +361,3 @@ }

subscribe(observer: ContextEventObserver): Subscription {
this.observers = this.observers ?? new Set();
this.setupEventHandlersIfNeeded();
this.observers.add(observer);
return new ContextSubscription(this, observer);
return this.subscriptionManager.subscribe(observer);
}

@@ -539,4 +370,3 @@

unsubscribe(observer: ContextEventObserver): boolean {
if (!this.observers) return false;
return this.observers.delete(observer);
return this.subscriptionManager.unsubscribe(observer);
}

@@ -555,16 +385,4 @@

this._debug('Closing context...');
this.observers = undefined;
if (this.notificationQueue != null) {
// Cancel the notification iterator
this.notificationQueue.return!(undefined).catch(err => {
this.handleNotificationError(err);
});
this.notificationQueue = undefined;
}
if (this._parent && this._parentEventListeners) {
for (const [event, listener] of this._parentEventListeners) {
this._parent.removeListener(event, listener);
}
this._parentEventListeners = undefined;
}
this.subscriptionManager.close();
this.tagIndexer.close();
}

@@ -577,4 +395,3 @@

isSubscribed(observer: ContextObserver) {
if (!this.observers) return false;
return this.observers.has(observer);
return this.subscriptionManager.isSubscribed(observer);
}

@@ -597,30 +414,2 @@

/**
* Publish an event to the registered observers. Please note the
* notification is queued and performed asynchronously so that we allow fluent
* APIs such as `ctx.bind('key').to(...).tag(...);` and give observers the
* fully populated binding.
*
* @param eventType - Event names: `bind` or `unbind`
* @param binding - Binding bound or unbound
* @param context - Owner context
* @param observers - Current set of context observers
*/
protected async notifyObservers(
eventType: ContextEventType,
binding: Readonly<Binding<unknown>>,
context: Context,
observers = this.observers,
) {
if (!observers || observers.size === 0) return;
for (const observer of observers) {
if (typeof observer === 'function') {
await observer(eventType, binding, context);
} else if (!observer.filter || observer.filter(binding)) {
await observer.observe(eventType, binding, context);
}
}
}
/**
* Check if a binding exists with the given key in the local context without

@@ -676,2 +465,7 @@ * delegating to the parent context

): Readonly<Binding<ValueType>>[] {
// Optimize if the binding filter is for tags
if (typeof pattern === 'function' && isBindingTagFilter(pattern)) {
return this._findByTagIndex(pattern.bindingTagPattern);
}
const bindings: Readonly<Binding<ValueType>>[] = [];

@@ -708,2 +502,14 @@ const filter = filterByKey(pattern);

/**
* Find bindings by tag leveraging indexes
* @param tag - Tag name pattern or name/value pairs
*/
protected _findByTagIndex<ValueType = BoundValue>(
tag: BindingTag | RegExp,
): Readonly<Binding<ValueType>>[] {
const currentBindings = this.tagIndexer.findByTagIndex(tag);
const parentBindings = this._parent && this._parent?._findByTagIndex(tag);
return this._mergeWithParent(currentBindings, parentBindings);
}
protected _mergeWithParent<ValueType>(

@@ -1014,23 +820,2 @@ childList: Readonly<Binding<ValueType>>[],

/**
* An implementation of `Subscription` interface for context events
*/
class ContextSubscription implements Subscription {
constructor(
protected context: Context,
protected observer: ContextEventObserver,
) {}
private _closed = false;
unsubscribe() {
this.context.unsubscribe(this.observer);
this._closed = true;
}
get closed() {
return this._closed;
}
}
/**
* Policy to control if a binding should be created for the context

@@ -1037,0 +822,0 @@ */

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

export * from './context';
export * from './context-event';
export * from './context-observer';
export * from './context-subscription';
export * from './context-view';

@@ -18,0 +20,0 @@ export * from './inject';

@@ -18,3 +18,2 @@ // Copyright IBM Corp. 2019. All Rights Reserved.

import {bind} from './binding-decorator';
import {filterByTag} from './binding-filter';
import {BindingSpec} from './binding-inspector';

@@ -51,8 +50,10 @@ import {sortBindingsByPhase} from './binding-sorter';

getGlobalInterceptorBindingKeys(): string[] {
const bindings: Readonly<Binding<Interceptor>>[] = this.find(
binding =>
filterByTag(ContextTags.GLOBAL_INTERCEPTOR)(binding) &&
// Only include interceptors that match the source type of the invocation
this.applicableTo(binding),
let bindings: Readonly<Binding<Interceptor>>[] = this.findByTag(
ContextTags.GLOBAL_INTERCEPTOR,
);
bindings = bindings.filter(binding =>
// Only include interceptors that match the source type of the invocation
this.applicableTo(binding),
);
this.sortGlobalInterceptorBindings(bindings);

@@ -59,0 +60,0 @@ const keys = bindings.map(b => b.key);

@@ -58,5 +58,3 @@ // Copyright IBM Corp. 2019. All Rights Reserved.

constructor(
// Make `parent` public so that the interceptor can add bindings to
// the request context, for example, tracing id
public readonly parent: Context,
parent: Context,
public readonly target: object,

@@ -63,0 +61,0 @@ public readonly methodName: string,

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

export type MapObject<T> = {[name: string]: T};
export type MapObject<T> = Record<string, T>;

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc