1 |
|
2 |
|
3 |
|
4 |
|
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 |
|
21 |
|
22 | export type LifeCycleObserverGroup = {
|
23 | |
24 |
|
25 |
|
26 | group: string;
|
27 | |
28 |
|
29 |
|
30 | bindings: Readonly<Binding<LifeCycleObserver>>[];
|
31 | };
|
32 |
|
33 | export type LifeCycleObserverOptions = {
|
34 | |
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | orderedGroups: string[];
|
41 | |
42 |
|
43 |
|
44 |
|
45 |
|
46 | disabledGroups?: string[];
|
47 | |
48 |
|
49 |
|
50 | parallel?: boolean;
|
51 | };
|
52 |
|
53 | export const DEFAULT_ORDERED_GROUPS = ['server'];
|
54 |
|
55 |
|
56 |
|
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 |
|
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 |
|
95 |
|
96 |
|
97 | protected getObserverGroup(
|
98 | binding: Readonly<Binding<LifeCycleObserver>>,
|
99 | ): string {
|
100 |
|
101 | let group = binding.tagMap[CoreTags.LIFE_CYCLE_OBSERVER_GROUP];
|
102 | if (!group) {
|
103 |
|
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 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 | protected sortObserverBindingsByGroup(
|
122 | bindings: Readonly<Binding<LifeCycleObserver>>[],
|
123 | ) {
|
124 |
|
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 |
|
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 |
|
151 |
|
152 |
|
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 |
|
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 |
|
183 |
|
184 |
|
185 |
|
186 | protected async invokeObserver(
|
187 | observer: LifeCycleObserver,
|
188 | event: keyof LifeCycleObserver,
|
189 | ) {
|
190 | if (typeof observer[event] === 'function') {
|
191 |
|
192 |
|
193 | await invokeMethod(observer, event, this.context, [undefined], {
|
194 | skipInterceptors: true,
|
195 | });
|
196 | }
|
197 | }
|
198 |
|
199 | |
200 |
|
201 |
|
202 |
|
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 |
|
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 |
|
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 |
|
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 |
|
261 |
|
262 | public async stop(): Promise<void> {
|
263 | debug('Stopping the %s...');
|
264 | const groups = this.getObserverGroupsByOrder();
|
265 |
|
266 | await this.notifyGroups(['stop'], groups, true);
|
267 | }
|
268 | }
|