neopg 2.0.2 → 2.0.4
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/ModelChain.js +20 -15
- package/lib/NeoPG.js +69 -2
- package/package.json +1 -1
- package/test/test-db.js +68 -1
package/lib/ModelChain.js
CHANGED
|
@@ -16,12 +16,10 @@ const FLOAT_TYPES = new Set([
|
|
|
16
16
|
])
|
|
17
17
|
|
|
18
18
|
class ModelChain {
|
|
19
|
-
constructor(ctx, def, schema
|
|
19
|
+
constructor(ctx, def, schema='public') {
|
|
20
20
|
this.ctx = ctx
|
|
21
21
|
this.def = def
|
|
22
|
-
this.sql = ctx.sql
|
|
23
|
-
|
|
24
|
-
this.tableName = def.tableName
|
|
22
|
+
this.sql = ctx ? ctx.sql : null
|
|
25
23
|
this.schema = schema
|
|
26
24
|
|
|
27
25
|
// --- 查询状态 ---
|
|
@@ -35,7 +33,7 @@ class ModelChain {
|
|
|
35
33
|
this._group = []
|
|
36
34
|
this._lock = null
|
|
37
35
|
|
|
38
|
-
this._isRaw =
|
|
36
|
+
this._isRaw = false
|
|
39
37
|
this._executed = false
|
|
40
38
|
}
|
|
41
39
|
|
|
@@ -50,7 +48,7 @@ class ModelChain {
|
|
|
50
48
|
_ensureActive() {
|
|
51
49
|
if (this._executed) {
|
|
52
50
|
throw new Error(
|
|
53
|
-
`[NeoPG] ModelChain for '${this.tableName}' has already been executed. ` +
|
|
51
|
+
`[NeoPG] ModelChain for '${this.def.tableName}' has already been executed. ` +
|
|
54
52
|
`Do NOT reuse the chain variable. Use .clone() if you need to fork queries.`
|
|
55
53
|
)
|
|
56
54
|
}
|
|
@@ -266,7 +264,7 @@ class ModelChain {
|
|
|
266
264
|
}
|
|
267
265
|
|
|
268
266
|
_buildSelectQuery() {
|
|
269
|
-
const t = this.sql(this.tableName)
|
|
267
|
+
const t = this.sql(this.def.tableName)
|
|
270
268
|
const c = this._columns ? this.sql(this._columns) : this.sql`*`
|
|
271
269
|
|
|
272
270
|
const w = this._buildWhere()
|
|
@@ -310,7 +308,7 @@ class ModelChain {
|
|
|
310
308
|
const dataQuery = this._buildSelectQuery()
|
|
311
309
|
|
|
312
310
|
// 2. 总数查询
|
|
313
|
-
const t = this.sql(this.tableName)
|
|
311
|
+
const t = this.sql(this.def.tableName)
|
|
314
312
|
const w = this._buildWhere()
|
|
315
313
|
const j = this._buildJoins()
|
|
316
314
|
const g = this._buildGroup()
|
|
@@ -338,7 +336,7 @@ class ModelChain {
|
|
|
338
336
|
async count() {
|
|
339
337
|
this._ensureActive()
|
|
340
338
|
try {
|
|
341
|
-
const t = this.sql(this.tableName)
|
|
339
|
+
const t = this.sql(this.def.tableName)
|
|
342
340
|
const w = this._buildWhere()
|
|
343
341
|
const j = this._buildJoins()
|
|
344
342
|
const g = this._buildGroup()
|
|
@@ -366,11 +364,11 @@ class ModelChain {
|
|
|
366
364
|
const inputs = isArray ? data : [data]
|
|
367
365
|
if (inputs.length === 0) throw new Error('[NeoPG] Insert data cannot be empty')
|
|
368
366
|
|
|
369
|
-
if (this.
|
|
367
|
+
if (!this._isRaw) {
|
|
370
368
|
this._prepareDataForInsert(inputs)
|
|
371
369
|
}
|
|
372
370
|
|
|
373
|
-
const fullTable = this.sql`${this.sql(this.schema)}.${this.sql(this.tableName)}`
|
|
371
|
+
const fullTable = this.sql`${this.sql(this.schema)}.${this.sql(this.def.tableName)}`
|
|
374
372
|
const retFragment = this._buildReturning()
|
|
375
373
|
|
|
376
374
|
const result = await this.sql`INSERT INTO ${fullTable} ${this.sql(inputs)} ${retFragment}`
|
|
@@ -389,10 +387,14 @@ class ModelChain {
|
|
|
389
387
|
this._ensureActive()
|
|
390
388
|
try {
|
|
391
389
|
if (!data || Object.keys(data).length === 0) throw new Error('[NeoPG] Update data cannot be empty')
|
|
392
|
-
|
|
390
|
+
|
|
391
|
+
if (!this._isRaw) {
|
|
392
|
+
this._prepareDataForUpdate(data)
|
|
393
|
+
}
|
|
394
|
+
|
|
393
395
|
if (this._conditions.length === 0) throw new Error('[NeoPG] UPDATE requires a WHERE condition')
|
|
394
396
|
|
|
395
|
-
const fullTable = this.sql`${this.sql(this.schema)}.${this.sql(this.tableName)}`
|
|
397
|
+
const fullTable = this.sql`${this.sql(this.schema)}.${this.sql(this.def.tableName)}`
|
|
396
398
|
const whereFragment = this._buildWhere()
|
|
397
399
|
const retFragment = this._buildReturning()
|
|
398
400
|
|
|
@@ -412,7 +414,7 @@ class ModelChain {
|
|
|
412
414
|
this._ensureActive()
|
|
413
415
|
try {
|
|
414
416
|
if (this._conditions.length === 0) throw new Error('[NeoPG] DELETE requires a WHERE condition')
|
|
415
|
-
const fullTable = this.sql`${this.sql(this.schema)}.${this.sql(this.tableName)}`
|
|
417
|
+
const fullTable = this.sql`${this.sql(this.schema)}.${this.sql(this.def.tableName)}`
|
|
416
418
|
const whereFragment = this._buildWhere()
|
|
417
419
|
const retFragment = this._buildReturning()
|
|
418
420
|
|
|
@@ -432,7 +434,7 @@ class ModelChain {
|
|
|
432
434
|
try {
|
|
433
435
|
if (!field) throw new Error(`[NeoPG] ${func} requires a field name.`)
|
|
434
436
|
|
|
435
|
-
const t = this.sql(this.tableName)
|
|
437
|
+
const t = this.sql(this.def.tableName)
|
|
436
438
|
const w = this._buildWhere()
|
|
437
439
|
const j = this._buildJoins()
|
|
438
440
|
const ft = this.sql`${this.sql(this.schema)}.${t}`
|
|
@@ -524,9 +526,11 @@ class ModelChain {
|
|
|
524
526
|
if (autoId && row[pk] === undefined) {
|
|
525
527
|
row[pk] = this.def.makeId(pkLen)
|
|
526
528
|
}
|
|
529
|
+
|
|
527
530
|
if (make_timestamp) {
|
|
528
531
|
for (const t of ts.insert) makeTimestamp(row, t)
|
|
529
532
|
}
|
|
533
|
+
|
|
530
534
|
for (const key in row) {
|
|
531
535
|
this.def.validateField(key, row[key])
|
|
532
536
|
}
|
|
@@ -538,6 +542,7 @@ class ModelChain {
|
|
|
538
542
|
if (ts.update && ts.update.length > 0) {
|
|
539
543
|
for (const t of ts.update) makeTimestamp(row, t)
|
|
540
544
|
}
|
|
545
|
+
|
|
541
546
|
for (const key in row) {
|
|
542
547
|
this.def.validateField(key, row[key])
|
|
543
548
|
}
|
package/lib/NeoPG.js
CHANGED
|
@@ -11,6 +11,17 @@ const path = require('node:path')
|
|
|
11
11
|
const fs = require('node:fs')
|
|
12
12
|
const { pathToFileURL } = require('node:url')
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* 将下划线命名转换为大驼峰命名 (e.g. shop_order -> ShopOrder)
|
|
16
|
+
*/
|
|
17
|
+
function toPascalCase(str) {
|
|
18
|
+
if (!str) return ''
|
|
19
|
+
|
|
20
|
+
return str.split('_')
|
|
21
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
22
|
+
.join('')
|
|
23
|
+
}
|
|
24
|
+
|
|
14
25
|
class NeoPG {
|
|
15
26
|
constructor(config) {
|
|
16
27
|
this.driver = postgres(config)
|
|
@@ -24,7 +35,9 @@ class NeoPG {
|
|
|
24
35
|
|
|
25
36
|
table(tableName, schema = null) {
|
|
26
37
|
const target = schema || this.defaultSchema
|
|
27
|
-
|
|
38
|
+
let m = new this.ModelChain(this, {tableName, isRaw: true}, target)
|
|
39
|
+
m._isRaw = true
|
|
40
|
+
return m
|
|
28
41
|
}
|
|
29
42
|
|
|
30
43
|
model(name, schema = null) {
|
|
@@ -32,7 +45,16 @@ class NeoPG {
|
|
|
32
45
|
if (!item) throw new Error(`[NeoPG] Model '${name}' not found.`)
|
|
33
46
|
|
|
34
47
|
const target = schema || this.defaultSchema
|
|
35
|
-
|
|
48
|
+
let m = new item.Class(this, item.def, target)
|
|
49
|
+
|
|
50
|
+
if (!m.def) {
|
|
51
|
+
m.ctx = this
|
|
52
|
+
m.sql = this.sql
|
|
53
|
+
m.def = item.def
|
|
54
|
+
m.schema = target
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return m
|
|
36
58
|
}
|
|
37
59
|
|
|
38
60
|
// --- 注册 ---
|
|
@@ -50,6 +72,41 @@ class NeoPG {
|
|
|
50
72
|
|
|
51
73
|
if (!rawSchema) throw new Error(`[NeoPG] Missing static schema for ${ModelClass.name}`)
|
|
52
74
|
|
|
75
|
+
// 如果没有显式指定 modelName,则尝试自动推断
|
|
76
|
+
if (!rawSchema.modelName) {
|
|
77
|
+
const className = ModelClass.name
|
|
78
|
+
|
|
79
|
+
const genericNames = ['AnonymousModel', 'ModelChain', 'Function', '']
|
|
80
|
+
|
|
81
|
+
// 1. 优先尝试使用类名 (如果类名不是通用的)
|
|
82
|
+
if (className && !genericNames.includes(className)) {
|
|
83
|
+
rawSchema.modelName = className
|
|
84
|
+
} else if (rawSchema.tableName) {
|
|
85
|
+
rawSchema.modelName = toPascalCase(rawSchema.tableName)
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
console.error(`\x1b[33;5m[NeoPG]Warning: modelName is not specified, `
|
|
88
|
+
+ `use ${rawSchema.modelName} as the modelName\x1b[0m`)
|
|
89
|
+
}, 100)
|
|
90
|
+
} else {
|
|
91
|
+
// 此时说明modelName无法确定,但是tableName也没有指定
|
|
92
|
+
throw new Error(`\x1b[31m[NeoPG] Missing modelName and tableName\x1b[0m`)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//经过以上处理,modelName已经确定了,此时若没有指定tableName则把modelName转换为小写作为tableName
|
|
97
|
+
if (!rawSchema.tableName) {
|
|
98
|
+
rawSchema.tableName = rawSchema.modelName.toLowerCase()
|
|
99
|
+
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
console.error(`\x1b[33;5m[NeoPG]Warning: tableName is not specified, `
|
|
102
|
+
+ `use ${rawSchema.modelName.toLowerCase()} as the tableName\x1b[0m`)
|
|
103
|
+
}, 100)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if ((/[a-z]/).test(rawSchema.modelName[0])) {
|
|
107
|
+
throw new Error(`\x1b[31;5m[NeoPG] ${rawSchema.modelName}: modelName must start with an uppercase letter.\x1b[0m`)
|
|
108
|
+
}
|
|
109
|
+
|
|
53
110
|
const def = new ModelDef(rawSchema)
|
|
54
111
|
|
|
55
112
|
//已经存在又不是更新,则报错
|
|
@@ -93,6 +150,16 @@ class NeoPG {
|
|
|
93
150
|
|
|
94
151
|
if (!options.schema) options.schema = this.defaultSchema
|
|
95
152
|
|
|
153
|
+
if (options.model) {
|
|
154
|
+
let model = this.registry.get(options.model)
|
|
155
|
+
|
|
156
|
+
if (!model) {
|
|
157
|
+
throw new Error(`[NeoPG] sync: ${options.model} not found.`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return await SchemaSync.execute(this.driver, model.def, this, options)
|
|
161
|
+
}
|
|
162
|
+
|
|
96
163
|
for (const item of this.registry.values()) {
|
|
97
164
|
await SchemaSync.execute(this.driver, item.def, this, options)
|
|
98
165
|
}
|
package/package.json
CHANGED
package/test/test-db.js
CHANGED
|
@@ -138,12 +138,79 @@ const User = {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
// 3. 注册
|
|
141
|
-
db.add(User)
|
|
141
|
+
db.add(User)
|
|
142
142
|
|
|
143
143
|
;(async () => {
|
|
144
144
|
await db.sync({force: true, debug: true})
|
|
145
145
|
// 插入
|
|
146
146
|
|
|
147
|
+
//测试modelName和tableName
|
|
148
|
+
|
|
149
|
+
class ShopOrder extends ModelChain {
|
|
150
|
+
static schema = {
|
|
151
|
+
column: {
|
|
152
|
+
id: {
|
|
153
|
+
type: dataTypes.ID
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
name: {
|
|
157
|
+
type: dataTypes.STRING(30)
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
order_no: {
|
|
161
|
+
type: dataTypes.STRING(40)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
constructor() {
|
|
167
|
+
super()
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
db.define(ShopOrder)
|
|
172
|
+
|
|
173
|
+
//未指定modelName
|
|
174
|
+
let Cart = {
|
|
175
|
+
tableName: 'cart',
|
|
176
|
+
column: {
|
|
177
|
+
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
db.define(Cart)
|
|
182
|
+
|
|
183
|
+
let Category = {
|
|
184
|
+
column: {}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
db.define(Category)
|
|
189
|
+
} catch (err) {
|
|
190
|
+
setTimeout(() => {
|
|
191
|
+
console.error(err.message)
|
|
192
|
+
}, 100)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
db.sync({force: true, debug: true, model: 'ShopOrder'})
|
|
196
|
+
|
|
197
|
+
await db.model('ShopOrder').where('1=1').delete()
|
|
198
|
+
await db.model('ShopOrder').insert([
|
|
199
|
+
{
|
|
200
|
+
name: 'topbit',
|
|
201
|
+
order_no: Math.random().toString(16)
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
{
|
|
205
|
+
name: 'neopg',
|
|
206
|
+
order_no: Math.random().toString(16)
|
|
207
|
+
}
|
|
208
|
+
])
|
|
209
|
+
|
|
210
|
+
console.log('get shoporder...\n',
|
|
211
|
+
await db.model('ShopOrder').where('1=1').find()
|
|
212
|
+
)
|
|
213
|
+
|
|
147
214
|
await db.model('User').where('1=1').delete()
|
|
148
215
|
|
|
149
216
|
try {
|