neopg 0.0.0 → 1.0.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/lib/ModelDef.js CHANGED
@@ -10,6 +10,9 @@ class ModelDef {
10
10
  this.modelName = rawSchema.modelName || rawSchema.tableName
11
11
  this.primaryKey = rawSchema.primaryKey || 'id'
12
12
  this.columns = rawSchema.column || {}
13
+ this.index = rawSchema.index || []
14
+ this.unique = rawSchema.unique || []
15
+ this.removeIndex = rawSchema.removeIndex || []
13
16
 
14
17
  this._parseColumns()
15
18
 
@@ -57,7 +60,7 @@ class ModelDef {
57
60
  throw new Error(`[NeoPG] Column name '${k}' in table '${this.tableName}' is FORBIDDEN.`)
58
61
  }
59
62
 
60
- if (!/^[a-z][a-z0-9_]*$/i.test(colName)) {
63
+ if (!(/^[a-z][a-z0-9_]*$/i).test(k)) {
61
64
  throw new Error(`[NeoPG] Column name '${k}' is invalid. Only alphanumeric and underscore allowed, must start with letter.`)
62
65
  }
63
66
  }
package/lib/NeoPG.js CHANGED
@@ -20,7 +20,7 @@ class NeoPG {
20
20
 
21
21
  table(tableName, schema = null) {
22
22
  const target = schema || this.defaultSchema
23
- return new this.ModelChain(this.driver, {tableName, isRaw: true}, target)
23
+ return new this.ModelChain(this, {tableName, isRaw: true}, target)
24
24
  }
25
25
 
26
26
  model(name, schema = null) {
@@ -28,13 +28,14 @@ class NeoPG {
28
28
  if (!item) throw new Error(`[NeoPG] Model '${name}' not found.`)
29
29
 
30
30
  const target = schema || this.defaultSchema
31
- return new item.Class(this.driver, item.def, target)
31
+ return new item.Class(this, item.def, target)
32
32
  }
33
33
 
34
34
  // --- 注册 ---
35
35
 
36
36
  add(input) {
37
- let ModelClass;
37
+ let ModelClass
38
+
38
39
  if (typeof input === 'function') {
39
40
  ModelClass = input
40
41
  } else {
@@ -54,6 +55,10 @@ class NeoPG {
54
55
  return this
55
56
  }
56
57
 
58
+ define(model) {
59
+ return this.add(model)
60
+ }
61
+
57
62
  // --- 事务 ---
58
63
  async transaction(callback) {
59
64
  return await this.driver.begin(async (trxSql) => {
@@ -62,13 +67,62 @@ class NeoPG {
62
67
  })
63
68
  }
64
69
 
70
+ begin(callback) {
71
+ return this.transaction(callback)
72
+ }
73
+
65
74
  // --- 同步 ---
66
75
  async sync(options = {}) {
76
+ if (!options || typeof options !== 'object') {
77
+ options = {}
78
+ }
79
+
80
+ if (!options.schema) options.schema = this.defaultSchema
81
+
67
82
  for (const item of this.registry.values()) {
68
83
  await SchemaSync.execute(this.driver, item.def, this, options)
69
84
  }
70
85
  }
71
86
 
87
+ /**
88
+ * 监听 Postgres 消息通道
89
+ * @param {string} channel - 通道名称
90
+ * @param {Function} callback - (payload) => {}
91
+ * @returns {Object} 包含 unlisten 方法的对象
92
+ */
93
+ async listen(channel, callback) {
94
+ // postgres.js 的 listen 返回一个 Promise<void>
95
+ // 但它内部会维持连接。我们需要提供一种方式来取消监听。
96
+ // postgres.js v3 使用 sql.listen(channel, cb) 并返回一个 state 对象用于 close
97
+ const listener = await this.sql.listen(channel, (payload) => {
98
+ // 可以在这里统一处理 JSON 解析等
99
+ try {
100
+ const data = JSON.parse(payload)
101
+ callback(data)
102
+ } catch (e) {
103
+ // 无法解析则返回原始字符串
104
+ callback(payload)
105
+ }
106
+ })
107
+
108
+ return {
109
+ // 返回一个句柄用于取消监听
110
+ close: () => listener.unlisten()
111
+ }
112
+ }
113
+
114
+ /**
115
+ * 发送通知
116
+ * @param {string} channel
117
+ * @param {string|Object} payload
118
+ */
119
+ async notify(channel, payload) {
120
+ const data = typeof payload === 'object' ? JSON.stringify(payload) : payload
121
+
122
+ // 使用 sql.notify 是最高效的
123
+ await this.sql.notify(channel, data)
124
+ }
125
+
72
126
  async close() {
73
127
  await this.driver.end()
74
128
  }
package/lib/SchemaSync.js CHANGED
@@ -32,17 +32,33 @@ const DefaultWithType = ['varchar', 'char', 'text', 'bytea', 'timestamp', 'times
32
32
 
33
33
  class SchemaSync {
34
34
 
35
+ static async createSchema(sql, schema) {
36
+ return sql.unsafe(`create schema if not exists ${schema};`)
37
+ }
38
+
35
39
  /**
36
40
  * 入口方法
37
41
  */
38
42
  static async execute(sql, def, ctx, options = {}) {
39
43
  const debug = options.debug || false;
40
44
  const force = options.force || false;
41
- const dropNotExistCol = options.dropNotExistCol || false;
42
- const schema = ctx.defaultSchema || 'public';
45
+ const dropNotExistCol = force || options.dropNotExistCol || false;
46
+ const schema = options.schema || ctx.defaultSchema || 'public';
43
47
  const tableName = def.tableName;
44
48
  const curTableName = `${schema}.${tableName}`;
45
49
 
50
+ // [递归锁初始化] 记录本次同步过程中已经处理过的 Model,防止死循环
51
+ if (!options.syncedModels) {
52
+ options.syncedModels = new Set();
53
+ }
54
+
55
+ // 如果该表在本次递归链中已经同步过,则跳过
56
+ if (options.syncedModels.has(def.modelName)) {
57
+ return;
58
+ }
59
+ // 标记当前表已开始同步
60
+ options.syncedModels.add(def.modelName);
61
+
46
62
  if (debug) console.log(`检测数据表 ${tableName} 的column...`);
47
63
 
48
64
  // 0. 检查列定义的合法性 (移植自 _checkFixColumn)
@@ -72,7 +88,7 @@ class SchemaSync {
72
88
  // 同步索引、约束、外键
73
89
  await this.syncIndex(sql, def, schema, curTableName, debug);
74
90
  await this.syncUnique(sql, def, schema, curTableName, debug);
75
- await this.syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx);
91
+ await this.syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx, options);
76
92
  return;
77
93
  }
78
94
 
@@ -80,7 +96,7 @@ class SchemaSync {
80
96
  // 获取现有列信息
81
97
  const cols = await sql`
82
98
  SELECT column_name, data_type, column_default, character_maximum_length,
83
- numeric_precision, numeric_scale, is_nullable
99
+ numeric_precision, numeric_scale, is_nullable, is_generated
84
100
  FROM information_schema.columns
85
101
  WHERE table_name = ${tableName}
86
102
  AND table_schema = ${schema}
@@ -99,7 +115,7 @@ class SchemaSync {
99
115
  await this.syncIndex(sql, def, schema, curTableName, debug);
100
116
  await this.syncUnique(sql, def, schema, curTableName, debug);
101
117
  await this.autoRemoveIndex(sql, def, schema, tableName, debug);
102
- await this.syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx);
118
+ await this.syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx, options);
103
119
 
104
120
  if (debug) console.log(` - 表结构同步完成 (${tableName}) - `);
105
121
  }
@@ -130,7 +146,7 @@ class SchemaSync {
130
146
 
131
147
  if (col.default !== undefined) {
132
148
  if (col.default === null) line += ' default null';
133
- else line += ` default $${qtag}$${col.default}$${qtag}$`;
149
+ else line += ` default $_${qtag}_$${col.default}$_${qtag}_$`;
134
150
  }
135
151
  }
136
152
  colSqls.push(line);
@@ -190,7 +206,7 @@ class SchemaSync {
190
206
 
191
207
  if (col.default !== undefined) {
192
208
  if (col.default === null) addSql += ' default null';
193
- else addSql += ` default $${qtag}$${col.default}$${qtag}$`;
209
+ else addSql += ` default $_${qtag}_$${col.default}$_${qtag}_$`;
194
210
  }
195
211
 
196
212
  if (debug) console.log(addSql);
@@ -215,7 +231,7 @@ class SchemaSync {
215
231
  await sql.unsafe(`ALTER TABLE ${curTableName} DROP COLUMN ${this.fmtColName(k)}`);
216
232
  let reAddSql = `ALTER TABLE ${curTableName} ADD COLUMN ${this.fmtColName(k)} ${col.type}`;
217
233
  if (col.notNull !== false) reAddSql += ' not null';
218
- if (col.default !== undefined) reAddSql += ` default $${qtag}$${col.default}$${qtag}$`;
234
+ if (col.default !== undefined) reAddSql += ` default $_${qtag}_$${col.default}$_${qtag}_$`;
219
235
  await sql.unsafe(reAddSql);
220
236
  col.changed = true; // 标记变更,供外键处理使用
221
237
  continue;
@@ -240,7 +256,7 @@ class SchemaSync {
240
256
  // 简单比对逻辑 (注:PG存储的默认值格式可能不同,这里仅作简单触发)
241
257
  // 实际生产中可能需要更复杂的解析,这里保留原逻辑结构
242
258
  // 原逻辑用了 _realDefault 方法,这里我们简单处理,仅当需要时设置
243
- let default_val_sql = col.default === null ? 'null' : `$${qtag}$${col.default}$${qtag}$`;
259
+ let default_val_sql = col.default === null ? 'null' : `$_${qtag}_$${col.default}$_${qtag}_$`;
244
260
  // 这里为了简化,每次都重设默认值(开销很小),或者你需要实现 _realDefault
245
261
  await sql.unsafe(`ALTER TABLE ${curTableName} ALTER COLUMN ${this.fmtColName(k)} SET DEFAULT ${default_val_sql}`);
246
262
  }
@@ -259,24 +275,43 @@ class SchemaSync {
259
275
 
260
276
  // 7. Drop Not Exist (Force Mode)
261
277
  if (dropNotExistCol) {
262
- for (let k in inf) {
263
- if (!def.columns[k] && !renameTable[k]) {
264
- await sql.unsafe(`ALTER TABLE ${curTableName} DROP COLUMN ${this.fmtColName(k)}`);
278
+ for (let dbColName in inf) {
279
+ // 如果代码中没定义,且不是刚改名的
280
+ if (!def.columns[dbColName] && !renameTable[dbColName]) {
281
+
282
+ const dbCol = inf[dbColName];
283
+
284
+ // [核心逻辑] 如果是数据库层面的虚拟列 (is_generated == 'ALWAYS'),豁免,不删除
285
+ if (dbCol.is_generated === 'ALWAYS') {
286
+ if (debug) console.log(`[NeoPG] Ignoring DB-only generated column: ${dbColName}`);
287
+ continue;
288
+ }
289
+
290
+ // [额外建议] 如果是 identity 列 (自增列的一种新形式),通常也建议豁免,防止误删
291
+ // if (dbCol.is_identity === 'YES') continue;
292
+
293
+ if (debug) console.log(`Deleting unused column: ${dbColName}`);
294
+ await sql.unsafe(`ALTER TABLE ${curTableName} DROP COLUMN ${this.fmtColName(dbColName)}`);
265
295
  }
266
296
  }
267
297
  }
298
+ /* for (let k in inf) {
299
+ if (!def.columns[k] && !renameTable[k]) {
300
+ await sql.unsafe(`ALTER TABLE ${curTableName} DROP COLUMN ${this.fmtColName(k)}`);
301
+ }
302
+ } */
268
303
  }
269
304
 
270
305
  // --- 索引同步 ---
271
306
  static async syncIndex(sql, def, schema, curTableName, debug) {
272
307
  // 假设索引定义在 def.rawSchema.index (数组)
273
308
  // ModelDef 需要暴露这个属性,或 def.indices
274
- const indices = def.rawSchema && def.rawSchema.index ? def.rawSchema.index : [];
309
+ const indices = def.index || [];
275
310
  if (!Array.isArray(indices)) return;
276
311
 
277
312
  for (const indname of indices) {
278
313
  // 检查 removeIndex 配置
279
- const removeIndex = def.rawSchema.removeIndex || [];
314
+ const removeIndex = def.removeIndex || [];
280
315
  if (removeIndex.includes(indname)) continue;
281
316
 
282
317
  // 检查列是否存在
@@ -310,7 +345,7 @@ class SchemaSync {
310
345
  }
311
346
 
312
347
  static async syncUnique(sql, def, schema, curTableName, debug) {
313
- const uniques = def.rawSchema && def.rawSchema.unique ? def.rawSchema.unique : [];
348
+ const uniques = def.unique || [];
314
349
  if (!Array.isArray(uniques)) return;
315
350
 
316
351
  for (const indname of uniques) {
@@ -347,8 +382,8 @@ class SchemaSync {
347
382
  const currentIdxNames = allIdx.map(i => i.indexname);
348
383
 
349
384
  // 2. 计算应该保留的索引名
350
- const indices = def.rawSchema && def.rawSchema.index ? def.rawSchema.index : [];
351
- const uniques = def.rawSchema && def.rawSchema.unique ? def.rawSchema.unique : [];
385
+ const indices = def.index || [];
386
+ const uniques = def.unique || [];
352
387
 
353
388
  const keepSet = new Set();
354
389
  const makeName = (n) => `${tableName}_${n.split(',').map(x=>x.trim()).filter(x=>x).join('_')}_idx`;
@@ -366,7 +401,7 @@ class SchemaSync {
366
401
  }
367
402
 
368
403
  // --- 外键同步 ---
369
- static async syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx) {
404
+ static async syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx, options) {
370
405
  // 1. 收集定义中的外键
371
406
  // 格式: { fkName: "xxx", createSql: "..." }
372
407
  let targetFKs = new Map();
@@ -378,27 +413,32 @@ class SchemaSync {
378
413
 
379
414
  // 解析 ref: "ModelName:colName"
380
415
  const [refModelName, refColName] = this._parseRef(col.ref, k);
416
+
417
+ // 查找被引用的 Model 定义
418
+ // 假设 ctx.registry.get 返回 { Class, def }
419
+ const targetModelItem = ctx.registry.get(refModelName);
381
420
 
382
- // 加载目标模型 (这里需要通过 ctx (NeoPG实例) 获取其他模型)
383
- // 假设 ctx.registry.get(refModelName) 能拿到
384
- // 或者如果是文件路径,尝试 require
385
-
386
- // 为了简化复刻,这里假设我们能拿到目标表名
387
- // 在原逻辑中是 require 文件,这里建议 NeoPG 注册机制解决
388
- // 这里做一个适配:
389
421
  let targetTableName = '';
390
- if (ctx.registry && ctx.registry.get(refModelName)) {
391
- targetTableName = ctx.registry.get(refModelName).def.tableName;
422
+
423
+ if (targetModelItem) {
424
+ targetTableName = targetModelItem.def.tableName;
425
+ // --- [关键] 递归同步 ---
426
+ // 在创建外键之前,确保目标表已经存在且结构最新
427
+ if (debug) console.log(`[Recursive Sync] Triggered by FK: ${def.modelName} -> ${refModelName}`);
428
+
429
+ await this.execute(sql, targetModelItem.def, ctx, options);
392
430
  } else {
393
- // 尝试作为表名直接使用 (Fallback)
394
- targetTableName = refModelName.toLowerCase();
431
+ // 目标模型未注册,可能是直接引用的表名 (Fallback)
432
+ if (debug) console.warn(`[NeoPG] Referenced model '${refModelName}' not found in registry. Using as table name.`);
433
+ targetTableName = refModelName.toLowerCase();
395
434
  }
396
435
 
397
- // 构建外键名
436
+ // 准备外键 SQL
398
437
  const fkName = `${def.tableName}_${k}_fkey`;
399
438
 
400
439
  // 构建 REFERENCES 子句
401
440
  let refSql = `REFERENCES ${schema}.${targetTableName} (${refColName})`;
441
+
402
442
  if (col.refActionUpdate) refSql += ` ON UPDATE ${col.refActionUpdate}`;
403
443
  else refSql += ` ON UPDATE CASCADE`; // 默认
404
444
 
@@ -493,14 +533,15 @@ class SchemaSync {
493
533
 
494
534
  static _parseRef(refstr, curColumn) {
495
535
  if (refstr.includes(':')) {
496
- const parts = refstr.split(':');
536
+ const parts = refstr.split(':')
497
537
  // 处理 Model:col 格式,取最后一部分做 col,前面做 Model
498
- const col = parts.pop();
499
- const model = parts.join(':');
500
- return [model, col];
538
+ const col = parts.pop()
539
+ const model = parts.join(':')
540
+ return [model, col]
501
541
  }
502
- return [refstr, curColumn];
542
+
543
+ return [refstr, curColumn]
503
544
  }
504
545
  }
505
546
 
506
- module.exports = SchemaSync;
547
+ module.exports = SchemaSync
@@ -9,14 +9,14 @@ class TransactionScope {
9
9
 
10
10
  table(tableName, schema = null) {
11
11
  const target = schema || this.parent.defaultSchema
12
- return new this.parent.ModelChain(this.driver, {tableName, isRaw: true}, target)
12
+ return new this.parent.ModelChain(this, {tableName, isRaw: true}, target)
13
13
  }
14
14
 
15
15
  model(name, schema = null) {
16
16
  const item = this.parent.registry.get(name)
17
17
  if (!item) throw new Error(`[NeoPG] Model '${name}' not found.`)
18
18
  const target = schema || this.parent.defaultSchema
19
- return new item.Class(this.driver, item.def, target)
19
+ return new item.Class(this, item.def, target)
20
20
  }
21
21
 
22
22
  async transaction(callback) {
@@ -24,6 +24,10 @@ class TransactionScope {
24
24
  return await callback(new TransactionScope(this.parent, sp))
25
25
  })
26
26
  }
27
+
28
+ begin(callback) {
29
+ return this.transaction(callback)
30
+ }
27
31
  }
28
32
 
29
33
  module.exports = TransactionScope
package/package.json CHANGED
@@ -1,11 +1,18 @@
1
1
  {
2
2
  "name": "neopg",
3
- "version": "0.0.0",
3
+ "version": "1.0.0",
4
4
  "description": "orm for postgres",
5
5
  "keywords": [
6
+ "neopg",
7
+ "NeoPG",
8
+ "pg",
9
+ "neo",
10
+ "pgorm",
11
+ "psqlorm",
6
12
  "postgres",
7
13
  "orm",
8
- "database"
14
+ "database",
15
+ "postgres.js"
9
16
  ],
10
17
  "homepage": "https://github.com/master-genius/neopg#readme",
11
18
  "bugs": {
package/test/test-db.js CHANGED
@@ -1,44 +1,217 @@
1
1
  // index.js
2
2
  const NeoPG = require('../lib/NeoPG.js')
3
- const ModelChain = NeoPG.ModelChain
3
+ const {dataTypes, ModelChain} = NeoPG
4
4
 
5
5
  // 1. 初始化
6
6
  const db = new NeoPG({
7
7
  host: '127.0.0.1',
8
- database: 'eoms',
9
- schema: 'ai' // 全局默认 Schema
8
+ user: 'wy',
9
+ database: 'wdata',
10
+ schema: 'test'
10
11
  });
11
12
 
12
13
  // 2. 定义模型
13
- const UserSchema = {
14
+ const User = {
14
15
  modelName: 'User',
15
- tableName: 'sys_users',
16
+ tableName: 'users',
17
+ primaryKey: 'id',
18
+
16
19
  column: {
17
- id: { type: 'varchar', primaryKey: true },
18
- name: { type: 'varchar', notNull: true },
19
- age: { type: 'int', default: 18 }
20
- }
21
- };
20
+ id : {
21
+ type: dataTypes.UID
22
+ },
23
+
24
+ /**
25
+ * @type {column}
26
+ */
27
+ username: {
28
+ type: dataTypes.STRING(50)
29
+ },
30
+
31
+ mobile: {
32
+ type: dataTypes.STRING(16)
33
+ },
34
+
35
+ mobile_state: {
36
+ type: dataTypes.SMALLINT,
37
+ default: 0
38
+ },
39
+
40
+ //真实姓名
41
+ realname: {
42
+ type: dataTypes.STRING(50),
43
+ default: ''
44
+ },
45
+
46
+ sex: {
47
+ type: dataTypes.SMALLINT,
48
+ default: 0
49
+ },
50
+
51
+ //其他信息,以JSON格式存储
52
+ info: {
53
+ type: dataTypes.TEXT,
54
+ default: ''
55
+ },
56
+
57
+ /**
58
+ * @type {column}
59
+ */
60
+ passwd: {
61
+ type: dataTypes.STRING(240)
62
+ },
63
+
64
+ //当验证失败,需要进行重复密码验证
65
+ repasswd: {
66
+ type: dataTypes.STRING(240)
67
+ },
68
+
69
+ /**
70
+ * @type {column}
71
+ */
72
+ level: {
73
+ type: 'smallint',
74
+ default: 1,
75
+ validate: v => {
76
+ return v >= 0 && v < 99
77
+ }
78
+ },
79
+
80
+ /**
81
+ * @type {column}
82
+ */
83
+ email: {
84
+ type: dataTypes.STRING(60),
85
+ default: ''
86
+ },
87
+
88
+ email_state: {
89
+ type: dataTypes.SMALLINT,
90
+ default: 0
91
+ },
92
+
93
+ create_time: {
94
+ type: dataTypes.BIGINT,
95
+ default: 0,
96
+ timestamp: 'insert'
97
+ },
98
+
99
+ update_time: {
100
+ type: dataTypes.BIGINT,
101
+ default: 0,
102
+ timestamp: 'update'
103
+ },
104
+
105
+ failed_count: {
106
+ type: dataTypes.INT,
107
+ default: 0
108
+ },
109
+
110
+ failed_time: {
111
+ type: dataTypes.BIGINT,
112
+ default: 0
113
+ },
114
+
115
+ forbid: {
116
+ type: dataTypes.SMALLINT,
117
+ default: 0
118
+ },
119
+
120
+ is_root: {
121
+ type: dataTypes.SMALLINT,
122
+ default: 0
123
+ }
124
+ },
125
+
126
+ //索引
127
+ index: [
128
+ 'create_time',
129
+ 'level',
130
+ 'is_root'
131
+ ],
132
+
133
+ //唯一索引
134
+ unique: [
135
+ 'username',
136
+ 'email'
137
+ ]
138
+ }
22
139
 
23
140
  // 3. 注册
24
- db.add(UserSchema);
141
+ db.add(User);
25
142
 
26
143
  ;(async () => {
144
+ await db.sync({force: true, debug: true})
27
145
  // 插入
28
- await db.model('User').insert({ name: 'Neo' });
29
146
 
30
- // 动态切换 Schema
31
- await db.model('User', 'tenant_a').select();
32
-
33
- // 链式切换
34
- await db.model('User').schema('tenant_b').where({ age: 20 }).select();
147
+ await db.model('User').where('1=1').delete()
148
+
149
+ try {
150
+ console.log(
151
+ await db.model('User')
152
+ .returning(['id', 'username', 'level', 'create_time'])
153
+ .insert([
154
+ {
155
+ username: 'Neo',
156
+ email: '123@w.com',
157
+ level: Math.floor((Math.random() * 105))
158
+ },
159
+ {
160
+ username: 'PG',
161
+ email: '1234@w.com',
162
+ level: Math.floor((Math.random() * 100))
163
+ }
164
+ ])
165
+ )
166
+ } catch(err) {
167
+ console.error('\x1b[7;5m随机测试:让level超过99,validate验证失败\x1b[0m')
168
+ }
35
169
 
36
170
  // 事务
37
171
  await db.transaction(async tx => {
38
- await tx.model('User').update({ age: 99 });
172
+ let data = {
173
+ level: Math.floor(Math.random() * 100),
174
+ info: `age=${Math.floor(Math.random() * 10 + 20)};delete from users;`
175
+ }
176
+
177
+ console.log('update', data)
178
+
179
+ let result = await tx.model('User').where(tx.sql`level > 10`).returning('*').update(data)
180
+ console.log(result)
181
+
182
+ console.log(
183
+ 'test avg',
184
+ await tx.model('User').avg('level')
185
+ )
186
+
187
+ console.log(
188
+ 'test max',
189
+ await tx.model('User').max('level')
190
+ )
191
+
192
+ console.log(
193
+ 'test min',
194
+ await tx.model('User').min('username')
195
+ )
196
+
197
+ let n = Math.floor(Math.random() * 10)
198
+
199
+ if (n > 7) {
200
+ console.error('\x1b[7;5m随机测试:将会让事物执行失败\x1b[0m')
201
+ await tx.model('User').insert({username: 'Neo'})
202
+ }
203
+
204
+ console.log('test count',
205
+ await tx.model('User').where('level', '<', 10).count()
206
+ )
207
+
39
208
  // 嵌套
40
- await tx.transaction(async subTx => {
209
+ /* await tx.transaction(async subTx => {
41
210
  await subTx.table('logs').insert({ msg: 'log' });
42
- });
43
- });
211
+ }); */
212
+ }).catch(err => {
213
+ console.error(err)
214
+ })
215
+
216
+ db.close()
44
217
  })();