typespeed 2.4.10 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/test.yml +6 -6
- package/CHANGELOG.md +75 -0
- package/README.md +57 -15
- package/app/src/test-bind.class.ts +41 -0
- package/dist/bind.decorator.js +50 -0
- package/dist/core.decorator.js +113 -26
- package/dist/database.decorator.js +122 -16
- package/dist/decorator-utils.js +42 -0
- package/dist/route.decorator.js +175 -10
- package/dist/typespeed.d.ts +28 -24
- package/dist/typespeed.js +37 -8
- package/introduction/decorator-next/dist/main.js +116 -0
- package/introduction/decorator-next/package.json +14 -0
- package/introduction/decorator-next/src/main.ts +64 -0
- package/introduction/decorator-next/tsconfig.json +17 -0
- package/introduction/decorator-next//346/225/231/347/250/213.md +384 -0
- package/introduction/tsconfig.json +3 -0
- package/package.json +2 -2
- package/src/bind.decorator.ts +50 -0
- package/src/core.decorator.ts +111 -25
- package/src/database.decorator.ts +120 -16
- package/src/decorator-utils.ts +53 -0
- package/src/route.decorator.ts +169 -10
- package/src/typespeed.d.ts +28 -24
- package/src/typespeed.ts +38 -7
- package/test/bind.test.ts +28 -0
- package/test/decorator-utils.test.ts +42 -0
- package/test-env/docker-compose.test.yml +31 -0
- package/test-env/mysql-init/init.sql +8 -0
- package/test-env//346/234/254/345/234/260Docker/346/265/213/350/257/225/346/226/271/346/241/210.md +69 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 装饰器双签名感知工具。
|
|
4
|
+
*
|
|
5
|
+
* typespeed 2.5.x 起,装饰器同时支持两套运行时签名:
|
|
6
|
+
* - legacy(experimentalDecorators: true,2.4.x 现状):
|
|
7
|
+
* 类装饰器 (ctor);方法/属性装饰器 (target, key);带 descriptor 的方法 (target, key, descriptor);参数 (target, key, index)
|
|
8
|
+
* - 标准(TC39 装饰器提案,当前 Stage 2.7,experimentalDecorators: false):
|
|
9
|
+
* 统一 (value, context),context 恒为带 kind 字段的对象
|
|
10
|
+
*
|
|
11
|
+
* 装饰器函数本质是普通函数,运行时收到什么签名由「调用方(用户代码)」的编译模式决定,
|
|
12
|
+
* 因此库可以单份源码、运行时按签名形态分流:legacy 分支逻辑与 2.4.x 逐字一致,标准分支为新增。
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.getStdArgs = exports.isStd = void 0;
|
|
16
|
+
/**
|
|
17
|
+
* 判断装饰器收到的运行时参数是否为标准装饰器签名 (value, context)。
|
|
18
|
+
*
|
|
19
|
+
* 判据(与 `2.5.x-新版装饰器使用方案.md` 一致):
|
|
20
|
+
* 参数个数 === 2 且第二个参数是「带 string 类型 kind 字段」的对象。
|
|
21
|
+
*
|
|
22
|
+
* 为什么这个判据可靠:
|
|
23
|
+
* - legacy 类装饰器只有 1 参 → 不满足 length === 2
|
|
24
|
+
* - legacy 方法/属性装饰器第二参是 string 的 key → 不是对象
|
|
25
|
+
* - legacy 带 descriptor 的方法 / 参数装饰器是 3 参 → 不满足 length === 2
|
|
26
|
+
* - 标准模式第二参恒是带 kind 的对象 → 命中
|
|
27
|
+
*/
|
|
28
|
+
function isStd(args) {
|
|
29
|
+
return args.length === 2
|
|
30
|
+
&& typeof args[1] === "object"
|
|
31
|
+
&& args[1] !== null
|
|
32
|
+
&& typeof args[1].kind === "string";
|
|
33
|
+
}
|
|
34
|
+
exports.isStd = isStd;
|
|
35
|
+
/**
|
|
36
|
+
* 从标准装饰器参数中解出 (value, context)。
|
|
37
|
+
* 仅应在 isStd(args) 为 true 时调用。
|
|
38
|
+
*/
|
|
39
|
+
function getStdArgs(args) {
|
|
40
|
+
return [args[0], args[1]];
|
|
41
|
+
}
|
|
42
|
+
exports.getStdArgs = getStdArgs;
|
package/dist/route.decorator.js
CHANGED
|
@@ -4,6 +4,8 @@ exports.jwt = exports.upload = exports.setRouter = exports.requestMapping = expo
|
|
|
4
4
|
const multiparty = require("multiparty");
|
|
5
5
|
const express_jwt_1 = require("express-jwt");
|
|
6
6
|
const core_decorator_1 = require("./core.decorator");
|
|
7
|
+
const decorator_utils_1 = require("./decorator-utils");
|
|
8
|
+
const bind_decorator_1 = require("./bind.decorator");
|
|
7
9
|
const routerMapper = {
|
|
8
10
|
"get": {},
|
|
9
11
|
"post": {},
|
|
@@ -28,7 +30,53 @@ function setRouter(app) {
|
|
|
28
30
|
}
|
|
29
31
|
exports.setRouter = setRouter;
|
|
30
32
|
function mapperFunction(method, value) {
|
|
31
|
-
return (
|
|
33
|
+
return (...args) => {
|
|
34
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
35
|
+
const [methodFn, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
36
|
+
ctx.addInitializer(function () {
|
|
37
|
+
const className = this.constructor.name;
|
|
38
|
+
const propertyKey = String(ctx.name);
|
|
39
|
+
applyRouteBind(className, propertyKey, methodFn);
|
|
40
|
+
routerMapper[method][value] = {
|
|
41
|
+
"path": value,
|
|
42
|
+
"name": [className, propertyKey].toString(),
|
|
43
|
+
"target": this.constructor,
|
|
44
|
+
"propertyKey": propertyKey,
|
|
45
|
+
"invoker": async (req, res, next) => {
|
|
46
|
+
const routerBean = (0, core_decorator_1.getComponent)(this.constructor);
|
|
47
|
+
try {
|
|
48
|
+
let paramTotal = routerBean[propertyKey].length;
|
|
49
|
+
if (routerParamsTotal[[className, propertyKey].toString()]) {
|
|
50
|
+
paramTotal = Math.max(paramTotal, routerParamsTotal[[className, propertyKey].toString()]);
|
|
51
|
+
}
|
|
52
|
+
const callArgs = [req, res, next];
|
|
53
|
+
if (paramTotal > 0) {
|
|
54
|
+
for (let i = 0; i < paramTotal; i++) {
|
|
55
|
+
if (routerParams[[className, propertyKey, i].toString()]) {
|
|
56
|
+
callArgs[i] = routerParams[[className, propertyKey, i].toString()](req, res, next);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const testResult = await routerBean[propertyKey].apply(routerBean, callArgs);
|
|
61
|
+
if (typeof testResult === "object") {
|
|
62
|
+
res.json(testResult);
|
|
63
|
+
}
|
|
64
|
+
else if (typeof testResult !== "undefined") {
|
|
65
|
+
res.send(testResult);
|
|
66
|
+
}
|
|
67
|
+
return testResult;
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
next(err);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const target = args[0];
|
|
78
|
+
const propertyKey = args[1];
|
|
79
|
+
applyRouteBind(target.constructor.name, propertyKey, target[propertyKey]);
|
|
32
80
|
routerMapper[method][value] = {
|
|
33
81
|
"path": value,
|
|
34
82
|
"name": [target.constructor.name, propertyKey].toString(),
|
|
@@ -65,7 +113,22 @@ function mapperFunction(method, value) {
|
|
|
65
113
|
};
|
|
66
114
|
};
|
|
67
115
|
}
|
|
68
|
-
function upload(
|
|
116
|
+
function upload(...args) {
|
|
117
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
118
|
+
const [, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
119
|
+
ctx.addInitializer(function () {
|
|
120
|
+
const key = [this.constructor.name, String(ctx.name)].toString();
|
|
121
|
+
if (routerMiddleware[key]) {
|
|
122
|
+
routerMiddleware[key].push(uploadMiddleware);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
routerMiddleware[key] = [uploadMiddleware];
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const target = args[0];
|
|
131
|
+
const propertyKey = args[1];
|
|
69
132
|
const key = [target.constructor.name, propertyKey].toString();
|
|
70
133
|
if (routerMiddleware[key]) {
|
|
71
134
|
routerMiddleware[key].push(uploadMiddleware);
|
|
@@ -83,7 +146,22 @@ function uploadMiddleware(req, res, next) {
|
|
|
83
146
|
});
|
|
84
147
|
}
|
|
85
148
|
function jwt(jwtConfig) {
|
|
86
|
-
return (
|
|
149
|
+
return (...args) => {
|
|
150
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
151
|
+
const [, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
152
|
+
ctx.addInitializer(function () {
|
|
153
|
+
const key = [this.constructor.name, String(ctx.name)].toString();
|
|
154
|
+
if (routerMiddleware[key]) {
|
|
155
|
+
routerMiddleware[key].push((0, express_jwt_1.expressjwt)(jwtConfig));
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
routerMiddleware[key] = [(0, express_jwt_1.expressjwt)(jwtConfig)];
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const target = args[0];
|
|
164
|
+
const propertyKey = args[1];
|
|
87
165
|
const key = [target.constructor.name, propertyKey].toString();
|
|
88
166
|
if (routerMiddleware[key]) {
|
|
89
167
|
routerMiddleware[key].push((0, express_jwt_1.expressjwt)(jwtConfig));
|
|
@@ -96,15 +174,34 @@ function jwt(jwtConfig) {
|
|
|
96
174
|
exports.jwt = jwt;
|
|
97
175
|
function before(constructorFunction, methodName) {
|
|
98
176
|
const targetBean = (0, core_decorator_1.getComponent)(constructorFunction);
|
|
99
|
-
return function (
|
|
177
|
+
return function (...args) {
|
|
178
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
179
|
+
const [, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
180
|
+
const hookKey = String(ctx.name);
|
|
181
|
+
ctx.addInitializer(function () {
|
|
182
|
+
const currentMethod = this[methodName];
|
|
183
|
+
if (currentMethod && currentMethod.length > 0) {
|
|
184
|
+
routerParamsTotal[[constructorFunction.name, methodName].toString()] = currentMethod.length;
|
|
185
|
+
}
|
|
186
|
+
Object.assign(this, {
|
|
187
|
+
[methodName]: function (...innerArgs) {
|
|
188
|
+
this[hookKey](...innerArgs);
|
|
189
|
+
return currentMethod.apply(this, innerArgs);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const target = args[0];
|
|
196
|
+
const propertyKey = args[1];
|
|
100
197
|
const currentMethod = targetBean[methodName];
|
|
101
198
|
if (currentMethod.length > 0) {
|
|
102
199
|
routerParamsTotal[[constructorFunction.name, methodName].toString()] = currentMethod.length;
|
|
103
200
|
}
|
|
104
201
|
Object.assign(targetBean, {
|
|
105
|
-
[methodName]: function (...
|
|
106
|
-
target[propertyKey](...
|
|
107
|
-
return currentMethod.apply(targetBean,
|
|
202
|
+
[methodName]: function (...innerArgs) {
|
|
203
|
+
target[propertyKey](...innerArgs);
|
|
204
|
+
return currentMethod.apply(targetBean, innerArgs);
|
|
108
205
|
}
|
|
109
206
|
});
|
|
110
207
|
};
|
|
@@ -112,14 +209,34 @@ function before(constructorFunction, methodName) {
|
|
|
112
209
|
exports.before = before;
|
|
113
210
|
function after(constructorFunction, methodName) {
|
|
114
211
|
const targetBean = (0, core_decorator_1.getComponent)(constructorFunction);
|
|
115
|
-
return function (
|
|
212
|
+
return function (...args) {
|
|
213
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
214
|
+
const [, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
215
|
+
const hookKey = String(ctx.name);
|
|
216
|
+
ctx.addInitializer(function () {
|
|
217
|
+
const currentMethod = this[methodName];
|
|
218
|
+
if (currentMethod && currentMethod.length > 0) {
|
|
219
|
+
routerParamsTotal[[constructorFunction.name, methodName].toString()] = currentMethod.length;
|
|
220
|
+
}
|
|
221
|
+
Object.assign(this, {
|
|
222
|
+
[methodName]: function (...innerArgs) {
|
|
223
|
+
const result = currentMethod.apply(this, innerArgs);
|
|
224
|
+
const afterResult = this[hookKey](result);
|
|
225
|
+
return afterResult !== null && afterResult !== void 0 ? afterResult : result;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const target = args[0];
|
|
232
|
+
const propertyKey = args[1];
|
|
116
233
|
const currentMethod = targetBean[methodName];
|
|
117
234
|
if (currentMethod.length > 0) {
|
|
118
235
|
routerParamsTotal[[constructorFunction.name, methodName].toString()] = currentMethod.length;
|
|
119
236
|
}
|
|
120
237
|
Object.assign(targetBean, {
|
|
121
|
-
[methodName]: function (...
|
|
122
|
-
const result = currentMethod.apply(targetBean,
|
|
238
|
+
[methodName]: function (...innerArgs) {
|
|
239
|
+
const result = currentMethod.apply(targetBean, innerArgs);
|
|
123
240
|
const afterResult = target[propertyKey](result);
|
|
124
241
|
return afterResult !== null && afterResult !== void 0 ? afterResult : result;
|
|
125
242
|
}
|
|
@@ -160,6 +277,54 @@ function getParamInFunction(fn, index) {
|
|
|
160
277
|
const result = code.slice(code.indexOf('(') + 1, code.indexOf(')')).match(/([^\s,]+)/g);
|
|
161
278
|
return result[index] || null;
|
|
162
279
|
}
|
|
280
|
+
/** 解析函数参数名列表(供 route @bind 做「参数名 → 索引」映射) */
|
|
281
|
+
function getParamNames(fn) {
|
|
282
|
+
const code = fn.toString().replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, '').replace(/=>.*$/mg, '').replace(/=[^,)]+/g, '');
|
|
283
|
+
const paramsStr = code.slice(code.indexOf('(') + 1, code.indexOf(')'));
|
|
284
|
+
if (!paramsStr.trim())
|
|
285
|
+
return [];
|
|
286
|
+
return paramsStr.split(',').map(p => {
|
|
287
|
+
const matched = p.trim().match(/([A-Za-z_$][\w$]*)/);
|
|
288
|
+
return matched ? matched[1] : p.trim();
|
|
289
|
+
}).filter(Boolean);
|
|
290
|
+
}
|
|
291
|
+
/** 把 route @bind 的「来源字符串」转成参数解析器 */
|
|
292
|
+
function getRouteSourceResolver(source, paramName) {
|
|
293
|
+
switch (source) {
|
|
294
|
+
case "req": return (req, res, next) => req;
|
|
295
|
+
case "res": return (req, res, next) => res;
|
|
296
|
+
case "next": return (req, res, next) => next;
|
|
297
|
+
case "reqBody": return (req, res, next) => req.body;
|
|
298
|
+
case "reqParam": return (req, res, next) => req.params[paramName];
|
|
299
|
+
case "reqQuery": return (req, res, next) => req.query[paramName];
|
|
300
|
+
case "reqForm": return (req, res, next) => req.body[paramName];
|
|
301
|
+
default: return null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/** 读取 @bind 声明,把「参数名 → 来源」转成 routerParams 的「索引 → 解析器」 */
|
|
305
|
+
function applyRouteBind(className, propertyKey, method) {
|
|
306
|
+
const bindMapping = (0, bind_decorator_1.getBindMapping)(className, propertyKey);
|
|
307
|
+
if (!bindMapping)
|
|
308
|
+
return;
|
|
309
|
+
const paramNames = getParamNames(method);
|
|
310
|
+
const nameToIndex = {};
|
|
311
|
+
paramNames.forEach((paramName, index) => {
|
|
312
|
+
if (paramName && nameToIndex[paramName] === undefined)
|
|
313
|
+
nameToIndex[paramName] = index;
|
|
314
|
+
});
|
|
315
|
+
for (const bindName in bindMapping) {
|
|
316
|
+
const source = bindMapping[bindName];
|
|
317
|
+
if (typeof source !== "string")
|
|
318
|
+
continue; // 跳过 database 的数值映射
|
|
319
|
+
const index = nameToIndex[bindName];
|
|
320
|
+
if (index === undefined)
|
|
321
|
+
continue;
|
|
322
|
+
const resolver = getRouteSourceResolver(source, bindName);
|
|
323
|
+
if (resolver) {
|
|
324
|
+
routerParams[[className, propertyKey, index].toString()] = resolver;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
163
328
|
function reqQuery(target, propertyKey, parameterIndex) {
|
|
164
329
|
const key = [target.constructor.name, propertyKey, parameterIndex].toString();
|
|
165
330
|
const paramName = getParamInFunction(target[propertyKey], parameterIndex);
|
package/dist/typespeed.d.ts
CHANGED
|
@@ -5,68 +5,74 @@ import "reflect-metadata";
|
|
|
5
5
|
|
|
6
6
|
/**设置路由中间件 */
|
|
7
7
|
declare function setRouter(app: express.Application): void;
|
|
8
|
-
|
|
9
|
-
declare function upload(
|
|
8
|
+
/**上传文件装饰器,装饰页面具备解析上传文件的能力(legacy 方法装饰器;2.5.x 起兼容标准 (value, context) 签名) */
|
|
9
|
+
declare function upload(...args: any[]): any;
|
|
10
10
|
/**
|
|
11
11
|
* 页面支持 JWT 鉴权能力
|
|
12
12
|
* @param jwtConfig jwt 配置
|
|
13
13
|
*/
|
|
14
|
-
declare function jwt(jwtConfig: any): (
|
|
14
|
+
declare function jwt(jwtConfig: any): (...args: any[]) => any;
|
|
15
15
|
/**
|
|
16
16
|
* GET 请求装饰器
|
|
17
17
|
* @param value 请求路径
|
|
18
18
|
*/
|
|
19
|
-
declare const getMapping: (value: string) => (
|
|
19
|
+
declare const getMapping: (value: string) => (...args: any[]) => any;
|
|
20
20
|
/**
|
|
21
21
|
* POST 请求装饰器
|
|
22
22
|
* @param value 请求路径
|
|
23
23
|
*/
|
|
24
|
-
declare const postMapping: (value: string) => (
|
|
24
|
+
declare const postMapping: (value: string) => (...args: any[]) => any;
|
|
25
25
|
/**
|
|
26
26
|
* 请求装饰器,不区分请求类型
|
|
27
27
|
* @param value 请求路径
|
|
28
28
|
*/
|
|
29
|
-
declare const requestMapping: (value: string) => (
|
|
29
|
+
declare const requestMapping: (value: string) => (...args: any[]) => any;
|
|
30
30
|
/**
|
|
31
31
|
* INSERT 装饰器
|
|
32
32
|
* 将方法作为 INSERT SQL 使用
|
|
33
33
|
* @param sql INSERT SQL 语句
|
|
34
34
|
*/
|
|
35
|
-
declare function insert(sql: string): (
|
|
35
|
+
declare function insert(sql: string): (...args: any[]) => any;
|
|
36
36
|
/**
|
|
37
37
|
* UPDATE 装饰器
|
|
38
38
|
* 将方法作为 UPDATE SQL 使用
|
|
39
39
|
* @param sql UPDATE SQL 语句
|
|
40
40
|
*/
|
|
41
|
-
declare function update(sql: string): (
|
|
41
|
+
declare function update(sql: string): (...args: any[]) => any;
|
|
42
42
|
/**
|
|
43
43
|
* DELETE 装饰器
|
|
44
44
|
* 将方法作为 DELETE SQL 使用
|
|
45
45
|
* @param sql DELETE SQL 语句
|
|
46
46
|
*/
|
|
47
|
-
declare function remove(sql: string): (
|
|
47
|
+
declare function remove(sql: string): (...args: any[]) => any;
|
|
48
48
|
/**
|
|
49
49
|
* SELECT 装饰器
|
|
50
50
|
* 将方法作为 SELECT SQL 使用
|
|
51
51
|
* @param sql SELECT SQL 语句
|
|
52
52
|
*/
|
|
53
|
-
declare function select(sql: string): (
|
|
53
|
+
declare function select(sql: string): (...args: any[]) => any;
|
|
54
54
|
/**
|
|
55
55
|
* SELECT 结果类型装饰器,和 @select 配合使用
|
|
56
56
|
* @param dataClass 结果类型
|
|
57
57
|
*/
|
|
58
|
-
declare function resultType(dataClass: any): (
|
|
58
|
+
declare function resultType(dataClass: any): (...args: any[]) => any;
|
|
59
59
|
/**
|
|
60
60
|
* SQL 参数装饰器,标注 SQL 语句的绑定参数值,和 @select @insert @update @remove 配合使用
|
|
61
61
|
* @param name 参数在 SQL 语句内的标记值
|
|
62
62
|
*/
|
|
63
63
|
declare function param(name: string): (target: any, propertyKey: string | symbol, parameterIndex: number) => void;
|
|
64
|
+
/**
|
|
65
|
+
* 方法级参数绑定装饰器(标准装饰器删除了参数装饰器后的替代,legacy 模式也可用)。
|
|
66
|
+
* database 场景映射 SQL 占位符 → 参数索引(值为 number);route 场景映射参数名 → 请求来源(值为 string)。
|
|
67
|
+
* @param mapping 绑定声明,如 { name: 0, id: 1 } 或 { id: "reqParam", body: "reqBody" }
|
|
68
|
+
*/
|
|
69
|
+
declare function bind(mapping: Record<string, string | number>): (...args: any[]) => any;
|
|
64
70
|
/**
|
|
65
71
|
* SELECT 缓存装饰器,自动缓存 SELECT 查询结果,和 @select 配合使用
|
|
66
72
|
* 当执行 @insert @update @remove 时,会自动清除缓存。
|
|
67
73
|
* @param ttl 缓存时间,单位秒
|
|
68
74
|
*/
|
|
69
|
-
declare function cache(ttl: number): (
|
|
75
|
+
declare function cache(ttl: number): (...args: any[]) => any;
|
|
70
76
|
/**模型数据操作类 */
|
|
71
77
|
declare class Model {
|
|
72
78
|
/**分页数据 */
|
|
@@ -139,23 +145,21 @@ declare class Model {
|
|
|
139
145
|
*
|
|
140
146
|
* 被装饰的类将作为应用程序入口,框架将启动该类的 main 方法
|
|
141
147
|
*/
|
|
142
|
-
declare function app
|
|
143
|
-
new(...args: any[]): {};
|
|
144
|
-
}>(constructor: T): void;
|
|
148
|
+
declare function app(...args: any[]): any;
|
|
145
149
|
/**获取配置文件中的配置项 */
|
|
146
150
|
declare function config(node: string): any;
|
|
147
|
-
/**组件装饰器,被装饰的类可以通过 @autoware
|
|
148
|
-
declare function component(
|
|
151
|
+
/**组件装饰器,被装饰的类可以通过 @autoware 取得实例(legacy 类装饰器;2.5.x 起兼容标准 (value, context) 签名) */
|
|
152
|
+
declare function component(...args: any[]): any;
|
|
149
153
|
/**获取组件实例函数,返回结果同 @autoware */
|
|
150
154
|
declare function getComponent(constructorFunction: any): any;
|
|
151
|
-
/**提供对象装饰器,框架将使用 @bean
|
|
152
|
-
declare function bean(
|
|
155
|
+
/**提供对象装饰器,框架将使用 @bean 装饰的方法来获取对象实例(legacy 方法装饰器;2.5.x 起兼容标准签名) */
|
|
156
|
+
declare function bean(...args: any[]): any;
|
|
153
157
|
/**获取对象实例函数,返回结果由 @bean 装饰的方法提供 */
|
|
154
158
|
declare function getBean(mappingClass: Function): any;
|
|
155
159
|
/**配置装饰器,装饰类成员变量值,获取配置文件中的配置项 */
|
|
156
160
|
declare function value(configPath: string): any;
|
|
157
161
|
/**自动装配装饰器,无参数版本,被装饰的类成员变量将自动注入实例 */
|
|
158
|
-
declare function autoware(
|
|
162
|
+
declare function autoware(...args: any[]): any;
|
|
159
163
|
/**自动装配装饰器,带参数版本,可输入参数作为实例初始化参数,被装饰的类成员变量将自动注入实例 */
|
|
160
164
|
declare function resource(...args: any[]): any;
|
|
161
165
|
/**日志函数,输出打印日志 */
|
|
@@ -165,11 +169,11 @@ declare function logx(message: any): void;
|
|
|
165
169
|
/**错误日志函数,输出打印错误日志 */
|
|
166
170
|
declare function error(message?: any, ...optionalParams: any[]): void;
|
|
167
171
|
/**路由页面前置执行装饰器,参数指向路由页面方法,被装饰的方法将在路由页面之前执行 */
|
|
168
|
-
declare function before(constructorFunction: any, methodName: string): (
|
|
172
|
+
declare function before(constructorFunction: any, methodName: string): (...args: any[]) => any;
|
|
169
173
|
/**路由页面后置执行装饰器,参数指向路由页面方法,被装饰的方法将在路由页面之后执行 */
|
|
170
|
-
declare function after(constructorFunction: any, methodName: string): (
|
|
174
|
+
declare function after(constructorFunction: any, methodName: string): (...args: any[]) => any;
|
|
171
175
|
/**定时程序装饰器,参数支持 crontab 格式字符串,可根据参数定时执行被装饰的方法 */
|
|
172
|
-
declare function schedule(cronTime: string | Date): (
|
|
176
|
+
declare function schedule(cronTime: string | Date): (...args: any[]) => any;
|
|
173
177
|
/**RabbitMQ 监听装饰器,参数是监听的队列名称,当接受到消息时将执行被装饰方法 */
|
|
174
178
|
declare function rabbitListener(queue: string): (target: any, propertyKey: string) => void;
|
|
175
179
|
/**Redis 监听装饰器,参数是监听的队列名称,当接受到消息时将执行被装饰方法 */
|
|
@@ -396,4 +400,4 @@ declare class SocketIo {
|
|
|
396
400
|
/**Socket IO 服务实现类 */
|
|
397
401
|
declare const io: IoServer;
|
|
398
402
|
|
|
399
|
-
export { ExpressServer, LogDefault, NodeCache, RabbitMQ, rabbitListener, redisSubscriber, ReadWriteDb, Redis, CacheFactory, DataSourceFactory, LogFactory, ServerFactory, AuthenticationFactory, next, reqBody, reqQuery, reqForm, reqParam, req, req as request, res, res as response, component, bean, resource, log, logx, app, before, after, value, error, config, autoware, getBean, getComponent, schedule, getMapping, postMapping, requestMapping, setRouter, upload, jwt, insert, update, remove, select, param, resultType, cache, Model, SocketIo, io };
|
|
403
|
+
export { ExpressServer, LogDefault, NodeCache, RabbitMQ, rabbitListener, redisSubscriber, ReadWriteDb, Redis, CacheFactory, DataSourceFactory, LogFactory, ServerFactory, AuthenticationFactory, next, reqBody, reqQuery, reqForm, reqParam, req, req as request, res, res as response, component, bean, resource, log, logx, app, before, after, value, error, config, autoware, getBean, getComponent, schedule, getMapping, postMapping, requestMapping, setRouter, upload, jwt, insert, update, remove, select, param, bind, resultType, cache, Model, SocketIo, io };
|
package/dist/typespeed.js
CHANGED
|
@@ -19,6 +19,7 @@ require("reflect-metadata");
|
|
|
19
19
|
const fs = require("fs");
|
|
20
20
|
const path = require("path");
|
|
21
21
|
const walkSync = require("walk-sync");
|
|
22
|
+
const decorator_utils_1 = require("./decorator-utils");
|
|
22
23
|
let globalConfig = {};
|
|
23
24
|
const corePath = __dirname;
|
|
24
25
|
const mainPath = path.dirname(getRootPath(new Error().stack.split("\n")) || process.argv[1]);
|
|
@@ -33,7 +34,18 @@ if (fs.existsSync(configFile)) {
|
|
|
33
34
|
}
|
|
34
35
|
globalConfig["MAIN_PATH"] = mainPath;
|
|
35
36
|
globalConfig["CORE_PATH"] = corePath;
|
|
36
|
-
function app(
|
|
37
|
+
function app(...args) {
|
|
38
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
39
|
+
const [, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
40
|
+
ctx.addInitializer(function () {
|
|
41
|
+
startApp(this.constructor);
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
startApp(args[0]);
|
|
46
|
+
}
|
|
47
|
+
exports.app = app;
|
|
48
|
+
function startApp(constructor) {
|
|
37
49
|
const coreFiles = walkSync(corePath, { globs: ['**/*.ts'], ignore: ['**/*.d.ts', 'scaffold/**'] });
|
|
38
50
|
const mainFiles = walkSync(mainPath, { globs: ['**/*.ts'] });
|
|
39
51
|
(async function () {
|
|
@@ -56,13 +68,28 @@ function app(constructor) {
|
|
|
56
68
|
main["main"]();
|
|
57
69
|
}());
|
|
58
70
|
}
|
|
59
|
-
exports.app = app;
|
|
60
71
|
function config(node) {
|
|
61
72
|
return globalConfig[node] || null;
|
|
62
73
|
}
|
|
63
74
|
exports.config = config;
|
|
64
75
|
function value(configPath) {
|
|
65
|
-
return function (
|
|
76
|
+
return function (...args) {
|
|
77
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
78
|
+
// 标准 field 装饰器:返回 initializer 注入配置值(标准模式下无 design:type)。
|
|
79
|
+
return (initialValue) => {
|
|
80
|
+
if (globalConfig === undefined) {
|
|
81
|
+
return initialValue;
|
|
82
|
+
}
|
|
83
|
+
let pathNodes = configPath.split(".");
|
|
84
|
+
let nodeValue = globalConfig;
|
|
85
|
+
for (let i = 0; i < pathNodes.length; i++) {
|
|
86
|
+
nodeValue = nodeValue[pathNodes[i]];
|
|
87
|
+
}
|
|
88
|
+
return nodeValue === undefined ? initialValue : nodeValue;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const target = args[0];
|
|
92
|
+
const propertyKey = args[1];
|
|
66
93
|
if (globalConfig === undefined) {
|
|
67
94
|
Object.defineProperty(target, propertyKey, {
|
|
68
95
|
get: () => {
|
|
@@ -86,10 +113,14 @@ function value(configPath) {
|
|
|
86
113
|
}
|
|
87
114
|
exports.value = value;
|
|
88
115
|
function getRootPath(lines) {
|
|
89
|
-
|
|
116
|
+
// 兼容新旧 Node 栈帧(Node<22: "Function.Module._load" / Node22+: "Function._load")、
|
|
117
|
+
// mocha(ts-node) 下 "Context.<anonymous>" 与中间可能插入的 "wrapModuleLoad" 帧:
|
|
118
|
+
// 按顺序出现匹配(不要求严格相邻),提高在各种加载器下的容错。
|
|
119
|
+
const macths = [/at Function(\.Module)?\._load/, "at Module.require", /at require\b/, /at (Object|Context)\.<anonymous>/];
|
|
90
120
|
let matchIndex = 0;
|
|
91
121
|
for (let line of lines) {
|
|
92
|
-
|
|
122
|
+
const matcher = macths[matchIndex];
|
|
123
|
+
if (matcher instanceof RegExp ? matcher.test(line) : line.includes(matcher)) {
|
|
93
124
|
if (matchIndex === macths.length - 1) {
|
|
94
125
|
let arr = line.split("(")[1].split(":");
|
|
95
126
|
arr.pop();
|
|
@@ -98,15 +129,13 @@ function getRootPath(lines) {
|
|
|
98
129
|
}
|
|
99
130
|
matchIndex++;
|
|
100
131
|
}
|
|
101
|
-
else {
|
|
102
|
-
matchIndex = 0;
|
|
103
|
-
}
|
|
104
132
|
}
|
|
105
133
|
return undefined;
|
|
106
134
|
}
|
|
107
135
|
__exportStar(require("./core.decorator"), exports);
|
|
108
136
|
__exportStar(require("./route.decorator"), exports);
|
|
109
137
|
__exportStar(require("./database.decorator"), exports);
|
|
138
|
+
__exportStar(require("./bind.decorator"), exports);
|
|
110
139
|
var log_factory_class_1 = require("./factory/log-factory.class");
|
|
111
140
|
Object.defineProperty(exports, "LogFactory", { enumerable: true, get: function () { return log_factory_class_1.default; } });
|
|
112
141
|
var cache_factory_class_1 = require("./factory/cache-factory.class");
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
3
|
+
var useValue = arguments.length > 2;
|
|
4
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
5
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
6
|
+
}
|
|
7
|
+
return useValue ? value : void 0;
|
|
8
|
+
};
|
|
9
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
10
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
11
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
12
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
13
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
14
|
+
var _, done = false;
|
|
15
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
16
|
+
var context = {};
|
|
17
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
18
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
19
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
20
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
21
|
+
if (kind === "accessor") {
|
|
22
|
+
if (result === void 0) continue;
|
|
23
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
24
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
25
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
26
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
27
|
+
}
|
|
28
|
+
else if (_ = accept(result)) {
|
|
29
|
+
if (kind === "field") initializers.unshift(_);
|
|
30
|
+
else descriptor[key] = _;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
34
|
+
done = true;
|
|
35
|
+
};
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
// Symbol.metadata polyfill:Node 22/24/26 都没有 Symbol.metadata,
|
|
41
|
+
// 标准装饰器的 context.metadata 依赖它,缺失会 TypeError,必须最先执行。
|
|
42
|
+
Symbol.metadata ??= Symbol("Symbol.metadata");
|
|
43
|
+
const express_1 = __importDefault(require("express"));
|
|
44
|
+
const typespeed_1 = require("../../../dist/typespeed");
|
|
45
|
+
class CacheBean {
|
|
46
|
+
name = "cache-bean";
|
|
47
|
+
}
|
|
48
|
+
let TestService = (() => {
|
|
49
|
+
let _classDecorators = [typespeed_1.component];
|
|
50
|
+
let _classDescriptor;
|
|
51
|
+
let _classExtraInitializers = [];
|
|
52
|
+
let _classThis;
|
|
53
|
+
let _instanceExtraInitializers = [];
|
|
54
|
+
let _getCache_decorators;
|
|
55
|
+
let _cache_decorators;
|
|
56
|
+
let _cache_initializers = [];
|
|
57
|
+
let _cache_extraInitializers = [];
|
|
58
|
+
let _test_decorators;
|
|
59
|
+
var TestService = class {
|
|
60
|
+
static { _classThis = this; }
|
|
61
|
+
static {
|
|
62
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
63
|
+
_getCache_decorators = [(0, typespeed_1.bean)(CacheBean)];
|
|
64
|
+
_cache_decorators = [(0, typespeed_1.autoware)(CacheBean)];
|
|
65
|
+
_test_decorators = [(0, typespeed_1.getMapping)("/std/test/:id"), (0, typespeed_1.bind)({ id: "reqParam" })];
|
|
66
|
+
__esDecorate(this, null, _getCache_decorators, { kind: "method", name: "getCache", static: false, private: false, access: { has: obj => "getCache" in obj, get: obj => obj.getCache }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
67
|
+
__esDecorate(this, null, _test_decorators, { kind: "method", name: "test", static: false, private: false, access: { has: obj => "test" in obj, get: obj => obj.test }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
68
|
+
__esDecorate(null, null, _cache_decorators, { kind: "field", name: "cache", static: false, private: false, access: { has: obj => "cache" in obj, get: obj => obj.cache, set: (obj, value) => { obj.cache = value; } }, metadata: _metadata }, _cache_initializers, _cache_extraInitializers);
|
|
69
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
70
|
+
TestService = _classThis = _classDescriptor.value;
|
|
71
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
72
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
73
|
+
}
|
|
74
|
+
getCache() { return new CacheBean(); }
|
|
75
|
+
cache = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _cache_initializers, void 0));
|
|
76
|
+
// 标准模式:无参数装饰器,@bind 声明「参数名 → 来源」
|
|
77
|
+
async test(id) {
|
|
78
|
+
return { id, cache: this.cache.name };
|
|
79
|
+
}
|
|
80
|
+
constructor() {
|
|
81
|
+
__runInitializers(this, _cache_extraInitializers);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
return TestService = _classThis;
|
|
85
|
+
})();
|
|
86
|
+
// 验证 1:@component / @autoware / @bean
|
|
87
|
+
const svc = (0, typespeed_1.getComponent)(TestService);
|
|
88
|
+
console.log("[component] registered:", svc !== undefined);
|
|
89
|
+
console.log("[autoware(CacheBean)] injected:", svc && svc.cache?.name);
|
|
90
|
+
console.log("[bean(CacheBean)] factory:", (0, typespeed_1.getBean)(CacheBean)?.name);
|
|
91
|
+
// 验证 2:@getMapping + @bind 路由,走 setRouter 真实 HTTP 请求
|
|
92
|
+
const app = (0, express_1.default)();
|
|
93
|
+
(0, typespeed_1.setRouter)(app);
|
|
94
|
+
const server = app.listen(0, () => {
|
|
95
|
+
const addr = server.address();
|
|
96
|
+
const port = addr.port;
|
|
97
|
+
httpGet(`http://127.0.0.1:${port}/std/test/123`, (body) => {
|
|
98
|
+
console.log("[route @bind] response:", body);
|
|
99
|
+
const parsed = JSON.parse(body);
|
|
100
|
+
const ok = svc !== undefined
|
|
101
|
+
&& svc.cache?.name === "cache-bean"
|
|
102
|
+
&& (0, typespeed_1.getBean)(CacheBean)?.name === "cache-bean"
|
|
103
|
+
&& parsed.id === "123"
|
|
104
|
+
&& parsed.cache === "cache-bean";
|
|
105
|
+
console.log(ok ? "STANDARD DECORATORS OK" : "STANDARD DECORATORS FAILED");
|
|
106
|
+
server.close();
|
|
107
|
+
process.exit(ok ? 0 : 1);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
function httpGet(url, cb) {
|
|
111
|
+
require("http").get(url, (res) => {
|
|
112
|
+
let data = "";
|
|
113
|
+
res.on("data", (chunk) => { data += chunk; });
|
|
114
|
+
res.on("end", () => cb(data));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "typespeed-example-std",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "typespeed 标准装饰器(TC39 Stage 2.7)示例:experimentalDecorators: false + Symbol.metadata polyfill",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc -p .",
|
|
8
|
+
"start": "node dist/main.js"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"typescript": "^5.9.3",
|
|
12
|
+
"@types/node": "^18.19.3"
|
|
13
|
+
}
|
|
14
|
+
}
|