neopg 0.0.1 → 1.0.1
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/LICENSE +21 -0
- package/README.cn.md +415 -0
- package/README.md +415 -0
- package/bin/neopg-model.js +207 -0
- package/images/neopg-programming.jpeg +0 -0
- package/images/neopg.png +0 -0
- package/lib/ModelChain.js +323 -15
- package/lib/NeoPG.js +60 -2
- package/lib/SchemaSync.js +62 -25
- package/lib/TransactionScope.js +4 -0
- package/package.json +12 -2
- package/test/test-db.js +57 -17
package/README.md
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# NeoPG
|
|
4
|
+
|
|
5
|
+
### The Next Generation PostgreSQL ORM for Node.js
|
|
6
|
+
|
|
7
|
+
**NeoPG** is a high-performance, zero-dependency ORM built directly on top of [postgres.js](https://github.com/porsager/postgres) — the fastest PostgreSQL client for Node.js.
|
|
8
|
+
|
|
9
|
+
It bridges the gap between the developer experience (DX) of a chainable Query Builder and the raw performance of native SQL Template Literals.
|
|
10
|
+
|
|
11
|
+
### [🪭 中文文档 ☯️](./README.cn.md)
|
|
12
|
+
|
|
13
|
+
## 🚀 Key Features
|
|
14
|
+
|
|
15
|
+
* **Powered by [postgres.js](https://github.com/porsager/postgres)**: Inherits the incredible speed and stability of the fastest PG client.
|
|
16
|
+
* **Zero Dependencies**: The core driver is vendored and optimized internally. No heavy dependency tree.
|
|
17
|
+
* **Hybrid API**: Enjoy the ease of **Chainable/Fluent APIs** (like `.where().select()`) combined with the power of **Tagged Template Literals**.
|
|
18
|
+
* **Performance First**: Zero string concatenation logic. All queries are compiled into efficient fragments and executed natively.
|
|
19
|
+
* **Auto Schema Sync**: define your models in code, and NeoPG syncs the table structure, indices, and foreign keys automatically.
|
|
20
|
+
* **Type Smart**: Automatic type casting for aggregations (`sum`, `avg` returns numbers, not strings) and JSON handling.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 📦 Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install neopg
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 🔌 Initialization
|
|
33
|
+
|
|
34
|
+
### Connect to Database
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
const NeoPG = require('neopg');
|
|
38
|
+
|
|
39
|
+
const config = {
|
|
40
|
+
host: 'localhost',
|
|
41
|
+
port: 5432,
|
|
42
|
+
database: 'my_db',
|
|
43
|
+
user: 'postgres',
|
|
44
|
+
password: 'password',
|
|
45
|
+
max: 10, // Connection pool size
|
|
46
|
+
idle_timeout: 30, // Idle connection timeout in seconds
|
|
47
|
+
debug: false, // Enable query logging
|
|
48
|
+
schema: 'public' // Default schema
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const db = new NeoPG(config);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Close Connection
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
await db.close()
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 📝 Defining a Model
|
|
63
|
+
|
|
64
|
+
Create a model file (e.g., `models/User.js`). Your class should extend `NeoPG.ModelChain`.
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
const { ModelChain, dataTypes } = require('neopg')
|
|
68
|
+
|
|
69
|
+
class User extends ModelChain {
|
|
70
|
+
static schema = {
|
|
71
|
+
tableName: 'users',
|
|
72
|
+
modelName: 'User', // Optional, defaults to tableName
|
|
73
|
+
primaryKey: 'id',
|
|
74
|
+
|
|
75
|
+
// Auto-sync table structure based on this definition
|
|
76
|
+
column: {
|
|
77
|
+
id: {
|
|
78
|
+
type: dataTypes.ID, // Auto-generates Snowflake-like ID
|
|
79
|
+
},
|
|
80
|
+
username: {
|
|
81
|
+
type: dataTypes.STRING(100),
|
|
82
|
+
required: true
|
|
83
|
+
},
|
|
84
|
+
email: {
|
|
85
|
+
type: dataTypes.STRING(255),
|
|
86
|
+
required: true
|
|
87
|
+
},
|
|
88
|
+
age: {
|
|
89
|
+
type: dataTypes.INT,
|
|
90
|
+
default: 18
|
|
91
|
+
},
|
|
92
|
+
meta: {
|
|
93
|
+
type: dataTypes.JSONB
|
|
94
|
+
},
|
|
95
|
+
created_at: {
|
|
96
|
+
type: dataTypes.BIGINT,
|
|
97
|
+
timestamp: 'insert' // Auto-fill on insert
|
|
98
|
+
},
|
|
99
|
+
updated_at: {
|
|
100
|
+
type: dataTypes.BIGINT,
|
|
101
|
+
timestamp: 'update' // Auto-fill on insert & update
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// Indexes
|
|
106
|
+
index: ['email', 'age'],
|
|
107
|
+
unique: ['username']
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = User
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 🛠 CLI Model Generator
|
|
115
|
+
|
|
116
|
+
NeoPG includes a built-in CLI tool to quickly generate model files with boilerplate code.
|
|
117
|
+
|
|
118
|
+
### Usage
|
|
119
|
+
|
|
120
|
+
Run via `npx` (no global installation required):
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npx neopg-model [options] [model_names...]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Options
|
|
127
|
+
|
|
128
|
+
* `--dir=<path>`: Specify the output directory (default: `./model`).
|
|
129
|
+
|
|
130
|
+
### Examples
|
|
131
|
+
|
|
132
|
+
**1. Basic Generation**
|
|
133
|
+
```bash
|
|
134
|
+
npx neopg-model user
|
|
135
|
+
# Creates: ./model/user.js
|
|
136
|
+
# Class: User
|
|
137
|
+
# Table: user
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**2. Naming Convention (Hyphenated)**
|
|
141
|
+
NeoPG automatically converts hyphenated names to **CamelCase** for the class and **snake_case** for the table.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npx neopg-model user-log
|
|
145
|
+
# Creates: ./model/user-log.js
|
|
146
|
+
# Class: UserLog
|
|
147
|
+
# Table: user_log
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**3. Multiple Models & Custom Directory**
|
|
151
|
+
```bash
|
|
152
|
+
npx neopg-model --dir=./src/models product order-item
|
|
153
|
+
# Creates:
|
|
154
|
+
# ./src/models/product.js
|
|
155
|
+
# ./src/models/order-item.js
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**4. ES Modules (.mjs)**
|
|
159
|
+
If you suffix the name with `.mjs`, it generates ESM syntax (`export default`).
|
|
160
|
+
```bash
|
|
161
|
+
npx neopg-model config.mjs
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## ⚙️ Registration & Sync
|
|
167
|
+
|
|
168
|
+
Initialize NeoPG and register your models. You can define models using classes or configuration objects.
|
|
169
|
+
|
|
170
|
+
### Registering Models
|
|
171
|
+
|
|
172
|
+
NeoPG provides three methods for registration:
|
|
173
|
+
|
|
174
|
+
* **`define(model)`**: The standard method. Throws an error if a model with the same name already exists.
|
|
175
|
+
* **`add(model)`**: Alias for `define`.
|
|
176
|
+
* **`set(model)`**: **Overwrites** the existing model if the name conflicts. Useful for hot-reloading or dynamic schema updates.
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
const User = require('./models/User')
|
|
180
|
+
|
|
181
|
+
// 1. Standard Registration (Safe)
|
|
182
|
+
// Will throw error: "[NeoPG] modelName conflict: User" if registered twice
|
|
183
|
+
db.define(User)
|
|
184
|
+
|
|
185
|
+
// 2. Force Overwrite (Reset)
|
|
186
|
+
// Updates the definition for 'User' even if it exists
|
|
187
|
+
db.set(User)
|
|
188
|
+
|
|
189
|
+
// 3. Register using a plain object (Quick prototype)
|
|
190
|
+
db.define({
|
|
191
|
+
tableName: 'logs',
|
|
192
|
+
column: {
|
|
193
|
+
message: 'string',
|
|
194
|
+
level: 'int'
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Syncing Database
|
|
201
|
+
|
|
202
|
+
Sync the table structure to the database based on registered models.
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
// Sync Table Structure (DDL)
|
|
206
|
+
// options: { force: true } will drop columns not defined in schema
|
|
207
|
+
await db.sync({ force: false })
|
|
208
|
+
|
|
209
|
+
console.log('Database synced!')
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 🔍 Querying
|
|
215
|
+
|
|
216
|
+
NeoPG provides a fluent, chainable API that feels natural to use.
|
|
217
|
+
|
|
218
|
+
### Basic Find
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
// Get all users
|
|
222
|
+
const users = await db.model('User').find();
|
|
223
|
+
|
|
224
|
+
// Select specific columns
|
|
225
|
+
const users = await db.model('User')
|
|
226
|
+
.select('id, username')
|
|
227
|
+
.limit(10)
|
|
228
|
+
.find();
|
|
229
|
+
|
|
230
|
+
// Get a single record
|
|
231
|
+
const user = await db.model('User').where({ id: '123' }).get();
|
|
232
|
+
|
|
233
|
+
// Pagination
|
|
234
|
+
const page2 = await db.model('User').page(2, 20).find(); // Page 2, Size 20
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Chained Where
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
await db.model('User')
|
|
241
|
+
// Object style (AND logic)
|
|
242
|
+
.where({
|
|
243
|
+
age: 18,
|
|
244
|
+
status: 'active'
|
|
245
|
+
})
|
|
246
|
+
// Operator style
|
|
247
|
+
.where('create_time', '>', 1600000000)
|
|
248
|
+
// SQL Fragment style (Powerful!)
|
|
249
|
+
.where('id IS NOT NULL')
|
|
250
|
+
.find();
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Complex Where with Template Literals
|
|
254
|
+
|
|
255
|
+
This is where NeoPG shines. You can mix raw SQL fragments safely using the `sql` tag from the context.
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
// db.sql is the native postgres instance
|
|
259
|
+
const { sql } = db;
|
|
260
|
+
|
|
261
|
+
await db.model('User')
|
|
262
|
+
.where({ status: 'active' })
|
|
263
|
+
// Safe parameter injection via Template Literals
|
|
264
|
+
.where(sql`age > ${20} AND email LIKE ${'%@gmail.com'}`)
|
|
265
|
+
.find();
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 📊 Aggregation
|
|
271
|
+
|
|
272
|
+
NeoPG handles type casting automatically (e.g., converting PostgreSQL `count` string results to Javascript Numbers).
|
|
273
|
+
|
|
274
|
+
```javascript
|
|
275
|
+
// Count
|
|
276
|
+
const total = await db.model('User').where({ age: 18 }).count();
|
|
277
|
+
|
|
278
|
+
// Max / Min
|
|
279
|
+
const maxAge = await db.model('User').max('age');
|
|
280
|
+
|
|
281
|
+
// Sum / Avg (Returns Number, not String)
|
|
282
|
+
const totalScore = await db.model('User').sum('score');
|
|
283
|
+
const avgScore = await db.model('User').avg('score');
|
|
284
|
+
|
|
285
|
+
// Group By
|
|
286
|
+
const stats = await db.model('User')
|
|
287
|
+
.select('city, count(*) as num')
|
|
288
|
+
.group('city')
|
|
289
|
+
.find();
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## ✏️ Write Operations
|
|
295
|
+
|
|
296
|
+
### Insert
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
// Insert one
|
|
300
|
+
const newUser = await db.model('User').insert({
|
|
301
|
+
username: 'neo',
|
|
302
|
+
email: 'neo@matrix.com'
|
|
303
|
+
});
|
|
304
|
+
// ID and Timestamps are automatically generated if configured in Schema
|
|
305
|
+
|
|
306
|
+
// Insert multiple (Batch)
|
|
307
|
+
await db.model('User').insert([
|
|
308
|
+
{ username: 'a' },
|
|
309
|
+
{ username: 'b' }
|
|
310
|
+
]);
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Update
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
const updated = await db.model('User')
|
|
317
|
+
.where({ id: '123' })
|
|
318
|
+
.update({
|
|
319
|
+
age: 99,
|
|
320
|
+
meta: { role: 'admin' }
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Delete
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
await db.model('User')
|
|
328
|
+
.where('age', '<', 10)
|
|
329
|
+
.delete();
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Returning Data
|
|
333
|
+
|
|
334
|
+
By default, write operations might not return data depending on the driver optimization. You can enforce it:
|
|
335
|
+
|
|
336
|
+
```javascript
|
|
337
|
+
const deletedUsers = await db.model('User')
|
|
338
|
+
.where('status', 'banned')
|
|
339
|
+
.returning('id, username') // or returning('*')
|
|
340
|
+
.delete();
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## ⚡ Raw SQL (Template Literals)
|
|
346
|
+
|
|
347
|
+
NeoPG exposes the full power of `postgres.js`. You don't need the ModelChain for everything.
|
|
348
|
+
|
|
349
|
+
> 📚 **Reference**: Full documentation for the SQL tag can be found at the [postgres.js GitHub page](https://github.com/porsager/postgres).
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
// Access the native driver
|
|
353
|
+
const sql = db.sql;
|
|
354
|
+
|
|
355
|
+
// Execute raw SQL safely
|
|
356
|
+
const users = await sql`
|
|
357
|
+
SELECT * FROM users
|
|
358
|
+
WHERE age > ${20}
|
|
359
|
+
`;
|
|
360
|
+
|
|
361
|
+
// Dynamic tables/columns using helper
|
|
362
|
+
const table = 'users';
|
|
363
|
+
const column = 'age';
|
|
364
|
+
const result = await sql`
|
|
365
|
+
SELECT ${sql(column)}
|
|
366
|
+
FROM ${sql(table)}
|
|
367
|
+
`;
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## 🤝 Transactions
|
|
373
|
+
|
|
374
|
+
NeoPG provides a unified transaction API. It supports nested transactions (Savepoints) automatically.
|
|
375
|
+
|
|
376
|
+
### Using NeoPG Context (Recommended)
|
|
377
|
+
|
|
378
|
+
```javascript
|
|
379
|
+
// Start a transaction scope
|
|
380
|
+
const result = await db.transaction(async (tx) => {
|
|
381
|
+
// 'tx' is a TransactionScope that mimics 'db'
|
|
382
|
+
|
|
383
|
+
// 1. Write operation
|
|
384
|
+
const user = await tx.model('User').insert({ username: 'alice' });
|
|
385
|
+
|
|
386
|
+
// 2. Read operation within transaction
|
|
387
|
+
const count = await tx.model('User').count();
|
|
388
|
+
|
|
389
|
+
// 3. Throwing an error will automatically ROLLBACK
|
|
390
|
+
if (count > 100) {
|
|
391
|
+
throw new Error('Limit reached');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return user;
|
|
395
|
+
});
|
|
396
|
+
// Automatically COMMITTED here if no error
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Using Raw Postgres Transaction
|
|
400
|
+
|
|
401
|
+
```javascript
|
|
402
|
+
await db.sql.begin(async (sql) => {
|
|
403
|
+
// sql is the transaction connection
|
|
404
|
+
await sql`INSERT INTO users (name) VALUES ('bob')`;
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## License
|
|
411
|
+
|
|
412
|
+
ISC
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+

|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const process = require('node:process');
|
|
8
|
+
|
|
9
|
+
// JS 关键字列表,防止类名冲突
|
|
10
|
+
const JS_KEYWORDS = new Set([
|
|
11
|
+
'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete',
|
|
12
|
+
'do', 'else', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import',
|
|
13
|
+
'in', 'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true',
|
|
14
|
+
'try', 'typeof', 'var', 'void', 'while', 'with', 'yield', 'let', 'static', 'enum', 'await',
|
|
15
|
+
'implements', 'interface', 'package', 'private', 'protected', 'public', 'arguments', 'eval'
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
// 帮助信息
|
|
19
|
+
function showHelp() {
|
|
20
|
+
console.log(`
|
|
21
|
+
Usage: neopg-model [options] [name1] [name2] ...
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--dir=<path> 指定模型文件保存目录 (默认: ./model)
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
neopg-model user-log order_info
|
|
28
|
+
neopg-model --dir=./src/models Product
|
|
29
|
+
`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 解析参数
|
|
33
|
+
function parseArgs() {
|
|
34
|
+
const args = process.argv.slice(2);
|
|
35
|
+
const config = {
|
|
36
|
+
dir: './model',
|
|
37
|
+
names: []
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (args.length === 0) {
|
|
41
|
+
showHelp();
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const arg of args) {
|
|
46
|
+
if (arg.startsWith('--dir=')) {
|
|
47
|
+
config.dir = arg.split('=')[1];
|
|
48
|
+
} else if (!arg.startsWith('-')) {
|
|
49
|
+
config.names.push(arg);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return config;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 验证名称合法性: 字母开头,只允许字母、数字、_、-
|
|
57
|
+
function isValidName(name) {
|
|
58
|
+
return /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 处理命名规则
|
|
62
|
+
function processName(inputName) {
|
|
63
|
+
// 1. 移除扩展名,但记录是否是 .mjs
|
|
64
|
+
let isMjs = inputName.endsWith('.mjs');
|
|
65
|
+
const cleanName = inputName.replace(/(\.js|\.mjs)$/, '');
|
|
66
|
+
|
|
67
|
+
// 2. 验证合法性
|
|
68
|
+
if (!isValidName(cleanName)) {
|
|
69
|
+
console.error(`\x1b[31m[Error]\x1b[0m 名称 "${inputName}" 不合法。必须以字母开头,只能包含字母、数字、下划线和连字符。`);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 3. 生成 tableName: 全小写,- 替换为 _
|
|
74
|
+
const tableName = cleanName.toLowerCase().replace(/-/g, '_');
|
|
75
|
+
|
|
76
|
+
// 4. 生成 modelName:
|
|
77
|
+
// - 去掉 '-'
|
|
78
|
+
// - '-' 后面的字母大写 (驼峰)
|
|
79
|
+
// - '_' 保留,_ 后面的字母不做特殊处理
|
|
80
|
+
// - 首字母大写
|
|
81
|
+
const parts = cleanName.split('-');
|
|
82
|
+
const modelName = parts.map((p, index) => {
|
|
83
|
+
// 每一部分的首字母大写
|
|
84
|
+
return p.charAt(0).toUpperCase() + p.slice(1);
|
|
85
|
+
}).join(''); // 直接连接,去掉了 -
|
|
86
|
+
|
|
87
|
+
// 检查关键字
|
|
88
|
+
if (JS_KEYWORDS.has(modelName)) {
|
|
89
|
+
console.warn(`\x1b[33m[Warning]\x1b[0m 生成的类名 "${modelName}" 是 JavaScript 关键字,建议修改名称。`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
raw: cleanName,
|
|
94
|
+
isMjs,
|
|
95
|
+
tableName,
|
|
96
|
+
modelName
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 生成 CommonJS 模板
|
|
101
|
+
function generateCJS(info) {
|
|
102
|
+
return `'use strict'\n\nconst { dataTypes, ModelChain } = require('neopg')
|
|
103
|
+
|
|
104
|
+
class ${info.modelName} extends ModelChain {
|
|
105
|
+
static schema = {
|
|
106
|
+
tableName: '${info.tableName}',
|
|
107
|
+
modelName: '${info.modelName}',
|
|
108
|
+
primaryKey: 'id',
|
|
109
|
+
column: {
|
|
110
|
+
id: {
|
|
111
|
+
type: dataTypes.ID
|
|
112
|
+
},
|
|
113
|
+
name: {
|
|
114
|
+
type: dataTypes.STRING(30),
|
|
115
|
+
default: ''
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
index: [],
|
|
119
|
+
unique: []
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = ${info.modelName}
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 生成 ESM 模板
|
|
128
|
+
function generateESM(info) {
|
|
129
|
+
return `'use strict'\n\nimport { dataTypes, ModelChain } from 'neopg'
|
|
130
|
+
|
|
131
|
+
class ${info.modelName} extends ModelChain {
|
|
132
|
+
static schema = {
|
|
133
|
+
tableName: '${info.tableName}',
|
|
134
|
+
modelName: '${info.modelName}',
|
|
135
|
+
primaryKey: 'id',
|
|
136
|
+
column: {
|
|
137
|
+
id: {
|
|
138
|
+
type: dataTypes.ID
|
|
139
|
+
},
|
|
140
|
+
name: {
|
|
141
|
+
type: dataTypes.STRING(30),
|
|
142
|
+
default: ''
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
index: [],
|
|
146
|
+
unique: []
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export default ${info.modelName}
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 主逻辑
|
|
155
|
+
function main() {
|
|
156
|
+
const config = parseArgs();
|
|
157
|
+
|
|
158
|
+
// 1. 确保目录存在
|
|
159
|
+
const targetDir = path.resolve(process.cwd(), config.dir);
|
|
160
|
+
if (!fs.existsSync(targetDir)) {
|
|
161
|
+
try {
|
|
162
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
163
|
+
console.log(`\x1b[32m[Info]\x1b[0m 创建目录: ${config.dir}`);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.error(`\x1b[31m[Error]\x1b[0m 无法创建目录 ${targetDir}: ${err.message}`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (config.names.length === 0) {
|
|
171
|
+
console.error('\x1b[31m[Error]\x1b[0m 未指定模型名称。');
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const name of config.names) {
|
|
176
|
+
const info = processName(name);
|
|
177
|
+
if (!info) continue;
|
|
178
|
+
|
|
179
|
+
const ext = info.isMjs ? '.mjs' : '.js';
|
|
180
|
+
const fileName = info.raw + ext;
|
|
181
|
+
const filePath = path.join(targetDir, fileName);
|
|
182
|
+
|
|
183
|
+
// 检查冲突:
|
|
184
|
+
// 1. 检查完全同名的文件
|
|
185
|
+
if (fs.existsSync(filePath)) {
|
|
186
|
+
console.error(`\x1b[31m[Skip]\x1b[0m 文件已存在: ${fileName}`);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 2. 检查 ModelName 命名的文件 (可选,防止 user.js 和 User.js 在某些系统混淆)
|
|
191
|
+
const modelNamePath = path.join(targetDir, info.modelName + ext);
|
|
192
|
+
if (fs.existsSync(modelNamePath) && modelNamePath.toLowerCase() !== filePath.toLowerCase()) {
|
|
193
|
+
console.warn(`\x1b[33m[Warning]\x1b[0m 存在同类名文件: ${info.modelName}${ext},可能会导致混淆。`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const content = info.isMjs ? generateESM(info) : generateCJS(info);
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
200
|
+
console.log(`\x1b[32m[Success]\x1b[0m 已创建模型: ${path.join(config.dir, fileName)} (Table: ${info.tableName}, Class: ${info.modelName})`);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
console.error(`\x1b[31m[Error]\x1b[0m 写入文件失败 ${fileName}: ${err.message}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
main();
|
|
Binary file
|
package/images/neopg.png
ADDED
|
Binary file
|