@eggjs/dal-runtime 4.0.0-beta.3 → 4.0.0-beta.5
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 +13 -1
- package/dist/BaseSqlMap.d.ts +18 -0
- package/dist/BaseSqlMap.js +247 -0
- package/dist/CodeGenerator.d.ts +17 -0
- package/dist/CodeGenerator.js +114 -0
- package/dist/DaoLoader.d.ts +8 -0
- package/dist/DaoLoader.js +16 -0
- package/dist/DataSource.d.ts +32 -0
- package/dist/DataSource.js +72 -0
- package/dist/DatabaseForker.d.ts +16 -0
- package/dist/DatabaseForker.js +47 -0
- package/dist/MySqlDataSource.d.ts +27 -0
- package/dist/MySqlDataSource.js +49 -0
- package/dist/NunjucksConverter.d.ts +76 -0
- package/dist/NunjucksConverter.js +90 -0
- package/dist/NunjucksUtil.d.ts +9 -0
- package/dist/NunjucksUtil.js +64 -0
- package/dist/SqlGenerator.d.ts +13 -0
- package/dist/SqlGenerator.js +224 -0
- package/dist/SqlMapLoader.d.ts +14 -0
- package/dist/SqlMapLoader.js +22 -0
- package/dist/SqlUtil.d.ts +6 -0
- package/dist/SqlUtil.js +193 -0
- package/dist/TableModelInstanceBuilder.d.ts +10 -0
- package/dist/TableModelInstanceBuilder.js +25 -0
- package/dist/TableSqlMap.d.ts +18 -0
- package/dist/TableSqlMap.js +81 -0
- package/dist/TemplateUtil.d.ts +22 -0
- package/dist/TemplateUtil.js +78 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +16 -0
- package/package.json +27 -28
- package/src/BaseSqlMap.d.ts +0 -15
- package/src/BaseSqlMap.js +0 -303
- package/src/CodeGenerator.d.ts +0 -14
- package/src/CodeGenerator.js +0 -138
- package/src/DaoLoader.d.ts +0 -4
- package/src/DaoLoader.js +0 -13
- package/src/DataSource.d.ts +0 -28
- package/src/DataSource.js +0 -79
- package/src/DatabaseForker.d.ts +0 -12
- package/src/DatabaseForker.js +0 -49
- package/src/MySqlDataSource.d.ts +0 -23
- package/src/MySqlDataSource.js +0 -63
- package/src/NunjucksConverter.d.ts +0 -73
- package/src/NunjucksConverter.js +0 -93
- package/src/NunjucksUtil.d.ts +0 -5
- package/src/NunjucksUtil.js +0 -74
- package/src/SqlGenerator.d.ts +0 -9
- package/src/SqlGenerator.js +0 -391
- package/src/SqlMapLoader.d.ts +0 -10
- package/src/SqlMapLoader.js +0 -19
- package/src/SqlUtil.d.ts +0 -3
- package/src/SqlUtil.js +0 -221
- package/src/TableModelInstanceBuilder.d.ts +0 -6
- package/src/TableModelInstanceBuilder.js +0 -22
- package/src/TableSqlMap.d.ts +0 -19
- package/src/TableSqlMap.js +0 -91
- package/src/TemplateUtil.d.ts +0 -19
- package/src/TemplateUtil.js +0 -86
- package/src/index.d.ts +0 -15
- package/src/index.js +0 -16
package/README.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# `@eggjs/dal-runtime`
|
|
2
2
|
|
|
3
|
+
[![NPM version][npm-image]][npm-url]
|
|
4
|
+
[![Known Vulnerabilities][snyk-image]][snyk-url]
|
|
5
|
+
[![npm download][download-image]][download-url]
|
|
6
|
+
[](https://nodejs.org/en/download/)
|
|
7
|
+
|
|
8
|
+
[npm-image]: https://img.shields.io/npm/v/@eggjs/dal-runtime.svg?style=flat-square
|
|
9
|
+
[npm-url]: https://npmjs.org/package/@eggjs/dal-runtime
|
|
10
|
+
[snyk-image]: https://snyk.io/test/npm/@eggjs/dal-runtime/badge.svg?style=flat-square
|
|
11
|
+
[snyk-url]: https://snyk.io/test/npm/@eggjs/dal-runtime
|
|
12
|
+
[download-image]: https://img.shields.io/npm/dm/@eggjs/dal-runtime.svg?style=flat-square
|
|
13
|
+
[download-url]: https://npmjs.org/package/@eggjs/dal-runtime
|
|
14
|
+
|
|
3
15
|
## Usage
|
|
4
16
|
|
|
5
|
-
Please read [@eggjs/tegg-dal-plugin](../../plugin/dal
|
|
17
|
+
Please read [@eggjs/tegg-dal-plugin](../../plugin/dal/README.md)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TableModel } from "@eggjs/dal-decorator";
|
|
2
|
+
import { GenerateSqlMap, Logger, SqlMap } from "@eggjs/tegg-types";
|
|
3
|
+
|
|
4
|
+
//#region src/BaseSqlMap.d.ts
|
|
5
|
+
declare class BaseSqlMapGenerator {
|
|
6
|
+
private readonly tableModel;
|
|
7
|
+
private readonly logger;
|
|
8
|
+
constructor(tableModel: TableModel, logger: Logger);
|
|
9
|
+
generateAllColumns(countIf: boolean): string;
|
|
10
|
+
generateFindByPrimary(): Array<GenerateSqlMap>;
|
|
11
|
+
generateFindByIndexes(): GenerateSqlMap[];
|
|
12
|
+
generateInsert(): string;
|
|
13
|
+
generateUpdate(): string;
|
|
14
|
+
generateDelete(): string;
|
|
15
|
+
load(): Record<string, SqlMap>;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { BaseSqlMapGenerator };
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { TemplateUtil } from "./TemplateUtil.js";
|
|
2
|
+
import _ from "lodash";
|
|
3
|
+
import { TableModel } from "@eggjs/dal-decorator";
|
|
4
|
+
import { ColumnType, IndexType, SqlType } from "@eggjs/tegg-types";
|
|
5
|
+
|
|
6
|
+
//#region src/BaseSqlMap.ts
|
|
7
|
+
var BaseSqlMapGenerator = class {
|
|
8
|
+
constructor(tableModel, logger) {
|
|
9
|
+
this.tableModel = tableModel;
|
|
10
|
+
this.logger = logger;
|
|
11
|
+
}
|
|
12
|
+
generateAllColumns(countIf) {
|
|
13
|
+
const str = this.tableModel.columns.map((t) => `\`${t.columnName}\``).join(",");
|
|
14
|
+
return countIf ? `{% if $$count == true %}0{% else %}${str}{% endif %}` : str;
|
|
15
|
+
}
|
|
16
|
+
generateFindByPrimary() {
|
|
17
|
+
const result = [];
|
|
18
|
+
const primary = this.tableModel.getPrimary();
|
|
19
|
+
if (!primary) {
|
|
20
|
+
this.logger.warn(`表 \`${this.tableModel.name}\` 没有主键,无法生成主键查询语句。`);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
let sql = `SELECT ${this.generateAllColumns(true)}
|
|
24
|
+
FROM \`${this.tableModel.name}\`
|
|
25
|
+
WHERE `;
|
|
26
|
+
sql += primary.keys.map((indexKey) => `\`${indexKey.columnName}\` = {{$${indexKey.propertyName}}}`).join(" AND ");
|
|
27
|
+
if (primary.keys.length === 1) result.push({
|
|
28
|
+
type: SqlType.SELECT,
|
|
29
|
+
name: `findBy${_.upperFirst(primary.keys[0].propertyName)}`,
|
|
30
|
+
sql
|
|
31
|
+
});
|
|
32
|
+
result.push({
|
|
33
|
+
name: "findByPrimary",
|
|
34
|
+
type: SqlType.SELECT,
|
|
35
|
+
sql
|
|
36
|
+
});
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
generateFindByIndexes() {
|
|
40
|
+
const sqlMaps = [];
|
|
41
|
+
for (const index of this.tableModel.indices) {
|
|
42
|
+
if (index.type === IndexType.PRIMARY) continue;
|
|
43
|
+
let sql = `SELECT ${this.generateAllColumns(true)}
|
|
44
|
+
FROM \`${this.tableModel.name}\`
|
|
45
|
+
WHERE `;
|
|
46
|
+
sql += index.keys.map((indexKey) => {
|
|
47
|
+
return `\`${indexKey.columnName}\` {{ "IS" if $${indexKey.propertyName} == null else "=" }} {{$${indexKey.propertyName}}}`;
|
|
48
|
+
}).join(" AND ");
|
|
49
|
+
const tempName = _.upperFirst(_.camelCase(index.keys.length === 1 ? index.keys[0].propertyName : index.name));
|
|
50
|
+
sqlMaps.push({
|
|
51
|
+
name: `findBy${tempName}`,
|
|
52
|
+
type: SqlType.SELECT,
|
|
53
|
+
sql
|
|
54
|
+
});
|
|
55
|
+
sqlMaps.push({
|
|
56
|
+
name: `findOneBy${tempName}`,
|
|
57
|
+
type: SqlType.SELECT,
|
|
58
|
+
sql: `${sql} LIMIT 0, 1`
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return sqlMaps;
|
|
62
|
+
}
|
|
63
|
+
generateInsert() {
|
|
64
|
+
let sql = `INSERT INTO \`${this.tableModel.name}\` `;
|
|
65
|
+
sql += "{% set ___first = true %}";
|
|
66
|
+
const keys = [];
|
|
67
|
+
const values = [];
|
|
68
|
+
for (const column of this.tableModel.columns) {
|
|
69
|
+
const { propertyName, columnName, type } = column;
|
|
70
|
+
if (column.propertyName !== "gmtCreate" && column.propertyName !== "gmtModified") {
|
|
71
|
+
keys.push(`
|
|
72
|
+
{% if $${propertyName} !== undefined %}
|
|
73
|
+
{% if ___first %}
|
|
74
|
+
{% set ___first = false %}
|
|
75
|
+
{% else %}
|
|
76
|
+
,
|
|
77
|
+
{% endif %}
|
|
78
|
+
|
|
79
|
+
\`${columnName}\`
|
|
80
|
+
{% endif %}
|
|
81
|
+
`.trim());
|
|
82
|
+
if (TemplateUtil.isSpatialType(column)) {
|
|
83
|
+
const filter = TemplateUtil.getSpatialFilter(column.type.type);
|
|
84
|
+
values.push(`
|
|
85
|
+
{% if $${propertyName} !== undefined %}
|
|
86
|
+
{% if ___first %}
|
|
87
|
+
{% set ___first = false %}
|
|
88
|
+
{% else %}
|
|
89
|
+
,
|
|
90
|
+
{% endif %}
|
|
91
|
+
|
|
92
|
+
{{$${propertyName} | ${filter}}}
|
|
93
|
+
{% endif %}
|
|
94
|
+
`.trim());
|
|
95
|
+
} else if (column.type.type === ColumnType.JSON) values.push(`
|
|
96
|
+
{% if $${propertyName} !== undefined %}
|
|
97
|
+
{% if ___first %}
|
|
98
|
+
{% set ___first = false %}
|
|
99
|
+
{% else %}
|
|
100
|
+
,
|
|
101
|
+
{% endif %}
|
|
102
|
+
|
|
103
|
+
{{$${propertyName} | toJson}}
|
|
104
|
+
{% endif %}
|
|
105
|
+
`.trim());
|
|
106
|
+
else values.push(`
|
|
107
|
+
{% if $${propertyName} !== undefined %}
|
|
108
|
+
{% if ___first %}
|
|
109
|
+
{% set ___first = false %}
|
|
110
|
+
{% else %}
|
|
111
|
+
,
|
|
112
|
+
{% endif %}
|
|
113
|
+
|
|
114
|
+
{{$${propertyName}}}
|
|
115
|
+
{% endif %}
|
|
116
|
+
`.trim());
|
|
117
|
+
} else {
|
|
118
|
+
let now;
|
|
119
|
+
if (type.type === ColumnType.INT) now = "UNIX_TIMESTAMP()";
|
|
120
|
+
else if (type.type === ColumnType.BIGINT) now = "ROUND(UNIX_TIMESTAMP(CURTIME(4)) * 1000)";
|
|
121
|
+
else if (type.type === ColumnType.DATETIME || type.type === ColumnType.TIMESTAMP) now = type.precision ? `NOW(${type.precision})` : "NOW()";
|
|
122
|
+
else this.logger.warn(`unknown type ${type.type} for ${propertyName}`);
|
|
123
|
+
keys.push(`
|
|
124
|
+
{% if ___first %}
|
|
125
|
+
{% set ___first = false %}
|
|
126
|
+
{% else %}
|
|
127
|
+
,
|
|
128
|
+
{% endif %}
|
|
129
|
+
|
|
130
|
+
\`${columnName}\`
|
|
131
|
+
`.trim());
|
|
132
|
+
values.push(`
|
|
133
|
+
{% if ___first %}
|
|
134
|
+
{% set ___first = false %}
|
|
135
|
+
{% else %}
|
|
136
|
+
,
|
|
137
|
+
{% endif %}
|
|
138
|
+
|
|
139
|
+
{{ $${propertyName} if $${propertyName} !== undefined else '${now}' }}
|
|
140
|
+
`.trim());
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
sql += `(${keys.join("")})`;
|
|
144
|
+
sql += "{% set ___first = true %}";
|
|
145
|
+
sql += `VALUES(${values.join("")});`;
|
|
146
|
+
return sql;
|
|
147
|
+
}
|
|
148
|
+
generateUpdate() {
|
|
149
|
+
const primary = this.tableModel.getPrimary();
|
|
150
|
+
if (!primary) {
|
|
151
|
+
this.logger.warn(`表 \`${this.tableModel.name}\` 没有主键,无法生成主键更新语句。`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
let sql = `UPDATE \`${this.tableModel.name}\` SET`;
|
|
155
|
+
sql += "{% set ___first = true %}";
|
|
156
|
+
const kv = [];
|
|
157
|
+
for (const column of this.tableModel.columns) {
|
|
158
|
+
const { type, propertyName, columnName } = column;
|
|
159
|
+
let now;
|
|
160
|
+
if (type.type === ColumnType.INT) now = "UNIX_TIMESTAMP()";
|
|
161
|
+
else if (type.type === ColumnType.BIGINT) now = "ROUND(UNIX_TIMESTAMP(CURTIME(4)) * 1000)";
|
|
162
|
+
else if (type.type === ColumnType.TIMESTAMP || type.type === ColumnType.DATETIME) now = type.precision ? `NOW(${type.precision})` : "NOW()";
|
|
163
|
+
const temp = propertyName !== "gmtModified" ? `
|
|
164
|
+
{% if $${propertyName} !== undefined %}
|
|
165
|
+
{% if ___first %}
|
|
166
|
+
{% set ___first = false %}
|
|
167
|
+
{% else %}
|
|
168
|
+
,
|
|
169
|
+
{% endif %}
|
|
170
|
+
|
|
171
|
+
\`${columnName}\` = {{$${propertyName}}}
|
|
172
|
+
{% endif %}
|
|
173
|
+
` : `
|
|
174
|
+
{% if ___first %}
|
|
175
|
+
{% set ___first = false %}
|
|
176
|
+
{% else %}
|
|
177
|
+
,
|
|
178
|
+
{% endif %}
|
|
179
|
+
|
|
180
|
+
\`${columnName}\` =
|
|
181
|
+
{{ $${propertyName} if $${propertyName} !== undefined else '${now}' }}
|
|
182
|
+
`;
|
|
183
|
+
kv.push(temp);
|
|
184
|
+
}
|
|
185
|
+
sql += kv.join("");
|
|
186
|
+
sql += `WHERE ${primary.keys.map((indexKey) => `\`${indexKey.columnName}\` = {{primary.${indexKey.propertyName}}}`).join(" AND ")}`;
|
|
187
|
+
return sql;
|
|
188
|
+
}
|
|
189
|
+
generateDelete() {
|
|
190
|
+
const primary = this.tableModel.getPrimary();
|
|
191
|
+
if (!primary) {
|
|
192
|
+
this.logger.warn(`表 \`${this.tableModel.name}\` 没有主键,无法生成主键删除语句。`);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
let sql = `DELETE
|
|
196
|
+
FROM \`${this.tableModel.name}\`
|
|
197
|
+
WHERE `;
|
|
198
|
+
sql += primary.keys.map((indexKey) => `\`${indexKey.columnName}\` = {{${indexKey.propertyName}}}`).join(" AND ");
|
|
199
|
+
return sql;
|
|
200
|
+
}
|
|
201
|
+
load() {
|
|
202
|
+
const map = {};
|
|
203
|
+
map.allColumns = {
|
|
204
|
+
type: SqlType.BLOCK,
|
|
205
|
+
content: this.generateAllColumns(false)
|
|
206
|
+
};
|
|
207
|
+
const sqlMaps = [
|
|
208
|
+
...this.generateFindByPrimary(),
|
|
209
|
+
...this.generateFindByIndexes(),
|
|
210
|
+
(
|
|
211
|
+
/**
|
|
212
|
+
* 插入
|
|
213
|
+
*/
|
|
214
|
+
{
|
|
215
|
+
name: "insert",
|
|
216
|
+
type: SqlType.INSERT,
|
|
217
|
+
sql: this.generateInsert()
|
|
218
|
+
}),
|
|
219
|
+
(
|
|
220
|
+
/**
|
|
221
|
+
* 主键更新
|
|
222
|
+
*/
|
|
223
|
+
{
|
|
224
|
+
name: "update",
|
|
225
|
+
type: SqlType.UPDATE,
|
|
226
|
+
sql: this.generateUpdate()
|
|
227
|
+
}),
|
|
228
|
+
(
|
|
229
|
+
/**
|
|
230
|
+
* 主键删除
|
|
231
|
+
*/
|
|
232
|
+
{
|
|
233
|
+
name: "delete",
|
|
234
|
+
type: SqlType.DELETE,
|
|
235
|
+
sql: this.generateDelete()
|
|
236
|
+
})
|
|
237
|
+
];
|
|
238
|
+
for (const sqlMap of sqlMaps) map[sqlMap.name] = {
|
|
239
|
+
type: sqlMap.type,
|
|
240
|
+
sql: sqlMap.sql
|
|
241
|
+
};
|
|
242
|
+
return map;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
//#endregion
|
|
247
|
+
export { BaseSqlMapGenerator };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { TableModel } from "@eggjs/dal-decorator";
|
|
2
|
+
import { CodeGeneratorOptions, Templates } from "@eggjs/tegg-types";
|
|
3
|
+
|
|
4
|
+
//#region src/CodeGenerator.d.ts
|
|
5
|
+
declare class CodeGenerator {
|
|
6
|
+
private readonly moduleDir;
|
|
7
|
+
private readonly moduleName;
|
|
8
|
+
private readonly teggPkg;
|
|
9
|
+
private readonly dalPkg;
|
|
10
|
+
constructor(options: CodeGeneratorOptions);
|
|
11
|
+
private njkEnv;
|
|
12
|
+
createNunjucksEnv(): void;
|
|
13
|
+
genCode(tplName: Templates, filePath: string, tableModel: TableModel): string;
|
|
14
|
+
generate(tableModel: TableModel): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { CodeGenerator };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { TemplateUtil } from "./TemplateUtil.js";
|
|
2
|
+
import { SqlGenerator } from "./SqlGenerator.js";
|
|
3
|
+
import _ from "lodash";
|
|
4
|
+
import { ColumnModel, TableModel } from "@eggjs/dal-decorator";
|
|
5
|
+
import { Templates } from "@eggjs/tegg-types";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
import js_beautify from "js-beautify";
|
|
9
|
+
import nunjucks from "nunjucks";
|
|
10
|
+
import { PrototypeUtil } from "@eggjs/core-decorator";
|
|
11
|
+
|
|
12
|
+
//#region src/CodeGenerator.ts
|
|
13
|
+
var CodeGenerator = class {
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.moduleDir = options.moduleDir;
|
|
16
|
+
this.moduleName = options.moduleName;
|
|
17
|
+
this.teggPkg = options.teggPkg ?? "@eggjs/tegg";
|
|
18
|
+
this.dalPkg = options.dalPkg ?? "@eggjs/tegg/dal";
|
|
19
|
+
this.createNunjucksEnv();
|
|
20
|
+
}
|
|
21
|
+
createNunjucksEnv() {
|
|
22
|
+
this.njkEnv = nunjucks.configure(path.join(__dirname, "./templates"), { autoescape: false });
|
|
23
|
+
this.njkEnv.addFilter("pascalCase", (name) => _.upperFirst(_.camelCase(name)));
|
|
24
|
+
this.njkEnv.addFilter("camelCase", (name) => _.camelCase(name));
|
|
25
|
+
this.njkEnv.addFilter("dbTypeToTSType", TemplateUtil.dbTypeToTsType);
|
|
26
|
+
}
|
|
27
|
+
genCode(tplName, filePath, tableModel) {
|
|
28
|
+
let tableModelAbsolutePath = PrototypeUtil.getFilePath(tableModel.clazz);
|
|
29
|
+
tableModelAbsolutePath = tableModelAbsolutePath.substring(0, tableModelAbsolutePath.length - 3);
|
|
30
|
+
const data = {
|
|
31
|
+
table: tableModel,
|
|
32
|
+
file: filePath,
|
|
33
|
+
fileName: path.basename(filePath),
|
|
34
|
+
clazzName: tableModel.clazz.name,
|
|
35
|
+
moduleName: this.moduleName,
|
|
36
|
+
teggPkg: this.teggPkg,
|
|
37
|
+
dalPkg: this.dalPkg,
|
|
38
|
+
id: tableModel.columns.find((t) => t.propertyName === "id"),
|
|
39
|
+
primaryIndex: tableModel.getPrimary(),
|
|
40
|
+
tableModelPath: TemplateUtil.importPath(tableModelAbsolutePath, path.dirname(filePath)),
|
|
41
|
+
extensionPath: `../../extension/${tableModel.clazz.name}Extension`,
|
|
42
|
+
structurePath: `../../structure/${tableModel.clazz.name}.json`,
|
|
43
|
+
sqlPath: `../../structure/${tableModel.clazz.name}.sql`,
|
|
44
|
+
columnMap: tableModel.columns.reduce((p, c) => {
|
|
45
|
+
p[c.propertyName] = c;
|
|
46
|
+
return p;
|
|
47
|
+
}, {})
|
|
48
|
+
};
|
|
49
|
+
return this.njkEnv.render(`${tplName}.njk`, data);
|
|
50
|
+
}
|
|
51
|
+
async generate(tableModel) {
|
|
52
|
+
let dalDir;
|
|
53
|
+
try {
|
|
54
|
+
await fs.access(path.join(this.moduleDir, "src"));
|
|
55
|
+
dalDir = path.join(this.moduleDir, "src/dal");
|
|
56
|
+
} catch {
|
|
57
|
+
dalDir = path.join(this.moduleDir, "dal");
|
|
58
|
+
}
|
|
59
|
+
const clazzFileName = path.basename(PrototypeUtil.getFilePath(tableModel.clazz));
|
|
60
|
+
const baseFileName = path.basename(clazzFileName, ".ts");
|
|
61
|
+
const paths = {
|
|
62
|
+
baseBizDAO: path.join(dalDir, `dao/base/Base${baseFileName}DAO.ts`),
|
|
63
|
+
bizDAO: path.join(dalDir, `dao/${baseFileName}DAO.ts`),
|
|
64
|
+
extension: path.join(dalDir, `extension/${baseFileName}Extension.ts`),
|
|
65
|
+
structure: path.join(dalDir, `structure/${baseFileName}.json`),
|
|
66
|
+
structureSql: path.join(dalDir, `structure/${baseFileName}.sql`)
|
|
67
|
+
};
|
|
68
|
+
await fs.mkdir(path.dirname(paths.structure), { recursive: true });
|
|
69
|
+
await fs.writeFile(paths.structure, JSON.stringify(tableModel, null, 2), "utf8");
|
|
70
|
+
const structureSql = new SqlGenerator().generate(tableModel);
|
|
71
|
+
await fs.writeFile(paths.structureSql, structureSql, "utf8");
|
|
72
|
+
const codes = [
|
|
73
|
+
{
|
|
74
|
+
templates: Templates.BASE_DAO,
|
|
75
|
+
filePath: paths.baseBizDAO,
|
|
76
|
+
beautify: true,
|
|
77
|
+
overwrite: true
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
templates: Templates.DAO,
|
|
81
|
+
filePath: paths.bizDAO,
|
|
82
|
+
beautify: true,
|
|
83
|
+
overwrite: false
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
templates: Templates.EXTENSION,
|
|
87
|
+
filePath: paths.extension,
|
|
88
|
+
beautify: false,
|
|
89
|
+
overwrite: false
|
|
90
|
+
}
|
|
91
|
+
];
|
|
92
|
+
for (const { templates, filePath, beautify, overwrite } of codes) {
|
|
93
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
94
|
+
const code = this.genCode(templates, filePath, tableModel);
|
|
95
|
+
let beautified;
|
|
96
|
+
if (beautify) beautified = js_beautify(code, {
|
|
97
|
+
brace_style: "preserve-inline",
|
|
98
|
+
indent_size: 2,
|
|
99
|
+
jslint_happy: true,
|
|
100
|
+
preserve_newlines: false
|
|
101
|
+
});
|
|
102
|
+
else beautified = code;
|
|
103
|
+
beautified = beautified.replace(/( )*\/\/ empty-line( )*/g, "").replace(/Promise( )*<( )*(.+?)( )*>/g, "Promise<$3>").replace(/Optional( )*<( )*(.+?)( )*>/g, "Optional<$3>").replace(/Record( )*<( )*(.+?)( )*>/g, "Record<$3>").replace(/Partial( )*<( )*(.+?)( )*>/g, "Partial<$3>").replace(/DataSource( )*<( )*(.+?)( )*>/g, "DataSource<$3>").replace(/ \? :/g, "?:");
|
|
104
|
+
if (overwrite !== true) try {
|
|
105
|
+
await fs.access(filePath);
|
|
106
|
+
continue;
|
|
107
|
+
} catch {}
|
|
108
|
+
await fs.writeFile(filePath, beautified, "utf8");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
//#endregion
|
|
114
|
+
export { CodeGenerator };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DaoInfoUtil } from "@eggjs/dal-decorator";
|
|
2
|
+
import { EggLoadUnitType } from "@eggjs/tegg-types";
|
|
3
|
+
import "@eggjs/tegg-types/dal";
|
|
4
|
+
import { LoaderFactory } from "@eggjs/tegg-loader";
|
|
5
|
+
|
|
6
|
+
//#region src/DaoLoader.ts
|
|
7
|
+
var DaoLoader = class {
|
|
8
|
+
static async loadDaos(moduleDir) {
|
|
9
|
+
return (await LoaderFactory.createLoader(moduleDir, EggLoadUnitType.MODULE).load()).filter((t) => {
|
|
10
|
+
return DaoInfoUtil.getIsDao(t);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { DaoLoader };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { MysqlDataSource } from "./MySqlDataSource.js";
|
|
2
|
+
import { TableSqlMap } from "./TableSqlMap.js";
|
|
3
|
+
import { TableModel } from "@eggjs/dal-decorator";
|
|
4
|
+
import { DataSource, PaginateData, SqlType } from "@eggjs/tegg-types";
|
|
5
|
+
|
|
6
|
+
//#region src/DataSource.d.ts
|
|
7
|
+
interface ExecuteSql {
|
|
8
|
+
sql: string;
|
|
9
|
+
template: string;
|
|
10
|
+
sqlType: SqlType;
|
|
11
|
+
}
|
|
12
|
+
declare class DataSource$1<T> implements DataSource<T> {
|
|
13
|
+
#private;
|
|
14
|
+
private readonly tableModel;
|
|
15
|
+
private readonly mysqlDataSource;
|
|
16
|
+
private readonly sqlMap;
|
|
17
|
+
constructor(tableModel: TableModel<T>, mysqlDataSource: MysqlDataSource, sqlMap: TableSqlMap);
|
|
18
|
+
/**
|
|
19
|
+
* public for aop execute to implement sql hint append
|
|
20
|
+
* @param sqlName - sql name
|
|
21
|
+
* @param data - sql data
|
|
22
|
+
*/
|
|
23
|
+
generateSql(sqlName: string, data: object): Promise<ExecuteSql>;
|
|
24
|
+
count(sqlName: string, data?: any): Promise<number>;
|
|
25
|
+
execute(sqlName: string, data?: any): Promise<Array<T>>;
|
|
26
|
+
executeRaw(sqlName: string, data?: any): Promise<Array<any>>;
|
|
27
|
+
executeScalar(sqlName: string, data?: any): Promise<T | null>;
|
|
28
|
+
executeRawScalar(sqlName: string, data?: any): Promise<any | null>;
|
|
29
|
+
paginate(sqlName: string, data: any, currentPage: number, perPageCount: number): Promise<PaginateData<T>>;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { DataSource$1 as DataSource, ExecuteSql };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import "./MySqlDataSource.js";
|
|
2
|
+
import "./TableSqlMap.js";
|
|
3
|
+
import { TableModelInstanceBuilder } from "./TableModelInstanceBuilder.js";
|
|
4
|
+
import { TableModel } from "@eggjs/dal-decorator";
|
|
5
|
+
|
|
6
|
+
//#region src/DataSource.ts
|
|
7
|
+
const PAGINATE_COUNT_WRAPPER = ["SELECT COUNT(0) as count FROM (", ") AS T"];
|
|
8
|
+
var DataSource = class {
|
|
9
|
+
constructor(tableModel, mysqlDataSource, sqlMap) {
|
|
10
|
+
this.tableModel = tableModel;
|
|
11
|
+
this.mysqlDataSource = mysqlDataSource;
|
|
12
|
+
this.sqlMap = sqlMap;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* public for aop execute to implement sql hint append
|
|
16
|
+
* @param sqlName - sql name
|
|
17
|
+
* @param data - sql data
|
|
18
|
+
*/
|
|
19
|
+
async generateSql(sqlName, data) {
|
|
20
|
+
const sql = this.sqlMap.generate(sqlName, data, this.mysqlDataSource.timezone);
|
|
21
|
+
const sqlType = this.sqlMap.getType(sqlName);
|
|
22
|
+
const template = this.sqlMap.getTemplateString(sqlName);
|
|
23
|
+
return {
|
|
24
|
+
sql,
|
|
25
|
+
sqlType,
|
|
26
|
+
template
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async count(sqlName, data) {
|
|
30
|
+
const newData = Object.assign({ $$count: true }, data);
|
|
31
|
+
const executeSql = await this.generateSql(sqlName, newData);
|
|
32
|
+
return await this.#paginateCount(executeSql.sql);
|
|
33
|
+
}
|
|
34
|
+
async execute(sqlName, data) {
|
|
35
|
+
const executeSql = await this.generateSql(sqlName, data);
|
|
36
|
+
return (await this.mysqlDataSource.query(executeSql.sql)).map((t) => {
|
|
37
|
+
return TableModelInstanceBuilder.buildInstance(this.tableModel, t);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async executeRaw(sqlName, data) {
|
|
41
|
+
const executeSql = await this.generateSql(sqlName, data);
|
|
42
|
+
return await this.mysqlDataSource.query(executeSql.sql);
|
|
43
|
+
}
|
|
44
|
+
async executeScalar(sqlName, data) {
|
|
45
|
+
const ret = await this.execute(sqlName, data);
|
|
46
|
+
if (!Array.isArray(ret)) return ret || null;
|
|
47
|
+
return ret[0] || null;
|
|
48
|
+
}
|
|
49
|
+
async executeRawScalar(sqlName, data) {
|
|
50
|
+
const ret = await this.executeRaw(sqlName, data);
|
|
51
|
+
if (!Array.isArray(ret)) return ret || null;
|
|
52
|
+
return ret[0] || null;
|
|
53
|
+
}
|
|
54
|
+
async paginate(sqlName, data, currentPage, perPageCount) {
|
|
55
|
+
const limit = `LIMIT ${(currentPage - 1) * perPageCount}, ${perPageCount}`;
|
|
56
|
+
const sql = (await this.generateSql(sqlName, data)).sql + " " + limit;
|
|
57
|
+
const countSql = (await this.generateSql(sqlName, Object.assign({ $$count: true }, data))).sql;
|
|
58
|
+
const ret = await Promise.all([this.mysqlDataSource.query(sql), this.#paginateCount(countSql)]);
|
|
59
|
+
return {
|
|
60
|
+
total: Number(ret[1]),
|
|
61
|
+
pageNum: currentPage,
|
|
62
|
+
rows: ret[0].map((t) => TableModelInstanceBuilder.buildInstance(this.tableModel, t))
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async #paginateCount(baseSQL) {
|
|
66
|
+
const sql = `${PAGINATE_COUNT_WRAPPER[0]}${baseSQL}${PAGINATE_COUNT_WRAPPER[1]}`;
|
|
67
|
+
return (await this.mysqlDataSource.query(sql))[0].count;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { DataSource };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DataSourceOptions } from "./MySqlDataSource.js";
|
|
2
|
+
|
|
3
|
+
//#region src/DatabaseForker.d.ts
|
|
4
|
+
declare class DatabaseForker {
|
|
5
|
+
private readonly env;
|
|
6
|
+
private readonly options;
|
|
7
|
+
constructor(env: string, options: DataSourceOptions);
|
|
8
|
+
shouldFork(): boolean;
|
|
9
|
+
forkDb(moduleDir: string): Promise<void>;
|
|
10
|
+
private forkTables;
|
|
11
|
+
private doForkTable;
|
|
12
|
+
private doCreateUtDb;
|
|
13
|
+
destroy(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { DatabaseForker };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { DaoLoader } from "./DaoLoader.js";
|
|
2
|
+
import "./MySqlDataSource.js";
|
|
3
|
+
import assert from "node:assert";
|
|
4
|
+
import { RDSClient } from "@eggjs/rds";
|
|
5
|
+
|
|
6
|
+
//#region src/DatabaseForker.ts
|
|
7
|
+
var DatabaseForker = class {
|
|
8
|
+
constructor(env, options) {
|
|
9
|
+
this.env = env;
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
shouldFork() {
|
|
13
|
+
return this.env === "unittest" && this.options.forkDb;
|
|
14
|
+
}
|
|
15
|
+
async forkDb(moduleDir) {
|
|
16
|
+
assert(this.shouldFork(), "fork db only run in unittest");
|
|
17
|
+
const { name, initSql, forkDb, database,...mysqlOptions } = this.options;
|
|
18
|
+
const client = new RDSClient(Object.assign(mysqlOptions));
|
|
19
|
+
const conn = await client.getConnection();
|
|
20
|
+
await this.doCreateUtDb(conn);
|
|
21
|
+
await this.forkTables(conn, moduleDir);
|
|
22
|
+
conn.release();
|
|
23
|
+
await client.end();
|
|
24
|
+
}
|
|
25
|
+
async forkTables(conn, moduleDir) {
|
|
26
|
+
const daoClazzList = await DaoLoader.loadDaos(moduleDir);
|
|
27
|
+
for (const clazz of daoClazzList) await this.doForkTable(conn, clazz.tableSql);
|
|
28
|
+
}
|
|
29
|
+
async doForkTable(conn, sqlFile) {
|
|
30
|
+
const sqls = sqlFile.split(";").filter((t) => !!t.trim());
|
|
31
|
+
for (const sql of sqls) await conn.query(sql);
|
|
32
|
+
}
|
|
33
|
+
async doCreateUtDb(conn) {
|
|
34
|
+
await conn.query(`CREATE DATABASE IF NOT EXISTS ${this.options.database};`);
|
|
35
|
+
await conn.query(`use ${this.options.database};`);
|
|
36
|
+
}
|
|
37
|
+
async destroy() {
|
|
38
|
+
assert(this.shouldFork(), "fork db only run in unittest");
|
|
39
|
+
const { name, initSql, forkDb, database,...mysqlOptions } = this.options;
|
|
40
|
+
const client = new RDSClient(Object.assign(mysqlOptions));
|
|
41
|
+
await client.query(`DROP DATABASE ${database}`);
|
|
42
|
+
await client.end();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { DatabaseForker };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Logger } from "@eggjs/tegg-types";
|
|
2
|
+
import { RDSClientOptions } from "@eggjs/rds";
|
|
3
|
+
import { Base } from "sdk-base";
|
|
4
|
+
|
|
5
|
+
//#region src/MySqlDataSource.d.ts
|
|
6
|
+
interface DataSourceOptions extends RDSClientOptions {
|
|
7
|
+
name: string;
|
|
8
|
+
initSql?: string;
|
|
9
|
+
forkDb?: boolean;
|
|
10
|
+
initRetryTimes?: number;
|
|
11
|
+
logger?: Logger;
|
|
12
|
+
}
|
|
13
|
+
declare class MysqlDataSource extends Base {
|
|
14
|
+
#private;
|
|
15
|
+
private client;
|
|
16
|
+
private readonly initSql;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly timezone?: string;
|
|
19
|
+
readonly rdsOptions: RDSClientOptions;
|
|
20
|
+
readonly forkDb?: boolean;
|
|
21
|
+
constructor(options: DataSourceOptions);
|
|
22
|
+
protected _init(): Promise<void>;
|
|
23
|
+
query<T = any>(sql: string): Promise<T>;
|
|
24
|
+
beginTransactionScope<T>(scope: () => Promise<T>): Promise<T>;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { DataSourceOptions, MysqlDataSource };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { RDSClient } from "@eggjs/rds";
|
|
2
|
+
import { Base } from "sdk-base";
|
|
3
|
+
|
|
4
|
+
//#region src/MySqlDataSource.ts
|
|
5
|
+
const DEFAULT_OPTIONS = {
|
|
6
|
+
supportBigNumbers: true,
|
|
7
|
+
bigNumberStrings: true,
|
|
8
|
+
trace: true
|
|
9
|
+
};
|
|
10
|
+
var MysqlDataSource = class extends Base {
|
|
11
|
+
#initRetryTimes;
|
|
12
|
+
#logger;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
super({ initMethod: "_init" });
|
|
15
|
+
const { name, initSql, forkDb, initRetryTimes, logger,...mysqlOptions } = options;
|
|
16
|
+
this.#logger = logger;
|
|
17
|
+
this.forkDb = forkDb;
|
|
18
|
+
this.initSql = initSql ?? "SELECT 1 + 1";
|
|
19
|
+
this.#initRetryTimes = initRetryTimes;
|
|
20
|
+
this.name = name;
|
|
21
|
+
this.timezone = options.timezone;
|
|
22
|
+
this.rdsOptions = Object.assign({}, DEFAULT_OPTIONS, mysqlOptions);
|
|
23
|
+
this.client = new RDSClient(this.rdsOptions);
|
|
24
|
+
}
|
|
25
|
+
async _init() {
|
|
26
|
+
if (this.initSql) await this.#doInit(1);
|
|
27
|
+
}
|
|
28
|
+
async #doInit(tryTimes) {
|
|
29
|
+
try {
|
|
30
|
+
this.#logger?.log(`${tryTimes} try to initialize dataSource ${this.name}`);
|
|
31
|
+
const st = Date.now();
|
|
32
|
+
await this.client.query(this.initSql);
|
|
33
|
+
this.#logger?.info(`dataSource initialization cost: ${Date.now() - st}, tryTimes: ${tryTimes}`);
|
|
34
|
+
} catch (e) {
|
|
35
|
+
this.#logger?.warn(`failed to initialize dataSource ${this.name}, tryTimes ${tryTimes}`, e);
|
|
36
|
+
if (!this.#initRetryTimes || tryTimes >= this.#initRetryTimes) throw e;
|
|
37
|
+
await this.#doInit(tryTimes + 1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async query(sql) {
|
|
41
|
+
return this.client.query(sql);
|
|
42
|
+
}
|
|
43
|
+
async beginTransactionScope(scope) {
|
|
44
|
+
return await this.client.beginTransactionScope(scope);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
export { MysqlDataSource };
|