@eggjs/core 6.4.1 → 6.6.0-beta.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/README.md +23 -27
- package/dist/base_context_class.d.ts +21 -0
- package/dist/base_context_class.js +40 -0
- package/dist/egg.d.ts +248 -0
- package/dist/egg.js +358 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/lifecycle.d.ts +84 -0
- package/dist/lifecycle.js +280 -0
- package/dist/loader/context_loader.d.ts +39 -0
- package/dist/loader/context_loader.js +79 -0
- package/dist/loader/egg_loader.d.ts +374 -0
- package/dist/loader/egg_loader.js +1184 -0
- package/dist/loader/file_loader.d.ts +105 -0
- package/dist/loader/file_loader.js +198 -0
- package/dist/singleton.d.ts +33 -0
- package/dist/singleton.js +107 -0
- package/{src/types.ts → dist/types.d.ts} +7 -7
- package/dist/utils/index.d.ts +19 -0
- package/dist/utils/index.js +103 -0
- package/dist/utils/sequencify.d.ts +16 -0
- package/dist/utils/sequencify.js +46 -0
- package/dist/utils/timing.d.ts +24 -0
- package/dist/utils/timing.js +85 -0
- package/package.json +35 -63
- package/dist/commonjs/base_context_class.d.ts +0 -16
- package/dist/commonjs/base_context_class.js +0 -41
- package/dist/commonjs/egg.d.ts +0 -247
- package/dist/commonjs/egg.js +0 -401
- package/dist/commonjs/index.d.ts +0 -12
- package/dist/commonjs/index.js +0 -32
- package/dist/commonjs/lifecycle.d.ts +0 -78
- package/dist/commonjs/lifecycle.js +0 -312
- package/dist/commonjs/loader/context_loader.d.ts +0 -35
- package/dist/commonjs/loader/context_loader.js +0 -110
- package/dist/commonjs/loader/egg_loader.d.ts +0 -369
- package/dist/commonjs/loader/egg_loader.js +0 -1543
- package/dist/commonjs/loader/file_loader.d.ts +0 -100
- package/dist/commonjs/loader/file_loader.js +0 -262
- package/dist/commonjs/package.json +0 -3
- package/dist/commonjs/singleton.d.ts +0 -29
- package/dist/commonjs/singleton.js +0 -125
- package/dist/commonjs/types.d.ts +0 -53
- package/dist/commonjs/types.js +0 -3
- package/dist/commonjs/utils/index.d.ts +0 -17
- package/dist/commonjs/utils/index.js +0 -117
- package/dist/commonjs/utils/sequencify.d.ts +0 -13
- package/dist/commonjs/utils/sequencify.js +0 -59
- package/dist/commonjs/utils/timing.d.ts +0 -21
- package/dist/commonjs/utils/timing.js +0 -100
- package/dist/esm/base_context_class.d.ts +0 -16
- package/dist/esm/base_context_class.js +0 -37
- package/dist/esm/egg.d.ts +0 -247
- package/dist/esm/egg.js +0 -388
- package/dist/esm/index.d.ts +0 -12
- package/dist/esm/index.js +0 -12
- package/dist/esm/lifecycle.d.ts +0 -78
- package/dist/esm/lifecycle.js +0 -305
- package/dist/esm/loader/context_loader.d.ts +0 -35
- package/dist/esm/loader/context_loader.js +0 -102
- package/dist/esm/loader/egg_loader.d.ts +0 -369
- package/dist/esm/loader/egg_loader.js +0 -1536
- package/dist/esm/loader/file_loader.d.ts +0 -100
- package/dist/esm/loader/file_loader.js +0 -255
- package/dist/esm/package.json +0 -3
- package/dist/esm/singleton.d.ts +0 -29
- package/dist/esm/singleton.js +0 -118
- package/dist/esm/types.d.ts +0 -53
- package/dist/esm/types.js +0 -2
- package/dist/esm/utils/index.d.ts +0 -17
- package/dist/esm/utils/index.js +0 -112
- package/dist/esm/utils/sequencify.d.ts +0 -13
- package/dist/esm/utils/sequencify.js +0 -56
- package/dist/esm/utils/timing.d.ts +0 -21
- package/dist/esm/utils/timing.js +0 -93
- package/dist/package.json +0 -4
- package/src/base_context_class.ts +0 -39
- package/src/egg.ts +0 -517
- package/src/index.ts +0 -14
- package/src/lifecycle.ts +0 -399
- package/src/loader/context_loader.ts +0 -121
- package/src/loader/egg_loader.ts +0 -1722
- package/src/loader/file_loader.ts +0 -308
- package/src/singleton.ts +0 -149
- package/src/utils/index.ts +0 -125
- package/src/utils/sequencify.ts +0 -70
- package/src/utils/timing.ts +0 -114
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Fun } from "../utils/index.js";
|
|
2
|
+
|
|
3
|
+
//#region src/loader/file_loader.d.ts
|
|
4
|
+
declare const FULLPATH: unique symbol;
|
|
5
|
+
declare const EXPORTS: unique symbol;
|
|
6
|
+
declare const CaseStyle: {
|
|
7
|
+
readonly camel: "camel";
|
|
8
|
+
readonly lower: "lower";
|
|
9
|
+
readonly upper: "upper";
|
|
10
|
+
};
|
|
11
|
+
type CaseStyle = (typeof CaseStyle)[keyof typeof CaseStyle];
|
|
12
|
+
type CaseStyleFunction = (filepath: string) => string[];
|
|
13
|
+
type FileLoaderInitializer = (exports: unknown, options: {
|
|
14
|
+
path: string;
|
|
15
|
+
pathName: string;
|
|
16
|
+
}) => unknown;
|
|
17
|
+
type FileLoaderFilter = (exports: unknown) => boolean;
|
|
18
|
+
interface FileLoaderOptions {
|
|
19
|
+
/** directories to be loaded */
|
|
20
|
+
directory: string | string[];
|
|
21
|
+
/** attach the target object from loaded files */
|
|
22
|
+
target: Record<string, any>;
|
|
23
|
+
/** match the files when load, support glob, default to all js files */
|
|
24
|
+
match?: string | string[];
|
|
25
|
+
/** ignore the files when load, support glob */
|
|
26
|
+
ignore?: string | string[];
|
|
27
|
+
/** custom file exports, receive two parameters, first is the inject object(if not js file, will be content buffer), second is an `options` object that contain `path` */
|
|
28
|
+
initializer?: FileLoaderInitializer;
|
|
29
|
+
/** determine whether invoke when exports is function */
|
|
30
|
+
call?: boolean;
|
|
31
|
+
/** determine whether override the property when get the same name */
|
|
32
|
+
override?: boolean;
|
|
33
|
+
/** an object that be the argument when invoke the function */
|
|
34
|
+
inject?: Record<string, any>;
|
|
35
|
+
/** a function that filter the exports which can be loaded */
|
|
36
|
+
filter?: FileLoaderFilter;
|
|
37
|
+
/** set property's case when converting a filepath to property list. */
|
|
38
|
+
caseStyle?: CaseStyle | CaseStyleFunction;
|
|
39
|
+
lowercaseFirst?: boolean;
|
|
40
|
+
}
|
|
41
|
+
interface FileLoaderParseItem {
|
|
42
|
+
fullpath: string;
|
|
43
|
+
properties: string[];
|
|
44
|
+
exports: object | Fun;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Load files from directory to target object.
|
|
48
|
+
* @since 1.0.0
|
|
49
|
+
*/
|
|
50
|
+
declare class FileLoader {
|
|
51
|
+
static get FULLPATH(): symbol;
|
|
52
|
+
static get EXPORTS(): symbol;
|
|
53
|
+
readonly options: FileLoaderOptions & Required<Pick<FileLoaderOptions, 'caseStyle'>>;
|
|
54
|
+
/**
|
|
55
|
+
* @class
|
|
56
|
+
* @param {Object} options - options
|
|
57
|
+
* @param {String|Array} options.directory - directories to be loaded
|
|
58
|
+
* @param {Object} options.target - attach the target object from loaded files
|
|
59
|
+
* @param {String} options.match - match the files when load, support glob, default to all js files
|
|
60
|
+
* @param {String} options.ignore - ignore the files when load, support glob
|
|
61
|
+
* @param {Function} options.initializer - custom file exports, receive two parameters, first is the inject object(if not js file, will be content buffer), second is an `options` object that contain `path`
|
|
62
|
+
* @param {Boolean} options.call - determine whether invoke when exports is function
|
|
63
|
+
* @param {Boolean} options.override - determine whether override the property when get the same name
|
|
64
|
+
* @param {Object} options.inject - an object that be the argument when invoke the function
|
|
65
|
+
* @param {Function} options.filter - a function that filter the exports which can be loaded
|
|
66
|
+
* @param {String|Function} options.caseStyle - set property's case when converting a filepath to property list.
|
|
67
|
+
*/
|
|
68
|
+
constructor(options: FileLoaderOptions);
|
|
69
|
+
/**
|
|
70
|
+
* attach items to target object. Mapping the directory to properties.
|
|
71
|
+
* `app/controller/group/repository.js` => `target.group.repository`
|
|
72
|
+
* @returns {Object} target
|
|
73
|
+
* @since 1.0.0
|
|
74
|
+
*/
|
|
75
|
+
load(): Promise<object>;
|
|
76
|
+
/**
|
|
77
|
+
* Parse files from given directories, then return an items list, each item contains properties and exports.
|
|
78
|
+
*
|
|
79
|
+
* For example, parse `app/controller/group/repository.js`
|
|
80
|
+
*
|
|
81
|
+
* ```
|
|
82
|
+
* module.exports = app => {
|
|
83
|
+
* return class RepositoryController extends app.Controller {};
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* It returns a item
|
|
88
|
+
*
|
|
89
|
+
* ```
|
|
90
|
+
* {
|
|
91
|
+
* properties: [ 'group', 'repository' ],
|
|
92
|
+
* exports: app => { ... },
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* `Properties` is an array that contains the directory of a filepath.
|
|
97
|
+
*
|
|
98
|
+
* `Exports` depends on type, if exports is a function, it will be called. if initializer is specified, it will be called with exports for customizing.
|
|
99
|
+
* @returns {Array} items
|
|
100
|
+
* @since 1.0.0
|
|
101
|
+
*/
|
|
102
|
+
protected parse(): Promise<FileLoaderParseItem[]>;
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
export { CaseStyle, CaseStyleFunction, EXPORTS, FULLPATH, FileLoader, FileLoaderFilter, FileLoaderInitializer, FileLoaderOptions, FileLoaderParseItem };
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import utils_default from "../utils/index.js";
|
|
2
|
+
import { debuglog } from "node:util";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { isSupportTypeScript } from "@eggjs/utils";
|
|
6
|
+
import assert from "node:assert";
|
|
7
|
+
import { isAsyncFunction, isClass, isGeneratorFunction, isPrimitive } from "is-type-of";
|
|
8
|
+
import globby from "globby";
|
|
9
|
+
|
|
10
|
+
//#region src/loader/file_loader.ts
|
|
11
|
+
const debug = debuglog("egg/core/file_loader");
|
|
12
|
+
const FULLPATH = Symbol("EGG_LOADER_ITEM_FULLPATH");
|
|
13
|
+
const EXPORTS = Symbol("EGG_LOADER_ITEM_EXPORTS");
|
|
14
|
+
const CaseStyle = {
|
|
15
|
+
camel: "camel",
|
|
16
|
+
lower: "lower",
|
|
17
|
+
upper: "upper"
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Load files from directory to target object.
|
|
21
|
+
* @since 1.0.0
|
|
22
|
+
*/
|
|
23
|
+
var FileLoader = class {
|
|
24
|
+
static get FULLPATH() {
|
|
25
|
+
return FULLPATH;
|
|
26
|
+
}
|
|
27
|
+
static get EXPORTS() {
|
|
28
|
+
return EXPORTS;
|
|
29
|
+
}
|
|
30
|
+
options;
|
|
31
|
+
/**
|
|
32
|
+
* @class
|
|
33
|
+
* @param {Object} options - options
|
|
34
|
+
* @param {String|Array} options.directory - directories to be loaded
|
|
35
|
+
* @param {Object} options.target - attach the target object from loaded files
|
|
36
|
+
* @param {String} options.match - match the files when load, support glob, default to all js files
|
|
37
|
+
* @param {String} options.ignore - ignore the files when load, support glob
|
|
38
|
+
* @param {Function} options.initializer - custom file exports, receive two parameters, first is the inject object(if not js file, will be content buffer), second is an `options` object that contain `path`
|
|
39
|
+
* @param {Boolean} options.call - determine whether invoke when exports is function
|
|
40
|
+
* @param {Boolean} options.override - determine whether override the property when get the same name
|
|
41
|
+
* @param {Object} options.inject - an object that be the argument when invoke the function
|
|
42
|
+
* @param {Function} options.filter - a function that filter the exports which can be loaded
|
|
43
|
+
* @param {String|Function} options.caseStyle - set property's case when converting a filepath to property list.
|
|
44
|
+
*/
|
|
45
|
+
constructor(options) {
|
|
46
|
+
assert(options.directory, "options.directory is required");
|
|
47
|
+
assert(options.target, "options.target is required");
|
|
48
|
+
this.options = {
|
|
49
|
+
caseStyle: CaseStyle.camel,
|
|
50
|
+
call: true,
|
|
51
|
+
override: false,
|
|
52
|
+
...options
|
|
53
|
+
};
|
|
54
|
+
if (this.options.lowercaseFirst === true) {
|
|
55
|
+
utils_default.deprecated("lowercaseFirst is deprecated, use caseStyle instead");
|
|
56
|
+
this.options.caseStyle = CaseStyle.lower;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* attach items to target object. Mapping the directory to properties.
|
|
61
|
+
* `app/controller/group/repository.js` => `target.group.repository`
|
|
62
|
+
* @returns {Object} target
|
|
63
|
+
* @since 1.0.0
|
|
64
|
+
*/
|
|
65
|
+
async load() {
|
|
66
|
+
const items = await this.parse();
|
|
67
|
+
const target = this.options.target;
|
|
68
|
+
for (const item of items) {
|
|
69
|
+
debug("[load] loading item: fullpath: %s, properties: %o", item.fullpath, item.properties);
|
|
70
|
+
item.properties.reduce((target$1, property, index) => {
|
|
71
|
+
let obj;
|
|
72
|
+
const properties = item.properties.slice(0, index + 1).join(".");
|
|
73
|
+
if (index === item.properties.length - 1) {
|
|
74
|
+
if (property in target$1 && !this.options.override) throw new Error(`can't overwrite property '${properties}' from ${target$1[property][FULLPATH]} by ${item.fullpath}`);
|
|
75
|
+
obj = item.exports;
|
|
76
|
+
if (obj && !isPrimitive(obj)) {
|
|
77
|
+
Reflect.set(obj, FULLPATH, item.fullpath);
|
|
78
|
+
Reflect.set(obj, EXPORTS, true);
|
|
79
|
+
}
|
|
80
|
+
} else obj = target$1[property] || {};
|
|
81
|
+
target$1[property] = obj;
|
|
82
|
+
if (debug.enabled) debug("[load] loaded item properties: %o => keys: %j, index: %d", properties, Object.keys(obj), index);
|
|
83
|
+
return obj;
|
|
84
|
+
}, target);
|
|
85
|
+
}
|
|
86
|
+
return target;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Parse files from given directories, then return an items list, each item contains properties and exports.
|
|
90
|
+
*
|
|
91
|
+
* For example, parse `app/controller/group/repository.js`
|
|
92
|
+
*
|
|
93
|
+
* ```
|
|
94
|
+
* module.exports = app => {
|
|
95
|
+
* return class RepositoryController extends app.Controller {};
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* It returns a item
|
|
100
|
+
*
|
|
101
|
+
* ```
|
|
102
|
+
* {
|
|
103
|
+
* properties: [ 'group', 'repository' ],
|
|
104
|
+
* exports: app => { ... },
|
|
105
|
+
* }
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* `Properties` is an array that contains the directory of a filepath.
|
|
109
|
+
*
|
|
110
|
+
* `Exports` depends on type, if exports is a function, it will be called. if initializer is specified, it will be called with exports for customizing.
|
|
111
|
+
* @returns {Array} items
|
|
112
|
+
* @since 1.0.0
|
|
113
|
+
*/
|
|
114
|
+
async parse() {
|
|
115
|
+
let files = this.options.match;
|
|
116
|
+
if (files) files = Array.isArray(files) ? files : [files];
|
|
117
|
+
else files = isSupportTypeScript() ? ["**/*.(js|ts)", "!**/*.d.ts"] : ["**/*.js"];
|
|
118
|
+
let ignore = this.options.ignore;
|
|
119
|
+
if (ignore) {
|
|
120
|
+
ignore = Array.isArray(ignore) ? ignore : [ignore];
|
|
121
|
+
ignore = ignore.filter((f) => !!f).map((f) => "!" + f);
|
|
122
|
+
files = files.concat(ignore);
|
|
123
|
+
}
|
|
124
|
+
let directories = this.options.directory;
|
|
125
|
+
if (!Array.isArray(directories)) directories = [directories];
|
|
126
|
+
const filter = typeof this.options.filter === "function" ? this.options.filter : null;
|
|
127
|
+
const items = [];
|
|
128
|
+
debug("[parse] parsing directories: %j", directories);
|
|
129
|
+
for (const directory of directories) {
|
|
130
|
+
const filepaths = globby.sync(files, { cwd: directory });
|
|
131
|
+
debug("[parse] globby files: %o, cwd: %o => %o", files, directory, filepaths);
|
|
132
|
+
for (const filepath of filepaths) {
|
|
133
|
+
const fullpath = path.join(directory, filepath);
|
|
134
|
+
if (!fs.statSync(fullpath).isFile()) continue;
|
|
135
|
+
if (filepath.endsWith(".js")) {
|
|
136
|
+
const filepathTs = filepath.replace(/\.js$/, ".ts");
|
|
137
|
+
if (filepaths.includes(filepathTs)) {
|
|
138
|
+
debug("[parse] ignore %s, because %s exists", fullpath, filepathTs);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const properties = getProperties(filepath, this.options.caseStyle);
|
|
143
|
+
const pathName = directory.split(/[/\\]/).slice(-1) + "." + properties.join(".");
|
|
144
|
+
const exports = await getExports(fullpath, this.options, pathName);
|
|
145
|
+
if (exports === null || exports === void 0 || filter && filter(exports) === false) continue;
|
|
146
|
+
if (isClass(exports)) {
|
|
147
|
+
exports.prototype.pathName = pathName;
|
|
148
|
+
exports.prototype.fullPath = fullpath;
|
|
149
|
+
}
|
|
150
|
+
items.push({
|
|
151
|
+
fullpath,
|
|
152
|
+
properties,
|
|
153
|
+
exports
|
|
154
|
+
});
|
|
155
|
+
debug("[parse] parse %s, properties %j, exports %o", fullpath, properties, exports);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return items;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
function getProperties(filepath, caseStyle) {
|
|
162
|
+
if (typeof caseStyle === "function") {
|
|
163
|
+
const result = caseStyle(filepath);
|
|
164
|
+
assert(Array.isArray(result), `caseStyle expect an array, but got ${JSON.stringify(result)}`);
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
return defaultCamelize(filepath, caseStyle);
|
|
168
|
+
}
|
|
169
|
+
async function getExports(fullpath, options, pathName) {
|
|
170
|
+
let exports = await utils_default.loadFile(fullpath);
|
|
171
|
+
if (options.initializer) {
|
|
172
|
+
exports = options.initializer(exports, {
|
|
173
|
+
path: fullpath,
|
|
174
|
+
pathName
|
|
175
|
+
});
|
|
176
|
+
debug("[getExports] after initializer => %o", exports);
|
|
177
|
+
}
|
|
178
|
+
if (isGeneratorFunction(exports)) throw new TypeError(`Support for generators was removed, fullpath: ${fullpath}`);
|
|
179
|
+
if (isClass(exports) || isAsyncFunction(exports)) return exports;
|
|
180
|
+
if (options.call && typeof exports === "function") {
|
|
181
|
+
exports = exports(options.inject);
|
|
182
|
+
if (exports !== null && exports !== void 0) return exports;
|
|
183
|
+
}
|
|
184
|
+
return exports;
|
|
185
|
+
}
|
|
186
|
+
function defaultCamelize(filepath, caseStyle) {
|
|
187
|
+
return filepath.slice(0, filepath.lastIndexOf(".")).split("/").map((property) => {
|
|
188
|
+
if (!/^[a-z][a-z0-9_-]*$/i.test(property)) throw new Error(`${property} is not match 'a-z0-9_-' in ${filepath}`);
|
|
189
|
+
property = property.replaceAll(/[_-][a-z]/gi, (s) => s.slice(1).toUpperCase());
|
|
190
|
+
let first = property[0];
|
|
191
|
+
if (caseStyle === CaseStyle.lower) first = first.toLowerCase();
|
|
192
|
+
else if (caseStyle === CaseStyle.upper) first = first.toUpperCase();
|
|
193
|
+
return first + property.slice(1);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
//#endregion
|
|
198
|
+
export { CaseStyle, EXPORTS, FULLPATH, FileLoader };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { EggCore } from "./egg.js";
|
|
2
|
+
|
|
3
|
+
//#region src/singleton.d.ts
|
|
4
|
+
type SingletonCreateMethod = (config: Record<string, any>, app: EggCore, clientName: string) => unknown | Promise<unknown>;
|
|
5
|
+
interface SingletonOptions {
|
|
6
|
+
name: string;
|
|
7
|
+
app: EggCore;
|
|
8
|
+
create: SingletonCreateMethod;
|
|
9
|
+
}
|
|
10
|
+
declare class Singleton<T = any> {
|
|
11
|
+
#private;
|
|
12
|
+
readonly clients: Map<string, T>;
|
|
13
|
+
readonly app: EggCore;
|
|
14
|
+
readonly create: SingletonCreateMethod;
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly options: Record<string, any>;
|
|
17
|
+
constructor(options: SingletonOptions);
|
|
18
|
+
init(): void | Promise<void>;
|
|
19
|
+
initSync(): void;
|
|
20
|
+
initAsync(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated please use `getSingletonInstance(id)` instead
|
|
23
|
+
*/
|
|
24
|
+
get(id: string): T;
|
|
25
|
+
/**
|
|
26
|
+
* Get singleton instance by id
|
|
27
|
+
*/
|
|
28
|
+
getSingletonInstance(id: string): T;
|
|
29
|
+
createInstance(config: Record<string, any>, clientName: string): T;
|
|
30
|
+
createInstanceAsync(config: Record<string, any>, clientName: string): Promise<T>;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { Singleton, SingletonCreateMethod, SingletonOptions };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { isAsyncFunction } from "is-type-of";
|
|
3
|
+
|
|
4
|
+
//#region src/singleton.ts
|
|
5
|
+
var Singleton = class {
|
|
6
|
+
clients = /* @__PURE__ */ new Map();
|
|
7
|
+
app;
|
|
8
|
+
create;
|
|
9
|
+
name;
|
|
10
|
+
options;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
assert(options.name, "[egg/core/singleton] Singleton#constructor options.name is required");
|
|
13
|
+
assert(options.app, "[egg/core/singleton] Singleton#constructor options.app is required");
|
|
14
|
+
assert(options.create, "[egg/core/singleton] Singleton#constructor options.create is required");
|
|
15
|
+
assert(!(options.name in options.app), `[egg/core/singleton] ${options.name} is already exists in app`);
|
|
16
|
+
this.app = options.app;
|
|
17
|
+
this.name = options.name;
|
|
18
|
+
this.create = options.create;
|
|
19
|
+
this.options = options.app.config[this.name] ?? {};
|
|
20
|
+
}
|
|
21
|
+
init() {
|
|
22
|
+
return isAsyncFunction(this.create) ? this.initAsync() : this.initSync();
|
|
23
|
+
}
|
|
24
|
+
initSync() {
|
|
25
|
+
const options = this.options;
|
|
26
|
+
assert(!(options.client && options.clients), `[egg/core/singleton] ${this.name} can not set options.client and options.clients both`);
|
|
27
|
+
if (options.client) {
|
|
28
|
+
const client = this.createInstance(options.client, options.name);
|
|
29
|
+
this.#setClientToApp(client);
|
|
30
|
+
this.#extendDynamicMethods(client);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (options.clients) {
|
|
34
|
+
for (const id of Object.keys(options.clients)) {
|
|
35
|
+
const client = this.createInstance(options.clients[id], id);
|
|
36
|
+
this.clients.set(id, client);
|
|
37
|
+
}
|
|
38
|
+
this.#setClientToApp(this);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this.#setClientToApp(this);
|
|
42
|
+
}
|
|
43
|
+
async initAsync() {
|
|
44
|
+
const options = this.options;
|
|
45
|
+
assert(!(options.client && options.clients), `[egg/core/singleton] ${this.name} can not set options.client and options.clients both`);
|
|
46
|
+
if (options.client) {
|
|
47
|
+
const client = await this.createInstanceAsync(options.client, options.name);
|
|
48
|
+
this.#setClientToApp(client);
|
|
49
|
+
this.#extendDynamicMethods(client);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (options.clients) {
|
|
53
|
+
await Promise.all(Object.keys(options.clients).map((id) => {
|
|
54
|
+
return this.createInstanceAsync(options.clients[id], id).then((client) => this.clients.set(id, client));
|
|
55
|
+
}));
|
|
56
|
+
this.#setClientToApp(this);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this.#setClientToApp(this);
|
|
60
|
+
}
|
|
61
|
+
#setClientToApp(client) {
|
|
62
|
+
Reflect.set(this.app, this.name, client);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* @deprecated please use `getSingletonInstance(id)` instead
|
|
66
|
+
*/
|
|
67
|
+
get(id) {
|
|
68
|
+
return this.clients.get(id);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get singleton instance by id
|
|
72
|
+
*/
|
|
73
|
+
getSingletonInstance(id) {
|
|
74
|
+
return this.clients.get(id);
|
|
75
|
+
}
|
|
76
|
+
createInstance(config, clientName) {
|
|
77
|
+
assert(!isAsyncFunction(this.create), `[egg/core/singleton] ${this.name} only support asynchronous creation, please use createInstanceAsync`);
|
|
78
|
+
config = {
|
|
79
|
+
...this.options.default,
|
|
80
|
+
...config
|
|
81
|
+
};
|
|
82
|
+
return this.create(config, this.app, clientName);
|
|
83
|
+
}
|
|
84
|
+
async createInstanceAsync(config, clientName) {
|
|
85
|
+
config = {
|
|
86
|
+
...this.options.default,
|
|
87
|
+
...config
|
|
88
|
+
};
|
|
89
|
+
return await this.create(config, this.app, clientName);
|
|
90
|
+
}
|
|
91
|
+
#extendDynamicMethods(client) {
|
|
92
|
+
assert(!client.createInstance, "[egg/core/singleton] singleton instance should not have createInstance method");
|
|
93
|
+
assert(!client.createInstanceAsync, "[egg/core/singleton] singleton instance should not have createInstanceAsync method");
|
|
94
|
+
try {
|
|
95
|
+
let extendable = client;
|
|
96
|
+
if (!Object.isExtensible(client) || Object.isFrozen(client)) extendable = client.__proto__ || client;
|
|
97
|
+
extendable.createInstance = this.createInstance.bind(this);
|
|
98
|
+
extendable.createInstanceAsync = this.createInstanceAsync.bind(this);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
this.app.coreLogger.warn("[egg/core/singleton] %s dynamic create is disabled because of client is un-extendable", this.name);
|
|
101
|
+
this.app.coreLogger.warn(err);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
//#endregion
|
|
107
|
+
export { Singleton };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
interface EggAppInfo {
|
|
2
3
|
/** package.json */
|
|
3
4
|
pkg: Record<string, any>;
|
|
4
5
|
/** the application name from package.json */
|
|
@@ -14,8 +15,7 @@ export interface EggAppInfo {
|
|
|
14
15
|
/** baseDir when local and unittest, HOME when other environment */
|
|
15
16
|
root: string;
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
-
export interface EggPluginInfo {
|
|
18
|
+
interface EggPluginInfo {
|
|
19
19
|
/** the plugin name, it can be used in `dep` */
|
|
20
20
|
name: string;
|
|
21
21
|
/** the package name of plugin */
|
|
@@ -36,8 +36,7 @@ export interface EggPluginInfo {
|
|
|
36
36
|
/** the file plugin config in. */
|
|
37
37
|
from: string;
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
export interface CustomLoaderConfigItem {
|
|
39
|
+
interface CustomLoaderConfigItem {
|
|
41
40
|
/** the directory of the custom loader */
|
|
42
41
|
directory: string;
|
|
43
42
|
/** the inject object, it can be app or ctx */
|
|
@@ -45,8 +44,7 @@ export interface CustomLoaderConfigItem {
|
|
|
45
44
|
/** whether load unit files */
|
|
46
45
|
loadunit?: boolean;
|
|
47
46
|
}
|
|
48
|
-
|
|
49
|
-
export interface EggAppConfig extends Record<string, any> {
|
|
47
|
+
interface EggAppConfig extends Record<string, any> {
|
|
50
48
|
coreMiddleware: string[];
|
|
51
49
|
middleware: string[];
|
|
52
50
|
customLoader?: Record<string, CustomLoaderConfigItem>;
|
|
@@ -54,3 +52,5 @@ export interface EggAppConfig extends Record<string, any> {
|
|
|
54
52
|
supportParams?: boolean;
|
|
55
53
|
};
|
|
56
54
|
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { CustomLoaderConfigItem, EggAppConfig, EggAppInfo, EggPluginInfo };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/utils/index.d.ts
|
|
2
|
+
type Fun = (...args: unknown[]) => unknown;
|
|
3
|
+
declare function getCalleeFromStack(withLine?: boolean, stackIndex?: number): string;
|
|
4
|
+
declare const _default: {
|
|
5
|
+
deprecated(message: string): void;
|
|
6
|
+
extensions: any;
|
|
7
|
+
extensionNames: string[];
|
|
8
|
+
existsPath(filepath: string): Promise<boolean>;
|
|
9
|
+
loadFile(filepath: string): Promise<any>;
|
|
10
|
+
resolvePath(filepath: string, options?: {
|
|
11
|
+
paths?: string[];
|
|
12
|
+
}): string;
|
|
13
|
+
methods: string[];
|
|
14
|
+
callFn(fn: Fun, args?: unknown[], ctx?: unknown): Promise<unknown>;
|
|
15
|
+
getCalleeFromStack: typeof getCalleeFromStack;
|
|
16
|
+
getResolvedFilename(filepath: string, baseDir: string): string;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { Fun, _default };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import BuiltinModule from "node:module";
|
|
2
|
+
import { debuglog } from "node:util";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { stat } from "node:fs/promises";
|
|
6
|
+
import { importModule, importResolve } from "@eggjs/utils";
|
|
7
|
+
|
|
8
|
+
//#region src/utils/index.ts
|
|
9
|
+
const debug = debuglog("egg/core/utils");
|
|
10
|
+
const extensions = (typeof module !== "undefined" && module.constructor.length > 1 ? module.constructor : BuiltinModule)._extensions;
|
|
11
|
+
const extensionNames = Object.keys(extensions).concat([".cjs", ".mjs"]);
|
|
12
|
+
debug("Module extensions: %j", extensionNames);
|
|
13
|
+
function getCalleeFromStack(withLine, stackIndex) {
|
|
14
|
+
stackIndex = stackIndex === void 0 ? 2 : stackIndex;
|
|
15
|
+
const limit = Error.stackTraceLimit;
|
|
16
|
+
const prep = Error.prepareStackTrace;
|
|
17
|
+
Error.prepareStackTrace = prepareObjectStackTrace;
|
|
18
|
+
Error.stackTraceLimit = 5;
|
|
19
|
+
const obj = {};
|
|
20
|
+
Error.captureStackTrace(obj);
|
|
21
|
+
let callSite = obj.stack[stackIndex];
|
|
22
|
+
let fileName = "";
|
|
23
|
+
if (callSite) {
|
|
24
|
+
fileName = callSite.getFileName();
|
|
25
|
+
/* istanbul ignore if */
|
|
26
|
+
if (fileName && fileName.endsWith("egg-mock/lib/app.js")) {
|
|
27
|
+
callSite = obj.stack[stackIndex + 1];
|
|
28
|
+
fileName = callSite.getFileName();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
Error.prepareStackTrace = prep;
|
|
32
|
+
Error.stackTraceLimit = limit;
|
|
33
|
+
if (!callSite || !fileName) return "<anonymous>";
|
|
34
|
+
if (!withLine) return fileName;
|
|
35
|
+
return `${fileName}:${callSite.getLineNumber()}:${callSite.getColumnNumber()}`;
|
|
36
|
+
}
|
|
37
|
+
var utils_default = {
|
|
38
|
+
deprecated(message) {
|
|
39
|
+
if (debug.enabled) console.trace("[@eggjs/core/deprecated] %s", message);
|
|
40
|
+
else {
|
|
41
|
+
console.log("[@eggjs/core/deprecated] %s", message);
|
|
42
|
+
console.log("[@eggjs/core/deprecated] set NODE_DEBUG=@eggjs/core/utils can show call stack");
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
extensions,
|
|
46
|
+
extensionNames,
|
|
47
|
+
async existsPath(filepath) {
|
|
48
|
+
try {
|
|
49
|
+
await stat(filepath);
|
|
50
|
+
return true;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
async loadFile(filepath) {
|
|
56
|
+
debug("[loadFile:start] filepath: %s", filepath);
|
|
57
|
+
try {
|
|
58
|
+
const extname = path.extname(filepath);
|
|
59
|
+
if (extname && !extensionNames.includes(extname) && extname !== ".ts") return fs.readFileSync(filepath);
|
|
60
|
+
return await importModule(filepath, { importDefaultOnly: true });
|
|
61
|
+
} catch (e) {
|
|
62
|
+
if (!(e instanceof Error)) {
|
|
63
|
+
console.trace(e);
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
const err = /* @__PURE__ */ new Error(`[egg/core] load file: ${filepath}, error: ${e.message}`);
|
|
67
|
+
err.cause = e;
|
|
68
|
+
debug("[loadFile] handle %s error: %s", filepath, e);
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
resolvePath(filepath, options) {
|
|
73
|
+
return importResolve(filepath, options);
|
|
74
|
+
},
|
|
75
|
+
methods: [
|
|
76
|
+
"head",
|
|
77
|
+
"options",
|
|
78
|
+
"get",
|
|
79
|
+
"put",
|
|
80
|
+
"patch",
|
|
81
|
+
"post",
|
|
82
|
+
"delete"
|
|
83
|
+
],
|
|
84
|
+
async callFn(fn, args, ctx) {
|
|
85
|
+
args = args || [];
|
|
86
|
+
if (typeof fn !== "function") return;
|
|
87
|
+
return ctx ? fn.call(ctx, ...args) : fn(...args);
|
|
88
|
+
},
|
|
89
|
+
getCalleeFromStack,
|
|
90
|
+
getResolvedFilename(filepath, baseDir) {
|
|
91
|
+
return filepath.replace(baseDir + path.sep, "").replace(/[/\\]/g, "/");
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Capture call site stack from v8.
|
|
96
|
+
* https://github.com/v8/v8/wiki/Stack-Trace-API
|
|
97
|
+
*/
|
|
98
|
+
function prepareObjectStackTrace(_obj, stack) {
|
|
99
|
+
return stack;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
export { utils_default as default };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/utils/sequencify.d.ts
|
|
2
|
+
interface SequencifyResult {
|
|
3
|
+
sequence: string[];
|
|
4
|
+
requires: Record<string, true>;
|
|
5
|
+
}
|
|
6
|
+
interface SequencifyTask {
|
|
7
|
+
dependencies: string[];
|
|
8
|
+
optionalDependencies: string[];
|
|
9
|
+
}
|
|
10
|
+
declare function sequencify(tasks: Record<string, SequencifyTask>, names: string[]): {
|
|
11
|
+
sequence: string[];
|
|
12
|
+
missingTasks: string[];
|
|
13
|
+
recursiveDependencies: string[];
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { SequencifyResult, SequencifyTask, sequencify };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { debuglog } from "node:util";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/sequencify.ts
|
|
4
|
+
const debug = debuglog("egg/core/utils/sequencify");
|
|
5
|
+
function sequence(tasks, names, result, missing, recursive, nest, optional, parent) {
|
|
6
|
+
for (const name of names) {
|
|
7
|
+
if (result.requires[name]) continue;
|
|
8
|
+
const node = tasks[name];
|
|
9
|
+
if (!node) {
|
|
10
|
+
if (optional === true) continue;
|
|
11
|
+
missing.push(name);
|
|
12
|
+
} else if (nest.includes(name)) {
|
|
13
|
+
nest.push(name);
|
|
14
|
+
recursive.push(...nest.slice(0));
|
|
15
|
+
nest.pop();
|
|
16
|
+
} else if (node.dependencies.length > 0 || node.optionalDependencies.length > 0) {
|
|
17
|
+
nest.push(name);
|
|
18
|
+
if (node.dependencies.length > 0) sequence(tasks, node.dependencies, result, missing, recursive, nest, optional, name);
|
|
19
|
+
if (node.optionalDependencies.length > 0) sequence(tasks, node.optionalDependencies, result, missing, recursive, nest, true, name);
|
|
20
|
+
nest.pop();
|
|
21
|
+
}
|
|
22
|
+
if (!optional) {
|
|
23
|
+
result.requires[name] = true;
|
|
24
|
+
debug("task: %s is enabled by %s", name, parent);
|
|
25
|
+
}
|
|
26
|
+
if (!result.sequence.includes(name)) result.sequence.push(name);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function sequencify(tasks, names) {
|
|
30
|
+
const result = {
|
|
31
|
+
sequence: [],
|
|
32
|
+
requires: {}
|
|
33
|
+
};
|
|
34
|
+
const missing = [];
|
|
35
|
+
const recursive = [];
|
|
36
|
+
sequence(tasks, names, result, missing, recursive, [], false, "app");
|
|
37
|
+
if (missing.length > 0 || recursive.length > 0) result.sequence = [];
|
|
38
|
+
return {
|
|
39
|
+
sequence: result.sequence.filter((item) => result.requires[item]),
|
|
40
|
+
missingTasks: missing,
|
|
41
|
+
recursiveDependencies: recursive
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { sequencify };
|