@eggjs/core 6.3.0-beta.1 → 6.3.1
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/dist/commonjs/base_context_class.d.ts +3 -3
- package/dist/commonjs/base_context_class.js +1 -1
- package/dist/commonjs/egg.d.ts +16 -9
- package/dist/commonjs/egg.js +28 -2
- package/dist/commonjs/index.d.ts +2 -0
- package/dist/commonjs/index.js +2 -1
- package/dist/commonjs/lifecycle.js +5 -1
- package/dist/commonjs/loader/context_loader.d.ts +3 -3
- package/dist/commonjs/loader/context_loader.js +1 -1
- package/dist/commonjs/loader/egg_loader.d.ts +4 -40
- package/dist/commonjs/loader/egg_loader.js +44 -32
- package/dist/commonjs/loader/file_loader.d.ts +5 -1
- package/dist/commonjs/loader/file_loader.js +18 -5
- package/dist/commonjs/singleton.d.ts +29 -0
- package/dist/commonjs/singleton.js +125 -0
- package/dist/commonjs/types.d.ts +53 -0
- package/dist/commonjs/types.js +3 -0
- package/dist/commonjs/utils/index.js +10 -5
- package/dist/commonjs/utils/sequencify.js +1 -1
- package/dist/commonjs/utils/timing.js +1 -1
- package/dist/esm/base_context_class.d.ts +3 -3
- package/dist/esm/base_context_class.js +1 -1
- package/dist/esm/egg.d.ts +16 -9
- package/dist/esm/egg.js +28 -2
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/lifecycle.js +6 -2
- package/dist/esm/loader/context_loader.d.ts +3 -3
- package/dist/esm/loader/context_loader.js +1 -1
- package/dist/esm/loader/egg_loader.d.ts +4 -40
- package/dist/esm/loader/egg_loader.js +48 -36
- package/dist/esm/loader/file_loader.d.ts +5 -1
- package/dist/esm/loader/file_loader.js +17 -4
- package/dist/esm/singleton.d.ts +29 -0
- package/dist/esm/singleton.js +118 -0
- package/dist/esm/types.d.ts +53 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/utils/index.js +10 -5
- package/dist/esm/utils/sequencify.js +1 -1
- package/dist/esm/utils/timing.js +1 -1
- package/dist/package.json +1 -1
- package/package.json +11 -13
- package/src/base_context_class.ts +3 -3
- package/src/egg.ts +38 -10
- package/src/index.ts +2 -0
- package/src/lifecycle.ts +5 -1
- package/src/loader/context_loader.ts +4 -4
- package/src/loader/egg_loader.ts +64 -82
- package/src/loader/file_loader.ts +16 -4
- package/src/singleton.ts +149 -0
- package/src/types.ts +56 -0
- package/src/utils/index.ts +9 -4
- package/src/utils/sequencify.ts +1 -1
- package/src/utils/timing.ts +1 -1
package/src/loader/egg_loader.ts
CHANGED
|
@@ -5,66 +5,35 @@ import { debuglog, inspect } from 'node:util';
|
|
|
5
5
|
import { homedir } from 'node-homedir';
|
|
6
6
|
import { isAsyncFunction, isClass, isGeneratorFunction, isObject, isPromise } from 'is-type-of';
|
|
7
7
|
import type { Logger } from 'egg-logger';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
getParamNames, readJSONSync, readJSON, exists,
|
|
10
|
+
} from 'utility';
|
|
9
11
|
import { extend } from 'extend2';
|
|
10
|
-
import { Request, Response,
|
|
12
|
+
import { Request, Response, Application, Context as KoaContext } from '@eggjs/koa';
|
|
13
|
+
import { register as tsconfigPathsRegister } from 'tsconfig-paths';
|
|
14
|
+
import { isESM, isSupportTypeScript } from '@eggjs/utils';
|
|
11
15
|
import { pathMatching, type PathMatchingOptions } from 'egg-path-matching';
|
|
12
16
|
import { now, diff } from 'performance-ms';
|
|
13
|
-
import { FULLPATH, FileLoader, FileLoaderOptions } from './file_loader.js';
|
|
17
|
+
import { CaseStyle, FULLPATH, FileLoader, FileLoaderOptions } from './file_loader.js';
|
|
14
18
|
import { ContextLoader, ContextLoaderOptions } from './context_loader.js';
|
|
15
19
|
import utils, { Fun } from '../utils/index.js';
|
|
16
20
|
import sequencify from '../utils/sequencify.js';
|
|
17
21
|
import { Timing } from '../utils/timing.js';
|
|
18
|
-
import type {
|
|
19
|
-
|
|
22
|
+
import type {
|
|
23
|
+
Context, EggCore, MiddlewareFunc,
|
|
24
|
+
} from '../egg.js';
|
|
25
|
+
import type { BaseContextClass } from '../base_context_class.js';
|
|
26
|
+
import type { EggAppConfig, EggAppInfo, EggPluginInfo } from '../types.js';
|
|
20
27
|
|
|
21
28
|
const debug = debuglog('@eggjs/core/loader/egg_loader');
|
|
22
29
|
|
|
23
30
|
const originalPrototypes: Record<string, any> = {
|
|
24
31
|
request: Request.prototype,
|
|
25
32
|
response: Response.prototype,
|
|
26
|
-
context:
|
|
33
|
+
context: KoaContext.prototype,
|
|
27
34
|
application: Application.prototype,
|
|
28
35
|
};
|
|
29
36
|
|
|
30
|
-
export interface EggAppInfo {
|
|
31
|
-
/** package.json */
|
|
32
|
-
pkg: Record<string, any>;
|
|
33
|
-
/** the application name from package.json */
|
|
34
|
-
name: string;
|
|
35
|
-
/** current directory of application */
|
|
36
|
-
baseDir: string;
|
|
37
|
-
/** equals to serverEnv */
|
|
38
|
-
env: string;
|
|
39
|
-
/** equals to serverScope */
|
|
40
|
-
scope: string;
|
|
41
|
-
/** home directory of the OS */
|
|
42
|
-
HOME: string;
|
|
43
|
-
/** baseDir when local and unittest, HOME when other environment */
|
|
44
|
-
root: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface EggPluginInfo {
|
|
48
|
-
/** the plugin name, it can be used in `dep` */
|
|
49
|
-
name: string;
|
|
50
|
-
/** the package name of plugin */
|
|
51
|
-
package?: string;
|
|
52
|
-
version?: string;
|
|
53
|
-
/** whether enabled */
|
|
54
|
-
enable: boolean;
|
|
55
|
-
implicitEnable?: boolean;
|
|
56
|
-
/** the directory of the plugin package */
|
|
57
|
-
path?: string;
|
|
58
|
-
/** the dependent plugins, you can use the plugin name */
|
|
59
|
-
dependencies: string[];
|
|
60
|
-
/** the optional dependent plugins. */
|
|
61
|
-
optionalDependencies: string[];
|
|
62
|
-
dependents?: string[];
|
|
63
|
-
/** specify the serverEnv that only enable the plugin in it */
|
|
64
|
-
env: string[];
|
|
65
|
-
/** the file plugin config in. */
|
|
66
|
-
from: string;
|
|
67
|
-
}
|
|
68
37
|
|
|
69
38
|
export interface EggLoaderOptions {
|
|
70
39
|
/** server env */
|
|
@@ -100,7 +69,6 @@ export class EggLoader {
|
|
|
100
69
|
readonly appInfo: EggAppInfo;
|
|
101
70
|
dirs?: EggDirInfo[];
|
|
102
71
|
|
|
103
|
-
|
|
104
72
|
/**
|
|
105
73
|
* @class
|
|
106
74
|
* @param {Object} options - options
|
|
@@ -130,12 +98,11 @@ export class EggLoader {
|
|
|
130
98
|
if (process.env.EGG_TYPESCRIPT === 'true' || (this.pkg.egg && this.pkg.egg.typescript)) {
|
|
131
99
|
// skip require tsconfig-paths if tsconfig.json not exists
|
|
132
100
|
const tsConfigFile = path.join(this.options.baseDir, 'tsconfig.json');
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
136
|
-
require('tsconfig-paths').register({ cwd: this.options.baseDir });
|
|
101
|
+
if (fs.existsSync(tsConfigFile)) {
|
|
102
|
+
tsconfigPathsRegister({ cwd: this.options.baseDir } as any);
|
|
137
103
|
} else {
|
|
138
|
-
this.logger.info(
|
|
104
|
+
this.logger.info(
|
|
105
|
+
'[@eggjs/core/egg_loader] skip register "tsconfig-paths" because tsconfig.json not exists at %s',
|
|
139
106
|
tsConfigFile);
|
|
140
107
|
}
|
|
141
108
|
}
|
|
@@ -634,19 +601,19 @@ export class EggLoader {
|
|
|
634
601
|
plugin.version = pkg.version;
|
|
635
602
|
}
|
|
636
603
|
// support commonjs and esm dist files
|
|
637
|
-
plugin.path = this.#formatPluginPathFromPackageJSON(plugin.path!, pkg);
|
|
604
|
+
plugin.path = await this.#formatPluginPathFromPackageJSON(plugin.path!, pkg);
|
|
638
605
|
}
|
|
639
606
|
|
|
640
607
|
const logger = this.options.logger;
|
|
641
608
|
if (!config) {
|
|
642
|
-
logger.warn(`[@eggjs/core
|
|
609
|
+
logger.warn(`[@eggjs/core/egg_loader] pkg.eggPlugin is missing in ${pluginPackage}`);
|
|
643
610
|
return;
|
|
644
611
|
}
|
|
645
612
|
|
|
646
613
|
if (config.name && config.strict !== false && config.name !== plugin.name) {
|
|
647
614
|
// pluginName is configured in config/plugin.js
|
|
648
615
|
// pluginConfigName is pkg.eggPlugin.name
|
|
649
|
-
logger.warn(`[@eggjs/core
|
|
616
|
+
logger.warn(`[@eggjs/core/egg_loader] pluginName(${plugin.name}) is different from pluginConfigName(${config.name})`);
|
|
650
617
|
}
|
|
651
618
|
|
|
652
619
|
// dep compatible
|
|
@@ -788,26 +755,36 @@ export class EggLoader {
|
|
|
788
755
|
}
|
|
789
756
|
}
|
|
790
757
|
|
|
791
|
-
#formatPluginPathFromPackageJSON(pluginPath: string, pluginPkg: {
|
|
758
|
+
async #formatPluginPathFromPackageJSON(pluginPath: string, pluginPkg: {
|
|
792
759
|
eggPlugin?: {
|
|
793
760
|
exports?: {
|
|
794
761
|
import?: string;
|
|
795
762
|
require?: string;
|
|
763
|
+
typescript?: string;
|
|
796
764
|
};
|
|
797
765
|
};
|
|
798
|
-
}) {
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
766
|
+
}): Promise<string> {
|
|
767
|
+
let realPluginPath = pluginPath;
|
|
768
|
+
const exports = pluginPkg.eggPlugin?.exports;
|
|
769
|
+
if (exports) {
|
|
770
|
+
if (isESM) {
|
|
771
|
+
if (exports.import) {
|
|
772
|
+
realPluginPath = path.join(pluginPath, exports.import);
|
|
803
773
|
}
|
|
804
774
|
} else {
|
|
805
|
-
if (
|
|
806
|
-
|
|
775
|
+
if (exports.require) {
|
|
776
|
+
realPluginPath = path.join(pluginPath, exports.require);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (exports.typescript && isSupportTypeScript()) {
|
|
780
|
+
if (!(await exists(realPluginPath))) {
|
|
781
|
+
// if require/import path not exists, use typescript path for development stage
|
|
782
|
+
realPluginPath = path.join(pluginPath, exports.typescript);
|
|
783
|
+
debug('[formatPluginPathFromPackageJSON] use typescript path %o', realPluginPath);
|
|
807
784
|
}
|
|
808
785
|
}
|
|
809
786
|
}
|
|
810
|
-
return
|
|
787
|
+
return realPluginPath;
|
|
811
788
|
}
|
|
812
789
|
|
|
813
790
|
#extendPlugins(targets: Record<string, EggPluginInfo>, plugins: Record<string, EggPluginInfo>) {
|
|
@@ -843,7 +820,7 @@ export class EggLoader {
|
|
|
843
820
|
|
|
844
821
|
/** start Config loader */
|
|
845
822
|
configMeta: Record<string, any>;
|
|
846
|
-
config:
|
|
823
|
+
config: EggAppConfig;
|
|
847
824
|
|
|
848
825
|
/**
|
|
849
826
|
* Load config/config.js
|
|
@@ -857,7 +834,10 @@ export class EggLoader {
|
|
|
857
834
|
this.timing.start('Load Config');
|
|
858
835
|
this.configMeta = {};
|
|
859
836
|
|
|
860
|
-
const target:
|
|
837
|
+
const target: EggAppConfig = {
|
|
838
|
+
middleware: [],
|
|
839
|
+
coreMiddleware: [],
|
|
840
|
+
};
|
|
861
841
|
|
|
862
842
|
// Load Application config first
|
|
863
843
|
const appConfig = await this.#preloadAppConfig();
|
|
@@ -1184,7 +1164,7 @@ export class EggLoader {
|
|
|
1184
1164
|
this.lifecycle.addFunctionAsBootHook(bootHook, bootFilePath);
|
|
1185
1165
|
debug('[loadBootHook] add bootHookFunction from %o', bootFilePath);
|
|
1186
1166
|
} else {
|
|
1187
|
-
this.options.logger.warn('[@eggjs/core
|
|
1167
|
+
this.options.logger.warn('[@eggjs/core/egg_loader] %s must exports a boot class', bootFilePath);
|
|
1188
1168
|
}
|
|
1189
1169
|
}
|
|
1190
1170
|
// init boots
|
|
@@ -1206,7 +1186,7 @@ export class EggLoader {
|
|
|
1206
1186
|
const servicePaths = this.getLoadUnits().map(unit => path.join(unit.path, 'app/service'));
|
|
1207
1187
|
options = {
|
|
1208
1188
|
call: true,
|
|
1209
|
-
caseStyle:
|
|
1189
|
+
caseStyle: CaseStyle.lower,
|
|
1210
1190
|
fieldClass: 'serviceClasses',
|
|
1211
1191
|
directory: servicePaths,
|
|
1212
1192
|
...options,
|
|
@@ -1246,7 +1226,7 @@ export class EggLoader {
|
|
|
1246
1226
|
opt = {
|
|
1247
1227
|
call: false,
|
|
1248
1228
|
override: true,
|
|
1249
|
-
caseStyle:
|
|
1229
|
+
caseStyle: CaseStyle.lower,
|
|
1250
1230
|
directory: middlewarePaths,
|
|
1251
1231
|
...opt,
|
|
1252
1232
|
};
|
|
@@ -1295,13 +1275,13 @@ export class EggLoader {
|
|
|
1295
1275
|
}
|
|
1296
1276
|
app.use(mw);
|
|
1297
1277
|
debug('[loadMiddleware] Use middleware: %s with options: %j', name, options);
|
|
1298
|
-
this.options.logger.info('[@eggjs/core
|
|
1278
|
+
this.options.logger.info('[@eggjs/core/egg_loader] Use middleware: %s', name);
|
|
1299
1279
|
} else {
|
|
1300
|
-
this.options.logger.info('[@eggjs/core
|
|
1280
|
+
this.options.logger.info('[@eggjs/core/egg_loader] Disable middleware: %s', name);
|
|
1301
1281
|
}
|
|
1302
1282
|
}
|
|
1303
1283
|
|
|
1304
|
-
this.options.logger.info('[@eggjs/core
|
|
1284
|
+
this.options.logger.info('[@eggjs/core/egg_loader] Loaded middleware from %j', middlewarePaths);
|
|
1305
1285
|
this.timing.end('Load Middleware');
|
|
1306
1286
|
|
|
1307
1287
|
// add router middleware, make sure router is the last middleware
|
|
@@ -1321,7 +1301,7 @@ export class EggLoader {
|
|
|
1321
1301
|
this.timing.start('Load Controller');
|
|
1322
1302
|
const controllerBase = path.join(this.options.baseDir, 'app/controller');
|
|
1323
1303
|
opt = {
|
|
1324
|
-
caseStyle:
|
|
1304
|
+
caseStyle: CaseStyle.lower,
|
|
1325
1305
|
directory: controllerBase,
|
|
1326
1306
|
initializer: (obj, opt) => {
|
|
1327
1307
|
// return class if it exports a function
|
|
@@ -1359,7 +1339,7 @@ export class EggLoader {
|
|
|
1359
1339
|
};
|
|
1360
1340
|
await this.loadToApp(controllerBase, 'controller', opt as FileLoaderOptions);
|
|
1361
1341
|
debug('[loadController] app.controller => %o', this.app.controller);
|
|
1362
|
-
this.options.logger.info('[@eggjs/core
|
|
1342
|
+
this.options.logger.info('[@eggjs/core/egg_loader] Controller loaded: %s', controllerBase);
|
|
1363
1343
|
this.timing.end('Load Controller');
|
|
1364
1344
|
}
|
|
1365
1345
|
/** end Controller loader */
|
|
@@ -1401,7 +1381,7 @@ export class EggLoader {
|
|
|
1401
1381
|
case 'ctx': {
|
|
1402
1382
|
assert(!(property in this.app.context), `customLoader should not override ctx.${property}`);
|
|
1403
1383
|
const options = {
|
|
1404
|
-
caseStyle:
|
|
1384
|
+
caseStyle: CaseStyle.lower,
|
|
1405
1385
|
fieldClass: `${property}Classes`,
|
|
1406
1386
|
...loaderConfig,
|
|
1407
1387
|
directory,
|
|
@@ -1412,7 +1392,7 @@ export class EggLoader {
|
|
|
1412
1392
|
case 'app': {
|
|
1413
1393
|
assert(!(property in this.app), `customLoader should not override app.${property}`);
|
|
1414
1394
|
const options = {
|
|
1415
|
-
caseStyle:
|
|
1395
|
+
caseStyle: CaseStyle.lower,
|
|
1416
1396
|
initializer: (Clazz: unknown) => {
|
|
1417
1397
|
return isClass(Clazz) ? new Clazz(this.app) : Clazz;
|
|
1418
1398
|
},
|
|
@@ -1531,10 +1511,11 @@ export class EggLoader {
|
|
|
1531
1511
|
* @param {Object} options - see {@link FileLoader}
|
|
1532
1512
|
* @since 1.0.0
|
|
1533
1513
|
*/
|
|
1534
|
-
async loadToApp(directory: string | string[], property: string | symbol,
|
|
1514
|
+
async loadToApp(directory: string | string[], property: string | symbol,
|
|
1515
|
+
options?: Omit<FileLoaderOptions, 'inject' | 'target'>) {
|
|
1535
1516
|
const target = {};
|
|
1536
1517
|
Reflect.set(this.app, property, target);
|
|
1537
|
-
|
|
1518
|
+
const loadOptions: FileLoaderOptions = {
|
|
1538
1519
|
...options,
|
|
1539
1520
|
directory: options?.directory ?? directory,
|
|
1540
1521
|
target,
|
|
@@ -1543,7 +1524,7 @@ export class EggLoader {
|
|
|
1543
1524
|
|
|
1544
1525
|
const timingKey = `Load "${String(property)}" to Application`;
|
|
1545
1526
|
this.timing.start(timingKey);
|
|
1546
|
-
await new FileLoader(
|
|
1527
|
+
await new FileLoader(loadOptions).load();
|
|
1547
1528
|
this.timing.end(timingKey);
|
|
1548
1529
|
}
|
|
1549
1530
|
|
|
@@ -1554,8 +1535,9 @@ export class EggLoader {
|
|
|
1554
1535
|
* @param {Object} options - see {@link ContextLoader}
|
|
1555
1536
|
* @since 1.0.0
|
|
1556
1537
|
*/
|
|
1557
|
-
async loadToContext(directory: string | string[], property: string | symbol,
|
|
1558
|
-
options
|
|
1538
|
+
async loadToContext(directory: string | string[], property: string | symbol,
|
|
1539
|
+
options?: Omit<ContextLoaderOptions, 'inject' | 'property'>) {
|
|
1540
|
+
const loadOptions: ContextLoaderOptions = {
|
|
1559
1541
|
...options,
|
|
1560
1542
|
directory: options?.directory || directory,
|
|
1561
1543
|
property,
|
|
@@ -1564,7 +1546,7 @@ export class EggLoader {
|
|
|
1564
1546
|
|
|
1565
1547
|
const timingKey = `Load "${String(property)}" to Context`;
|
|
1566
1548
|
this.timing.start(timingKey);
|
|
1567
|
-
await new ContextLoader(
|
|
1549
|
+
await new ContextLoader(loadOptions).load();
|
|
1568
1550
|
this.timing.end(timingKey);
|
|
1569
1551
|
}
|
|
1570
1552
|
|
|
@@ -1691,7 +1673,7 @@ function wrapControllerClass(Controller: typeof BaseContextClass, fullPath: stri
|
|
|
1691
1673
|
}
|
|
1692
1674
|
|
|
1693
1675
|
function controllerMethodToMiddleware(Controller: typeof BaseContextClass, key: string) {
|
|
1694
|
-
return function classControllerMiddleware(this:
|
|
1676
|
+
return function classControllerMiddleware(this: Context, ...args: any[]) {
|
|
1695
1677
|
const controller: any = new Controller(this);
|
|
1696
1678
|
if (!this.app.config.controller?.supportParams) {
|
|
1697
1679
|
args = [ this ];
|
|
@@ -1727,7 +1709,7 @@ function wrapObject(obj: Record<string, any>, fullPath: string, prefix?: string)
|
|
|
1727
1709
|
}
|
|
1728
1710
|
|
|
1729
1711
|
function objectFunctionToMiddleware(func: Fun) {
|
|
1730
|
-
async function objectControllerMiddleware(this:
|
|
1712
|
+
async function objectControllerMiddleware(this: Context, ...args: any[]) {
|
|
1731
1713
|
if (!this.app.config.controller?.supportParams) {
|
|
1732
1714
|
args = [ this ];
|
|
1733
1715
|
}
|
|
@@ -6,12 +6,17 @@ import globby from 'globby';
|
|
|
6
6
|
import { isClass, isGeneratorFunction, isAsyncFunction, isPrimitive } from 'is-type-of';
|
|
7
7
|
import utils, { Fun } from '../utils/index.js';
|
|
8
8
|
|
|
9
|
-
const debug = debuglog('@eggjs/core
|
|
9
|
+
const debug = debuglog('@eggjs/core/file_loader');
|
|
10
10
|
|
|
11
11
|
export const FULLPATH = Symbol('EGG_LOADER_ITEM_FULLPATH');
|
|
12
12
|
export const EXPORTS = Symbol('EGG_LOADER_ITEM_EXPORTS');
|
|
13
13
|
|
|
14
|
-
export
|
|
14
|
+
export enum CaseStyle {
|
|
15
|
+
camel = 'camel',
|
|
16
|
+
lower = 'lower',
|
|
17
|
+
upper = 'upper',
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
export type CaseStyleFunction = (filepath: string) => string[];
|
|
16
21
|
export type FileLoaderInitializer = (exports: unknown, options: { path: string; pathName: string }) => unknown;
|
|
17
22
|
export type FileLoaderFilter = (exports: unknown) => boolean;
|
|
@@ -79,7 +84,7 @@ export class FileLoader {
|
|
|
79
84
|
assert(options.directory, 'options.directory is required');
|
|
80
85
|
assert(options.target, 'options.target is required');
|
|
81
86
|
this.options = {
|
|
82
|
-
caseStyle:
|
|
87
|
+
caseStyle: CaseStyle.camel,
|
|
83
88
|
call: true,
|
|
84
89
|
override: false,
|
|
85
90
|
...options,
|
|
@@ -88,7 +93,7 @@ export class FileLoader {
|
|
|
88
93
|
// compatible old options _lowercaseFirst_
|
|
89
94
|
if (this.options.lowercaseFirst === true) {
|
|
90
95
|
utils.deprecated('lowercaseFirst is deprecated, use caseStyle instead');
|
|
91
|
-
this.options.caseStyle =
|
|
96
|
+
this.options.caseStyle = CaseStyle.lower;
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
99
|
|
|
@@ -185,6 +190,13 @@ export class FileLoader {
|
|
|
185
190
|
for (const filepath of filepaths) {
|
|
186
191
|
const fullpath = path.join(directory, filepath);
|
|
187
192
|
if (!fs.statSync(fullpath).isFile()) continue;
|
|
193
|
+
if (filepath.endsWith('.js')) {
|
|
194
|
+
const filepathTs = filepath.replace(/\.js$/, '.ts');
|
|
195
|
+
if (filepaths.includes(filepathTs)) {
|
|
196
|
+
debug('[parse] ignore %s, because %s exists', fullpath, filepathTs);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
188
200
|
// get properties
|
|
189
201
|
// app/service/foo/bar.js => [ 'foo', 'bar' ]
|
|
190
202
|
const properties = getProperties(filepath, this.options.caseStyle);
|
package/src/singleton.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { isAsyncFunction } from 'is-type-of';
|
|
3
|
+
import type { EggCore } from './egg.js';
|
|
4
|
+
|
|
5
|
+
export type SingletonCreateMethod =
|
|
6
|
+
(config: Record<string, any>, app: EggCore, clientName: string) => unknown | Promise<unknown>;
|
|
7
|
+
|
|
8
|
+
export interface SingletonOptions {
|
|
9
|
+
name: string;
|
|
10
|
+
app: EggCore;
|
|
11
|
+
create: SingletonCreateMethod;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class Singleton<T = any> {
|
|
15
|
+
readonly clients = new Map<string, T>();
|
|
16
|
+
readonly app: EggCore;
|
|
17
|
+
readonly create: SingletonCreateMethod;
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly options: Record<string, any>;
|
|
20
|
+
|
|
21
|
+
constructor(options: SingletonOptions) {
|
|
22
|
+
assert(options.name, '[@eggjs/core/singleton] Singleton#constructor options.name is required');
|
|
23
|
+
assert(options.app, '[@eggjs/core/singleton] Singleton#constructor options.app is required');
|
|
24
|
+
assert(options.create, '[@eggjs/core/singleton] Singleton#constructor options.create is required');
|
|
25
|
+
assert(!(options.name in options.app), `[@eggjs/core/singleton] ${options.name} is already exists in app`);
|
|
26
|
+
this.app = options.app;
|
|
27
|
+
this.name = options.name;
|
|
28
|
+
this.create = options.create;
|
|
29
|
+
this.options = options.app.config[this.name] ?? {};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
init() {
|
|
33
|
+
return isAsyncFunction(this.create) ? this.initAsync() : this.initSync();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
initSync() {
|
|
37
|
+
const options = this.options;
|
|
38
|
+
assert(!(options.client && options.clients),
|
|
39
|
+
`[@eggjs/core/singleton] ${this.name} can not set options.client and options.clients both`);
|
|
40
|
+
|
|
41
|
+
// alias app[name] as client, but still support createInstance method
|
|
42
|
+
if (options.client) {
|
|
43
|
+
const client = this.createInstance(options.client, options.name);
|
|
44
|
+
this.#setClientToApp(client);
|
|
45
|
+
this.#extendDynamicMethods(client);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// multi client, use app[name].getSingletonInstance(id)
|
|
50
|
+
if (options.clients) {
|
|
51
|
+
Object.keys(options.clients).forEach(id => {
|
|
52
|
+
const client = this.createInstance(options.clients[id], id);
|
|
53
|
+
this.clients.set(id, client);
|
|
54
|
+
});
|
|
55
|
+
this.#setClientToApp(this);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// no config.clients and config.client
|
|
60
|
+
this.#setClientToApp(this);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async initAsync() {
|
|
64
|
+
const options = this.options;
|
|
65
|
+
assert(!(options.client && options.clients),
|
|
66
|
+
`[@eggjs/core/singleton] ${this.name} can not set options.client and options.clients both`);
|
|
67
|
+
|
|
68
|
+
// alias app[name] as client, but still support createInstance method
|
|
69
|
+
if (options.client) {
|
|
70
|
+
const client = await this.createInstanceAsync(options.client, options.name);
|
|
71
|
+
this.#setClientToApp(client);
|
|
72
|
+
this.#extendDynamicMethods(client);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// multi client, use app[name].getInstance(id)
|
|
77
|
+
if (options.clients) {
|
|
78
|
+
await Promise.all(Object.keys(options.clients).map((id: string) => {
|
|
79
|
+
return this.createInstanceAsync(options.clients[id], id)
|
|
80
|
+
.then(client => this.clients.set(id, client));
|
|
81
|
+
}));
|
|
82
|
+
this.#setClientToApp(this);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// no config.clients and config.client
|
|
87
|
+
this.#setClientToApp(this);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#setClientToApp(client: unknown) {
|
|
91
|
+
Reflect.set(this.app, this.name, client);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @deprecated please use `getSingletonInstance(id)` instead
|
|
96
|
+
*/
|
|
97
|
+
get(id: string) {
|
|
98
|
+
return this.clients.get(id)!;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get singleton instance by id
|
|
103
|
+
*/
|
|
104
|
+
getSingletonInstance(id: string) {
|
|
105
|
+
return this.clients.get(id)!;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
createInstance(config: Record<string, any>, clientName: string) {
|
|
109
|
+
// async creator only support createInstanceAsync
|
|
110
|
+
assert(!isAsyncFunction(this.create),
|
|
111
|
+
`[@eggjs/core/singleton] ${this.name} only support synchronous creation, please use createInstanceAsync`);
|
|
112
|
+
// options.default will be merge in to options.clients[id]
|
|
113
|
+
config = {
|
|
114
|
+
...this.options.default,
|
|
115
|
+
...config,
|
|
116
|
+
};
|
|
117
|
+
return (this.create as SingletonCreateMethod)(config, this.app, clientName) as T;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async createInstanceAsync(config: Record<string, any>, clientName: string) {
|
|
121
|
+
// options.default will be merge in to options.clients[id]
|
|
122
|
+
config = {
|
|
123
|
+
...this.options.default,
|
|
124
|
+
...config,
|
|
125
|
+
};
|
|
126
|
+
return await this.create(config, this.app, clientName) as T;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#extendDynamicMethods(client: any) {
|
|
130
|
+
assert(!client.createInstance, '[@eggjs/core/singleton] singleton instance should not have createInstance method');
|
|
131
|
+
assert(!client.createInstanceAsync, '[@eggjs/core/singleton] singleton instance should not have createInstanceAsync method');
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
let extendable = client;
|
|
135
|
+
// Object.preventExtensions() or Object.freeze()
|
|
136
|
+
if (!Object.isExtensible(client) || Object.isFrozen(client)) {
|
|
137
|
+
// eslint-disable-next-line no-proto
|
|
138
|
+
extendable = client.__proto__ || client;
|
|
139
|
+
}
|
|
140
|
+
extendable.createInstance = this.createInstance.bind(this);
|
|
141
|
+
extendable.createInstanceAsync = this.createInstanceAsync.bind(this);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
this.app.coreLogger.warn(
|
|
144
|
+
'[@eggjs/core/singleton] %s dynamic create is disabled because of client is un-extendable',
|
|
145
|
+
this.name);
|
|
146
|
+
this.app.coreLogger.warn(err);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface EggAppInfo {
|
|
2
|
+
/** package.json */
|
|
3
|
+
pkg: Record<string, any>;
|
|
4
|
+
/** the application name from package.json */
|
|
5
|
+
name: string;
|
|
6
|
+
/** current directory of application */
|
|
7
|
+
baseDir: string;
|
|
8
|
+
/** equals to serverEnv */
|
|
9
|
+
env: string;
|
|
10
|
+
/** equals to serverScope */
|
|
11
|
+
scope: string;
|
|
12
|
+
/** home directory of the OS */
|
|
13
|
+
HOME: string;
|
|
14
|
+
/** baseDir when local and unittest, HOME when other environment */
|
|
15
|
+
root: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface EggPluginInfo {
|
|
19
|
+
/** the plugin name, it can be used in `dep` */
|
|
20
|
+
name: string;
|
|
21
|
+
/** the package name of plugin */
|
|
22
|
+
package?: string;
|
|
23
|
+
version?: string;
|
|
24
|
+
/** whether enabled */
|
|
25
|
+
enable: boolean;
|
|
26
|
+
implicitEnable?: boolean;
|
|
27
|
+
/** the directory of the plugin package */
|
|
28
|
+
path?: string;
|
|
29
|
+
/** the dependent plugins, you can use the plugin name */
|
|
30
|
+
dependencies: string[];
|
|
31
|
+
/** the optional dependent plugins. */
|
|
32
|
+
optionalDependencies: string[];
|
|
33
|
+
dependents?: string[];
|
|
34
|
+
/** specify the serverEnv that only enable the plugin in it */
|
|
35
|
+
env: string[];
|
|
36
|
+
/** the file plugin config in. */
|
|
37
|
+
from: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CustomLoaderConfigItem {
|
|
41
|
+
/** the directory of the custom loader */
|
|
42
|
+
directory: string;
|
|
43
|
+
/** the inject object, it can be app or ctx */
|
|
44
|
+
inject: string;
|
|
45
|
+
/** whether load unit files */
|
|
46
|
+
loadunit?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface EggAppConfig extends Record<string, any> {
|
|
50
|
+
coreMiddleware: string[];
|
|
51
|
+
middleware: string[];
|
|
52
|
+
customLoader?: Record<string, CustomLoaderConfigItem>;
|
|
53
|
+
controller?: {
|
|
54
|
+
supportParams?: boolean;
|
|
55
|
+
};
|
|
56
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { stat } from 'node:fs/promises';
|
|
|
5
5
|
import BuiltinModule from 'node:module';
|
|
6
6
|
import { importResolve, importModule } from '@eggjs/utils';
|
|
7
7
|
|
|
8
|
-
const debug = debuglog('@eggjs/core
|
|
8
|
+
const debug = debuglog('@eggjs/core/utils');
|
|
9
9
|
|
|
10
10
|
export type Fun = (...args: any[]) => any;
|
|
11
11
|
|
|
@@ -55,10 +55,10 @@ function getCalleeFromStack(withLine?: boolean, stackIndex?: number) {
|
|
|
55
55
|
export default {
|
|
56
56
|
deprecated(message: string) {
|
|
57
57
|
if (debug.enabled) {
|
|
58
|
-
console.trace('[@eggjs/core
|
|
58
|
+
console.trace('[@eggjs/core/deprecated] %s', message);
|
|
59
59
|
} else {
|
|
60
|
-
console.
|
|
61
|
-
console.
|
|
60
|
+
console.log('[@eggjs/core/deprecated] %s', message);
|
|
61
|
+
console.log('[@eggjs/core/deprecated] set NODE_DEBUG=@eggjs/core/utils can show call stack');
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
64
|
|
|
@@ -84,6 +84,11 @@ export default {
|
|
|
84
84
|
const obj = await importModule(filepath, { importDefaultOnly: true });
|
|
85
85
|
return obj;
|
|
86
86
|
} catch (e: any) {
|
|
87
|
+
if (!e.message && typeof e !== 'string') {
|
|
88
|
+
// ts error: test/fixtures/apps/app-ts/app/extend/context.ts(5,17): error TS2339: Property 'url' does not exist on type 'Context'
|
|
89
|
+
console.trace(e);
|
|
90
|
+
throw e;
|
|
91
|
+
}
|
|
87
92
|
const err = new Error(`[@eggjs/core] load file: ${filepath}, error: ${e.message}`);
|
|
88
93
|
err.cause = e;
|
|
89
94
|
debug('[loadFile] handle %s error: %s', filepath, e);
|
package/src/utils/sequencify.ts
CHANGED
package/src/utils/timing.ts
CHANGED