neopg 1.0.0 → 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/README.cn.md CHANGED
@@ -64,7 +64,7 @@ await db.close();
64
64
  创建一个模型文件(例如 `models/User.js`)。您的类应当继承自 `NeoPG.ModelChain`。
65
65
 
66
66
  ```javascript
67
- const { ModelChain, dataTypes } = require('neopg');
67
+ const { ModelChain, dataTypes } = require('neopg')
68
68
 
69
69
  class User extends ModelChain {
70
70
  static schema = {
@@ -106,29 +106,108 @@ class User extends ModelChain {
106
106
  index: ['email', 'age'],
107
107
  // 唯一索引定义
108
108
  unique: ['username']
109
- };
109
+ }
110
110
  }
111
111
 
112
- module.exports = User;
112
+ module.exports = User
113
+ ```
114
+
115
+ ## 🛠 CLI 模型生成器
116
+
117
+ NeoPG 内置了一个 CLI 工具,可以快速生成带有样板代码的模型文件。
118
+
119
+ ### 用法
120
+
121
+ 通过 `npx` 直接运行(无需全局安装):
122
+
123
+ ```bash
124
+ npx neopg-model [选项] [模型名称...]
125
+ ```
126
+
127
+ ### 选项
128
+
129
+ * `--dir=<path>`: 指定输出目录(默认:`./model`)。
130
+
131
+ ### 示例
132
+
133
+ **1. 基础生成**
134
+ ```bash
135
+ npx neopg-model user
136
+ # 创建文件: ./model/user.js
137
+ # 类名: User
138
+ # 表名: user
139
+ ```
140
+
141
+ **2. 命名规范(连字符处理)**
142
+ 输入带连字符的名称,NeoPG 会自动将类名转换为 **大驼峰(CamelCase)**,将表名转换为 **下划线(snake_case)**。
143
+
144
+ ```bash
145
+ npx neopg-model user-log
146
+ # 创建文件: ./model/user-log.js
147
+ # 类名: UserLog
148
+ # 表名: user_log
149
+ ```
150
+
151
+ **3. 批量生成与自定义目录**
152
+ ```bash
153
+ npx neopg-model --dir=./src/models product order-item
154
+ # 创建:
155
+ # ./src/models/product.js
156
+ # ./src/models/order-item.js
157
+ ```
158
+
159
+ **4. ES Modules (.mjs)**
160
+ 如果在名称后加上 `.mjs` 后缀,将生成 ESM 语法(`export default`)的文件。
161
+ ```bash
162
+ npx neopg-model config.mjs
113
163
  ```
114
164
 
115
165
  ---
116
166
 
117
167
  ## ⚙️ 注册与同步
118
168
 
119
- 初始化 NeoPG 并注册您的模型。您还可以将表结构定义同步到数据库中。
169
+ 初始化 NeoPG 并注册您的模型。您可以使用类(Class)或配置对象(Object)来定义模型。
170
+
171
+ ### 注册模型
172
+
173
+ NeoPG 提供了三种注册方法以应对不同场景:
174
+
175
+ * **`define(model)`**:标准注册方法。如果同名模型已存在,会抛出错误(`modelName conflict`),防止意外覆盖。
176
+ * **`add(model)`**:同 `define`,行为一致。
177
+ * **`set(model)`**:**强制覆盖/重置**。如果模型已存在,则更新其定义。适用于热重载或动态 Schema 场景。
120
178
 
121
179
  ```javascript
122
- const User = require('./models/User');
180
+ const User = require('./models/User')
181
+
182
+ // 1. 标准注册 (安全模式)
183
+ // 如果 'User' 已经被注册过,此处会报错
184
+ db.define(User)
185
+
186
+ // 2. 强制覆盖 (重置模式)
187
+ // 即使 'User' 已存在,也会使用新的定义覆盖它
188
+ db.set(User)
189
+
190
+ // 3. 使用纯对象注册 (快速原型)
191
+ db.define({
192
+ tableName: 'logs',
193
+ column: {
194
+ message: 'string',
195
+ level: 'int'
196
+ }
197
+ })
123
198
 
124
- // 1. 注册模型
125
- db.define(User);
199
+ ```
200
+
201
+ ### 同步数据库
126
202
 
