neopg 1.0.1 → 2.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/README.cn.md CHANGED
@@ -212,6 +212,28 @@ console.log('数据库结构已同步!')
212
212
 
213
213
  ---
214
214
 
215
+ ### 📂 自动加载模型
216
+
217
+ NeoPG 支持扫描指定目录并自动注册所有模型,无需手动逐个引入。
218
+
219
+ **加载规则:**
220
+ * 仅加载 `.js` 和 `.mjs` 后缀的文件。
221
+ * **忽略**以 `_` 开头的文件(可用作目录内的共享工具或基类)。
222
+ * **忽略**以 `!` 开头的文件(可用作临时禁用的模型)。
223
+
224
+ ```javascript
225
+ const db = new NeoPG(config)
226
+
227
+ // 自动加载 ./models 目录下的所有模型
228
+ // 注意:这是一个异步方法,因为它兼容 ESM (.mjs) 的动态导入
229
+ await db.loadModels('./models')
230
+
231
+ // 加载完成后即可同步或使用
232
+ await db.sync()
233
+ ```
234
+
235
+ ---
236
+
215
237
  ## 🔍 查询数据
216
238
 
217
239
  NeoPG 提供了自然流畅的链式 API。
@@ -239,16 +261,13 @@ const page2 = await db.model('User').page(2, 20).find(); // 第 2 页,每页 2
239
261
 
240
262
  ```javascript
241
263
  await db.model('User')
242
- // 对象风格 (自动处理 AND)
243
264
  .where({
244
265
  age: 18,
245
266
  status: 'active'
246
267
  })
247
- // 操作符风格
248
268
  .where('create_time', '>', 1600000000)
249
- // SQL 片段风格 (强大且灵活!)
250
269
  .where('id IS NOT NULL')
251
- .find();
270
+ .find()
252
271
  ```
253
272
 
254
273
  ### 结合模板字符串的复杂查询
@@ -259,9 +278,9 @@ await db.model('User')
259
278
  // db.sql 是原生的 postgres 实例
260
279
  const { sql } = db;
261
280
 
281
+ // 通过模板字符串安全地注入参数
262
282
  await db.model('User')
263
283
  .where({ status: 'active' })
264
- // 通过模板字符串安全地注入参数
265
284
  .where(sql`age > ${20} AND email LIKE ${'%@gmail.com'}`)
266
285
  .find();
267
286
  ```
package/README.md CHANGED
@@ -211,6 +211,28 @@ console.log('Database synced!')
211
211
 
212
212
  ---
213
213
 
214
+ ### 📂 Auto-loading Models
215
+
216
+ Instead of manually importing and defining each model, you can load all models from a directory.
217
+
218
+ **Rules:**
219
+ * Only `.js` and `.mjs` files are loaded.
220
+ * Files starting with `_` are ignored (useful for utils/helpers).
221
+ * Files starting with `!` are ignored (useful for disabled models).
222
+
223
+ ```javascript
224
+ const db = new NeoPG(config)
225
+
226
+ // Load all models from the './models' directory
227
+ // This is asynchronous because it supports .mjs dynamic imports
228
+ await db.loadModels('./models')
229
+
230
+ // Now you can sync and use them
231
+ await db.sync()
232
+ ```
233
+
234
+ ---
235
+
214
236
  ## 🔍 Querying
215
237
 
216
238
  NeoPG provides a fluent, chainable API that feels natural to use.
@@ -238,16 +260,13 @@ const page2 = await db.model('User').page(2, 20).find(); // Page 2, Size 20
238
260
 
239
261
  ```javascript
240
262
  await db.model('User')
241
- // Object style (AND logic)
242
263
  .where({
243
264
  age: 18,
244
265
  status: 'active'
245
266
  })
246
- // Operator style
247
267
  .where('create_time', '>', 1600000000)
248
- // SQL Fragment style (Powerful!)
249
268
  .where('id IS NOT NULL')
250
- .find();
269
+ .find()
251
270
  ```
252
271
 
253
272
  ### Complex Where with Template Literals
@@ -300,14 +319,14 @@ const stats = await db.model('User')
300
319
  const newUser = await db.model('User').insert({
301
320
  username: 'neo',
302
321
  email: 'neo@matrix.com'
303
- });
322
+ })
304
323
  // ID and Timestamps are automatically generated if configured in Schema
305
324
 
306
325
  // Insert multiple (Batch)
307
326
  await db.model('User').insert([
308
327
  { username: 'a' },
309
328
  { username: 'b' }
310
- ]);
329
+ ])
311
330
  ```
312
331
 
313
332
  ### Update
