@eggjs/core 6.0.0-beta.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/LICENSE +21 -0
- package/README.md +296 -0
- package/dist/commonjs/base_context_class.d.ts +16 -0
- package/dist/commonjs/base_context_class.js +41 -0
- package/dist/commonjs/egg.d.ts +204 -0
- package/dist/commonjs/egg.js +346 -0
- package/dist/commonjs/index.d.ts +5 -0
- package/dist/commonjs/index.js +26 -0
- package/dist/commonjs/lifecycle.d.ts +75 -0
- package/dist/commonjs/lifecycle.js +306 -0
- package/dist/commonjs/loader/context_loader.d.ts +24 -0
- package/dist/commonjs/loader/context_loader.js +109 -0
- package/dist/commonjs/loader/egg_loader.d.ts +405 -0
- package/dist/commonjs/loader/egg_loader.js +1497 -0
- package/dist/commonjs/loader/file_loader.d.ts +96 -0
- package/dist/commonjs/loader/file_loader.js +248 -0
- package/dist/commonjs/package.json +3 -0
- package/dist/commonjs/types.d.ts +1 -0
- package/dist/commonjs/types.js +403 -0
- package/dist/commonjs/utils/index.d.ts +14 -0
- package/dist/commonjs/utils/index.js +146 -0
- package/dist/commonjs/utils/sequencify.d.ts +13 -0
- package/dist/commonjs/utils/sequencify.js +59 -0
- package/dist/commonjs/utils/timing.d.ts +22 -0
- package/dist/commonjs/utils/timing.js +100 -0
- package/dist/esm/base_context_class.d.ts +16 -0
- package/dist/esm/base_context_class.js +37 -0
- package/dist/esm/egg.d.ts +204 -0
- package/dist/esm/egg.js +339 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/lifecycle.d.ts +75 -0
- package/dist/esm/lifecycle.js +276 -0
- package/dist/esm/loader/context_loader.d.ts +24 -0
- package/dist/esm/loader/context_loader.js +102 -0
- package/dist/esm/loader/egg_loader.d.ts +405 -0
- package/dist/esm/loader/egg_loader.js +1490 -0
- package/dist/esm/loader/file_loader.d.ts +96 -0
- package/dist/esm/loader/file_loader.js +241 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/types.d.ts +1 -0
- package/dist/esm/types.js +402 -0
- package/dist/esm/utils/index.d.ts +14 -0
- package/dist/esm/utils/index.js +141 -0
- package/dist/esm/utils/sequencify.d.ts +13 -0
- package/dist/esm/utils/sequencify.js +56 -0
- package/dist/esm/utils/timing.d.ts +22 -0
- package/dist/esm/utils/timing.js +93 -0
- package/package.json +103 -0
- package/src/base_context_class.ts +39 -0
- package/src/egg.ts +430 -0
- package/src/index.ts +6 -0
- package/src/lifecycle.ts +363 -0
- package/src/loader/context_loader.ts +121 -0
- package/src/loader/egg_loader.ts +1703 -0
- package/src/loader/file_loader.ts +295 -0
- package/src/types.ts +447 -0
- package/src/utils/index.ts +154 -0
- package/src/utils/sequencify.ts +70 -0
- package/src/utils/timing.ts +114 -0
package/src/lifecycle.ts
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { debuglog } from 'node:util';
|
|
4
|
+
import is, { isClass } from 'is-type-of';
|
|
5
|
+
import ReadyObject from 'get-ready';
|
|
6
|
+
import type { ReadyFunctionArg } from 'get-ready';
|
|
7
|
+
import { Ready } from 'ready-callback';
|
|
8
|
+
import { EggConsoleLogger } from 'egg-logger';
|
|
9
|
+
import utils from './utils/index.js';
|
|
10
|
+
import type { Fun } from './utils/index.js';
|
|
11
|
+
import type { EggCore } from './egg.js';
|
|
12
|
+
|
|
13
|
+
const debug = debuglog('@eggjs/core:lifecycle');
|
|
14
|
+
|
|
15
|
+
export interface ILifecycleBoot {
|
|
16
|
+
// loader auto set 'fullPath' property on boot class
|
|
17
|
+
fullPath?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Ready to call configDidLoad,
|
|
20
|
+
* Config, plugin files are referred,
|
|
21
|
+
* this is the last chance to modify the config.
|
|
22
|
+
*/
|
|
23
|
+
configWillLoad?(): void;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Config, plugin files have loaded
|
|
27
|
+
*/
|
|
28
|
+
configDidLoad?(): void;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* All files have loaded, start plugin here
|
|
32
|
+
*/
|
|
33
|
+
didLoad?(): Promise<void>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* All plugins have started, can do some thing before app ready
|
|
37
|
+
*/
|
|
38
|
+
willReady?(): Promise<void>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Worker is ready, can do some things,
|
|
42
|
+
* don't need to block the app boot
|
|
43
|
+
*/
|
|
44
|
+
didReady?(err?: Error): Promise<void>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Server is listening
|
|
48
|
+
*/
|
|
49
|
+
serverDidReady?(): Promise<void>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Do some thing before app close
|
|
53
|
+
*/
|
|
54
|
+
beforeClose?(): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type BootImplClass<T = object> = (new(...args: any[]) => T) & ILifecycleBoot;
|
|
58
|
+
|
|
59
|
+
export interface LifecycleOptions {
|
|
60
|
+
baseDir: string;
|
|
61
|
+
app: EggCore;
|
|
62
|
+
logger: EggConsoleLogger;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class Lifecycle extends EventEmitter {
|
|
66
|
+
#init: boolean;
|
|
67
|
+
#readyObject: ReadyObject;
|
|
68
|
+
#bootHooks: (BootImplClass | ILifecycleBoot)[];
|
|
69
|
+
#boots: ILifecycleBoot[];
|
|
70
|
+
#isClosed: boolean;
|
|
71
|
+
#closeFunctionSet: Set<Fun>;
|
|
72
|
+
loadReady: Ready;
|
|
73
|
+
bootReady: Ready;
|
|
74
|
+
options: LifecycleOptions;
|
|
75
|
+
readyTimeout: number;
|
|
76
|
+
|
|
77
|
+
constructor(options: Partial<LifecycleOptions>) {
|
|
78
|
+
super();
|
|
79
|
+
options.logger = options.logger ?? new EggConsoleLogger();
|
|
80
|
+
this.options = options as LifecycleOptions;
|
|
81
|
+
this.#readyObject = new ReadyObject();
|
|
82
|
+
this.#bootHooks = [];
|
|
83
|
+
this.#boots = [];
|
|
84
|
+
this.#closeFunctionSet = new Set();
|
|
85
|
+
this.#isClosed = false;
|
|
86
|
+
this.#init = false;
|
|
87
|
+
|
|
88
|
+
this.timing.start('Application Start');
|
|
89
|
+
// get app timeout from env or use default timeout 10 second
|
|
90
|
+
const eggReadyTimeoutEnv = parseInt(process.env.EGG_READY_TIMEOUT_ENV || '10000');
|
|
91
|
+
assert(
|
|
92
|
+
Number.isInteger(eggReadyTimeoutEnv),
|
|
93
|
+
`process.env.EGG_READY_TIMEOUT_ENV ${process.env.EGG_READY_TIMEOUT_ENV} should be able to parseInt.`);
|
|
94
|
+
this.readyTimeout = eggReadyTimeoutEnv;
|
|
95
|
+
|
|
96
|
+
this.#initReady();
|
|
97
|
+
this
|
|
98
|
+
.on('ready_stat', data => {
|
|
99
|
+
this.logger.info('[egg:core:ready_stat] end ready task %s, remain %j', data.id, data.remain);
|
|
100
|
+
})
|
|
101
|
+
.on('ready_timeout', id => {
|
|
102
|
+
this.logger.warn('[egg:core:ready_timeout] %s seconds later %s was still unable to finish.', this.readyTimeout / 1000, id);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
this.ready(err => {
|
|
106
|
+
this.triggerDidReady(err);
|
|
107
|
+
debug('app ready');
|
|
108
|
+
this.timing.end('Application Start');
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
ready(arg?: ReadyFunctionArg) {
|
|
113
|
+
return this.#readyObject.ready(arg);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get app() {
|
|
117
|
+
return this.options.app;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
get logger() {
|
|
121
|
+
return this.options.logger;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
get timing() {
|
|
125
|
+
return this.app.timing;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
legacyReadyCallback(name: string, opt?: object) {
|
|
129
|
+
const timingKeyPrefix = 'readyCallback';
|
|
130
|
+
const timing = this.timing;
|
|
131
|
+
const cb = this.loadReady.readyCallback(name, opt);
|
|
132
|
+
const timingKey = `${timingKeyPrefix} in ` + utils.getResolvedFilename(name, this.app.baseDir);
|
|
133
|
+
this.timing.start(timingKey);
|
|
134
|
+
debug('register legacyReadyCallback');
|
|
135
|
+
return function legacyReadyCallback(...args: any[]) {
|
|
136
|
+
timing.end(timingKey);
|
|
137
|
+
debug('end legacyReadyCallback');
|
|
138
|
+
cb(...args);
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
addBootHook(bootHootOrBootClass: BootImplClass | ILifecycleBoot) {
|
|
143
|
+
assert(this.#init === false, 'do not add hook when lifecycle has been initialized');
|
|
144
|
+
this.#bootHooks.push(bootHootOrBootClass);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
addFunctionAsBootHook<T = EggCore>(hook: (app: T) => void) {
|
|
148
|
+
assert(this.#init === false, 'do not add hook when lifecycle has been initialized');
|
|
149
|
+
// app.js is exported as a function
|
|
150
|
+
// call this function in configDidLoad
|
|
151
|
+
this.#bootHooks.push(class Boot implements ILifecycleBoot {
|
|
152
|
+
app: T;
|
|
153
|
+
constructor(app: T) {
|
|
154
|
+
this.app = app;
|
|
155
|
+
}
|
|
156
|
+
configDidLoad() {
|
|
157
|
+
hook(this.app);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* init boots and trigger config did config
|
|
164
|
+
*/
|
|
165
|
+
init() {
|
|
166
|
+
assert(this.#init === false, 'lifecycle have been init');
|
|
167
|
+
this.#init = true;
|
|
168
|
+
this.#boots = this.#bootHooks.map(BootHootOrBootClass => {
|
|
169
|
+
if (isClass(BootHootOrBootClass)) {
|
|
170
|
+
return new BootHootOrBootClass(this.app);
|
|
171
|
+
}
|
|
172
|
+
return BootHootOrBootClass;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
registerBeforeStart(scope: Fun, name: string) {
|
|
177
|
+
debug('add registerBeforeStart, name: %o', name);
|
|
178
|
+
this.#registerReadyCallback({
|
|
179
|
+
scope,
|
|
180
|
+
ready: this.loadReady,
|
|
181
|
+
timingKeyPrefix: 'Before Start',
|
|
182
|
+
scopeFullName: name,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
registerBeforeClose(fn: Fun) {
|
|
187
|
+
assert(is.function(fn), 'argument should be function');
|
|
188
|
+
assert(this.#isClosed === false, 'app has been closed');
|
|
189
|
+
this.#closeFunctionSet.add(fn);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async close() {
|
|
193
|
+
// close in reverse order: first created, last closed
|
|
194
|
+
const closeFns = Array.from(this.#closeFunctionSet);
|
|
195
|
+
for (const fn of closeFns.reverse()) {
|
|
196
|
+
await utils.callFn(fn);
|
|
197
|
+
this.#closeFunctionSet.delete(fn);
|
|
198
|
+
}
|
|
199
|
+
// Be called after other close callbacks
|
|
200
|
+
this.app.emit('close');
|
|
201
|
+
this.removeAllListeners();
|
|
202
|
+
this.app.removeAllListeners();
|
|
203
|
+
this.#isClosed = true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
triggerConfigWillLoad() {
|
|
207
|
+
debug('trigger configWillLoad start');
|
|
208
|
+
for (const boot of this.#boots) {
|
|
209
|
+
if (typeof boot.configWillLoad === 'function') {
|
|
210
|
+
boot.configWillLoad();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
debug('trigger configWillLoad end');
|
|
214
|
+
this.triggerConfigDidLoad();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
triggerConfigDidLoad() {
|
|
218
|
+
debug('trigger configDidLoad start');
|
|
219
|
+
for (const boot of this.#boots) {
|
|
220
|
+
if (typeof boot.configDidLoad === 'function') {
|
|
221
|
+
boot.configDidLoad();
|
|
222
|
+
}
|
|
223
|
+
// function boot hook register after configDidLoad trigger
|
|
224
|
+
if (typeof boot.beforeClose === 'function') {
|
|
225
|
+
const beforeClose = boot.beforeClose.bind(boot);
|
|
226
|
+
this.registerBeforeClose(beforeClose);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
debug('trigger configDidLoad end');
|
|
230
|
+
this.triggerDidLoad();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
triggerDidLoad() {
|
|
234
|
+
debug('trigger didLoad start');
|
|
235
|
+
debug('loadReady start');
|
|
236
|
+
this.loadReady.start();
|
|
237
|
+
for (const boot of this.#boots) {
|
|
238
|
+
if (typeof boot.didLoad === 'function') {
|
|
239
|
+
const didLoad = boot.didLoad.bind(boot);
|
|
240
|
+
this.#registerReadyCallback({
|
|
241
|
+
scope: didLoad,
|
|
242
|
+
ready: this.loadReady,
|
|
243
|
+
timingKeyPrefix: 'Did Load',
|
|
244
|
+
scopeFullName: boot.fullPath + ':didLoad',
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
triggerWillReady() {
|
|
251
|
+
debug('trigger willReady start');
|
|
252
|
+
debug('bootReady start');
|
|
253
|
+
this.bootReady.start();
|
|
254
|
+
for (const boot of this.#boots) {
|
|
255
|
+
if (typeof boot.willReady === 'function') {
|
|
256
|
+
const willReady = boot.willReady.bind(boot);
|
|
257
|
+
this.#registerReadyCallback({
|
|
258
|
+
scope: willReady,
|
|
259
|
+
ready: this.bootReady,
|
|
260
|
+
timingKeyPrefix: 'Will Ready',
|
|
261
|
+
scopeFullName: boot.fullPath + ':willReady',
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
triggerDidReady(err?: Error) {
|
|
268
|
+
debug('trigger didReady start');
|
|
269
|
+
return (async () => {
|
|
270
|
+
for (const boot of this.#boots) {
|
|
271
|
+
if (typeof boot.didReady === 'function') {
|
|
272
|
+
try {
|
|
273
|
+
await boot.didReady(err);
|
|
274
|
+
} catch (e) {
|
|
275
|
+
this.emit('error', e);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
debug('trigger didReady end');
|
|
280
|
+
})();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
triggerServerDidReady() {
|
|
284
|
+
debug('trigger serverDidReady start');
|
|
285
|
+
return (async () => {
|
|
286
|
+
for (const boot of this.#boots) {
|
|
287
|
+
if (typeof boot.serverDidReady !== 'function') {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
await boot.serverDidReady();
|
|
292
|
+
} catch (err) {
|
|
293
|
+
this.emit('error', err);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
debug('trigger serverDidReady end');
|
|
297
|
+
})();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
#initReady() {
|
|
301
|
+
debug('loadReady init');
|
|
302
|
+
this.loadReady = new Ready({ timeout: this.readyTimeout, lazyStart: true });
|
|
303
|
+
this.#delegateReadyEvent(this.loadReady);
|
|
304
|
+
this.loadReady.ready((err?: Error) => {
|
|
305
|
+
debug('loadReady end, err: %o', err);
|
|
306
|
+
debug('trigger didLoad end');
|
|
307
|
+
if (err) {
|
|
308
|
+
this.ready(err);
|
|
309
|
+
} else {
|
|
310
|
+
this.triggerWillReady();
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
debug('bootReady init');
|
|
315
|
+
this.bootReady = new Ready({ timeout: this.readyTimeout, lazyStart: true });
|
|
316
|
+
this.#delegateReadyEvent(this.bootReady);
|
|
317
|
+
this.bootReady.ready((err?: Error) => {
|
|
318
|
+
debug('bootReady end, err: %o', err);
|
|
319
|
+
debug('trigger willReady end');
|
|
320
|
+
this.ready(err || true);
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
#delegateReadyEvent(ready: Ready) {
|
|
325
|
+
ready.once('error', (err?: Error) => ready.ready(err));
|
|
326
|
+
ready.on('ready_timeout', (id: any) => this.emit('ready_timeout', id));
|
|
327
|
+
ready.on('ready_stat', (data: any) => this.emit('ready_stat', data));
|
|
328
|
+
ready.on('error', (err?: Error) => this.emit('error', err));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
#registerReadyCallback(args: {
|
|
332
|
+
scope: Fun;
|
|
333
|
+
ready: Ready;
|
|
334
|
+
timingKeyPrefix: string;
|
|
335
|
+
scopeFullName?: string;
|
|
336
|
+
}) {
|
|
337
|
+
const { scope, ready, timingKeyPrefix, scopeFullName } = args;
|
|
338
|
+
if (typeof scope !== 'function') {
|
|
339
|
+
throw new Error('boot only support function');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// get filename from stack if scopeFullName is undefined
|
|
343
|
+
const name = scopeFullName || utils.getCalleeFromStack(true, 4);
|
|
344
|
+
const timingKey = `${timingKeyPrefix} in ` + utils.getResolvedFilename(name, this.app.baseDir);
|
|
345
|
+
|
|
346
|
+
this.timing.start(timingKey);
|
|
347
|
+
|
|
348
|
+
debug('[registerReadyCallback] start name: %o', name);
|
|
349
|
+
const done = ready.readyCallback(name);
|
|
350
|
+
|
|
351
|
+
// ensure scope executes after load completed
|
|
352
|
+
process.nextTick(() => {
|
|
353
|
+
utils.callFn(scope).then(() => {
|
|
354
|
+
debug('[registerReadyCallback] end name: %o', name);
|
|
355
|
+
done();
|
|
356
|
+
this.timing.end(timingKey);
|
|
357
|
+
}, (err: Error) => {
|
|
358
|
+
done(err);
|
|
359
|
+
this.timing.end(timingKey);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { type ContextDelegation } from '@eggjs/koa';
|
|
3
|
+
import { isClass, isPrimitive } from 'is-type-of';
|
|
4
|
+
import { FileLoader, EXPORTS, type FileLoaderOptions } from './file_loader.js';
|
|
5
|
+
|
|
6
|
+
const CLASS_LOADER = Symbol('classLoader');
|
|
7
|
+
|
|
8
|
+
interface ClassLoaderOptions {
|
|
9
|
+
ctx: ContextDelegation;
|
|
10
|
+
properties: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class ClassLoader {
|
|
14
|
+
readonly _cache = new Map();
|
|
15
|
+
_ctx: ContextDelegation;
|
|
16
|
+
|
|
17
|
+
constructor(options: ClassLoaderOptions) {
|
|
18
|
+
assert(options.ctx, 'options.ctx is required');
|
|
19
|
+
const properties = options.properties;
|
|
20
|
+
this._ctx = options.ctx;
|
|
21
|
+
|
|
22
|
+
for (const property in properties) {
|
|
23
|
+
this.#defineProperty(property, properties[property]);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#defineProperty(property: string, values: any) {
|
|
28
|
+
Object.defineProperty(this, property, {
|
|
29
|
+
get() {
|
|
30
|
+
let instance: any = this._cache.get(property);
|
|
31
|
+
if (!instance) {
|
|
32
|
+
instance = getInstance(values, this._ctx);
|
|
33
|
+
this._cache.set(property, instance);
|
|
34
|
+
}
|
|
35
|
+
return instance;
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ContextLoaderOptions extends Omit<FileLoaderOptions, 'target'> {
|
|
42
|
+
/** required inject */
|
|
43
|
+
inject: Record<string, any>;
|
|
44
|
+
/** property name defined to target */
|
|
45
|
+
property: string;
|
|
46
|
+
/** determine the field name of inject object. */
|
|
47
|
+
fieldClass?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Same as {@link FileLoader}, but it will attach file to `inject[fieldClass]`.
|
|
52
|
+
* The exports will be lazy loaded, such as `ctx.group.repository`.
|
|
53
|
+
* @augments FileLoader
|
|
54
|
+
* @since 1.0.0
|
|
55
|
+
*/
|
|
56
|
+
export class ContextLoader extends FileLoader {
|
|
57
|
+
readonly #inject: Record<string, any>;
|
|
58
|
+
/**
|
|
59
|
+
* @class
|
|
60
|
+
* @param {Object} options - options same as {@link FileLoader}
|
|
61
|
+
* @param {String} options.fieldClass - determine the field name of inject object.
|
|
62
|
+
*/
|
|
63
|
+
constructor(options: ContextLoaderOptions) {
|
|
64
|
+
assert(options.property, 'options.property is required');
|
|
65
|
+
assert(options.inject, 'options.inject is required');
|
|
66
|
+
const target = {};
|
|
67
|
+
if (options.fieldClass) {
|
|
68
|
+
options.inject[options.fieldClass] = target;
|
|
69
|
+
}
|
|
70
|
+
super({
|
|
71
|
+
...options,
|
|
72
|
+
target,
|
|
73
|
+
});
|
|
74
|
+
this.#inject = this.options.inject!;
|
|
75
|
+
|
|
76
|
+
const app = this.#inject;
|
|
77
|
+
const property = options.property;
|
|
78
|
+
// define ctx.service
|
|
79
|
+
Object.defineProperty(app.context, property, {
|
|
80
|
+
get() {
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
82
|
+
const ctx = this;
|
|
83
|
+
// distinguish property cache,
|
|
84
|
+
// cache's lifecycle is the same with this context instance
|
|
85
|
+
// e.x. ctx.service1 and ctx.service2 have different cache
|
|
86
|
+
if (!ctx[CLASS_LOADER]) {
|
|
87
|
+
ctx[CLASS_LOADER] = new Map();
|
|
88
|
+
}
|
|
89
|
+
const classLoader: Map<string, ClassLoader> = ctx[CLASS_LOADER];
|
|
90
|
+
let instance = classLoader.get(property);
|
|
91
|
+
if (!instance) {
|
|
92
|
+
instance = getInstance(target, ctx);
|
|
93
|
+
classLoader.set(property, instance!);
|
|
94
|
+
}
|
|
95
|
+
return instance;
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getInstance(values: any, ctx: ContextDelegation) {
|
|
102
|
+
// it's a directory when it has no exports
|
|
103
|
+
// then use ClassLoader
|
|
104
|
+
const Class = values[EXPORTS] ? values : null;
|
|
105
|
+
let instance;
|
|
106
|
+
if (Class) {
|
|
107
|
+
if (isClass(Class)) {
|
|
108
|
+
instance = new Class(ctx);
|
|
109
|
+
} else {
|
|
110
|
+
// it's just an object
|
|
111
|
+
instance = Class;
|
|
112
|
+
}
|
|
113
|
+
// Can't set property to primitive, so check again
|
|
114
|
+
// e.x. module.exports = 1;
|
|
115
|
+
} else if (isPrimitive(values)) {
|
|
116
|
+
instance = values;
|
|
117
|
+
} else {
|
|
118
|
+
instance = new ClassLoader({ ctx, properties: values });
|
|
119
|
+
}
|
|
120
|
+
return instance;
|
|
121
|
+
}
|