@loopback/context 1.23.5 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -0
- package/dist/binding-config.js +1 -1
- package/dist/binding-config.js.map +1 -1
- package/dist/binding-filter.d.ts +19 -1
- package/dist/binding-filter.js +40 -7
- package/dist/binding-filter.js.map +1 -1
- package/dist/binding-inspector.js +12 -11
- package/dist/binding-inspector.js.map +1 -1
- package/dist/binding-sorter.js +2 -2
- package/dist/binding-sorter.js.map +1 -1
- package/dist/binding.d.ts +42 -4
- package/dist/binding.js +40 -10
- package/dist/binding.js.map +1 -1
- package/dist/context-event.d.ts +23 -0
- package/dist/context-event.js +7 -0
- package/dist/context-event.js.map +1 -0
- package/dist/context-observer.d.ts +1 -36
- package/dist/context-subscription.d.ts +147 -0
- package/dist/context-subscription.js +336 -0
- package/dist/context-subscription.js.map +1 -0
- package/dist/context-tag-indexer.d.ts +42 -0
- package/dist/context-tag-indexer.js +134 -0
- package/dist/context-tag-indexer.js.map +1 -0
- package/dist/context-view.d.ts +2 -1
- package/dist/context-view.js +5 -2
- package/dist/context-view.js.map +1 -1
- package/dist/context.d.ts +35 -66
- package/dist/context.js +78 -250
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/inject-config.js +3 -3
- package/dist/inject-config.js.map +1 -1
- package/dist/inject.d.ts +2 -2
- package/dist/inject.js +18 -11
- package/dist/inject.js.map +1 -1
- package/dist/interception-proxy.d.ts +15 -3
- package/dist/interception-proxy.js +20 -4
- package/dist/interception-proxy.js.map +1 -1
- package/dist/interceptor-chain.js +5 -2
- package/dist/interceptor-chain.js.map +1 -1
- package/dist/interceptor.d.ts +6 -0
- package/dist/interceptor.js +38 -12
- package/dist/interceptor.js.map +1 -1
- package/dist/invocation.d.ts +20 -2
- package/dist/invocation.js +14 -12
- package/dist/invocation.js.map +1 -1
- package/dist/keys.d.ts +6 -0
- package/dist/keys.js +6 -0
- package/dist/keys.js.map +1 -1
- package/dist/resolution-session.d.ts +1 -0
- package/dist/resolution-session.js +13 -6
- package/dist/resolution-session.js.map +1 -1
- package/dist/resolver.js +13 -8
- package/dist/resolver.js.map +1 -1
- package/dist/value-promise.d.ts +1 -3
- package/package.json +9 -9
- package/src/binding-config.ts +1 -1
- package/src/binding-filter.ts +61 -9
- package/src/binding-inspector.ts +6 -8
- package/src/binding-sorter.ts +2 -2
- package/src/binding.ts +73 -9
- package/src/context-event.ts +30 -0
- package/src/context-observer.ts +1 -38
- package/src/context-subscription.ts +403 -0
- package/src/context-tag-indexer.ts +149 -0
- package/src/context-view.ts +3 -6
- package/src/context.ts +94 -293
- package/src/index.ts +5 -3
- package/src/inject-config.ts +3 -3
- package/src/inject.ts +19 -10
- package/src/interception-proxy.ts +25 -3
- package/src/interceptor-chain.ts +1 -1
- package/src/interceptor.ts +34 -8
- package/src/invocation.ts +26 -7
- package/src/keys.ts +7 -0
- package/src/resolution-session.ts +9 -5
- package/src/resolver.ts +5 -5
- package/src/value-promise.ts +1 -1
package/src/context.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import debugFactory from 'debug';
|
|
7
7
|
import {EventEmitter} from 'events';
|
|
8
8
|
import {v1 as uuidv1} from 'uuid';
|
|
9
9
|
import {Binding, BindingTag} from './binding';
|
|
@@ -11,16 +11,18 @@ import {
|
|
|
11
11
|
ConfigurationResolver,
|
|
12
12
|
DefaultConfigurationResolver,
|
|
13
13
|
} from './binding-config';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
BindingFilter,
|
|
16
|
+
filterByKey,
|
|
17
|
+
filterByTag,
|
|
18
|
+
isBindingTagFilter,
|
|
19
|
+
} from './binding-filter';
|
|
15
20
|
import {BindingAddress, BindingKey} from './binding-key';
|
|
16
21
|
import {BindingComparator} from './binding-sorter';
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Notification,
|
|
22
|
-
Subscription,
|
|
23
|
-
} from './context-observer';
|
|
22
|
+
import {ContextEvent} from './context-event';
|
|
23
|
+
import {ContextEventObserver, ContextObserver} from './context-observer';
|
|
24
|
+
import {ContextSubscriptionManager, Subscription} from './context-subscription';
|
|
25
|
+
import {ContextTagIndexer} from './context-tag-indexer';
|
|
24
26
|
import {ContextView} from './context-view';
|
|
25
27
|
import {ContextBindings} from './keys';
|
|
26
28
|
import {
|
|
@@ -36,21 +38,6 @@ import {
|
|
|
36
38
|
ValueOrPromise,
|
|
37
39
|
} from './value-promise';
|
|
38
40
|
|
|
39
|
-
/**
|
|
40
|
-
* Polyfill Symbol.asyncIterator as required by TypeScript for Node 8.x.
|
|
41
|
-
* See https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html
|
|
42
|
-
*/
|
|
43
|
-
if (!Symbol.asyncIterator) {
|
|
44
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
-
(Symbol as any).asyncIterator = Symbol.for('Symbol.asyncIterator');
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* WARNING: This following import must happen after the polyfill. The
|
|
49
|
-
* `auto-import` by an IDE such as VSCode may move the import before the
|
|
50
|
-
* polyfill. It must be then fixed manually.
|
|
51
|
-
*/
|
|
52
|
-
import {iterator, multiple} from 'p-event';
|
|
53
|
-
|
|
54
41
|
const debug = debugFactory('loopback:context');
|
|
55
42
|
|
|
56
43
|
/**
|
|
@@ -68,41 +55,24 @@ export class Context extends EventEmitter {
|
|
|
68
55
|
protected readonly registry: Map<string, Binding> = new Map();
|
|
69
56
|
|
|
70
57
|
/**
|
|
71
|
-
*
|
|
72
|
-
*/
|
|
73
|
-
protected _parent?: Context;
|
|
74
|
-
|
|
75
|
-
protected configResolver: ConfigurationResolver;
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Event listeners for parent context keyed by event names. It keeps track
|
|
79
|
-
* of listeners from this context against its parent so that we can remove
|
|
80
|
-
* these listeners when this context is closed.
|
|
58
|
+
* Indexer for bindings by tag
|
|
81
59
|
*/
|
|
82
|
-
protected
|
|
83
|
-
| Map<
|
|
84
|
-
string,
|
|
85
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
-
(...args: any[]) => void
|
|
87
|
-
>
|
|
88
|
-
| undefined;
|
|
60
|
+
protected readonly tagIndexer: ContextTagIndexer;
|
|
89
61
|
|
|
90
62
|
/**
|
|
91
|
-
*
|
|
92
|
-
* first observer is added.
|
|
63
|
+
* Manager for observer subscriptions
|
|
93
64
|
*/
|
|
94
|
-
|
|
65
|
+
readonly subscriptionManager: ContextSubscriptionManager;
|
|
95
66
|
|
|
96
67
|
/**
|
|
97
|
-
*
|
|
98
|
-
* processed by observers.
|
|
68
|
+
* Parent context
|
|
99
69
|
*/
|
|
100
|
-
|
|
70
|
+
protected _parent?: Context;
|
|
101
71
|
|
|
102
72
|
/**
|
|
103
|
-
*
|
|
73
|
+
* Configuration resolver
|
|
104
74
|
*/
|
|
105
|
-
|
|
75
|
+
protected configResolver: ConfigurationResolver;
|
|
106
76
|
|
|
107
77
|
/**
|
|
108
78
|
* Create a new context.
|
|
@@ -128,12 +98,27 @@ export class Context extends EventEmitter {
|
|
|
128
98
|
*/
|
|
129
99
|
constructor(_parent?: Context | string, name?: string) {
|
|
130
100
|
super();
|
|
101
|
+
// The number of listeners can grow with the number of child contexts
|
|
102
|
+
// For example, each request can add a listener to the RestServer and the
|
|
103
|
+
// listener is removed when the request processing is finished.
|
|
104
|
+
// See https://github.com/strongloop/loopback-next/issues/4363
|
|
105
|
+
this.setMaxListeners(Infinity);
|
|
131
106
|
if (typeof _parent === 'string') {
|
|
132
107
|
name = _parent;
|
|
133
108
|
_parent = undefined;
|
|
134
109
|
}
|
|
135
110
|
this._parent = _parent;
|
|
136
|
-
this.name = name
|
|
111
|
+
this.name = name ?? uuidv1();
|
|
112
|
+
this.tagIndexer = new ContextTagIndexer(this);
|
|
113
|
+
this.subscriptionManager = new ContextSubscriptionManager(this);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @internal
|
|
118
|
+
* Getter for ContextSubscriptionManager
|
|
119
|
+
*/
|
|
120
|
+
get parent() {
|
|
121
|
+
return this._parent;
|
|
137
122
|
}
|
|
138
123
|
|
|
139
124
|
/**
|
|
@@ -141,8 +126,7 @@ export class Context extends EventEmitter {
|
|
|
141
126
|
* as the prefix
|
|
142
127
|
* @param args - Arguments for the debug
|
|
143
128
|
*/
|
|
144
|
-
|
|
145
|
-
private _debug(...args: any[]) {
|
|
129
|
+
private _debug(...args: unknown[]) {
|
|
146
130
|
/* istanbul ignore if */
|
|
147
131
|
if (!debug.enabled) return;
|
|
148
132
|
const formatter = args.shift();
|
|
@@ -154,176 +138,22 @@ export class Context extends EventEmitter {
|
|
|
154
138
|
}
|
|
155
139
|
|
|
156
140
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
141
|
+
* A strongly-typed method to emit context events
|
|
142
|
+
* @param type Event type
|
|
143
|
+
* @param event Context event
|
|
160
144
|
*/
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
this.addParentEventListener('bind');
|
|
165
|
-
this.addParentEventListener('unbind');
|
|
166
|
-
|
|
167
|
-
// The following are two async functions. Returned promises are ignored as
|
|
168
|
-
// they are long-running background tasks.
|
|
169
|
-
this.startNotificationTask().catch(err => {
|
|
170
|
-
this.handleNotificationError(err);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
let ctx = this._parent;
|
|
174
|
-
while (ctx) {
|
|
175
|
-
ctx.setupEventHandlersIfNeeded();
|
|
176
|
-
ctx = ctx._parent;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Add an event listener to its parent context so that this context will
|
|
182
|
-
* be notified of parent events, such as `bind` or `unbind`.
|
|
183
|
-
* @param event - Event name
|
|
184
|
-
*/
|
|
185
|
-
private addParentEventListener(event: string) {
|
|
186
|
-
if (this._parent == null) return;
|
|
187
|
-
|
|
188
|
-
// Keep track of parent event listeners so that we can remove them
|
|
189
|
-
this._parentEventListeners = this._parentEventListeners || new Map();
|
|
190
|
-
if (this._parentEventListeners.has(event)) return;
|
|
191
|
-
|
|
192
|
-
const parentEventListener = (
|
|
193
|
-
binding: Readonly<Binding<unknown>>,
|
|
194
|
-
context: Context,
|
|
195
|
-
) => {
|
|
196
|
-
// Propagate the event to this context only if the binding key does not
|
|
197
|
-
// exist in this context. The parent binding is shadowed if there is a
|
|
198
|
-
// binding with the same key in this one.
|
|
199
|
-
if (this.contains(binding.key)) {
|
|
200
|
-
this._debug(
|
|
201
|
-
'Event %s %s is not re-emitted from %s to %s',
|
|
202
|
-
event,
|
|
203
|
-
binding.key,
|
|
204
|
-
context.name,
|
|
205
|
-
this.name,
|
|
206
|
-
);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
this._debug(
|
|
210
|
-
'Re-emitting %s %s from %s to %s',
|
|
211
|
-
event,
|
|
212
|
-
binding.key,
|
|
213
|
-
context.name,
|
|
214
|
-
this.name,
|
|
215
|
-
);
|
|
216
|
-
this.emit(event, binding, context);
|
|
217
|
-
};
|
|
218
|
-
this._parentEventListeners.set(event, parentEventListener);
|
|
219
|
-
// Listen on the parent context events
|
|
220
|
-
this._parent.on(event, parentEventListener);
|
|
145
|
+
emitEvent<T extends ContextEvent>(type: string, event: T) {
|
|
146
|
+
this.emit(type, event);
|
|
221
147
|
}
|
|
222
148
|
|
|
223
149
|
/**
|
|
224
|
-
*
|
|
225
|
-
* @param err
|
|
150
|
+
* Emit an `error` event
|
|
151
|
+
* @param err Error
|
|
226
152
|
*/
|
|
227
|
-
|
|
228
|
-
// Bubbling up the error event over the context chain
|
|
229
|
-
// until we find an error listener
|
|
230
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
231
|
-
let ctx: Context | undefined = this;
|
|
232
|
-
while (ctx) {
|
|
233
|
-
if (ctx.listenerCount('error') === 0) {
|
|
234
|
-
// No error listener found, try its parent
|
|
235
|
-
ctx = ctx._parent;
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
this._debug('Emitting error to context %s', ctx.name, err);
|
|
239
|
-
ctx.emit('error', err);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
// No context with error listeners found
|
|
243
|
-
this._debug('No error handler is configured for the context chain', err);
|
|
244
|
-
// Let it crash now by emitting an error event
|
|
153
|
+
emitError(err: unknown) {
|
|
245
154
|
this.emit('error', err);
|
|
246
155
|
}
|
|
247
156
|
|
|
248
|
-
/**
|
|
249
|
-
* Start a background task to listen on context events and notify observers
|
|
250
|
-
*/
|
|
251
|
-
private startNotificationTask() {
|
|
252
|
-
// Set up listeners on `bind` and `unbind` for notifications
|
|
253
|
-
this.setupNotification('bind', 'unbind');
|
|
254
|
-
|
|
255
|
-
// Create an async iterator for the `notification` event as a queue
|
|
256
|
-
this.notificationQueue = iterator(this, 'notification');
|
|
257
|
-
|
|
258
|
-
return this.processNotifications();
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Process notification events as they arrive on the queue
|
|
263
|
-
*/
|
|
264
|
-
private async processNotifications() {
|
|
265
|
-
const events = this.notificationQueue;
|
|
266
|
-
if (events == null) return;
|
|
267
|
-
for await (const {eventType, binding, context, observers} of events) {
|
|
268
|
-
// The loop will happen asynchronously upon events
|
|
269
|
-
try {
|
|
270
|
-
// The execution of observers happen in the Promise micro-task queue
|
|
271
|
-
await this.notifyObservers(eventType, binding, context, observers);
|
|
272
|
-
this.pendingNotifications--;
|
|
273
|
-
this._debug(
|
|
274
|
-
'Observers notified for %s of binding %s',
|
|
275
|
-
eventType,
|
|
276
|
-
binding.key,
|
|
277
|
-
);
|
|
278
|
-
this.emit('observersNotified', {eventType, binding});
|
|
279
|
-
} catch (err) {
|
|
280
|
-
this.pendingNotifications--;
|
|
281
|
-
this._debug('Error caught from observers', err);
|
|
282
|
-
// Errors caught from observers. Emit it to the current context.
|
|
283
|
-
// If no error listeners are registered, crash the process.
|
|
284
|
-
this.emit('error', err);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Listen on given event types and emit `notification` event. This method
|
|
291
|
-
* merge multiple event types into one for notification.
|
|
292
|
-
* @param eventTypes - Context event types
|
|
293
|
-
*/
|
|
294
|
-
private setupNotification(...eventTypes: ContextEventType[]) {
|
|
295
|
-
for (const eventType of eventTypes) {
|
|
296
|
-
this.on(eventType, (binding, context) => {
|
|
297
|
-
// No need to schedule notifications if no observers are present
|
|
298
|
-
if (!this.observers || this.observers.size === 0) return;
|
|
299
|
-
// Track pending events
|
|
300
|
-
this.pendingNotifications++;
|
|
301
|
-
// Take a snapshot of current observers to ensure notifications of this
|
|
302
|
-
// event will only be sent to current ones. Emit a new event to notify
|
|
303
|
-
// current context observers.
|
|
304
|
-
this.emit('notification', {
|
|
305
|
-
eventType,
|
|
306
|
-
binding,
|
|
307
|
-
context,
|
|
308
|
-
observers: new Set(this.observers),
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Wait until observers are notified for all of currently pending notification
|
|
316
|
-
* events.
|
|
317
|
-
*
|
|
318
|
-
* This method is for test only to perform assertions after observers are
|
|
319
|
-
* notified for relevant events.
|
|
320
|
-
*/
|
|
321
|
-
protected async waitUntilPendingNotificationsDone(timeout?: number) {
|
|
322
|
-
const count = this.pendingNotifications;
|
|
323
|
-
if (count === 0) return;
|
|
324
|
-
await multiple(this, 'observersNotified', {count, timeout});
|
|
325
|
-
}
|
|
326
|
-
|
|
327
157
|
/**
|
|
328
158
|
* Create a binding with the given key in the context. If a locked binding
|
|
329
159
|
* already exists with the same key, an error will be thrown.
|
|
@@ -350,16 +180,20 @@ export class Context extends EventEmitter {
|
|
|
350
180
|
const keyExists = this.registry.has(key);
|
|
351
181
|
if (keyExists) {
|
|
352
182
|
existingBinding = this.registry.get(key);
|
|
353
|
-
const bindingIsLocked = existingBinding
|
|
183
|
+
const bindingIsLocked = existingBinding?.isLocked;
|
|
354
184
|
if (bindingIsLocked)
|
|
355
185
|
throw new Error(`Cannot rebind key "${key}" to a locked binding`);
|
|
356
186
|
}
|
|
357
187
|
this.registry.set(key, binding);
|
|
358
188
|
if (existingBinding !== binding) {
|
|
359
189
|
if (existingBinding != null) {
|
|
360
|
-
this.
|
|
190
|
+
this.emitEvent('unbind', {
|
|
191
|
+
binding: existingBinding,
|
|
192
|
+
context: this,
|
|
193
|
+
type: 'unbind',
|
|
194
|
+
});
|
|
361
195
|
}
|
|
362
|
-
this.
|
|
196
|
+
this.emitEvent('bind', {binding, context: this, type: 'bind'});
|
|
363
197
|
}
|
|
364
198
|
return this;
|
|
365
199
|
}
|
|
@@ -502,10 +336,10 @@ export class Context extends EventEmitter {
|
|
|
502
336
|
const binding = this.registry.get(key);
|
|
503
337
|
// If not found, return `false`
|
|
504
338
|
if (binding == null) return false;
|
|
505
|
-
if (binding
|
|
339
|
+
if (binding?.isLocked)
|
|
506
340
|
throw new Error(`Cannot unbind key "${key}" of a locked binding`);
|
|
507
341
|
this.registry.delete(key);
|
|
508
|
-
this.
|
|
342
|
+
this.emitEvent('unbind', {binding, context: this, type: 'unbind'});
|
|
509
343
|
return true;
|
|
510
344
|
}
|
|
511
345
|
|
|
@@ -514,10 +348,7 @@ export class Context extends EventEmitter {
|
|
|
514
348
|
* @param observer - Context observer instance or function
|
|
515
349
|
*/
|
|
516
350
|
subscribe(observer: ContextEventObserver): Subscription {
|
|
517
|
-
|
|
518
|
-
this.setupEventHandlersIfNeeded();
|
|
519
|
-
this.observers.add(observer);
|
|
520
|
-
return new ContextSubscription(this, observer);
|
|
351
|
+
return this.subscriptionManager.subscribe(observer);
|
|
521
352
|
}
|
|
522
353
|
|
|
523
354
|
/**
|
|
@@ -525,8 +356,7 @@ export class Context extends EventEmitter {
|
|
|
525
356
|
* @param observer - Context event observer
|
|
526
357
|
*/
|
|
527
358
|
unsubscribe(observer: ContextEventObserver): boolean {
|
|
528
|
-
|
|
529
|
-
return this.observers.delete(observer);
|
|
359
|
+
return this.subscriptionManager.unsubscribe(observer);
|
|
530
360
|
}
|
|
531
361
|
|
|
532
362
|
/**
|
|
@@ -540,20 +370,8 @@ export class Context extends EventEmitter {
|
|
|
540
370
|
*/
|
|
541
371
|
close() {
|
|
542
372
|
this._debug('Closing context...');
|
|
543
|
-
this.
|
|
544
|
-
|
|
545
|
-
// Cancel the notification iterator
|
|
546
|
-
this.notificationQueue.return!(undefined).catch(err => {
|
|
547
|
-
this.handleNotificationError(err);
|
|
548
|
-
});
|
|
549
|
-
this.notificationQueue = undefined;
|
|
550
|
-
}
|
|
551
|
-
if (this._parent && this._parentEventListeners) {
|
|
552
|
-
for (const [event, listener] of this._parentEventListeners) {
|
|
553
|
-
this._parent.removeListener(event, listener);
|
|
554
|
-
}
|
|
555
|
-
this._parentEventListeners = undefined;
|
|
556
|
-
}
|
|
373
|
+
this.subscriptionManager.close();
|
|
374
|
+
this.tagIndexer.close();
|
|
557
375
|
}
|
|
558
376
|
|
|
559
377
|
/**
|
|
@@ -561,8 +379,7 @@ export class Context extends EventEmitter {
|
|
|
561
379
|
* @param observer - Context observer
|
|
562
380
|
*/
|
|
563
381
|
isSubscribed(observer: ContextObserver) {
|
|
564
|
-
|
|
565
|
-
return this.observers.has(observer);
|
|
382
|
+
return this.subscriptionManager.isSubscribed(observer);
|
|
566
383
|
}
|
|
567
384
|
|
|
568
385
|
/**
|
|
@@ -579,34 +396,6 @@ export class Context extends EventEmitter {
|
|
|
579
396
|
return view;
|
|
580
397
|
}
|
|
581
398
|
|
|
582
|
-
/**
|
|
583
|
-
* Publish an event to the registered observers. Please note the
|
|
584
|
-
* notification is queued and performed asynchronously so that we allow fluent
|
|
585
|
-
* APIs such as `ctx.bind('key').to(...).tag(...);` and give observers the
|
|
586
|
-
* fully populated binding.
|
|
587
|
-
*
|
|
588
|
-
* @param eventType - Event names: `bind` or `unbind`
|
|
589
|
-
* @param binding - Binding bound or unbound
|
|
590
|
-
* @param context - Owner context
|
|
591
|
-
* @param observers - Current set of context observers
|
|
592
|
-
*/
|
|
593
|
-
protected async notifyObservers(
|
|
594
|
-
eventType: ContextEventType,
|
|
595
|
-
binding: Readonly<Binding<unknown>>,
|
|
596
|
-
context: Context,
|
|
597
|
-
observers = this.observers,
|
|
598
|
-
) {
|
|
599
|
-
if (!observers || observers.size === 0) return;
|
|
600
|
-
|
|
601
|
-
for (const observer of observers) {
|
|
602
|
-
if (typeof observer === 'function') {
|
|
603
|
-
await observer(eventType, binding, context);
|
|
604
|
-
} else if (!observer.filter || observer.filter(binding)) {
|
|
605
|
-
await observer.observe(eventType, binding, context);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
399
|
/**
|
|
611
400
|
* Check if a binding exists with the given key in the local context without
|
|
612
401
|
* delegating to the parent context
|
|
@@ -658,6 +447,11 @@ export class Context extends EventEmitter {
|
|
|
658
447
|
find<ValueType = BoundValue>(
|
|
659
448
|
pattern?: string | RegExp | BindingFilter,
|
|
660
449
|
): Readonly<Binding<ValueType>>[] {
|
|
450
|
+
// Optimize if the binding filter is for tags
|
|
451
|
+
if (typeof pattern === 'function' && isBindingTagFilter(pattern)) {
|
|
452
|
+
return this._findByTagIndex(pattern.bindingTagPattern);
|
|
453
|
+
}
|
|
454
|
+
|
|
661
455
|
const bindings: Readonly<Binding<ValueType>>[] = [];
|
|
662
456
|
const filter = filterByKey(pattern);
|
|
663
457
|
|
|
@@ -689,6 +483,18 @@ export class Context extends EventEmitter {
|
|
|
689
483
|
return this.find(filterByTag(tagFilter));
|
|
690
484
|
}
|
|
691
485
|
|
|
486
|
+
/**
|
|
487
|
+
* Find bindings by tag leveraging indexes
|
|
488
|
+
* @param tag - Tag name pattern or name/value pairs
|
|
489
|
+
*/
|
|
490
|
+
protected _findByTagIndex<ValueType = BoundValue>(
|
|
491
|
+
tag: BindingTag | RegExp,
|
|
492
|
+
): Readonly<Binding<ValueType>>[] {
|
|
493
|
+
const currentBindings = this.tagIndexer.findByTagIndex(tag);
|
|
494
|
+
const parentBindings = this._parent && this._parent?._findByTagIndex(tag);
|
|
495
|
+
return this._mergeWithParent(currentBindings, parentBindings);
|
|
496
|
+
}
|
|
497
|
+
|
|
692
498
|
protected _mergeWithParent<ValueType>(
|
|
693
499
|
childList: Readonly<Binding<ValueType>>[],
|
|
694
500
|
parentList?: Readonly<Binding<ValueType>>[],
|
|
@@ -885,7 +691,7 @@ export class Context extends EventEmitter {
|
|
|
885
691
|
return this._parent.getBinding<ValueType>(key, options);
|
|
886
692
|
}
|
|
887
693
|
|
|
888
|
-
if (options
|
|
694
|
+
if (options?.optional) return undefined;
|
|
889
695
|
throw new Error(
|
|
890
696
|
`The key '${key}' is not bound to any value in context ${this.name}`,
|
|
891
697
|
);
|
|
@@ -968,33 +774,28 @@ export class Context extends EventEmitter {
|
|
|
968
774
|
/**
|
|
969
775
|
* Create a plain JSON object for the context
|
|
970
776
|
*/
|
|
971
|
-
toJSON():
|
|
972
|
-
const
|
|
777
|
+
toJSON(): object {
|
|
778
|
+
const bindings: Record<string, object> = {};
|
|
973
779
|
for (const [k, v] of this.registry) {
|
|
974
|
-
|
|
780
|
+
bindings[k] = v.toJSON();
|
|
975
781
|
}
|
|
976
|
-
return
|
|
782
|
+
return bindings;
|
|
977
783
|
}
|
|
978
|
-
}
|
|
979
784
|
|
|
980
|
-
/**
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
get closed() {
|
|
997
|
-
return this._closed;
|
|
785
|
+
/**
|
|
786
|
+
* Inspect the context and dump out a JSON object representing the context
|
|
787
|
+
* hierarchy
|
|
788
|
+
*/
|
|
789
|
+
// TODO(rfeng): Evaluate https://nodejs.org/api/util.html#util_custom_inspection_functions_on_objects
|
|
790
|
+
inspect(): object {
|
|
791
|
+
const json: Record<string, unknown> = {
|
|
792
|
+
name: this.name,
|
|
793
|
+
bindings: this.toJSON(),
|
|
794
|
+
};
|
|
795
|
+
if (this._parent) {
|
|
796
|
+
json.parent = this._parent.inspect();
|
|
797
|
+
}
|
|
798
|
+
return json;
|
|
998
799
|
}
|
|
999
800
|
}
|
|
1000
801
|
|
package/src/index.ts
CHANGED
|
@@ -12,16 +12,18 @@ export * from './binding-inspector';
|
|
|
12
12
|
export * from './binding-key';
|
|
13
13
|
export * from './binding-sorter';
|
|
14
14
|
export * from './context';
|
|
15
|
+
export * from './context-event';
|
|
15
16
|
export * from './context-observer';
|
|
17
|
+
export * from './context-subscription';
|
|
16
18
|
export * from './context-view';
|
|
17
|
-
export * from './interceptor-chain';
|
|
18
19
|
export * from './inject';
|
|
20
|
+
export * from './inject-config';
|
|
19
21
|
export * from './interception-proxy';
|
|
20
22
|
export * from './interceptor';
|
|
21
|
-
export * from './
|
|
23
|
+
export * from './interceptor-chain';
|
|
24
|
+
export * from './invocation';
|
|
22
25
|
export * from './keys';
|
|
23
26
|
export * from './provider';
|
|
24
27
|
export * from './resolution-session';
|
|
25
28
|
export * from './resolver';
|
|
26
29
|
export * from './value-promise';
|
|
27
|
-
export * from './invocation';
|
package/src/inject-config.ts
CHANGED
|
@@ -65,7 +65,7 @@ export function config(
|
|
|
65
65
|
propertyPath?: string | ConfigInjectionMetadata,
|
|
66
66
|
metadata?: ConfigInjectionMetadata,
|
|
67
67
|
) {
|
|
68
|
-
propertyPath = propertyPath
|
|
68
|
+
propertyPath = propertyPath ?? '';
|
|
69
69
|
if (typeof propertyPath === 'object') {
|
|
70
70
|
metadata = propertyPath;
|
|
71
71
|
propertyPath = '';
|
|
@@ -87,7 +87,7 @@ export namespace config {
|
|
|
87
87
|
propertyPath?: string | ConfigInjectionMetadata,
|
|
88
88
|
metadata?: ConfigInjectionMetadata,
|
|
89
89
|
) {
|
|
90
|
-
propertyPath = propertyPath
|
|
90
|
+
propertyPath = propertyPath ?? '';
|
|
91
91
|
if (typeof propertyPath === 'object') {
|
|
92
92
|
metadata = propertyPath;
|
|
93
93
|
propertyPath = '';
|
|
@@ -109,7 +109,7 @@ export namespace config {
|
|
|
109
109
|
propertyPath?: string | ConfigInjectionMetadata,
|
|
110
110
|
metadata?: ConfigInjectionMetadata,
|
|
111
111
|
) {
|
|
112
|
-
propertyPath = propertyPath
|
|
112
|
+
propertyPath = propertyPath ?? '';
|
|
113
113
|
if (typeof propertyPath === 'object') {
|
|
114
114
|
metadata = propertyPath;
|
|
115
115
|
propertyPath = '';
|
package/src/inject.ts
CHANGED
|
@@ -311,11 +311,11 @@ export namespace inject {
|
|
|
311
311
|
* @param metadata - Metadata for the injection
|
|
312
312
|
*/
|
|
313
313
|
export const binding = function injectBinding(
|
|
314
|
-
bindingKey
|
|
314
|
+
bindingKey?: BindingAddress,
|
|
315
315
|
metadata?: InjectBindingMetadata,
|
|
316
316
|
) {
|
|
317
317
|
metadata = Object.assign({decorator: '@inject.binding'}, metadata);
|
|
318
|
-
return inject(bindingKey, metadata, resolveAsBinding);
|
|
318
|
+
return inject(bindingKey ?? '', metadata, resolveAsBinding);
|
|
319
319
|
};
|
|
320
320
|
|
|
321
321
|
/**
|
|
@@ -395,7 +395,7 @@ export function assertTargetType(
|
|
|
395
395
|
const targetName = ResolutionSession.describeInjection(injection).targetName;
|
|
396
396
|
const targetType = inspectTargetType(injection);
|
|
397
397
|
if (targetType && targetType !== expectedType) {
|
|
398
|
-
expectedTypeName = expectedTypeName
|
|
398
|
+
expectedTypeName = expectedTypeName ?? expectedType.name;
|
|
399
399
|
throw new Error(
|
|
400
400
|
`The type of ${targetName} (${targetType.name}) is not ${expectedTypeName}`,
|
|
401
401
|
);
|
|
@@ -440,14 +440,21 @@ function resolveAsSetter(ctx: Context, injection: Injection) {
|
|
|
440
440
|
`@inject.setter (${targetName}) does not allow BindingFilter.`,
|
|
441
441
|
);
|
|
442
442
|
}
|
|
443
|
+
if (bindingSelector === '') {
|
|
444
|
+
throw new Error('Binding key is not set for @inject.setter');
|
|
445
|
+
}
|
|
443
446
|
// No resolution session should be propagated into the setter
|
|
444
447
|
return function setter(value: unknown) {
|
|
445
448
|
const binding = findOrCreateBindingForInjection(ctx, injection);
|
|
446
|
-
binding
|
|
449
|
+
binding!.to(value);
|
|
447
450
|
};
|
|
448
451
|
}
|
|
449
452
|
|
|
450
|
-
function resolveAsBinding(
|
|
453
|
+
function resolveAsBinding(
|
|
454
|
+
ctx: Context,
|
|
455
|
+
injection: Injection,
|
|
456
|
+
session: ResolutionSession,
|
|
457
|
+
) {
|
|
451
458
|
const targetName = assertTargetType(injection, Binding);
|
|
452
459
|
const bindingSelector = injection.bindingSelector;
|
|
453
460
|
if (!isBindingAddress(bindingSelector)) {
|
|
@@ -455,13 +462,15 @@ function resolveAsBinding(ctx: Context, injection: Injection) {
|
|
|
455
462
|
`@inject.binding (${targetName}) does not allow BindingFilter.`,
|
|
456
463
|
);
|
|
457
464
|
}
|
|
458
|
-
return findOrCreateBindingForInjection(ctx, injection);
|
|
465
|
+
return findOrCreateBindingForInjection(ctx, injection, session);
|
|
459
466
|
}
|
|
460
467
|
|
|
461
468
|
function findOrCreateBindingForInjection(
|
|
462
469
|
ctx: Context,
|
|
463
470
|
injection: Injection<unknown>,
|
|
471
|
+
session?: ResolutionSession,
|
|
464
472
|
) {
|
|
473
|
+
if (injection.bindingSelector === '') return session?.currentBinding;
|
|
465
474
|
const bindingCreation =
|
|
466
475
|
injection.metadata &&
|
|
467
476
|
(injection.metadata as InjectBindingMetadata).bindingCreation;
|
|
@@ -544,7 +553,7 @@ export function describeInjectedArguments(
|
|
|
544
553
|
target: Object,
|
|
545
554
|
method?: string,
|
|
546
555
|
): Readonly<Injection>[] {
|
|
547
|
-
method = method
|
|
556
|
+
method = method ?? '';
|
|
548
557
|
|
|
549
558
|
// Try to read from cache
|
|
550
559
|
const cache =
|
|
@@ -554,7 +563,7 @@ export function describeInjectedArguments(
|
|
|
554
563
|
{
|
|
555
564
|
ownMetadataOnly: true,
|
|
556
565
|
},
|
|
557
|
-
)
|
|
566
|
+
) ?? {};
|
|
558
567
|
let meta: Readonly<Injection>[] = cache[method];
|
|
559
568
|
if (meta) return meta;
|
|
560
569
|
|
|
@@ -575,7 +584,7 @@ export function describeInjectedArguments(
|
|
|
575
584
|
target,
|
|
576
585
|
method,
|
|
577
586
|
options,
|
|
578
|
-
)
|
|
587
|
+
) ?? [];
|
|
579
588
|
|
|
580
589
|
// Cache the result
|
|
581
590
|
cache[method] = meta;
|
|
@@ -686,6 +695,6 @@ export function describeInjectedProperties(
|
|
|
686
695
|
MetadataInspector.getAllPropertyMetadata<Readonly<Injection>>(
|
|
687
696
|
PROPERTIES_KEY,
|
|
688
697
|
target,
|
|
689
|
-
)
|
|
698
|
+
) ?? {};
|
|
690
699
|
return metadata;
|
|
691
700
|
}
|