@loopback/core 4.0.0-alpha.9 → 4.0.2
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/LICENSE +25 -0
- package/README.md +77 -2
- package/dist/application.d.ts +341 -0
- package/dist/application.js +554 -0
- package/dist/application.js.map +1 -0
- package/dist/component.d.ts +80 -0
- package/dist/component.js +59 -0
- package/dist/component.js.map +1 -0
- package/dist/extension-point.d.ts +121 -0
- package/dist/extension-point.js +227 -0
- package/dist/extension-point.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +97 -0
- package/dist/keys.js +109 -0
- package/dist/keys.js.map +1 -0
- package/dist/lifecycle-registry.d.ts +91 -0
- package/dist/lifecycle-registry.js +191 -0
- package/dist/lifecycle-registry.js.map +1 -0
- package/dist/lifecycle.d.ts +47 -0
- package/dist/lifecycle.js +56 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/mixin-target.d.ts +60 -0
- package/{lib6/internal-types.js → dist/mixin-target.js} +2 -3
- package/dist/mixin-target.js.map +1 -0
- package/dist/server.d.ts +16 -0
- package/{lib6/component.js → dist/server.js} +2 -2
- package/dist/server.js.map +1 -0
- package/dist/service.d.ts +63 -0
- package/dist/service.js +151 -0
- package/dist/service.js.map +1 -0
- package/package.json +39 -37
- package/src/application.ts +719 -0
- package/src/component.ts +155 -0
- package/src/extension-point.ts +312 -0
- package/src/index.ts +29 -0
- package/src/keys.ts +144 -0
- package/src/lifecycle-registry.ts +268 -0
- package/src/lifecycle.ts +90 -0
- package/src/mixin-target.ts +69 -0
- package/src/server.ts +22 -0
- package/src/service.ts +211 -0
- package/index.d.ts +0 -6
- package/index.js +0 -9
- package/lib/application.d.ts +0 -54
- package/lib/application.js +0 -81
- package/lib/application.js.map +0 -1
- package/lib/component.d.ts +0 -2
- package/lib/component.js +0 -7
- package/lib/component.js.map +0 -1
- package/lib/http-handler.d.ts +0 -16
- package/lib/http-handler.js +0 -62
- package/lib/http-handler.js.map +0 -1
- package/lib/index.d.ts +0 -17
- package/lib/index.js +0 -37
- package/lib/index.js.map +0 -1
- package/lib/internal-types.d.ts +0 -29
- package/lib/internal-types.js +0 -8
- package/lib/internal-types.js.map +0 -1
- package/lib/keys.d.ts +0 -7
- package/lib/keys.js +0 -16
- package/lib/keys.js.map +0 -1
- package/lib/parser.d.ts +0 -11
- package/lib/parser.js +0 -96
- package/lib/parser.js.map +0 -1
- package/lib/promisify.d.ts +0 -3
- package/lib/promisify.js +0 -34
- package/lib/promisify.js.map +0 -1
- package/lib/router/metadata.d.ts +0 -12
- package/lib/router/metadata.js +0 -30
- package/lib/router/metadata.js.map +0 -1
- package/lib/router/routing-table.d.ts +0 -16
- package/lib/router/routing-table.js +0 -97
- package/lib/router/routing-table.js.map +0 -1
- package/lib/sequence.d.ts +0 -55
- package/lib/sequence.js +0 -99
- package/lib/sequence.js.map +0 -1
- package/lib/server.d.ts +0 -23
- package/lib/server.js +0 -64
- package/lib/server.js.map +0 -1
- package/lib/writer.d.ts +0 -11
- package/lib/writer.js +0 -34
- package/lib/writer.js.map +0 -1
- package/lib6/application.d.ts +0 -54
- package/lib6/application.js +0 -81
- package/lib6/application.js.map +0 -1
- package/lib6/component.d.ts +0 -2
- package/lib6/component.js.map +0 -1
- package/lib6/http-handler.d.ts +0 -16
- package/lib6/http-handler.js +0 -72
- package/lib6/http-handler.js.map +0 -1
- package/lib6/index.d.ts +0 -17
- package/lib6/index.js +0 -37
- package/lib6/index.js.map +0 -1
- package/lib6/internal-types.d.ts +0 -29
- package/lib6/internal-types.js.map +0 -1
- package/lib6/keys.d.ts +0 -7
- package/lib6/keys.js +0 -16
- package/lib6/keys.js.map +0 -1
- package/lib6/parser.d.ts +0 -11
- package/lib6/parser.js +0 -106
- package/lib6/parser.js.map +0 -1
- package/lib6/promisify.d.ts +0 -3
- package/lib6/promisify.js +0 -34
- package/lib6/promisify.js.map +0 -1
- package/lib6/router/metadata.d.ts +0 -12
- package/lib6/router/metadata.js +0 -30
- package/lib6/router/metadata.js.map +0 -1
- package/lib6/router/routing-table.d.ts +0 -16
- package/lib6/router/routing-table.js +0 -97
- package/lib6/router/routing-table.js.map +0 -1
- package/lib6/sequence.d.ts +0 -55
- package/lib6/sequence.js +0 -109
- package/lib6/sequence.js.map +0 -1
- package/lib6/server.d.ts +0 -23
- package/lib6/server.js +0 -74
- package/lib6/server.js.map +0 -1
- package/lib6/writer.d.ts +0 -11
- package/lib6/writer.js +0 -34
- package/lib6/writer.js.map +0 -1
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/core
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Binding,
|
|
8
|
+
Context,
|
|
9
|
+
ContextView,
|
|
10
|
+
inject,
|
|
11
|
+
invokeMethod,
|
|
12
|
+
sortBindingsByPhase,
|
|
13
|
+
} from '@loopback/context';
|
|
14
|
+
import debugFactory from 'debug';
|
|
15
|
+
import {CoreBindings, CoreTags} from './keys';
|
|
16
|
+
import {LifeCycleObserver, lifeCycleObserverFilter} from './lifecycle';
|
|
17
|
+
const debug = debugFactory('loopback:core:lifecycle');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A group of life cycle observers
|
|
21
|
+
*/
|
|
22
|
+
export type LifeCycleObserverGroup = {
|
|
23
|
+
/**
|
|
24
|
+
* Observer group name
|
|
25
|
+
*/
|
|
26
|
+
group: string;
|
|
27
|
+
/**
|
|
28
|
+
* Bindings for observers within the group
|
|
29
|
+
*/
|
|
30
|
+
bindings: Readonly<Binding<LifeCycleObserver>>[];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type LifeCycleObserverOptions = {
|
|
34
|
+
/**
|
|
35
|
+
* Control the order of observer groups for notifications. For example,
|
|
36
|
+
* with `['datasource', 'server']`, the observers in `datasource` group are
|
|
37
|
+
* notified before those in `server` group during `start`. Please note that
|
|
38
|
+
* observers are notified in the reverse order during `stop`.
|
|
39
|
+
*/
|
|
40
|
+
orderedGroups: string[];
|
|
41
|
+
/**
|
|
42
|
+
* Override and disable lifecycle observer groups. This setting applies to
|
|
43
|
+
* both ordered groups (i.e. those defined in `orderedGroups`) and unordered
|
|
44
|
+
* groups.
|
|
45
|
+
*/
|
|
46
|
+
disabledGroups?: string[];
|
|
47
|
+
/**
|
|
48
|
+
* Notify observers of the same group in parallel, default to `true`
|
|
49
|
+
*/
|
|
50
|
+
parallel?: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const DEFAULT_ORDERED_GROUPS = ['server'];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A context-based registry for life cycle observers
|
|
57
|
+
*/
|
|
58
|
+
export class LifeCycleObserverRegistry implements LifeCycleObserver {
|
|
59
|
+
constructor(
|
|
60
|
+
@inject.context()
|
|
61
|
+
protected readonly context: Context,
|
|
62
|
+
@inject.view(lifeCycleObserverFilter)
|
|
63
|
+
protected readonly observersView: ContextView<LifeCycleObserver>,
|
|
64
|
+
@inject(CoreBindings.LIFE_CYCLE_OBSERVER_OPTIONS, {optional: true})
|
|
65
|
+
protected readonly options: LifeCycleObserverOptions = {
|
|
66
|
+
parallel: true,
|
|
67
|
+
orderedGroups: DEFAULT_ORDERED_GROUPS,
|
|
68
|
+
},
|
|
69
|
+
) {}
|
|
70
|
+
|
|
71
|
+
setOrderedGroups(groups: string[]) {
|
|
72
|
+
this.options.orderedGroups = groups;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get observer groups ordered by the group
|
|
77
|
+
*/
|
|
78
|
+
public getObserverGroupsByOrder(): LifeCycleObserverGroup[] {
|
|
79
|
+
const bindings = this.observersView.bindings;
|
|
80
|
+
const groups = this.sortObserverBindingsByGroup(bindings);
|
|
81
|
+
if (debug.enabled) {
|
|
82
|
+
debug(
|
|
83
|
+
'Observer groups: %j',
|
|
84
|
+
groups.map(g => ({
|
|
85
|
+
group: g.group,
|
|
86
|
+
bindings: g.bindings.map(b => b.key),
|
|
87
|
+
})),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return groups;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get the group for a given life cycle observer binding
|
|
95
|
+
* @param binding - Life cycle observer binding
|
|
96
|
+
*/
|
|
97
|
+
protected getObserverGroup(
|
|
98
|
+
binding: Readonly<Binding<LifeCycleObserver>>,
|
|
99
|
+
): string {
|
|
100
|
+
// First check if there is an explicit group name in the tag
|
|
101
|
+
let group = binding.tagMap[CoreTags.LIFE_CYCLE_OBSERVER_GROUP];
|
|
102
|
+
if (!group) {
|
|
103
|
+
// Fall back to a tag that matches one of the groups
|
|
104
|
+
group = this.options.orderedGroups.find(g => binding.tagMap[g] === g);
|
|
105
|
+
}
|
|
106
|
+
group = group || '';
|
|
107
|
+
debug(
|
|
108
|
+
'Binding %s is configured with observer group %s',
|
|
109
|
+
binding.key,
|
|
110
|
+
group,
|
|
111
|
+
);
|
|
112
|
+
return group;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Sort the life cycle observer bindings so that we can start/stop them
|
|
117
|
+
* in the right order. By default, we can start other observers before servers
|
|
118
|
+
* and stop them in the reverse order
|
|
119
|
+
* @param bindings - Life cycle observer bindings
|
|
120
|
+
*/
|
|
121
|
+
protected sortObserverBindingsByGroup(
|
|
122
|
+
bindings: Readonly<Binding<LifeCycleObserver>>[],
|
|
123
|
+
) {
|
|
124
|
+
// Group bindings in a map
|
|
125
|
+
const groupMap: Map<string, Readonly<Binding<LifeCycleObserver>>[]> =
|
|
126
|
+
new Map();
|
|
127
|
+
sortBindingsByPhase(
|
|
128
|
+
bindings,
|
|
129
|
+
CoreTags.LIFE_CYCLE_OBSERVER_GROUP,
|
|
130
|
+
this.options.orderedGroups,
|
|
131
|
+
);
|
|
132
|
+
for (const binding of bindings) {
|
|
133
|
+
const group = this.getObserverGroup(binding);
|
|
134
|
+
let bindingsInGroup = groupMap.get(group);
|
|
135
|
+
if (bindingsInGroup == null) {
|
|
136
|
+
bindingsInGroup = [];
|
|
137
|
+
groupMap.set(group, bindingsInGroup);
|
|
138
|
+
}
|
|
139
|
+
bindingsInGroup.push(binding);
|
|
140
|
+
}
|
|
141
|
+
// Create an array for group entries
|
|
142
|
+
const groups: LifeCycleObserverGroup[] = [];
|
|
143
|
+
for (const [group, bindingsInGroup] of groupMap) {
|
|
144
|
+
groups.push({group, bindings: bindingsInGroup});
|
|
145
|
+
}
|
|
146
|
+
return groups;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Notify an observer group of the given event
|
|
151
|
+
* @param group - A group of bindings for life cycle observers
|
|
152
|
+
* @param event - Event name
|
|
153
|
+
*/
|
|
154
|
+
protected async notifyObservers(
|
|
155
|
+
observers: LifeCycleObserver[],
|
|
156
|
+
bindings: Readonly<Binding<LifeCycleObserver>>[],
|
|
157
|
+
event: keyof LifeCycleObserver,
|
|
158
|
+
) {
|
|
159
|
+
if (!this.options.parallel) {
|
|
160
|
+
let index = 0;
|
|
161
|
+
for (const observer of observers) {
|
|
162
|
+
debug(
|
|
163
|
+
'Invoking %s observer for binding %s',
|
|
164
|
+
event,
|
|
165
|
+
bindings[index].key,
|
|
166
|
+
);
|
|
167
|
+
index++;
|
|
168
|
+
await this.invokeObserver(observer, event);
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Parallel invocation
|
|
174
|
+
const notifiers = observers.map((observer, index) => {
|
|
175
|
+
debug('Invoking %s observer for binding %s', event, bindings[index].key);
|
|
176
|
+
return this.invokeObserver(observer, event);
|
|
177
|
+
});
|
|
178
|
+
await Promise.all(notifiers);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Invoke an observer for the given event
|
|
183
|
+
* @param observer - A life cycle observer
|
|
184
|
+
* @param event - Event name
|
|
185
|
+
*/
|
|
186
|
+
protected async invokeObserver(
|
|
187
|
+
observer: LifeCycleObserver,
|
|
188
|
+
event: keyof LifeCycleObserver,
|
|
189
|
+
) {
|
|
190
|
+
if (typeof observer[event] === 'function') {
|
|
191
|
+
// Supply `undefined` for legacy callback function expected by
|
|
192
|
+
// DataSource.stop()
|
|
193
|
+
await invokeMethod(observer, event, this.context, [undefined], {
|
|
194
|
+
skipInterceptors: true,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Emit events to the observer groups
|
|
201
|
+
* @param events - Event names
|
|
202
|
+
* @param groups - Observer groups
|
|
203
|
+
*/
|
|
204
|
+
protected async notifyGroups(
|
|
205
|
+
events: (keyof LifeCycleObserver)[],
|
|
206
|
+
groups: LifeCycleObserverGroup[],
|
|
207
|
+
reverse = false,
|
|
208
|
+
) {
|
|
209
|
+
const observers = await this.observersView.values();
|
|
210
|
+
const bindings = this.observersView.bindings;
|
|
211
|
+
const found = observers.some(observer =>
|
|
212
|
+
events.some(e => typeof observer[e] === 'function'),
|
|
213
|
+
);
|
|
214
|
+
if (!found) return;
|
|
215
|
+
if (reverse) {
|
|
216
|
+
// Do not reverse the original `groups` in place
|
|
217
|
+
groups = [...groups].reverse();
|
|
218
|
+
}
|
|
219
|
+
for (const group of groups) {
|
|
220
|
+
if (this.options.disabledGroups?.includes(group.group)) {
|
|
221
|
+
debug('Notification skipped (Group is disabled): %s', group.group);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const observersForGroup: LifeCycleObserver[] = [];
|
|
225
|
+
const bindingsInGroup = reverse
|
|
226
|
+
? group.bindings.reverse()
|
|
227
|
+
: group.bindings;
|
|
228
|
+
for (const binding of bindingsInGroup) {
|
|
229
|
+
const index = bindings.indexOf(binding);
|
|
230
|
+
observersForGroup.push(observers[index]);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const event of events) {
|
|
234
|
+
debug('Beginning notification %s of %s...', event);
|
|
235
|
+
await this.notifyObservers(observersForGroup, group.bindings, event);
|
|
236
|
+
debug('Finished notification %s of %s', event);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Notify all life cycle observers by group of `init`
|
|
243
|
+
*/
|
|
244
|
+
public async init(): Promise<void> {
|
|
245
|
+
debug('Initializing the %s...');
|
|
246
|
+
const groups = this.getObserverGroupsByOrder();
|
|
247
|
+
await this.notifyGroups(['init'], groups);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Notify all life cycle observers by group of `start`
|
|
252
|
+
*/
|
|
253
|
+
public async start(): Promise<void> {
|
|
254
|
+
debug('Starting the %s...');
|
|
255
|
+
const groups = this.getObserverGroupsByOrder();
|
|
256
|
+
await this.notifyGroups(['start'], groups);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Notify all life cycle observers by group of `stop`
|
|
261
|
+
*/
|
|
262
|
+
public async stop(): Promise<void> {
|
|
263
|
+
debug('Stopping the %s...');
|
|
264
|
+
const groups = this.getObserverGroupsByOrder();
|
|
265
|
+
// Stop in the reverse order
|
|
266
|
+
await this.notifyGroups(['stop'], groups, true);
|
|
267
|
+
}
|
|
268
|
+
}
|
package/src/lifecycle.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/core
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Binding,
|
|
8
|
+
BindingSpec,
|
|
9
|
+
BindingTagFilter,
|
|
10
|
+
Constructor,
|
|
11
|
+
filterByTag,
|
|
12
|
+
injectable,
|
|
13
|
+
ValueOrPromise,
|
|
14
|
+
} from '@loopback/context';
|
|
15
|
+
import {CoreTags} from './keys';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Observers to handle life cycle init/start/stop events
|
|
19
|
+
*/
|
|
20
|
+
export interface LifeCycleObserver {
|
|
21
|
+
/**
|
|
22
|
+
* The method to be invoked during `init`. It will only be called at most once
|
|
23
|
+
* for a given application instance.
|
|
24
|
+
*/
|
|
25
|
+
init?(...injectedArgs: unknown[]): ValueOrPromise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* The method to be invoked during `start`
|
|
28
|
+
*/
|
|
29
|
+
start?(...injectedArgs: unknown[]): ValueOrPromise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* The method to be invoked during `stop`
|
|
32
|
+
*/
|
|
33
|
+
stop?(...injectedArgs: unknown[]): ValueOrPromise<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const lifeCycleMethods: (keyof LifeCycleObserver)[] = ['init', 'start', 'stop'];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Test if an object implements LifeCycleObserver
|
|
40
|
+
* @param obj - An object
|
|
41
|
+
*/
|
|
42
|
+
export function isLifeCycleObserver(obj: object): obj is LifeCycleObserver {
|
|
43
|
+
const candidate = obj as Partial<LifeCycleObserver>;
|
|
44
|
+
return lifeCycleMethods.some(m => typeof candidate[m] === 'function');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Test if a class implements LifeCycleObserver
|
|
49
|
+
* @param ctor - A class
|
|
50
|
+
*/
|
|
51
|
+
export function isLifeCycleObserverClass(
|
|
52
|
+
ctor: Constructor<unknown>,
|
|
53
|
+
): ctor is Constructor<LifeCycleObserver> {
|
|
54
|
+
return ctor.prototype && isLifeCycleObserver(ctor.prototype);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A `BindingTemplate` function to configure the binding as life cycle observer
|
|
59
|
+
* by tagging it with `CoreTags.LIFE_CYCLE_OBSERVER`.
|
|
60
|
+
*
|
|
61
|
+
* @param binding - Binding object
|
|
62
|
+
*/
|
|
63
|
+
export function asLifeCycleObserver<T = unknown>(binding: Binding<T>) {
|
|
64
|
+
return binding.tag(CoreTags.LIFE_CYCLE_OBSERVER);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Find all life cycle observer bindings. By default, a binding tagged with
|
|
69
|
+
* `CoreTags.LIFE_CYCLE_OBSERVER`. It's used as `BindingFilter`.
|
|
70
|
+
*/
|
|
71
|
+
export const lifeCycleObserverFilter: BindingTagFilter = filterByTag(
|
|
72
|
+
CoreTags.LIFE_CYCLE_OBSERVER,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Sugar decorator to mark a class as life cycle observer
|
|
77
|
+
* @param group - Optional observer group name
|
|
78
|
+
* @param specs - Optional bindings specs
|
|
79
|
+
*/
|
|
80
|
+
export function lifeCycleObserver(group = '', ...specs: BindingSpec[]) {
|
|
81
|
+
return injectable(
|
|
82
|
+
asLifeCycleObserver,
|
|
83
|
+
{
|
|
84
|
+
tags: {
|
|
85
|
+
[CoreTags.LIFE_CYCLE_OBSERVER_GROUP]: group,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
...specs,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Copyright IBM Corp. and LoopBack contributors 2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/core
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {Constructor} from '@loopback/context';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A replacement for `typeof Target` to be used in mixin class definitions.
|
|
10
|
+
* This is a workaround for TypeScript limitation described in
|
|
11
|
+
* - https://github.com/microsoft/TypeScript/issues/17293
|
|
12
|
+
* - https://github.com/microsoft/TypeScript/issues/17744
|
|
13
|
+
* - https://github.com/microsoft/TypeScript/issues/36060
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* export function MyMixin<T extends MixinTarget<Application>>(superClass: T) {
|
|
19
|
+
* return class extends superClass {
|
|
20
|
+
* // contribute new class members
|
|
21
|
+
* }
|
|
22
|
+
* };
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* TypeScript does not allow class mixins to access protected members from
|
|
26
|
+
* the base class. You can use the following approach as a workaround:
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
30
|
+
* // @ts-ignore
|
|
31
|
+
* (this as unknown as {YourBaseClass}).protectedMember
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* The directive `@ts-ignore` suppresses compiler error about accessing
|
|
35
|
+
* a protected member from outside. Unfortunately, it also disables other
|
|
36
|
+
* compile-time checks (e.g. to verify that a protected method was invoked
|
|
37
|
+
* with correct arguments, and so on). This is the same behavior you
|
|
38
|
+
* would get by using `Constructor<any>` instead of `MixinTarget<Application>`.
|
|
39
|
+
* The major improvement is that TypeScript can still infer the return
|
|
40
|
+
* type of the protected member, therefore `any` is NOT introduced to subsequent
|
|
41
|
+
* code.
|
|
42
|
+
*
|
|
43
|
+
* TypeScript also does not allow mixin class to overwrite a method inherited
|
|
44
|
+
* from a mapped type, see https://github.com/microsoft/TypeScript/issues/38496
|
|
45
|
+
* As a workaround, use `@ts-ignore` to disable the error.
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* export function RepositoryMixin<T extends MixinTarget<Application>>(
|
|
49
|
+
* superClass: T,
|
|
50
|
+
* ) {
|
|
51
|
+
* return class extends superClass {
|
|
52
|
+
* // @ts-ignore
|
|
53
|
+
* public component<C extends Component = Component>(
|
|
54
|
+
* componentCtor: Constructor<C>,
|
|
55
|
+
* nameOrOptions?: string | BindingFromClassOptions,
|
|
56
|
+
* ) {
|
|
57
|
+
* const binding = super.component(componentCtor, nameOrOptions);
|
|
58
|
+
* // ...
|
|
59
|
+
* return binding;
|
|
60
|
+
* }
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export type MixinTarget<T extends object> = Constructor<{
|
|
65
|
+
// Enumerate only public members to avoid the following compiler error:
|
|
66
|
+
// Property '(name)' of exported class expression
|
|
67
|
+
// may not be private or protected.ts(4094)
|
|
68
|
+
[P in keyof T]: T[P];
|
|
69
|
+
}>;
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Copyright IBM Corp. and LoopBack contributors 2017,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/core
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {LifeCycleObserver} from './lifecycle';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Defines the requirements to implement a Server for LoopBack applications:
|
|
10
|
+
* start() : Promise<void>
|
|
11
|
+
* stop() : Promise<void>
|
|
12
|
+
* It is recommended that each Server implementation creates its own child
|
|
13
|
+
* Context, which inherits from the parent Application context. This way,
|
|
14
|
+
* any Server-specific bindings will remain local to the Server instance,
|
|
15
|
+
* and will avoid polluting its parent module scope.
|
|
16
|
+
*/
|
|
17
|
+
export interface Server extends LifeCycleObserver {
|
|
18
|
+
/**
|
|
19
|
+
* Tells whether the server is listening for connections or not
|
|
20
|
+
*/
|
|
21
|
+
readonly listening: boolean;
|
|
22
|
+
}
|
package/src/service.ts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// Copyright IBM Corp. and LoopBack contributors 2019,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/core
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Binding,
|
|
8
|
+
BindingFilter,
|
|
9
|
+
BindingFromClassOptions,
|
|
10
|
+
BindingTemplate,
|
|
11
|
+
bindingTemplateFor,
|
|
12
|
+
ContextTags,
|
|
13
|
+
ContextView,
|
|
14
|
+
createBindingFromClass,
|
|
15
|
+
DecoratorFactory,
|
|
16
|
+
inject,
|
|
17
|
+
InjectionMetadata,
|
|
18
|
+
isDynamicValueProviderClass,
|
|
19
|
+
isProviderClass,
|
|
20
|
+
MetadataInspector,
|
|
21
|
+
transformValueOrPromise,
|
|
22
|
+
} from '@loopback/context';
|
|
23
|
+
import {ServiceOrProviderClass} from './application';
|
|
24
|
+
import {CoreTags} from './keys';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Representing an interface for services. In TypeScript, the `interface` does
|
|
28
|
+
* not have reflections at runtime. We use a string, a symbol or a Function as
|
|
29
|
+
* the type for the service interface.
|
|
30
|
+
*/
|
|
31
|
+
export type ServiceInterface = string | symbol | Function;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Options to register a service binding
|
|
35
|
+
*/
|
|
36
|
+
export interface ServiceOptions extends BindingFromClassOptions {
|
|
37
|
+
interface?: ServiceInterface;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `@service` injects a service instance that matches the class or interface.
|
|
42
|
+
*
|
|
43
|
+
* @param serviceInterface - Interface for the service. It can be in one of the
|
|
44
|
+
* following forms:
|
|
45
|
+
*
|
|
46
|
+
* - A class, such as MyService
|
|
47
|
+
* - A string that identifies the interface, such as `'MyService'`
|
|
48
|
+
* - A symbol that identifies the interface, such as `Symbol('MyService')`
|
|
49
|
+
*
|
|
50
|
+
* If not provided, the value is inferred from the design:type of the parameter
|
|
51
|
+
* or property
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
*
|
|
56
|
+
* const ctx = new Context();
|
|
57
|
+
* ctx.bind('my-service').toClass(MyService);
|
|
58
|
+
* ctx.bind('logger').toClass(Logger);
|
|
59
|
+
*
|
|
60
|
+
* export class MyController {
|
|
61
|
+
* constructor(@service(MyService) private myService: MyService) {}
|
|
62
|
+
*
|
|
63
|
+
* @service()
|
|
64
|
+
* private logger: Logger;
|
|
65
|
+
* }
|
|
66
|
+
*
|
|
67
|
+
* ctx.bind('my-controller').toClass(MyController);
|
|
68
|
+
* await myController = ctx.get<MyController>('my-controller');
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function service(
|
|
72
|
+
serviceInterface?: ServiceInterface,
|
|
73
|
+
metadata?: InjectionMetadata,
|
|
74
|
+
) {
|
|
75
|
+
return inject(
|
|
76
|
+
'',
|
|
77
|
+
{decorator: '@service', ...metadata},
|
|
78
|
+
(ctx, injection, session) => {
|
|
79
|
+
let serviceType = serviceInterface;
|
|
80
|
+
if (!serviceType) {
|
|
81
|
+
if (typeof injection.methodDescriptorOrParameterIndex === 'number') {
|
|
82
|
+
serviceType = MetadataInspector.getDesignTypeForMethod(
|
|
83
|
+
injection.target,
|
|
84
|
+
injection.member!,
|
|
85
|
+
)?.parameterTypes[injection.methodDescriptorOrParameterIndex];
|
|
86
|
+
} else {
|
|
87
|
+
serviceType = MetadataInspector.getDesignTypeForProperty(
|
|
88
|
+
injection.target,
|
|
89
|
+
injection.member!,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (serviceType === undefined) {
|
|
94
|
+
const targetName = DecoratorFactory.getTargetName(
|
|
95
|
+
injection.target,
|
|
96
|
+
injection.member,
|
|
97
|
+
injection.methodDescriptorOrParameterIndex,
|
|
98
|
+
);
|
|
99
|
+
const msg =
|
|
100
|
+
`No design-time type metadata found while inspecting ${targetName}. ` +
|
|
101
|
+
'You can either use `@service(ServiceClass)` or ensure `emitDecoratorMetadata` is enabled in your TypeScript configuration. ' +
|
|
102
|
+
'Run `tsc --showConfig` to print the final TypeScript configuration of your project.';
|
|
103
|
+
throw new Error(msg);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (serviceType === Object || serviceType === Array) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
'Service class cannot be inferred from design type. Use @service(ServiceClass).',
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
const view = new ContextView(ctx, filterByServiceInterface(serviceType));
|
|
112
|
+
const result = view.resolve({
|
|
113
|
+
optional: metadata?.optional,
|
|
114
|
+
asProxyWithInterceptors: metadata?.asProxyWithInterceptors,
|
|
115
|
+
session,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const serviceTypeName =
|
|
119
|
+
typeof serviceType === 'string'
|
|
120
|
+
? serviceType
|
|
121
|
+
: typeof serviceType === 'symbol'
|
|
122
|
+
? serviceType.toString()
|
|
123
|
+
: serviceType.name;
|
|
124
|
+
return transformValueOrPromise(result, values => {
|
|
125
|
+
if (values.length === 1) return values[0];
|
|
126
|
+
if (values.length >= 1) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`More than one bindings found for ${serviceTypeName}`,
|
|
129
|
+
);
|
|
130
|
+
} else {
|
|
131
|
+
if (metadata?.optional) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
throw new Error(
|
|
135
|
+
`No binding found for ${serviceTypeName}. Make sure a service ` +
|
|
136
|
+
`binding is created in context ${ctx.name} with serviceInterface (${serviceTypeName}).`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Create a binding filter by service class
|
|
146
|
+
* @param serviceInterface - Service class matching the one used by `binding.toClass()`
|
|
147
|
+
* @param options - Options to control if subclasses should be skipped for matching
|
|
148
|
+
*/
|
|
149
|
+
export function filterByServiceInterface(
|
|
150
|
+
serviceInterface: ServiceInterface,
|
|
151
|
+
): BindingFilter {
|
|
152
|
+
return binding =>
|
|
153
|
+
binding.valueConstructor === serviceInterface ||
|
|
154
|
+
binding.tagMap[CoreTags.SERVICE_INTERFACE] === serviceInterface;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Create a service binding from a class or provider
|
|
159
|
+
* @param cls - Service class or provider
|
|
160
|
+
* @param options - Service options
|
|
161
|
+
*/
|
|
162
|
+
export function createServiceBinding<S>(
|
|
163
|
+
cls: ServiceOrProviderClass<S>,
|
|
164
|
+
options: ServiceOptions = {},
|
|
165
|
+
): Binding<S> {
|
|
166
|
+
let name = options.name;
|
|
167
|
+
if (!name && isProviderClass(cls)) {
|
|
168
|
+
// Trim `Provider` from the default service name
|
|
169
|
+
// This is needed to keep backward compatibility
|
|
170
|
+
const templateFn = bindingTemplateFor(cls);
|
|
171
|
+
const template = Binding.bind<S>('template').apply(templateFn);
|
|
172
|
+
if (
|
|
173
|
+
template.tagMap[ContextTags.PROVIDER] &&
|
|
174
|
+
!template.tagMap[ContextTags.NAME]
|
|
175
|
+
) {
|
|
176
|
+
// The class is a provider and no `name` tag is found
|
|
177
|
+
name = cls.name.replace(/Provider$/, '');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (!name && isDynamicValueProviderClass(cls)) {
|
|
181
|
+
// Trim `Provider` from the default service name
|
|
182
|
+
const templateFn = bindingTemplateFor(cls);
|
|
183
|
+
const template = Binding.bind<S>('template').apply(templateFn);
|
|
184
|
+
if (
|
|
185
|
+
template.tagMap[ContextTags.DYNAMIC_VALUE_PROVIDER] &&
|
|
186
|
+
!template.tagMap[ContextTags.NAME]
|
|
187
|
+
) {
|
|
188
|
+
// The class is a provider and no `name` tag is found
|
|
189
|
+
name = cls.name.replace(/Provider$/, '');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const binding = createBindingFromClass(cls, {
|
|
193
|
+
name,
|
|
194
|
+
type: CoreTags.SERVICE,
|
|
195
|
+
...options,
|
|
196
|
+
}).apply(asService(options.interface ?? cls));
|
|
197
|
+
return binding;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Create a binding template for a service interface
|
|
202
|
+
* @param serviceInterface - Service interface
|
|
203
|
+
*/
|
|
204
|
+
export function asService(serviceInterface: ServiceInterface): BindingTemplate {
|
|
205
|
+
return function serviceTemplate(binding: Binding) {
|
|
206
|
+
binding.tag({
|
|
207
|
+
[ContextTags.TYPE]: CoreTags.SERVICE,
|
|
208
|
+
[CoreTags.SERVICE_INTERFACE]: serviceInterface,
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
}
|
package/index.d.ts
DELETED
package/index.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Copyright IBM Corp. 2017. All Rights Reserved.
|
|
2
|
-
// Node module: @loopback/core
|
|
3
|
-
// This file is licensed under the MIT License.
|
|
4
|
-
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
-
|
|
6
|
-
const nodeMajorVersion = +process.versions.node.split('.')[0];
|
|
7
|
-
module.exports = nodeMajorVersion >= 7 ?
|
|
8
|
-
require('./lib') :
|
|
9
|
-
require('./lib6');
|