@@ -384,16 +403,15 @@ const result = await db.transaction(async (tx) => {
384
403
  const user = await tx.model('User').insert({ username: 'alice' });
385
404
 
386
405
  // 2. Read operation within transaction
387
- const count = await tx.model('User').count();
406
+ const count = await tx.model('User').count()
388
407
 
389
408
  // 3. Throwing an error will automatically ROLLBACK
390
409
  if (count > 100) {
391
- throw new Error('Limit reached');
410
+ throw new Error('Limit reached')
392
411
  }
393
412
 
394
- return user;
395
- });
396
- // Automatically COMMITTED here if no error
413
+ return user
414
+ })
397
415
  ```
398
416
 
399
417
  ### Using Raw Postgres Transaction
@@ -402,7 +420,7 @@ const result = await db.transaction(async (tx) => {
402
420
  await db.sql.begin(async (sql) => {
403
421
  // sql is the transaction connection
404
422
  await sql`INSERT INTO users (name) VALUES ('bob')`;
405
- });
423
+ })
406
424
  ```
407
425
 
408
426
  ---
package/lib/NeoPG.js CHANGED
@@ -7,6 +7,10 @@ const TransactionScope = require('./TransactionScope.js')
7
7
  const ModelChain = require('./ModelChain.js')
8
8
  const dataTypes = require('./dataTypes.js')
9
9
 
10
+ const path = require('node:path')
11
+ const fs = require('node:fs')
12
+ const { pathToFileURL } = require('node:url')
13
+
10
14
  class NeoPG {
11
15
  constructor(config) {
12
16
  this.driver = postgres(config)
@@ -136,6 +140,64 @@ class NeoPG {
136
140
  async close() {
137
141
  await this.driver.end()
138
142
  }
143
+
144
+ /**
145
+ * 自动加载指定目录下的模型文件
146
+ * 规则:
147
+ * 1. 只加载 .js 和 .mjs 文件
148
+ * 2. 跳过以 '_' 开头的文件 (视为内部共享模块)
149
+ * 3. 跳过以 '!' 开头的文件 (视为保留或禁用文件)
150
+ *
151
+ * @param {string} dirPath - 目录路径 (相对 process.cwd() 或绝对路径)
152
+ */
153
+ async loadModels(dirPath) {
154
+ // 解析绝对路径
155
+ const absPath = path.isAbsolute(dirPath)
156
+ ? dirPath
157
+ : path.resolve(process.cwd(), dirPath)
158
+
159
+ if (!fs.existsSync(absPath)) {
160
+ throw new Error(`[NeoPG] Models directory not found: ${absPath}`)
161
+ }
162
+
163
+ const files = fs.readdirSync(absPath)
164
+
165
+ for (const file of files) {
166
+ // 过滤规则
167
+ if (file.startsWith('_') || file.startsWith('!')) continue
168
+
169
+ const ext = path.extname(file)
170
+
171
+ if (ext !== '.js' && ext !== '.mjs') continue
172
+
173
+ const fullFilePath = path.join(absPath, file)
174
+ let modelExport
175
+
176
+ try {
177
+ if (ext === '.mjs') {
178
+ // 处理 ESM 动态导入
179
+ // Windows 下 import() 需要 file:// 协议路径
180
+ const fileUrl = pathToFileURL(fullFilePath).href
181
+ const imported = await import(fileUrl)
182
+ modelExport = imported.default
183
+ } else {
184
+ // 处理 CommonJS
185
+ modelExport = require(fullFilePath)
186
+ }
187
+
188
+ // 注册模型 (如果导出为空或不是合法对象/类,add 方法内部会抛错或处理)
189
+ if (modelExport) {
190
+ this.add(modelExport)
191
+ }
192
+ } catch (err) {
193
+ console.error(`[NeoPG] Failed to load model: ${file}`)
194
+ throw err
195
+ }
196
+ }
197
+
198
+ return this
199
+ }
200
+
139
201
  }
140
202
 
141
203
  NeoPG.dataTypes = dataTypes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neopg",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "description": "orm for postgres",
5
5
  "keywords": [
6
6
  "neopg",
package/test/test-db.js CHANGED
@@ -154,12 +154,21 @@ db.add(User);
154
154
  {
155
155
  username: 'Neo',
156
156
  email: '123@w.com',
157
+ sex: 1,
157
158
  level: Math.floor((Math.random() * 105))
158
159
  },
159
160
  {
160
161
  username: 'PG',
161
162
  email: '1234@w.com',
163
+ sex: 2,
162
164
  level: Math.floor((Math.random() * 100))
165
+ },
166
+
167
+ {
168
+ username: 'NPG',
169
+ email: '1235@w.com',
170
+ sex: 3,
171
+ level: 3
163
172
  }
164
173
  ])
165
174
  )
@@ -179,6 +188,12 @@ db.add(User);
179
188
  let result = await tx.model('User').where(tx.sql`level > 10`).returning('*').update(data)
180
189
  console.log(result)
181
190
 
191
+ let sex = 3
192
+ console.log(
193
+ 'test condition or',
194
+ await tx.model('User').where(tx.sql`(sex = ${sex} or level > 10)`).select(['id', 'level', 'username', 'sex']).find()
195
+ )
196
+
182
197
  console.log(
183
198
  'test avg',
184
199
  await tx.model('User').avg('level')