127
- // 2. 同步表结构 (DDL)
203
+ 根据已注册的模型同步数据库表结构。
204
+
205
+ ```javascript
206
+ // 同步表结构 (DDL)
128
207
  // options: { force: true } 开启强制模式,会删除 Schema 中未定义的字段,请谨慎使用
129
- await db.sync({ force: false });
208
+ await db.sync({ force: false })
130
209
 
131
- console.log('数据库结构已同步!');
210
+ console.log('数据库结构已同步!')
132
211
  ```
133
212
 
134
213
  ---
package/README.md CHANGED
@@ -54,7 +54,7 @@ const db = new NeoPG(config);
54
54
  ### Close Connection
55
55
 
56
56
  ```javascript
57
- await db.close();
57
+ await db.close()
58
58
  ```
59
59
 
60
60
  ---
@@ -64,7 +64,7 @@ await db.close();
64
64
  Create a model file (e.g., `models/User.js`). Your class should extend `NeoPG.ModelChain`.
65
65
 
66
66
  ```javascript
67
- const { ModelChain, dataTypes } = require('neopg');
67
+ const { ModelChain, dataTypes } = require('neopg')
68
68
 
69
69
  class User extends ModelChain {
70
70
  static schema = {
@@ -105,29 +105,108 @@ class User extends ModelChain {
105
105
  // Indexes
106
106
  index: ['email', 'age'],
107
107
  unique: ['username']
108
- };
108
+ }
109
109
  }
110
110
 
111
- module.exports = User;
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
112
162
  ```
113
163
 
114
164
  ---
115
165
 
116
166
  ## ⚙️ Registration & Sync
117
167
 
118
- Initialize NeoPG and register your models. You can also sync the table structure to the database.
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.
119
177
 
120
178
  ```javascript
121
- const User = require('./models/User');
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
+ })
122
197
 
123
- // 1. Register Model
124
- db.define(User);
198
+ ```
199
+
200
+ ### Syncing Database
125
201
 
126
- // 2. Sync Table Structure (DDL)
202
+ Sync the table structure to the database based on registered models.
203
+
204
+ ```javascript
205
+ // Sync Table Structure (DDL)
127
206
  // options: { force: true } will drop columns not defined in schema
128
- await db.sync({ force: false });
207
+ await db.sync({ force: false })
129
208
 
130
- console.log('Database synced!');
209
+ console.log('Database synced!')
131
210
  ```
132
211
 
133
212
  ---
@@ -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();
package/lib/NeoPG.js CHANGED
@@ -33,7 +33,7 @@ class NeoPG {
33
33
 
34
34
  // --- 注册 ---
35
35
 
36
- add(input) {
36
+ add(input, is_reset=false) {
37
37
  let ModelClass
38
38
 
39
39
  if (typeof input === 'function') {
@@ -43,10 +43,16 @@ class NeoPG {
43
43
  }
44
44
 
45
45
  const rawSchema = ModelClass.schema
46
+
46
47
  if (!rawSchema) throw new Error(`[NeoPG] Missing static schema for ${ModelClass.name}`)
47
48
 
48
49
  const def = new ModelDef(rawSchema)
49
50
 
51
+ //已经存在又不是更新,则报错
52
+ if (!is_reset && this.registry.has(def.modelName)) {
53
+ throw new Error(`[NeoPG] modelName conflict: ${def.modelName}`)
54
+ }
55
+
50
56
  this.registry.set(def.modelName, {
51
57
  Class: ModelClass,
52
58
  def: def
@@ -59,6 +65,10 @@ class NeoPG {
59
65
  return this.add(model)
60
66
  }
61
67
 
68
+ set(model) {
69
+ return this.add(model, true)
70
+ }
71
+
62
72
  // --- 事务 ---
63
73
  async transaction(callback) {
64
74
  return await this.driver.begin(async (trxSql) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neopg",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "orm for postgres",
5
5
  "keywords": [
6
6
  "neopg",
@@ -18,6 +18,9 @@
18
18
  "bugs": {
19
19
  "url": "https://github.com/master-genius/neopg/issues"
20
20
  },
21
+ "bin": {
22
+ "neopg-model": "./bin/neopg-model.js"
23
+ },
21
24
  "repository": {
22
25
  "type": "git",
23
26
  "url": "git+https://github.com/master-genius/neopg.git"