jsql-neo 1.1.0 → 1.2.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/index.js CHANGED
@@ -21,5 +21,6 @@
21
21
 
22
22
  module.exports = {
23
23
  Database: require('./lib/database'),
24
- Table: require('./lib/table')
24
+ Table: require('./lib/table'),
25
+ Query: require('./lib/query')
25
26
  };
package/lib/database.js CHANGED
@@ -1,11 +1,12 @@
1
1
  // © Vexify 2026 All Rights Reserved.
2
2
  /**
3
3
  * JSQL Database — 数据库管理器
4
- * 管理表集合、文件持久化、自动保存
4
+ * 支持加密、插件系统、迁移、版本历史、变更流、响应式查询
5
5
  */
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
+ const crypto = require('crypto');
9
10
  const Table = require('./table');
10
11
 
11
12
  class Database {
@@ -13,8 +14,10 @@ class Database {
13
14
  * @param {string} filePath - 数据库文件路径,传 null 或 ':memory:' 使用内存模式
14
15
  * @param {object} options
15
16
  * @param {boolean} options.autoSave - 是否自动保存(默认 true)
16
- * @param {number} options.autoSaveInterval - 自动保存间隔 ms(默认 0,即每次操作后保存)
17
+ * @param {number} options.autoSaveInterval - 自动保存间隔 ms(默认 0
17
18
  * @param {boolean} options.pretty - JSON 是否格式化(默认 true)
19
+ * @param {string} options.encryptKey - 加密密钥(16/24/32 字节),启用 AES-256-CBC
20
+ * @param {boolean} options.versioning - 是否启用版本历史(默认 false)
18
21
  */
19
22
  constructor(filePath, options = {}) {
20
23
  this._filePath = filePath;
@@ -24,11 +27,19 @@ class Database {
24
27
  autoSaveInterval: options.autoSaveInterval || 0,
25
28
  pretty: options.pretty !== false
26
29
  };
27
- this._tables = {}; // 表名 -> Table 实例
30
+ this._tables = {};
28
31
  this._dirty = false;
29
32
  this._autoSaveTimer = null;
30
- this._loading = false; // 加载中,不触发自动保存
31
- this._transaction = null; // 事务快照 { tables, autoIncrements }
33
+ this._loading = false;
34
+ this._transaction = null;
35
+ this._encryptKey = options.encryptKey || null;
36
+ this._versioning = options.versioning || false;
37
+ this._versions = {}; // tableName -> [{ rows, timestamp }]
38
+ this._maxVersions = options.maxVersions || 50;
39
+ this._plugins = [];
40
+ this._migrations = [];
41
+ this._observers = []; // 响应式观察者
42
+ this._changeStream = null; // 变更流
32
43
 
33
44
  // 从文件加载
34
45
  if (!this._memoryMode && fs.existsSync(this._filePath)) {
@@ -42,17 +53,6 @@ class Database {
42
53
  // 表管理
43
54
  // ============================================================
44
55
 
45
- /**
46
- * 创建表
47
- * @param {string} name - 表名
48
- * @param {object} schema - 表结构定义
49
- * @example
50
- * db.createTable('users', {
51
- * id: { type: 'integer', primaryKey: true, autoIncrement: true },
52
- * name: { type: 'string', required: true },
53
- * email: { type: 'string', unique: true }
54
- * })
55
- */
56
56
  createTable(name, schema) {
57
57
  if (this._tables[name]) {
58
58
  throw new Error(`Table '${name}' already exists`);
@@ -60,39 +60,32 @@ class Database {
60
60
  const table = new Table(name, schema, this);
61
61
  this._tables[name] = table;
62
62
 
63
- // 动态代理到 Database 实例上,方便 db.users.insert() 调用
64
63
  Object.defineProperty(this, name, {
65
64
  get: () => this._tables[name],
66
65
  enumerable: true,
67
66
  configurable: true
68
67
  });
69
68
 
69
+ if (this._versioning) {
70
+ this._versions[name] = [];
71
+ }
72
+
70
73
  this._markDirty();
71
74
  return table;
72
75
  }
73
76
 
74
- /**
75
- * 删除表
76
- */
77
77
  dropTable(name) {
78
- if (!this._tables[name]) {
79
- throw new Error(`Table '${name}' does not exist`);
80
- }
78
+ if (!this._tables[name]) throw new Error(`Table '${name}' does not exist`);
81
79
  delete this._tables[name];
82
80
  delete this[name];
81
+ delete this._versions[name];
83
82
  this._markDirty();
84
83
  }
85
84
 
86
- /**
87
- * 判断表是否存在
88
- */
89
85
  hasTable(name) {
90
86
  return !!this._tables[name];
91
87
  }
92
88
 
93
- /**
94
- * 获取所有表名
95
- */
96
89
  getTables() {
97
90
  return Object.keys(this._tables);
98
91
  }
@@ -101,47 +94,27 @@ class Database {
101
94
  // 事务
102
95
  // ============================================================
103
96
 
104
- /**
105
- * 开始事务
106
- * 保存当前数据快照,后续操作可回滚
107
- */
108
97
  begin() {
109
- if (this._transaction) {
110
- throw new Error('Transaction already in progress');
111
- }
112
- this._transaction = {
113
- rows: {},
114
- autoIncrements: {}
115
- };
98
+ if (this._transaction) throw new Error('Transaction already in progress');
99
+ this._transaction = { rows: {}, autoIncrements: {} };
116
100
  for (const [name, table] of Object.entries(this._tables)) {
117
101
  this._transaction.rows[name] = table._rows.map(r => ({ ...r }));
118
102
  this._transaction.autoIncrements[name] = table._autoIncrement;
119
103
  }
120
104
  }
121
105
 
122
- /**
123
- * 提交事务
124
- */
125
106
  commit() {
126
- if (!this._transaction) {
127
- throw new Error('No transaction in progress');
128
- }
107
+ if (!this._transaction) throw new Error('No transaction in progress');
129
108
  this._transaction = null;
130
109
  this._markDirty();
131
110
  }
132
111
 
133
- /**
134
- * 回滚事务
135
- */
136
112
  rollback() {
137
- if (!this._transaction) {
138
- throw new Error('No transaction in progress');
139
- }
113
+ if (!this._transaction) throw new Error('No transaction in progress');
140
114
  for (const [name, table] of Object.entries(this._tables)) {
141
115
  if (this._transaction.rows[name]) {
142
116
  table._rows = this._transaction.rows[name];
143
117
  table._autoIncrement = this._transaction.autoIncrements[name];
144
- // 重建索引
145
118
  for (const field of Object.keys(table._indexes)) {
146
119
  table.createIndex(field);
147
120
  }
@@ -150,41 +123,216 @@ class Database {
150
123
  this._transaction = null;
151
124
  }
152
125
 
153
- /**
154
- * 是否在事务中
155
- */
156
126
  inTransaction() {
157
127
  return !!this._transaction;
158
128
  }
159
129
 
160
130
  // ============================================================
161
- // 备份与恢复
131
+ // 版本历史
162
132
  // ============================================================
163
133
 
164
134
  /**
165
- * 备份到文件
135
+ * 保存当前快照
166
136
  */
137
+ snapshot(tableName) {
138
+ if (!this._versioning) return;
139
+ const table = this._tables[tableName];
140
+ if (!table) return;
141
+ if (!this._versions[tableName]) this._versions[tableName] = [];
142
+
143
+ // 空表跳过
144
+ if (table._rows.length === 0) return;
145
+
146
+ // 去重:如果当前状态与上次快照相同,跳过
147
+ const currentHash = JSON.stringify(table._rows);
148
+ const versions = this._versions[tableName];
149
+ if (versions.length > 0 && versions[versions.length - 1]._hash === currentHash) return;
150
+
151
+ versions.push({
152
+ rows: table._rows.map(r => ({ ...r })),
153
+ timestamp: Date.now(),
154
+ _hash: currentHash
155
+ });
156
+ if (versions.length > this._maxVersions) {
157
+ versions.shift();
158
+ }
159
+ }
160
+
161
+ /**
162
+ * 回退到指定版本
163
+ */
164
+ undo(tableName, steps = 1) {
165
+ if (!this._versioning) throw new Error('Versioning is not enabled');
166
+ const versions = this._versions[tableName];
167
+ if (!versions || versions.length < 2) throw new Error('No versions to undo');
168
+ const table = this._tables[tableName];
169
+ if (!table) throw new Error(`Table '${tableName}' not found`);
170
+
171
+ for (let i = 0; i < steps && versions.length > 1; i++) {
172
+ versions.pop(); // 丢弃最新版本
173
+ const snapshot = versions[versions.length - 1]; // 恢复到上一个版本
174
+ table._rows = snapshot.rows.map(r => ({ ...r }));
175
+ table._autoIncrement = 0;
176
+ for (const row of table._rows) {
177
+ if (table._autoIncrementField && row[table._autoIncrementField] > table._autoIncrement) {
178
+ table._autoIncrement = row[table._autoIncrementField];
179
+ }
180
+ }
181
+ for (const field of Object.keys(table._indexes)) {
182
+ table.createIndex(field);
183
+ }
184
+ }
185
+ this._markDirty();
186
+ return versions.length;
187
+ }
188
+
189
+ getVersions(tableName) {
190
+ return this._versions[tableName] || [];
191
+ }
192
+
193
+ // ============================================================
194
+ // 变更流 & 响应式
195
+ // ============================================================
196
+
197
+ /**
198
+ * 监听变更事件
199
+ * @param {function} callback - (event) => {} event: { type, table, data, timestamp }
200
+ */
201
+ onChange(callback) {
202
+ this._observers.push(callback);
203
+ return () => {
204
+ this._observers = this._observers.filter(cb => cb !== callback);
205
+ };
206
+ }
207
+
208
+ _emitChange(type, table, data) {
209
+ const event = { type, table, data, timestamp: Date.now() };
210
+ for (const cb of this._observers) {
211
+ try { cb(event); } catch (e) { /* ignore */ }
212
+ }
213
+ }
214
+
215
+ /**
216
+ * 创建变更流(返回异步迭代器)
217
+ */
218
+ createChangeStream() {
219
+ const events = [];
220
+ let resolve = null;
221
+ const unsubscribe = this.onChange(event => {
222
+ events.push(event);
223
+ if (resolve) {
224
+ resolve();
225
+ resolve = null;
226
+ }
227
+ });
228
+
229
+ return {
230
+ [Symbol.asyncIterator]() {
231
+ return {
232
+ next: async () => {
233
+ while (events.length === 0) {
234
+ await new Promise(r => resolve = r);
235
+ }
236
+ return { value: events.shift(), done: false };
237
+ }
238
+ };
239
+ },
240
+ close: unsubscribe
241
+ };
242
+ }
243
+
244
+ // ============================================================
245
+ // 插件系统
246
+ // ============================================================
247
+
248
+ /**
249
+ * 注册插件
250
+ * @param {object} plugin - { name, install(db) }
251
+ */
252
+ use(plugin) {
253
+ if (typeof plugin.install !== 'function') {
254
+ throw new Error('Plugin must have an install() method');
255
+ }
256
+ this._plugins.push(plugin);
257
+ plugin.install(this);
258
+ return this;
259
+ }
260
+
261
+ // ============================================================
262
+ // 迁移
263
+ // ============================================================
264
+
265
+ /**
266
+ * 注册迁移
267
+ * @param {object} migration - { version, name, up(db), down(db) }
268
+ */
269
+ addMigration(migration) {
270
+ this._migrations.push(migration);
271
+ this._migrations.sort((a, b) => a.version - b.version);
272
+ }
273
+
274
+ /**
275
+ * 执行迁移到指定版本
276
+ */
277
+ migrate(targetVersion) {
278
+ const current = this._getCurrentVersion();
279
+ if (targetVersion === undefined) {
280
+ targetVersion = this._migrations.length > 0
281
+ ? this._migrations[this._migrations.length - 1].version
282
+ : 0;
283
+ }
284
+
285
+ if (targetVersion > current) {
286
+ for (const m of this._migrations) {
287
+ if (m.version > current && m.version <= targetVersion) {
288
+ m.up(this);
289
+ this._setCurrentVersion(m.version);
290
+ }
291
+ }
292
+ } else if (targetVersion < current) {
293
+ for (const m of this._migrations.reverse()) {
294
+ if (m.version <= current && m.version > targetVersion) {
295
+ m.down(this);
296
+ this._setCurrentVersion(m.version - 1);
297
+ }
298
+ }
299
+ }
300
+ }
301
+
302
+ _getCurrentVersion() {
303
+ return this._tables['__meta__']
304
+ ? (this._tables['__meta__'].findOne({ key: 'version' })?.value || 0)
305
+ : 0;
306
+ }
307
+
308
+ _setCurrentVersion(version) {
309
+ if (!this._tables['__meta__']) {
310
+ this.createTable('__meta__', {
311
+ key: { type: 'string', primaryKey: true },
312
+ value: { type: 'any' }
313
+ });
314
+ }
315
+ this._tables['__meta__'].upsert({ key: 'version', value: version });
316
+ }
317
+
318
+ // ============================================================
319
+ // 备份与恢复
320
+ // ============================================================
321
+
167
322
  backup(filePath) {
168
323
  const data = this.export();
169
324
  const json = this._options.pretty
170
325
  ? JSON.stringify(data, null, 2)
171
326
  : JSON.stringify(data);
172
- const dir = path.dirname(filePath);
173
- if (!fs.existsSync(dir)) {
174
- fs.mkdirSync(dir, { recursive: true });
175
- }
176
- fs.writeFileSync(filePath, json, 'utf8');
327
+ this._writeFile(filePath, json);
177
328
  return filePath;
178
329
  }
179
330
 
180
- /**
181
- * 从文件恢复(覆盖当前数据)
182
- */
183
331
  restore(filePath) {
184
332
  if (!fs.existsSync(filePath)) {
185
333
  throw new Error(`Backup file not found: ${filePath}`);
186
334
  }
187
- const json = fs.readFileSync(filePath, 'utf8');
335
+ const json = this._readFile(filePath);
188
336
  const data = JSON.parse(json);
189
337
  for (const [name, rows] of Object.entries(data)) {
190
338
  if (this._tables[name]) {
@@ -198,39 +346,35 @@ class Database {
198
346
  // 持久化
199
347
  // ============================================================
200
348
 
201
- /**
202
- * 手动保存到文件
203
- */
204
349
  save() {
350
+ // 版本快照(内存模式也支持)
351
+ if (this._versioning) {
352
+ for (const name of Object.keys(this._tables)) {
353
+ this.snapshot(name);
354
+ }
355
+ }
356
+
205
357
  if (this._memoryMode) return;
206
358
 
207
- const data = { __schema__: {} };
359
+ const data = { __schema__: {}, __meta__: { version: this._getCurrentVersion() } };
208
360
  for (const [name, table] of Object.entries(this._tables)) {
361
+ if (name === '__meta__') continue;
209
362
  data.__schema__[name] = table._schema;
210
363
  data[name] = table.toJSON();
211
364
  }
212
365
 
213
- const dir = path.dirname(this._filePath);
214
- if (!fs.existsSync(dir)) {
215
- fs.mkdirSync(dir, { recursive: true });
216
- }
217
-
218
366
  const json = this._options.pretty
219
367
  ? JSON.stringify(data, null, 2)
220
368
  : JSON.stringify(data);
221
- fs.writeFileSync(this._filePath, json, 'utf8');
369
+ this._writeFile(this._filePath, json);
222
370
  this._dirty = false;
223
371
  }
224
372
 
225
- /**
226
- * 从文件加载
227
- */
228
373
  _load() {
229
374
  try {
230
- const json = fs.readFileSync(this._filePath, 'utf8');
375
+ const json = this._readFile(this._filePath);
231
376
  const data = JSON.parse(json);
232
377
 
233
- // 先根据 schema 重建表
234
378
  if (data.__schema__) {
235
379
  for (const [name, schema] of Object.entries(data.__schema__)) {
236
380
  if (!this._tables[name]) {
@@ -239,9 +383,8 @@ class Database {
239
383
  }
240
384
  }
241
385
 
242
- // 加载数据
243
386
  for (const [name, rows] of Object.entries(data)) {
244
- if (name !== '__schema__' && this._tables[name]) {
387
+ if (name !== '__schema__' && name !== '__meta__' && this._tables[name]) {
245
388
  this._tables[name]._loadRows(rows);
246
389
  }
247
390
  }
@@ -250,12 +393,9 @@ class Database {
250
393
  }
251
394
  }
252
395
 
253
- /**
254
- * 标记脏数据(触发自动保存)
255
- */
256
396
  _markDirty() {
257
397
  this._dirty = true;
258
- if (this._loading) return; // 加载中不保存
398
+ if (this._loading) return;
259
399
  if (this._options.autoSave && !this._autoSaveTimer) {
260
400
  if (this._options.autoSaveInterval > 0) {
261
401
  this._autoSaveTimer = setTimeout(() => {
@@ -263,15 +403,11 @@ class Database {
263
403
  if (this._dirty) this.save();
264
404
  }, this._options.autoSaveInterval);
265
405
  } else {
266
- // 立即保存(同步)
267
406
  this.save();
268
407
  }
269
408
  }
270
409
  }
271
410
 
272
- /**
273
- * 关闭数据库(保存并清理)
274
- */
275
411
  close() {
276
412
  if (this._autoSaveTimer) {
277
413
  clearTimeout(this._autoSaveTimer);
@@ -283,20 +419,15 @@ class Database {
283
419
  this._tables = {};
284
420
  }
285
421
 
286
- /**
287
- * 导出数据库为 JSON 对象
288
- */
289
422
  export() {
290
423
  const data = {};
291
424
  for (const [name, table] of Object.entries(this._tables)) {
425
+ if (name === '__meta__') continue;
292
426
  data[name] = table.toJSON();
293
427
  }
294
428
  return data;
295
429
  }
296
430
 
297
- /**
298
- * 从 JSON 对象导入数据
299
- */
300
431
  import(data) {
301
432
  for (const [name, rows] of Object.entries(data)) {
302
433
  if (this._tables[name]) {
@@ -305,6 +436,35 @@ class Database {
305
436
  }
306
437
  this._markDirty();
307
438
  }
439
+
440
+ // ============================================================
441
+ // 加密(内部)
442
+ // ============================================================
443
+
444
+ _writeFile(filePath, content) {
445
+ if (this._encryptKey) {
446
+ const iv = crypto.randomBytes(16);
447
+ const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(this._encryptKey.padEnd(32).slice(0, 32)), iv);
448
+ const encrypted = Buffer.concat([cipher.update(content, 'utf8'), cipher.final()]);
449
+ const data = Buffer.concat([iv, encrypted]);
450
+ fs.writeFileSync(filePath, data.toString('base64'), 'utf8');
451
+ } else {
452
+ const dir = path.dirname(filePath);
453
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
454
+ fs.writeFileSync(filePath, content, 'utf8');
455
+ }
456
+ }
457
+
458
+ _readFile(filePath) {
459
+ if (this._encryptKey) {
460
+ const data = Buffer.from(fs.readFileSync(filePath, 'utf8'), 'base64');
461
+ const iv = data.slice(0, 16);
462
+ const encrypted = data.slice(16);
463
+ const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(this._encryptKey.padEnd(32).slice(0, 32)), iv);
464
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
465
+ }
466
+ return fs.readFileSync(filePath, 'utf8');
467
+ }
308
468
  }
309
469
 
310
470
  module.exports = Database;