jsql-neo 1.0.0 → 1.2.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/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,10 +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; // 加载中,不触发自动保存
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; // 变更流
31
43
 
32
44
  // 从文件加载
33
45
  if (!this._memoryMode && fs.existsSync(this._filePath)) {
@@ -41,17 +53,6 @@ class Database {
41
53
  // 表管理
42
54
  // ============================================================
43
55
 
44
- /**
45
- * 创建表
46
- * @param {string} name - 表名
47
- * @param {object} schema - 表结构定义
48
- * @example
49
- * db.createTable('users', {
50
- * id: { type: 'integer', primaryKey: true, autoIncrement: true },
51
- * name: { type: 'string', required: true },
52
- * email: { type: 'string', unique: true }
53
- * })
54
- */
55
56
  createTable(name, schema) {
56
57
  if (this._tables[name]) {
57
58
  throw new Error(`Table '${name}' already exists`);
@@ -59,80 +60,321 @@ class Database {
59
60
  const table = new Table(name, schema, this);
60
61
  this._tables[name] = table;
61
62
 
62
- // 动态代理到 Database 实例上,方便 db.users.insert() 调用
63
63
  Object.defineProperty(this, name, {
64
64
  get: () => this._tables[name],
65
65
  enumerable: true,
66
66
  configurable: true
67
67
  });
68
68
 
69
+ if (this._versioning) {
70
+ this._versions[name] = [];
71
+ }
72
+
69
73
  this._markDirty();
70
74
  return table;
71
75
  }
72
76
 
73
- /**
74
- * 删除表
75
- */
76
77
  dropTable(name) {
77
- if (!this._tables[name]) {
78
- throw new Error(`Table '${name}' does not exist`);
79
- }
78
+ if (!this._tables[name]) throw new Error(`Table '${name}' does not exist`);
80
79
  delete this._tables[name];
81
80
  delete this[name];
81
+ delete this._versions[name];
82
82
  this._markDirty();
83
83
  }
84
84
 
85
- /**
86
- * 判断表是否存在
87
- */
88
85
  hasTable(name) {
89
86
  return !!this._tables[name];
90
87
  }
91
88
 
92
- /**
93
- * 获取所有表名
94
- */
95
89
  getTables() {
96
90
  return Object.keys(this._tables);
97
91
  }
98
92
 
99
93
  // ============================================================
100
- // 持久化
94
+ // 事务
95
+ // ============================================================
96
+
97
+ begin() {
98
+ if (this._transaction) throw new Error('Transaction already in progress');
99
+ this._transaction = { rows: {}, autoIncrements: {} };
100
+ for (const [name, table] of Object.entries(this._tables)) {
101
+ this._transaction.rows[name] = table._rows.map(r => ({ ...r }));
102
+ this._transaction.autoIncrements[name] = table._autoIncrement;
103
+ }
104
+ }
105
+
106
+ commit() {
107
+ if (!this._transaction) throw new Error('No transaction in progress');
108
+ this._transaction = null;
109
+ this._markDirty();
110
+ }
111
+
112
+ rollback() {
113
+ if (!this._transaction) throw new Error('No transaction in progress');
114
+ for (const [name, table] of Object.entries(this._tables)) {
115
+ if (this._transaction.rows[name]) {
116
+ table._rows = this._transaction.rows[name];
117
+ table._autoIncrement = this._transaction.autoIncrements[name];
118
+ for (const field of Object.keys(table._indexes)) {
119
+ table.createIndex(field);
120
+ }
121
+ }
122
+ }
123
+ this._transaction = null;
124
+ }
125
+
126
+ inTransaction() {
127
+ return !!this._transaction;
128
+ }
129
+
130
+ // ============================================================
131
+ // 版本历史
132
+ // ============================================================
133
+
134
+ /**
135
+ * 保存当前快照
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
+ // 迁移
101
263
  // ============================================================
102
264
 
103
265
  /**
104
- * 手动保存到文件
266
+ * 注册迁移
267
+ * @param {object} migration - { version, name, up(db), down(db) }
105
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
+
322
+ backup(filePath) {
323
+ const data = this.export();
324
+ const json = this._options.pretty
325
+ ? JSON.stringify(data, null, 2)
326
+ : JSON.stringify(data);
327
+ this._writeFile(filePath, json);
328
+ return filePath;
329
+ }
330
+
331
+ restore(filePath) {
332
+ if (!fs.existsSync(filePath)) {
333
+ throw new Error(`Backup file not found: ${filePath}`);
334
+ }
335
+ const json = this._readFile(filePath);
336
+ const data = JSON.parse(json);
337
+ for (const [name, rows] of Object.entries(data)) {
338
+ if (this._tables[name]) {
339
+ this._tables[name]._loadRows(rows);
340
+ }
341
+ }
342
+ this._markDirty();
343
+ }
344
+
345
+ // ============================================================
346
+ // 持久化
347
+ // ============================================================
348
+
106
349
  save() {
350
+ // 版本快照(内存模式也支持)
351
+ if (this._versioning) {
352
+ for (const name of Object.keys(this._tables)) {
353
+ this.snapshot(name);
354
+ }
355
+ }
356
+
107
357
  if (this._memoryMode) return;
108
358
 
109
- const data = { __schema__: {} };
359
+ const data = { __schema__: {}, __meta__: { version: this._getCurrentVersion() } };
110
360
  for (const [name, table] of Object.entries(this._tables)) {
361
+ if (name === '__meta__') continue;
111
362
  data.__schema__[name] = table._schema;
112
363
  data[name] = table.toJSON();
113
364
  }
114
365
 
115
- const dir = path.dirname(this._filePath);
116
- if (!fs.existsSync(dir)) {
117
- fs.mkdirSync(dir, { recursive: true });
118
- }
119
-
120
366
  const json = this._options.pretty
121
367
  ? JSON.stringify(data, null, 2)
122
368
  : JSON.stringify(data);
123
- fs.writeFileSync(this._filePath, json, 'utf8');
369
+ this._writeFile(this._filePath, json);
124
370
  this._dirty = false;
125
371
  }
126
372
 
127
- /**
128
- * 从文件加载
129
- */
130
373
  _load() {
131
374
  try {
132
- const json = fs.readFileSync(this._filePath, 'utf8');
375
+ const json = this._readFile(this._filePath);
133
376
  const data = JSON.parse(json);
134
377
 
135
- // 先根据 schema 重建表
136
378
  if (data.__schema__) {
137
379
  for (const [name, schema] of Object.entries(data.__schema__)) {
138
380
  if (!this._tables[name]) {
@@ -141,9 +383,8 @@ class Database {
141
383
  }
142
384
  }
143
385
 
144
- // 加载数据
145
386
  for (const [name, rows] of Object.entries(data)) {
146
- if (name !== '__schema__' && this._tables[name]) {
387
+ if (name !== '__schema__' && name !== '__meta__' && this._tables[name]) {
147
388
  this._tables[name]._loadRows(rows);
148
389
  }
149
390
  }
@@ -152,12 +393,9 @@ class Database {
152
393
  }
153
394
  }
154
395
 
155
- /**
156
- * 标记脏数据(触发自动保存)
157
- */
158
396
  _markDirty() {
159
397
  this._dirty = true;
160
- if (this._loading) return; // 加载中不保存
398
+ if (this._loading) return;
161
399
  if (this._options.autoSave && !this._autoSaveTimer) {
162
400
  if (this._options.autoSaveInterval > 0) {
163
401
  this._autoSaveTimer = setTimeout(() => {
@@ -165,15 +403,11 @@ class Database {
165
403
  if (this._dirty) this.save();
166
404
  }, this._options.autoSaveInterval);
167
405
  } else {
168
- // 立即保存(同步)
169
406
  this.save();
170
407
  }
171
408
  }
172
409
  }
173
410
 
174
- /**
175
- * 关闭数据库(保存并清理)
176
- */
177
411
  close() {
178
412
  if (this._autoSaveTimer) {
179
413
  clearTimeout(this._autoSaveTimer);
@@ -185,20 +419,15 @@ class Database {
185
419
  this._tables = {};
186
420
  }
187
421
 
188
- /**
189
- * 导出数据库为 JSON 对象
190
- */
191
422
  export() {
192
423
  const data = {};
193
424
  for (const [name, table] of Object.entries(this._tables)) {
425
+ if (name === '__meta__') continue;
194
426
  data[name] = table.toJSON();
195
427
  }
196
428
  return data;
197
429
  }
198
430
 
199
- /**
200
- * 从 JSON 对象导入数据
201
- */
202
431
  import(data) {
203
432
  for (const [name, rows] of Object.entries(data)) {
204
433
  if (this._tables[name]) {
@@ -207,6 +436,35 @@ class Database {
207
436
  }
208
437
  this._markDirty();
209
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
+ }
210
468
  }
211
469
 
212
470
  module.exports = Database;