@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.
Files changed (54) hide show
  1. package/dist/commonjs/base_context_class.d.ts +3 -3
  2. package/dist/commonjs/base_context_class.js +1 -1
  3. package/dist/commonjs/egg.d.ts +16 -9
  4. package/dist/commonjs/egg.js +28 -2
  5. package/dist/commonjs/index.d.ts +2 -0
  6. package/dist/commonjs/index.js +2 -1
  7. package/dist/commonjs/lifecycle.js +5 -1
  8. package/dist/commonjs/loader/context_loader.d.ts +3 -3
  9. package/dist/commonjs/loader/context_loader.js +1 -1
  10. package/dist/commonjs/loader/egg_loader.d.ts +4 -40
  11. package/dist/commonjs/loader/egg_loader.js +44 -32
  12. package/dist/commonjs/loader/file_loader.d.ts +5 -1
  13. package/dist/commonjs/loader/file_loader.js +18 -5
  14. package/dist/commonjs/singleton.d.ts +29 -0
  15. package/dist/commonjs/singleton.js +125 -0
  16. package/dist/commonjs/types.d.ts +53 -0
  17. package/dist/commonjs/types.js +3 -0
  18. package/dist/commonjs/utils/index.js +10 -5
  19. package/dist/commonjs/utils/sequencify.js +1 -1
  20. package/dist/commonjs/utils/timing.js +1 -1
  21. package/dist/esm/base_context_class.d.ts +3 -3
  22. package/dist/esm/base_context_class.js +1 -1
  23. package/dist/esm/egg.d.ts +16 -9
  24. package/dist/esm/egg.js +28 -2
  25. package/dist/esm/index.d.ts +2 -0
  26. package/dist/esm/index.js +2 -1
  27. package/dist/esm/lifecycle.js +6 -2
  28. package/dist/esm/loader/context_loader.d.ts +3 -3
  29. package/dist/esm/loader/context_loader.js +1 -1
  30. package/dist/esm/loader/egg_loader.d.ts +4 -40
  31. package/dist/esm/loader/egg_loader.js +48 -36
  32. package/dist/esm/loader/file_loader.d.ts +5 -1
  33. package/dist/esm/loader/file_loader.js +17 -4
  34. package/dist/esm/singleton.d.ts +29 -0
  35. package/dist/esm/singleton.js +118 -0
  36. package/dist/esm/types.d.ts +53 -0
  37. package/dist/esm/types.js +2 -0
  38. package/dist/esm/utils/index.js +10 -5
  39. package/dist/esm/utils/sequencify.js +1 -1
  40. package/dist/esm/utils/timing.js +1 -1
  41. package/dist/package.json +1 -1
  42. package/package.json +11 -13
  43. package/src/base_context_class.ts +3 -3
  44. package/src/egg.ts +38 -10
  45. package/src/index.ts +2 -0
  46. package/src/lifecycle.ts +5 -1
  47. package/src/loader/context_loader.ts +4 -4
  48. package/src/loader/egg_loader.ts +64 -82
  49. package/src/loader/file_loader.ts +16 -4
  50. package/src/singleton.ts +149 -0
  51. package/src/types.ts +56 -0
  52. package/src/utils/index.ts +9 -4
  53. package/src/utils/sequencify.ts +1 -1
  54. package/src/utils/timing.ts +1 -1
@@ -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 { getParamNames, readJSONSync, readJSON } from 'utility';
8
+ import {
9
+ getParamNames, readJSONSync, readJSON, exists,
10
+ } from 'utility';
9
11
  import { extend } from 'extend2';
10
- import { Request, Response, Context, Application } from '@eggjs/koa';
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 { ContextDelegation, EggCore, MiddlewareFunc } from '../egg.js';
19
- import { BaseContextClass } from '../base_context_class.js';
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: Context.prototype,
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
- // FIXME: support esm
134
- if (fs.existsSync(tsConfigFile) && typeof require === 'function') {
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('[@eggjs/core:egg_loader] skip register "tsconfig-paths" because tsconfig.json not exists at %s',
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:egg_loader] pkg.eggPlugin is missing in ${pluginPackage}`);
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:egg_loader] pluginName(${plugin.name}) is different from pluginConfigName(${config.name})`);
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
- if (pluginPkg.eggPlugin?.exports) {
800
- if (typeof require === 'function') {
801
- if (pluginPkg.eggPlugin.exports.require) {
802
- pluginPath = path.join(pluginPath, pluginPkg.eggPlugin.exports.require);
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 (pluginPkg.eggPlugin.exports.import) {
806
- pluginPath = path.join(pluginPath, pluginPkg.eggPlugin.exports.import);
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 pluginPath;
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: Record<string, any>;
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: Record<string, any> = {};
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:egg_loader] %s must exports a boot class', bootFilePath);
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: 'lower',
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: 'lower',
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:egg_loader] Use middleware: %s', name);
1278
+ this.options.logger.info('[@eggjs/core/egg_loader] Use middleware: %s', name);
1299
1279
  } else {
1300
- this.options.logger.info('[@eggjs/core:egg_loader] Disable middleware: %s', name);
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:egg_loader] Loaded middleware from %j', middlewarePaths);
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: 'lower',
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:egg_loader] Controller loaded: %s', controllerBase);
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: 'lower',
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: 'lower',
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, options?: FileLoaderOptions) {
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
- options = {
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(options).load();
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, options?: ContextLoaderOptions) {
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(options).load();
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: ContextDelegation, ...args: any[]) {
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: ContextDelegation, ...args: any[]) {
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:file_loader');
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 type CaseStyle = 'camel' | 'lower' | 'upper';
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: 'camel',
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 = 'lower';
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);
@@ -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
+ }
@@ -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:utils');
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:deprecated] %s', message);
58
+ console.trace('[@eggjs/core/deprecated] %s', message);
59
59
  } else {
60
- console.warn('[@eggjs/core:deprecated] %s', message);
61
- console.warn('[@eggjs/core:deprecated] set NODE_DEBUG=@eggjs/core:utils can show call stack');
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);
@@ -1,6 +1,6 @@
1
1
  import { debuglog } from 'node:util';
2
2
 
3
- const debug = debuglog('@eggjs/core:utils:sequencify');
3
+ const debug = debuglog('@eggjs/core/utils/sequencify');
4
4
 
5
5
  export interface SequencifyResult {
6
6
  sequence: string[];
@@ -2,7 +2,7 @@ import { EOL } from 'node:os';
2
2
  import { debuglog } from 'node:util';
3
3
  import assert from 'node:assert';
4
4
 
5
- const debug = debuglog('@eggjs/core:utils:timing');
5
+ const debug = debuglog('@eggjs/core/utils/timing');
6
6
 
7
7
  export interface TimingItem {
8
8
  name: string;