jsql-neo 1.1.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 +2 -1
- package/lib/database.js +258 -98
- package/lib/query.js +435 -0
- package/lib/table.js +350 -299
- package/package.json +1 -1
package/index.js
CHANGED
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 = {};
|
|
30
|
+
this._tables = {};
|
|
28
31
|
this._dirty = false;
|
|
29
32
|
this._autoSaveTimer = null;
|
|
30
|
-
this._loading = false;
|
|
31
|
-
this._transaction = null;
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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;
|