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
|
@@ -21,10 +21,10 @@ jobs:
|
|
|
21
21
|
ports:
|
|
22
22
|
- 5672
|
|
23
23
|
steps:
|
|
24
|
-
- uses: actions/
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
- uses: actions/setup-node@v4
|
|
25
26
|
with:
|
|
26
|
-
node-version:
|
|
27
|
-
- uses: actions/checkout@v2
|
|
27
|
+
node-version: 22
|
|
28
28
|
- name: Setup MySQL
|
|
29
29
|
run: |
|
|
30
30
|
sudo /etc/init.d/mysql start
|
|
@@ -32,12 +32,12 @@ jobs:
|
|
|
32
32
|
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(100) DEFAULT NULL,PRIMARY KEY (`id`)) \
|
|
33
33
|
ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;SHOW TABLES;' -uroot -proot
|
|
34
34
|
- name: 'Cache node_modules'
|
|
35
|
-
uses: actions/cache@
|
|
35
|
+
uses: actions/cache@v4
|
|
36
36
|
with:
|
|
37
37
|
path: ~/.npm
|
|
38
|
-
key: ${{ runner.os }}-node-
|
|
38
|
+
key: ${{ runner.os }}-node-v22-${{ hashFiles('**/package.json') }}
|
|
39
39
|
restore-keys: |
|
|
40
|
-
${{ runner.os }}-node-
|
|
40
|
+
${{ runner.os }}-node-v22-
|
|
41
41
|
- name: Install Dependencies
|
|
42
42
|
run: npm install
|
|
43
43
|
- name: Run Tests with Coverage
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
typespeed 版本演进记录。本文件 2026-09-06 从 git 历史反推建立;git 最早提交 2022-09-18(ch01)。
|
|
4
|
+
|
|
5
|
+
## 2.5.x
|
|
6
|
+
|
|
7
|
+
- **2.5.0**:装饰器双轨——支持 TC39 标准装饰器(Stage 2.7),legacy 装饰器零破坏。单份源码运行时双签名感知(`isStd` 判据),覆盖 core/route/database 三族 + 入口 `app`/`value`;新增方法级 `@bind` 装饰器(route 参数名→来源、database 占位符→索引,替代标准模式删除的参数装饰器);`@autoware`/`@resource`/`@bean` 支持显式 token(标准模式无 `design:type`);类型声明宽松化;新增标准模式示例 `introduction/decorator-next/` + `isStd` 单测(2026-09-07)
|
|
8
|
+
|
|
9
|
+
## 2.4.x
|
|
10
|
+
|
|
11
|
+
- **2.4.11**:README 四入口、CI 修复(action 升 v4、node 22)、CHANGELOG 建立、getRootPath 兼容 Node22/mocha(2026-09-06)
|
|
12
|
+
- **2.4.10**:升级 jsonwebtoken 和 mysql2 库(2024-05-07)
|
|
13
|
+
- **2.4.9**:修复 SocketIO、MQ、Redis 等对象获取不准确问题(2024-04-22)
|
|
14
|
+
- **2.4.8**:修复 socket.io 未从 bean 工厂获取装饰类的问题;修复 select 返回值不为 null 的问题(2024-04-22)
|
|
15
|
+
- **2.4.7**:logx 日志模块(2024-04-21)
|
|
16
|
+
- **2.4.6**:修复 redis 实例错误问题(2024-03-17)
|
|
17
|
+
- **2.4.5**:版本发布(2023-12-14)
|
|
18
|
+
- **2.4.4**:版本发布(2023-12-01)
|
|
19
|
+
- **2.4.3**:`@redisSubscriber` 装饰器在无 redis 配置时抛异常;修复生成 dist 目录问题;getRootPath 兼容 Windows(2023-11-15)
|
|
20
|
+
- **2.4.2**:修复 database where 条件 AND 匹配超过 2 个时拼装出错的问题(2023-10-23)
|
|
21
|
+
- **2.4.1**:补充 introduction 目录的装饰器允许配置(2023-08-04)
|
|
22
|
+
- **2.4.0**:socket.io 完成——disconnect / handshake(connected)/ 房间示例 / 全部测试用例(2023-08-04)
|
|
23
|
+
|
|
24
|
+
## 2.3.x(2023-06 ~ 2023-07)
|
|
25
|
+
|
|
26
|
+
- **2.3.6**:增加应用目录路径配置(2023-07-06)
|
|
27
|
+
- **2.3.5**:增加 redis 订阅模式(需开启两个 redis 实例);增加 zranking / zrevranking 返回排名与分数的 Map,方便开发排行榜逻辑(2023-06-29)
|
|
28
|
+
- **2.3.4**:CI 完善——限制只有 main 分支执行测试、调整测试 node 版本为 16、增加覆盖率 badge;改正 MQ 测试方法(2023-06-28)
|
|
29
|
+
- **2.3.3**:修复 jwt 拦截器问题;fixed d.ts
|
|
30
|
+
- **2.3.2**:start 增加 callback 参数;修改拦截器逻辑
|
|
31
|
+
- **2.3.1**:版本发布
|
|
32
|
+
- **2.3.0**:测试体系搭建——首个测试程序、cookie / session 测试、second page 测试(header / 文件上传 / jwt)、数据库读写测试(修正 resultType 问题)、测试程序增加 rabbitmq 配置
|
|
33
|
+
|
|
34
|
+
## 2.2.x
|
|
35
|
+
|
|
36
|
+
- **2.2.1**:修改并增加测试逻辑
|
|
37
|
+
- **2.2.0**:express 返回 listen 对象方便停止服务;修改 core 获取应用程序 main 地址的方式,使通过其他方式启动的程序也能正常获得应用地址;更新获取应用程序路径方法(第一层引入时检查堆栈)
|
|
38
|
+
|
|
39
|
+
## 2.1.x
|
|
40
|
+
|
|
41
|
+
- **2.1.3**:去除无关依赖
|
|
42
|
+
- **2.1.2**:升级 consolidate;`@reqParam` / `@reqQuery` 可省略参数直接使用参数名
|
|
43
|
+
- **2.1.1**:版本发布
|
|
44
|
+
- **2.1.0**:去除实验相关代码,更新引用地址
|
|
45
|
+
|
|
46
|
+
## 2.0.x
|
|
47
|
+
|
|
48
|
+
- **2.0.23**:增加 9 个请求方法的注释
|
|
49
|
+
- **2.0.22**:完成 9 个请求参数的赋值装饰器;解决路由方法参数个数统计问题(before / after 一并处理);增加全局权限认证方法,完成全局认证类测试(JWT)
|
|
50
|
+
- **2.0.21**:增加 debug 日志打印
|
|
51
|
+
- **2.0.20**:增加 README 内容
|
|
52
|
+
- **2.0.19**:修正 README 内一个图标失效问题
|
|
53
|
+
- **2.0.18**:增加导出内容的注释
|
|
54
|
+
- **2.0.17 / 2.0.16**:版本发布
|
|
55
|
+
- **2.0.15**:添加 d.ts 文件并去除自动生成的
|
|
56
|
+
- **2.0.14**:将 rabbitmq 整合
|
|
57
|
+
- **2.0.13**:初始情况 rabbitmq 不监听;解决 async / await 问题;初步完成 mq 装饰器与发送
|
|
58
|
+
- **2.0.12**:增加 Rabbit MQ 的测试
|
|
59
|
+
- **2.0.11 / 2.0.10 / 2.0.9 / 2.0.8 / 2.0.7**:发布与 package.json 脚本调整
|
|
60
|
+
- **2.0.6**:修正 command 命令出错问题;补充装饰器介绍示例代码、访问器装饰器、入门材料
|
|
61
|
+
- **2.0.5 / 2.0.4**:版本发布
|
|
62
|
+
- **2.0.3**:修正 command 命令出错问题;更新脚手架的模板文件以避免 tsc 编译
|
|
63
|
+
- **2.0.2**:增加脚手架(typespeed 命令行)
|
|
64
|
+
- **2.0.1**:首个 2.0 发布(调整 npm 发布脚本、统一载入入口)
|
|
65
|
+
|
|
66
|
+
## 早期原型期(2022-09 起,v2.0 之前)
|
|
67
|
+
|
|
68
|
+
基于 ch01 / ch02 系列实验提交(`685ee1b` 起)的框架核心能力开发:
|
|
69
|
+
|
|
70
|
+
- **装饰器体系**:`@start` / `@app` / `@bean` / `@log` / 注入 / 参数注入(autoware / inject 调整)
|
|
71
|
+
- **AOP**:before / after 拦截完成
|
|
72
|
+
- **Web 路由**:修饰器路由、路由中间件(basicAuth / jwt / upload 先后顺序处理)、404 与 500 错误页面、async / await 支持
|
|
73
|
+
- **数据层**:Insert / Update / Delete / Select 完成;SQL 参数注入(`?` 编号绑定);ResultType 标记查询结果类型;ORM 化(Model 继承:findAll / findOne / create / delete / findCount / update / incr / decr);分页查询;nodeCache 缓存(新增/修改时按版本号刷新)
|
|
74
|
+
- **基础设施**:静态资源、favicon、compression、cookies、session(Redis 存储)、文件上传(multiparty + 路由中间件)、多模版引擎(consolidate)、配置系统(全局配置 + 覆盖)、读写库(主从)接入
|
|
75
|
+
- **集成**:Redis 一般使用(避免启动连不上报错)、schedule 定时器、外部载入结构调整(统一载入入口)
|
package/README.md
CHANGED
|
@@ -1,32 +1,74 @@
|
|
|
1
1
|
# TypeSpeed Framework
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
](https://
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
### 资料体系
|
|
28
|
+
|
|
29
|
+
TypeSpeed 不只提供一个框架,还配有完整的「书 + 视频 + 双源码」学习资料:
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
* **书**:《TypeScript 框架开发实践(微课视频版)》(曾振中 著,清华大学出版社)——[京东购买](https://item.jd.com/14275373.html)
|
|
34
|
+
|
|
35
|
+
* **视频课程**:随书微课视频(每节配套源码快照见 ts-practice)
|
|
36
|
+
|
|
37
|
+
* **附书源码・ts-book**:[https://github.com/speedphp/ts-book](https://github.com/speedphp/ts-book)—— 按章组织(chapter01 \~ chapter06),每章含可运行示例
|
|
38
|
+
|
|
39
|
+
* **视频代码・ts-practice**:[https://github.com/speedphp/ts-practice](https://github.com/speedphp/ts-practice)—— 按书籍章节号打 tag(如 `4.10.4` = 第 4 章第 10 节第 4 步),每节视频对应一份可跑源码快照
|
|
10
40
|
|
|
11
41
|
### 特点
|
|
12
42
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
* 遵循 MIT 许可的开源项目。
|
|
46
|
+
|
|
47
|
+
* 提供 31 个 TypeScript 装饰器,这些装饰器构成了完整的 Web 框架功能,包括对象管理、Web 路由、数据库操作等。
|
|
48
|
+
|
|
49
|
+
* 通过命令行脚手架,可以快速创建项目,零配置启动。
|
|
50
|
+
|
|
51
|
+
* 基于依赖注入、AOP、中间件和启动扫描等编程理念开发实现。
|
|
52
|
+
|
|
53
|
+
* 底层采用 ExpressJS 实现,提供稳定、快速的 Web 服务。
|
|
54
|
+
|
|
55
|
+
* 框架提供了两种风格不同却各具优点的数据操作模式。
|
|
19
56
|
|
|
20
57
|
### 安装
|
|
21
58
|
|
|
22
59
|
TypeSpeed 环境要求 NodeJS 16+,TypeScript 运行时。
|
|
23
60
|
|
|
24
|
-
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
```
|
|
25
64
|
npm install typespeed -g
|
|
26
65
|
```
|
|
66
|
+
|
|
27
67
|
### 使用
|
|
28
68
|
|
|
29
|
-
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
```
|
|
30
72
|
typespeed new blog
|
|
31
73
|
|
|
32
74
|
cd ./blog
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { component, getMapping, bind, resource, autoware, bean, insert } from "../../src/typespeed";
|
|
2
|
+
import UserModel from "./user-model.class";
|
|
3
|
+
|
|
4
|
+
class TestBean {
|
|
5
|
+
name = "test-bean";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@component
|
|
9
|
+
export default class TestBind {
|
|
10
|
+
|
|
11
|
+
@bean(TestBean)
|
|
12
|
+
getBeanInstance() { return new TestBean(); }
|
|
13
|
+
|
|
14
|
+
@autoware(TestBean)
|
|
15
|
+
private autowired: TestBean;
|
|
16
|
+
|
|
17
|
+
@resource(UserModel, "user")
|
|
18
|
+
private userModel: UserModel;
|
|
19
|
+
|
|
20
|
+
@getMapping("/bind/param/:id")
|
|
21
|
+
@bind({ id: "reqParam", name: "reqQuery" })
|
|
22
|
+
async bindParam(id: string, name: string) {
|
|
23
|
+
return { id, name };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@getMapping("/bind/token")
|
|
27
|
+
async bindToken(req, res) {
|
|
28
|
+
res.send(this.autowired.name + "|" + (this.userModel ? "model-ok" : "model-null"));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@getMapping("/bind/db")
|
|
32
|
+
async bindDb(req, res) {
|
|
33
|
+
const id = req.query.id || 1;
|
|
34
|
+
const newId = await this.addRowByBind("bind name " + id, id);
|
|
35
|
+
res.send("bind insert: " + newId);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@insert("Insert into `user` (id, name) values (#{id}, #{name})")
|
|
39
|
+
@bind({ name: 0, id: 1 })
|
|
40
|
+
private async addRowByBind(newName: string, id: number) { }
|
|
41
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getBindMapping = exports.bind = void 0;
|
|
4
|
+
const decorator_utils_1 = require("./decorator-utils");
|
|
5
|
+
/**
|
|
6
|
+
* 方法级参数绑定注册表:key = `[className, methodName]`,value = 绑定声明。
|
|
7
|
+
* 标准装饰器删除了参数装饰器,@bind 是方法级替代(legacy 模式同样可用)。
|
|
8
|
+
* 按 value 类型区分两种语义:
|
|
9
|
+
* - database 场景:{ SQL占位符名: 参数索引 }(值为 number)
|
|
10
|
+
* - route 场景:{ 参数名: 请求来源 }(值为 string,如 "reqParam" / "reqBody" / "reqQuery")
|
|
11
|
+
*/
|
|
12
|
+
const bindParamMap = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* @bind 方法级参数绑定装饰器。
|
|
15
|
+
*
|
|
16
|
+
* database 散参绑定(对象传参已是主路径,@bind 作散参补充):
|
|
17
|
+
* ```
|
|
18
|
+
* @insert("Insert into `user` (id, name) values (#{id}, #{name})")
|
|
19
|
+
* @bind({ name: 0, id: 1 })
|
|
20
|
+
* async addRow(newName: string, id: number) { }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* route 参数绑定(替代 req/res/reqBody/reqParam/reqQuery/reqForm 参数装饰器):
|
|
24
|
+
* ```
|
|
25
|
+
* @getMapping("/user/:id")
|
|
26
|
+
* @bind({ id: "reqParam", body: "reqBody", q: "reqQuery" })
|
|
27
|
+
* async getUser(id: string, body: any, q: string) { }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
function bind(mapping) {
|
|
31
|
+
return function (...args) {
|
|
32
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
33
|
+
const [, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
34
|
+
const methodName = String(ctx.name);
|
|
35
|
+
ctx.addInitializer(function () {
|
|
36
|
+
bindParamMap.set([this.constructor.name, methodName].toString(), mapping);
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const target = args[0];
|
|
41
|
+
const propertyKey = args[1];
|
|
42
|
+
bindParamMap.set([target.constructor.name, propertyKey].toString(), mapping);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
exports.bind = bind;
|
|
46
|
+
/** 读取某方法上的 @bind 声明(route/database 内部使用) */
|
|
47
|
+
function getBindMapping(className, methodName) {
|
|
48
|
+
return bindParamMap.get([className, methodName].toString());
|
|
49
|
+
}
|
|
50
|
+
exports.getBindMapping = getBindMapping;
|
package/dist/core.decorator.js
CHANGED
|
@@ -4,10 +4,19 @@ exports.schedule = exports.getComponent = exports.getBean = exports.autoware = e
|
|
|
4
4
|
require("reflect-metadata");
|
|
5
5
|
const cron = require("cron");
|
|
6
6
|
const log_factory_class_1 = require("./factory/log-factory.class");
|
|
7
|
+
const decorator_utils_1 = require("./decorator-utils");
|
|
7
8
|
const resourceObjects = new Map();
|
|
8
9
|
const beanMapper = new Map();
|
|
9
10
|
const objectMapper = new Map();
|
|
10
|
-
function component(
|
|
11
|
+
function component(...args) {
|
|
12
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
13
|
+
const [ctor, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
14
|
+
ctx.addInitializer(function () {
|
|
15
|
+
objectMapper.set(this.name, new this());
|
|
16
|
+
});
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const constructorFunction = args[0];
|
|
11
20
|
objectMapper.set(constructorFunction.name, new constructorFunction());
|
|
12
21
|
}
|
|
13
22
|
exports.component = component;
|
|
@@ -15,45 +24,114 @@ function getComponent(constructorFunction) {
|
|
|
15
24
|
return objectMapper.get(constructorFunction.name);
|
|
16
25
|
}
|
|
17
26
|
exports.getComponent = getComponent;
|
|
18
|
-
function bean(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
function bean(...args) {
|
|
28
|
+
if (args.length >= 2) {
|
|
29
|
+
// 直接装饰器形式:@bean(无 token,走 legacy design:returntype)
|
|
30
|
+
return beanWithToken(undefined)(...args);
|
|
31
|
+
}
|
|
32
|
+
// 工厂形式:@bean(Token)(显式返回类型 token,标准模式必需)
|
|
33
|
+
return beanWithToken(args[0]);
|
|
25
34
|
}
|
|
26
35
|
exports.bean = bean;
|
|
36
|
+
function beanWithToken(token) {
|
|
37
|
+
return function (...args) {
|
|
38
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
39
|
+
const [, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
40
|
+
const key = String(ctx.name);
|
|
41
|
+
ctx.addInitializer(function () {
|
|
42
|
+
beanMapper.set(token ? token.name : key, {
|
|
43
|
+
"target": this, "propertyKey": key,
|
|
44
|
+
"factory": this[key]()
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const target = args[0];
|
|
50
|
+
const propertyKey = args[1];
|
|
51
|
+
let returnType = token || Reflect.getMetadata("design:returntype", target, propertyKey);
|
|
52
|
+
beanMapper.set(returnType.name, {
|
|
53
|
+
"target": target, "propertyKey": propertyKey,
|
|
54
|
+
"factory": target[propertyKey]()
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
}
|
|
27
58
|
function getBean(mappingClass) {
|
|
28
59
|
const bean = beanMapper.get(mappingClass.name);
|
|
29
60
|
return bean["factory"];
|
|
30
61
|
}
|
|
31
62
|
exports.getBean = getBean;
|
|
32
|
-
function autoware(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
63
|
+
function autoware(...args) {
|
|
64
|
+
if (args.length >= 2) {
|
|
65
|
+
// 直接装饰器形式:@autoware(无 token,走 legacy design:type)
|
|
66
|
+
return autowareWithToken(undefined)(...args);
|
|
67
|
+
}
|
|
68
|
+
// 工厂形式:@autoware(Token)(显式 token,标准模式必需)
|
|
69
|
+
return autowareWithToken(args[0]);
|
|
70
|
+
}
|
|
71
|
+
exports.autoware = autoware;
|
|
72
|
+
function autowareWithToken(token) {
|
|
73
|
+
return function (...args) {
|
|
74
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
75
|
+
// 标准 field 装饰器:标准模式无 design:type,有 token 才能注入。
|
|
76
|
+
return (initialValue) => {
|
|
77
|
+
if (!token)
|
|
78
|
+
return initialValue;
|
|
79
|
+
const bean = beanMapper.get(token.name);
|
|
80
|
+
if (bean !== undefined)
|
|
81
|
+
return bean["factory"];
|
|
82
|
+
return new token();
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const target = args[0];
|
|
86
|
+
const propertyKey = args[1];
|
|
87
|
+
const type = token || Reflect.getMetadata("design:type", target, propertyKey);
|
|
88
|
+
Object.defineProperty(target, propertyKey, {
|
|
89
|
+
get: () => {
|
|
90
|
+
const targetObject = beanMapper.get(type.name);
|
|
91
|
+
if (targetObject === undefined) {
|
|
92
|
+
const resourceKey = [target.constructor.name, propertyKey, type.name].toString();
|
|
93
|
+
if (!resourceObjects[resourceKey]) {
|
|
94
|
+
resourceObjects[resourceKey] = new type();
|
|
95
|
+
}
|
|
96
|
+
return resourceObjects[resourceKey];
|
|
41
97
|
}
|
|
42
|
-
return
|
|
98
|
+
return targetObject["factory"];
|
|
43
99
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 解析 @resource(...args) 的首参:若首参是 Function(类/token),作为显式 token,
|
|
105
|
+
* 其余作为构造参数;否则全部作为构造参数(legacy 行为,如 @resource("user"))。
|
|
106
|
+
*/
|
|
107
|
+
function extractTokenAndArgs(args) {
|
|
108
|
+
if (args.length > 0 && typeof args[0] === "function") {
|
|
109
|
+
return [args[0], args.slice(1)];
|
|
110
|
+
}
|
|
111
|
+
return [undefined, args];
|
|
47
112
|
}
|
|
48
|
-
exports.autoware = autoware;
|
|
49
113
|
function resource(...args) {
|
|
50
|
-
|
|
51
|
-
|
|
114
|
+
const [token, initArgs] = extractTokenAndArgs(args);
|
|
115
|
+
return (...decoratorArgs) => {
|
|
116
|
+
if ((0, decorator_utils_1.isStd)(decoratorArgs)) {
|
|
117
|
+
// 标准 field 装饰器:标准模式无 design:type,需显式 token。
|
|
118
|
+
return (initialValue) => {
|
|
119
|
+
if (!token)
|
|
120
|
+
return initialValue;
|
|
121
|
+
const bean = beanMapper.get(token.name);
|
|
122
|
+
if (bean !== undefined)
|
|
123
|
+
return bean["factory"];
|
|
124
|
+
return new token(...initArgs);
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const target = decoratorArgs[0];
|
|
128
|
+
const propertyKey = decoratorArgs[1];
|
|
129
|
+
const type = token || Reflect.getMetadata("design:type", target, propertyKey);
|
|
52
130
|
Object.defineProperty(target, propertyKey, {
|
|
53
131
|
get: () => {
|
|
54
132
|
const resourceKey = [target.constructor.name, propertyKey, type.name].toString();
|
|
55
133
|
if (!resourceObjects[resourceKey]) {
|
|
56
|
-
resourceObjects[resourceKey] = new type(...
|
|
134
|
+
resourceObjects[resourceKey] = new type(...initArgs);
|
|
57
135
|
}
|
|
58
136
|
return resourceObjects[resourceKey];
|
|
59
137
|
}
|
|
@@ -93,7 +171,16 @@ function error(message, ...optionalParams) {
|
|
|
93
171
|
}
|
|
94
172
|
exports.error = error;
|
|
95
173
|
function schedule(cronTime) {
|
|
96
|
-
return (
|
|
174
|
+
return (...args) => {
|
|
175
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
176
|
+
const [, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
177
|
+
ctx.addInitializer(function () {
|
|
178
|
+
new cron.CronJob(cronTime, this[String(ctx.name)]).start();
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const target = args[0];
|
|
183
|
+
const propertyKey = args[1];
|
|
97
184
|
new cron.CronJob(cronTime, target[propertyKey]).start();
|
|
98
185
|
};
|
|
99
186
|
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Model = exports.cache = exports.resultType = exports.param = exports.select = exports.remove = exports.update = exports.insert = void 0;
|
|
4
4
|
const core_decorator_1 = require("./core.decorator");
|
|
5
|
+
const decorator_utils_1 = require("./decorator-utils");
|
|
6
|
+
const bind_decorator_1 = require("./bind.decorator");
|
|
5
7
|
const cache_factory_class_1 = require("./factory/cache-factory.class");
|
|
6
8
|
const data_source_factory_class_1 = require("./factory/data-source-factory.class");
|
|
7
9
|
const paramMetadataKey = Symbol('param');
|
|
@@ -10,9 +12,24 @@ const cacheDefindMap = new Map();
|
|
|
10
12
|
const tableVersionMap = new Map();
|
|
11
13
|
let cacheBean;
|
|
12
14
|
function insert(sql) {
|
|
13
|
-
return (
|
|
14
|
-
|
|
15
|
-
const
|
|
15
|
+
return (...args) => {
|
|
16
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
17
|
+
const [, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
18
|
+
const propertyKey = String(ctx.name);
|
|
19
|
+
return async function (...callArgs) {
|
|
20
|
+
const result = await queryForExecute(sql, callArgs, this, propertyKey);
|
|
21
|
+
if (cacheBean && result.affectedRows > 0) {
|
|
22
|
+
const [tableName, tableVersion] = getTableAndVersion("insert", sql);
|
|
23
|
+
tableVersionMap.set(tableName, tableVersion + 1);
|
|
24
|
+
}
|
|
25
|
+
return result.insertId;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const target = args[0];
|
|
29
|
+
const propertyKey = args[1];
|
|
30
|
+
const descriptor = args[2];
|
|
31
|
+
descriptor.value = async (...callArgs) => {
|
|
32
|
+
const result = await queryForExecute(sql, callArgs, target, propertyKey);
|
|
16
33
|
if (cacheBean && result.affectedRows > 0) {
|
|
17
34
|
const [tableName, tableVersion] = getTableAndVersion("insert", sql);
|
|
18
35
|
tableVersionMap.set(tableName, tableVersion + 1);
|
|
@@ -23,9 +40,24 @@ function insert(sql) {
|
|
|
23
40
|
}
|
|
24
41
|
exports.insert = insert;
|
|
25
42
|
function update(sql) {
|
|
26
|
-
return (
|
|
27
|
-
|
|
28
|
-
const
|
|
43
|
+
return (...args) => {
|
|
44
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
45
|
+
const [, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
46
|
+
const propertyKey = String(ctx.name);
|
|
47
|
+
return async function (...callArgs) {
|
|
48
|
+
const result = await queryForExecute(sql, callArgs, this, propertyKey);
|
|
49
|
+
if (cacheBean && result.affectedRows > 0) {
|
|
50
|
+
const [tableName, tableVersion] = getTableAndVersion("update", sql);
|
|
51
|
+
tableVersionMap.set(tableName, tableVersion + 1);
|
|
52
|
+
}
|
|
53
|
+
return result.affectedRows;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const target = args[0];
|
|
57
|
+
const propertyKey = args[1];
|
|
58
|
+
const descriptor = args[2];
|
|
59
|
+
descriptor.value = async (...callArgs) => {
|
|
60
|
+
const result = await queryForExecute(sql, callArgs, target, propertyKey);
|
|
29
61
|
if (cacheBean && result.affectedRows > 0) {
|
|
30
62
|
const [tableName, tableVersion] = getTableAndVersion("update", sql);
|
|
31
63
|
tableVersionMap.set(tableName, tableVersion + 1);
|
|
@@ -36,9 +68,24 @@ function update(sql) {
|
|
|
36
68
|
}
|
|
37
69
|
exports.update = update;
|
|
38
70
|
function remove(sql) {
|
|
39
|
-
return (
|
|
40
|
-
|
|
41
|
-
const
|
|
71
|
+
return (...args) => {
|
|
72
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
73
|
+
const [, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
74
|
+
const propertyKey = String(ctx.name);
|
|
75
|
+
return async function (...callArgs) {
|
|
76
|
+
const result = await queryForExecute(sql, callArgs, this, propertyKey);
|
|
77
|
+
if (cacheBean && result.affectedRows > 0) {
|
|
78
|
+
const [tableName, tableVersion] = getTableAndVersion("delete", sql);
|
|
79
|
+
tableVersionMap.set(tableName, tableVersion + 1);
|
|
80
|
+
}
|
|
81
|
+
return result.affectedRows;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const target = args[0];
|
|
85
|
+
const propertyKey = args[1];
|
|
86
|
+
const descriptor = args[2];
|
|
87
|
+
descriptor.value = async (...callArgs) => {
|
|
88
|
+
const result = await queryForExecute(sql, callArgs, target, propertyKey);
|
|
42
89
|
if (cacheBean && result.affectedRows > 0) {
|
|
43
90
|
const [tableName, tableVersion] = getTableAndVersion("delete", sql);
|
|
44
91
|
tableVersionMap.set(tableName, tableVersion + 1);
|
|
@@ -49,9 +96,35 @@ function remove(sql) {
|
|
|
49
96
|
}
|
|
50
97
|
exports.remove = remove;
|
|
51
98
|
function select(sql) {
|
|
52
|
-
return (
|
|
53
|
-
|
|
54
|
-
const [
|
|
99
|
+
return (...args) => {
|
|
100
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
101
|
+
const [, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
102
|
+
const propertyKey = String(ctx.name);
|
|
103
|
+
return async function (...callArgs) {
|
|
104
|
+
const [newSql, sqlValues] = convertSQLParams(sql, this, propertyKey, callArgs);
|
|
105
|
+
const resultType = resultTypeMap.get([this.constructor.name, propertyKey].toString());
|
|
106
|
+
if (cacheBean && cacheDefindMap.has([this.constructor.name, propertyKey].toString())) {
|
|
107
|
+
const [tableName, tableVersion] = getTableAndVersion("select", newSql);
|
|
108
|
+
const cacheKey = JSON.stringify([tableName, tableVersion, newSql, sqlValues]);
|
|
109
|
+
if (cacheBean.get(cacheKey)) {
|
|
110
|
+
return cacheBean.get(cacheKey);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const rows = await actionQuery(newSql, sqlValues, resultType);
|
|
114
|
+
cacheBean.set(cacheKey, rows, cacheDefindMap.get([this.constructor.name, propertyKey].toString()));
|
|
115
|
+
return rows;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
return await actionQuery(newSql, sqlValues, resultType);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const target = args[0];
|
|
124
|
+
const propertyKey = args[1];
|
|
125
|
+
const descriptor = args[2];
|
|
126
|
+
descriptor.value = async (...callArgs) => {
|
|
127
|
+
const [newSql, sqlValues] = convertSQLParams(sql, target, propertyKey, callArgs);
|
|
55
128
|
const resultType = resultTypeMap.get([target.constructor.name, propertyKey].toString());
|
|
56
129
|
if (cacheBean && cacheDefindMap.has([target.constructor.name, propertyKey].toString())) {
|
|
57
130
|
const [tableName, tableVersion] = getTableAndVersion("select", newSql);
|
|
@@ -73,7 +146,16 @@ function select(sql) {
|
|
|
73
146
|
}
|
|
74
147
|
exports.select = select;
|
|
75
148
|
function resultType(dataClass) {
|
|
76
|
-
return function (
|
|
149
|
+
return function (...args) {
|
|
150
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
151
|
+
const [, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
152
|
+
ctx.addInitializer(function () {
|
|
153
|
+
resultTypeMap.set([this.constructor.name, String(ctx.name)].toString(), dataClass);
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const target = args[0];
|
|
158
|
+
const propertyKey = args[1];
|
|
77
159
|
resultTypeMap.set([target.constructor.name, propertyKey].toString(), dataClass);
|
|
78
160
|
//never return
|
|
79
161
|
};
|
|
@@ -125,8 +207,17 @@ function convertSQLParams(decoratorSQL, target, propertyKey, args) {
|
|
|
125
207
|
argsVal = new Map(Object.getOwnPropertyNames(args[0]).map((valName) => [valName, args[0][valName]]));
|
|
126
208
|
}
|
|
127
209
|
else {
|
|
128
|
-
|
|
129
|
-
|
|
210
|
+
// 优先 @param(legacy reflect-metadata),否则回退 @bind(方法级声明,标准模式)
|
|
211
|
+
let existingParameters = Reflect.getOwnMetadata(paramMetadataKey, target, propertyKey);
|
|
212
|
+
if (!existingParameters) {
|
|
213
|
+
const bindMapping = (0, bind_decorator_1.getBindMapping)(target.constructor.name, propertyKey);
|
|
214
|
+
if (bindMapping) {
|
|
215
|
+
existingParameters = Object.entries(bindMapping)
|
|
216
|
+
.filter(([, value]) => typeof value === "number")
|
|
217
|
+
.map(([name, value]) => [name, value]);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
argsVal = new Map((existingParameters || []).map(([argName, argIdx]) => [argName, args[argIdx]]));
|
|
130
221
|
}
|
|
131
222
|
const regExp = /#{(\w+)}/;
|
|
132
223
|
let match;
|
|
@@ -139,7 +230,22 @@ function convertSQLParams(decoratorSQL, target, propertyKey, args) {
|
|
|
139
230
|
return [decoratorSQL, queryValues];
|
|
140
231
|
}
|
|
141
232
|
function cache(ttl) {
|
|
142
|
-
return function (
|
|
233
|
+
return function (...args) {
|
|
234
|
+
if ((0, decorator_utils_1.isStd)(args)) {
|
|
235
|
+
const [, ctx] = (0, decorator_utils_1.getStdArgs)(args);
|
|
236
|
+
ctx.addInitializer(function () {
|
|
237
|
+
cacheDefindMap.set([this.constructor.name, String(ctx.name)].toString(), ttl);
|
|
238
|
+
if (cacheBean == null) {
|
|
239
|
+
const cacheFactory = (0, core_decorator_1.getBean)(cache_factory_class_1.default);
|
|
240
|
+
if (cacheFactory || cacheFactory["factory"]) {
|
|
241
|
+
cacheBean = cacheFactory["factory"];
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const target = args[0];
|
|
248
|
+
const propertyKey = args[1];
|
|
143
249
|
cacheDefindMap.set([target.constructor.name, propertyKey].toString(), ttl);
|
|
144
250
|
if (cacheBean == null) {
|
|
145
251
|
const cacheFactory = (0, core_decorator_1.getBean)(cache_factory_class_1.default);
|