deepbase-json 3.8.0 → 3.8.2
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/package.json +2 -2
- package/src/JsonDriver.js +97 -16
- package/src/index.d.ts +12 -2
- package/test/test.js +209 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepbase-json",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.2",
|
|
4
4
|
"description": "⚡ DeepBase JSON - filesystem driver",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.cjs",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"steno": "^0.4.4"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
|
-
"deepbase": "^3.8.
|
|
21
|
+
"deepbase": "^3.8.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"mocha": "^10.8.2"
|
package/src/JsonDriver.js
CHANGED
|
@@ -7,7 +7,16 @@ import lockfile from 'proper-lockfile';
|
|
|
7
7
|
export class JsonDriver extends DeepBaseDriver {
|
|
8
8
|
static _instances = {};
|
|
9
9
|
|
|
10
|
-
constructor({
|
|
10
|
+
constructor({
|
|
11
|
+
name,
|
|
12
|
+
path,
|
|
13
|
+
stringify,
|
|
14
|
+
parse,
|
|
15
|
+
multiProcess,
|
|
16
|
+
encodeForMemory,
|
|
17
|
+
decodeFromMemory,
|
|
18
|
+
...opts
|
|
19
|
+
} = {}) {
|
|
11
20
|
super(opts);
|
|
12
21
|
|
|
13
22
|
this.name = name || "default";
|
|
@@ -15,6 +24,8 @@ export class JsonDriver extends DeepBaseDriver {
|
|
|
15
24
|
this.stringify = stringify || ((obj) => JSON.stringify(obj, null, 4));
|
|
16
25
|
this.parse = parse || JSON.parse;
|
|
17
26
|
this.multiProcess = multiProcess || false;
|
|
27
|
+
this.encodeForMemory = encodeForMemory || (value => value);
|
|
28
|
+
this.decodeFromMemory = decodeFromMemory || (value => value);
|
|
18
29
|
|
|
19
30
|
this.path = pathModule.resolve(this.path);
|
|
20
31
|
this.fileName = pathModule.join(this.path, `${this.name}.json`);
|
|
@@ -31,11 +42,15 @@ export class JsonDriver extends DeepBaseDriver {
|
|
|
31
42
|
// Queue for serializing concurrent operations
|
|
32
43
|
this._operationQueue = Promise.resolve();
|
|
33
44
|
this._queueLock = false;
|
|
45
|
+
this._disposing = false;
|
|
46
|
+
this._disposed = false;
|
|
47
|
+
this._disposePromise = null;
|
|
34
48
|
|
|
35
49
|
JsonDriver._instances[this.fileName] = this;
|
|
36
50
|
}
|
|
37
51
|
|
|
38
52
|
_connectSync() {
|
|
53
|
+
this._assertUsable();
|
|
39
54
|
if (this._connected) return;
|
|
40
55
|
|
|
41
56
|
if (!fs.existsSync(this.path)) {
|
|
@@ -44,10 +59,11 @@ export class JsonDriver extends DeepBaseDriver {
|
|
|
44
59
|
|
|
45
60
|
if (fs.existsSync(this.fileName)) {
|
|
46
61
|
const fileContent = fs.readFileSync(this.fileName, "utf8");
|
|
47
|
-
|
|
62
|
+
const parsed = fileContent ? this.parse(fileContent) : {};
|
|
63
|
+
this.obj = this._encodeForMemory(parsed, []);
|
|
48
64
|
} else {
|
|
49
65
|
// Create the file so proper-lockfile can lock it in multiProcess mode
|
|
50
|
-
fs.writeFileSync(this.fileName, this.
|
|
66
|
+
fs.writeFileSync(this.fileName, this._serializeMemory());
|
|
51
67
|
}
|
|
52
68
|
|
|
53
69
|
this._connected = true;
|
|
@@ -58,19 +74,25 @@ export class JsonDriver extends DeepBaseDriver {
|
|
|
58
74
|
}
|
|
59
75
|
|
|
60
76
|
async disconnect() {
|
|
77
|
+
if (this._disposed) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (this._disposePromise) {
|
|
81
|
+
return this._disposePromise;
|
|
82
|
+
}
|
|
83
|
+
await this._operationQueue;
|
|
61
84
|
await this._saveToFile();
|
|
62
85
|
this._connected = false;
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
async get(...args) {
|
|
89
|
+
this._assertUsable();
|
|
66
90
|
// In multiProcess mode, re-read from disk for fresh data
|
|
67
91
|
if (this.multiProcess) {
|
|
68
92
|
this._refreshFromDisk();
|
|
69
93
|
}
|
|
70
94
|
const value = this._getRecursive(this.obj, args.slice());
|
|
71
|
-
return
|
|
72
|
-
? this.parse(this.stringify(value))
|
|
73
|
-
: value;
|
|
95
|
+
return this._decodeFromMemory(value, args);
|
|
74
96
|
}
|
|
75
97
|
|
|
76
98
|
getSync(...args) {
|
|
@@ -79,21 +101,20 @@ export class JsonDriver extends DeepBaseDriver {
|
|
|
79
101
|
}
|
|
80
102
|
this._connectSync();
|
|
81
103
|
const value = this._getRecursive(this.obj, args.slice());
|
|
82
|
-
return
|
|
83
|
-
? this.parse(this.stringify(value))
|
|
84
|
-
: value;
|
|
104
|
+
return this._decodeFromMemory(value, args);
|
|
85
105
|
}
|
|
86
106
|
|
|
87
107
|
async set(...args) {
|
|
88
108
|
return this._queueOperation(async () => {
|
|
89
109
|
if (args.length < 2) {
|
|
90
|
-
|
|
110
|
+
const value = this._encodeForMemory(args[0], []);
|
|
111
|
+
this.obj = value;
|
|
91
112
|
await this._saveToFile();
|
|
92
113
|
return [];
|
|
93
114
|
}
|
|
94
115
|
|
|
95
116
|
const keys = args.slice(0, -1);
|
|
96
|
-
const value = args[args.length - 1];
|
|
117
|
+
const value = this._encodeForMemory(args[args.length - 1], keys);
|
|
97
118
|
|
|
98
119
|
// Make a copy to avoid modifying the original array
|
|
99
120
|
this._setRecursive(this.obj, keys.slice(), value);
|
|
@@ -143,7 +164,8 @@ export class JsonDriver extends DeepBaseDriver {
|
|
|
143
164
|
return this._queueOperation(async () => {
|
|
144
165
|
const func = args.pop();
|
|
145
166
|
const currentValue = this._getRecursive(this.obj, args.slice());
|
|
146
|
-
const
|
|
167
|
+
const decodedValue = this._decodeFromMemory(currentValue, args);
|
|
168
|
+
const newValue = func(decodedValue);
|
|
147
169
|
await this._setInternal(...args, newValue);
|
|
148
170
|
return args;
|
|
149
171
|
});
|
|
@@ -191,13 +213,14 @@ export class JsonDriver extends DeepBaseDriver {
|
|
|
191
213
|
// Internal set without queuing (for use within queued operations)
|
|
192
214
|
async _setInternal(...args) {
|
|
193
215
|
if (args.length < 2) {
|
|
194
|
-
|
|
216
|
+
const value = this._encodeForMemory(args[0], []);
|
|
217
|
+
this.obj = value;
|
|
195
218
|
await this._saveToFile();
|
|
196
219
|
return [];
|
|
197
220
|
}
|
|
198
221
|
|
|
199
222
|
const keys = args.slice(0, -1);
|
|
200
|
-
const value = args[args.length - 1];
|
|
223
|
+
const value = this._encodeForMemory(args[args.length - 1], keys);
|
|
201
224
|
|
|
202
225
|
this._setRecursive(this.obj, keys.slice(), value);
|
|
203
226
|
await this._saveToFile();
|
|
@@ -208,12 +231,14 @@ export class JsonDriver extends DeepBaseDriver {
|
|
|
208
231
|
_refreshFromDisk() {
|
|
209
232
|
if (fs.existsSync(this.fileName)) {
|
|
210
233
|
const fileContent = fs.readFileSync(this.fileName, "utf8");
|
|
211
|
-
|
|
234
|
+
const parsed = fileContent ? this.parse(fileContent) : {};
|
|
235
|
+
this.obj = this._encodeForMemory(parsed, []);
|
|
212
236
|
}
|
|
213
237
|
}
|
|
214
238
|
|
|
215
239
|
// Queue operations to prevent race conditions
|
|
216
240
|
async _queueOperation(operation) {
|
|
241
|
+
this._assertUsable();
|
|
217
242
|
const previousOperation = this._operationQueue;
|
|
218
243
|
|
|
219
244
|
let resolver;
|
|
@@ -248,6 +273,62 @@ export class JsonDriver extends DeepBaseDriver {
|
|
|
248
273
|
resolver();
|
|
249
274
|
}
|
|
250
275
|
}
|
|
276
|
+
|
|
277
|
+
async dispose({ clearMemory = true, releaseInstance = true } = {}) {
|
|
278
|
+
if (this._disposePromise) {
|
|
279
|
+
return this._disposePromise;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
this._disposing = true;
|
|
283
|
+
this._disposePromise = (async () => {
|
|
284
|
+
try {
|
|
285
|
+
await this._operationQueue;
|
|
286
|
+
if (this._connected) {
|
|
287
|
+
await this._saveToFile();
|
|
288
|
+
}
|
|
289
|
+
this._connected = false;
|
|
290
|
+
|
|
291
|
+
if (clearMemory) {
|
|
292
|
+
this.obj = {};
|
|
293
|
+
}
|
|
294
|
+
if (releaseInstance && JsonDriver._instances[this.fileName] === this) {
|
|
295
|
+
delete JsonDriver._instances[this.fileName];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this._disposed = true;
|
|
299
|
+
} catch (error) {
|
|
300
|
+
this._disposing = false;
|
|
301
|
+
this._disposePromise = null;
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
})();
|
|
305
|
+
|
|
306
|
+
return this._disposePromise;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
_assertUsable() {
|
|
310
|
+
if (this._disposing || this._disposed) {
|
|
311
|
+
throw new Error('JsonDriver has been disposed');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
_clone(value) {
|
|
316
|
+
return typeof value === 'object' && value !== null
|
|
317
|
+
? this.parse(this.stringify(value))
|
|
318
|
+
: value;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
_encodeForMemory(value, path) {
|
|
322
|
+
return this.encodeForMemory(this._clone(value), path.map(String));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
_decodeFromMemory(value, path) {
|
|
326
|
+
return this.decodeFromMemory(this._clone(value), path.map(String));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
_serializeMemory() {
|
|
330
|
+
return this.stringify(this._decodeFromMemory(this.obj, []));
|
|
331
|
+
}
|
|
251
332
|
|
|
252
333
|
_setRecursive(obj, keys, value) {
|
|
253
334
|
if (keys.length === 0) return;
|
|
@@ -278,7 +359,7 @@ export class JsonDriver extends DeepBaseDriver {
|
|
|
278
359
|
}
|
|
279
360
|
|
|
280
361
|
async _saveToFile() {
|
|
281
|
-
const serializedData = this.
|
|
362
|
+
const serializedData = this._serializeMemory();
|
|
282
363
|
if (this.multiProcess) {
|
|
283
364
|
// Sync write ensures data is on disk before lock release
|
|
284
365
|
fs.writeFileSync(this.fileName, serializedData);
|
package/src/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { DeepBaseDriver, DeepBaseDriverOptions } from 'deepbase';
|
|
1
|
+
import { DeepBaseDriver, DeepBaseDriverOptions, DisposeOptions } from 'deepbase';
|
|
2
|
+
|
|
3
|
+
export type MemoryTransform = (value: unknown, path: string[]) => unknown;
|
|
2
4
|
|
|
3
5
|
export interface JsonDriverOptions extends DeepBaseDriverOptions {
|
|
4
6
|
name?: string;
|
|
@@ -7,6 +9,10 @@ export interface JsonDriverOptions extends DeepBaseDriverOptions {
|
|
|
7
9
|
parse?: (str: string) => any;
|
|
8
10
|
/** Enable cross-process file locking for safe multi-process access */
|
|
9
11
|
multiProcess?: boolean;
|
|
12
|
+
/** Transform a defensive copy before it enters the in-memory cache */
|
|
13
|
+
encodeForMemory?: MemoryTransform;
|
|
14
|
+
/** Transform a defensive copy when it leaves the in-memory cache */
|
|
15
|
+
decodeFromMemory?: MemoryTransform;
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
export class JsonDriver extends DeepBaseDriver {
|
|
@@ -17,7 +23,11 @@ export class JsonDriver extends DeepBaseDriver {
|
|
|
17
23
|
fileName: string;
|
|
18
24
|
stringify: (obj: any) => string;
|
|
19
25
|
parse: (str: string) => any;
|
|
20
|
-
|
|
26
|
+
encodeForMemory: MemoryTransform;
|
|
27
|
+
decodeFromMemory: MemoryTransform;
|
|
28
|
+
obj: unknown;
|
|
29
|
+
|
|
30
|
+
dispose(options?: DisposeOptions): Promise<void>;
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
export default JsonDriver;
|
package/test/test.js
CHANGED
|
@@ -315,6 +315,215 @@ describe('JsonDriver', function() {
|
|
|
315
315
|
});
|
|
316
316
|
});
|
|
317
317
|
|
|
318
|
+
describe('Memory transforms', function() {
|
|
319
|
+
function transformSecrets(value, path, transform) {
|
|
320
|
+
if (path.at(-1) === 'privateKey' || path.at(-1) === 'mnemonic') {
|
|
321
|
+
return transform(value);
|
|
322
|
+
}
|
|
323
|
+
if (Array.isArray(value)) {
|
|
324
|
+
return value.map((item, index) =>
|
|
325
|
+
transformSecrets(item, [...path, String(index)], transform)
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
if (value !== null && typeof value === 'object') {
|
|
329
|
+
return Object.fromEntries(
|
|
330
|
+
Object.entries(value).map(([key, item]) => [
|
|
331
|
+
key,
|
|
332
|
+
transformSecrets(item, [...path, key], transform)
|
|
333
|
+
])
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
return value;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function createMemoryTransforms() {
|
|
340
|
+
return {
|
|
341
|
+
encodeForMemory(value, path) {
|
|
342
|
+
return transformSecrets(value, path, secret => ({ sealed: secret }));
|
|
343
|
+
},
|
|
344
|
+
decodeFromMemory(value, path) {
|
|
345
|
+
return transformSecrets(value, path, sealed => {
|
|
346
|
+
if (!sealed || typeof sealed !== 'object' || !('sealed' in sealed)) {
|
|
347
|
+
throw new Error('Authentication failed');
|
|
348
|
+
}
|
|
349
|
+
return sealed.sealed;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
it('encodes only secrets while preserving queryable structure', async function() {
|
|
356
|
+
const driver = new JsonDriver({
|
|
357
|
+
name: 'memory-transform',
|
|
358
|
+
path: testDataPath,
|
|
359
|
+
...createMemoryTransforms()
|
|
360
|
+
});
|
|
361
|
+
const secureDb = new DeepBase(driver);
|
|
362
|
+
await secureDb.connect();
|
|
363
|
+
|
|
364
|
+
const account = {
|
|
365
|
+
address: '0x123',
|
|
366
|
+
privateKey: 'secret',
|
|
367
|
+
wallet: { mnemonic: 'words' }
|
|
368
|
+
};
|
|
369
|
+
await secureDb.set('account', account);
|
|
370
|
+
account.privateKey = 'caller-mutation';
|
|
371
|
+
|
|
372
|
+
assert.strictEqual(driver.obj.account.address, '0x123');
|
|
373
|
+
assert.deepStrictEqual(driver.obj.account.privateKey, { sealed: 'secret' });
|
|
374
|
+
assert.deepStrictEqual(driver.obj.account.wallet.mnemonic, { sealed: 'words' });
|
|
375
|
+
assert.strictEqual(await secureDb.get('account', 'address'), '0x123');
|
|
376
|
+
|
|
377
|
+
const decoded = await secureDb.get('account');
|
|
378
|
+
assert.deepStrictEqual(decoded, {
|
|
379
|
+
address: '0x123',
|
|
380
|
+
privateKey: 'secret',
|
|
381
|
+
wallet: { mnemonic: 'words' }
|
|
382
|
+
});
|
|
383
|
+
decoded.address = 'returned-mutation';
|
|
384
|
+
assert.strictEqual(driver.obj.account.address, '0x123');
|
|
385
|
+
await secureDb.dispose();
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('decodes values, entries and upd callbacks without exposing cache references', async function() {
|
|
389
|
+
const driver = new JsonDriver({
|
|
390
|
+
name: 'memory-collections',
|
|
391
|
+
path: testDataPath,
|
|
392
|
+
...createMemoryTransforms()
|
|
393
|
+
});
|
|
394
|
+
const secureDb = new DeepBase(driver);
|
|
395
|
+
await secureDb.connect();
|
|
396
|
+
await secureDb.set('accounts', 'main', {
|
|
397
|
+
address: '0x123',
|
|
398
|
+
privateKey: 'old-secret'
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
assert.deepStrictEqual(await secureDb.values('accounts'), [{
|
|
402
|
+
address: '0x123',
|
|
403
|
+
privateKey: 'old-secret'
|
|
404
|
+
}]);
|
|
405
|
+
assert.deepStrictEqual(await secureDb.entries('accounts'), [[
|
|
406
|
+
'main',
|
|
407
|
+
{ address: '0x123', privateKey: 'old-secret' }
|
|
408
|
+
]]);
|
|
409
|
+
|
|
410
|
+
const addedPath = await driver.add('accounts', {
|
|
411
|
+
address: '0x456',
|
|
412
|
+
privateKey: 'added-secret'
|
|
413
|
+
});
|
|
414
|
+
assert.deepStrictEqual(
|
|
415
|
+
driver.obj.accounts[addedPath.at(-1)].privateKey,
|
|
416
|
+
{ sealed: 'added-secret' }
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
await secureDb.upd('accounts', 'main', account => {
|
|
420
|
+
assert.strictEqual(account.privateKey, 'old-secret');
|
|
421
|
+
account.privateKey = 'new-secret';
|
|
422
|
+
return account;
|
|
423
|
+
});
|
|
424
|
+
assert.deepStrictEqual(driver.obj.accounts.main.privateKey, { sealed: 'new-secret' });
|
|
425
|
+
await secureDb.dispose();
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('keeps the existing decoded disk format and encodes parsed data on load', async function() {
|
|
429
|
+
const options = {
|
|
430
|
+
name: 'memory-persistence',
|
|
431
|
+
path: testDataPath,
|
|
432
|
+
...createMemoryTransforms()
|
|
433
|
+
};
|
|
434
|
+
const firstDriver = new JsonDriver(options);
|
|
435
|
+
const firstDb = new DeepBase(firstDriver);
|
|
436
|
+
await firstDb.connect();
|
|
437
|
+
await firstDb.set('account', { address: '0x123', privateKey: 'secret' });
|
|
438
|
+
await firstDb.dispose();
|
|
439
|
+
|
|
440
|
+
const filePath = path.join(testDataPath, 'memory-persistence.json');
|
|
441
|
+
assert.deepStrictEqual(JSON.parse(fs.readFileSync(filePath, 'utf8')), {
|
|
442
|
+
account: { address: '0x123', privateKey: 'secret' }
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const secondDriver = new JsonDriver(options);
|
|
446
|
+
const secondDb = new DeepBase(secondDriver);
|
|
447
|
+
await secondDb.connect();
|
|
448
|
+
assert.deepStrictEqual(secondDriver.obj.account.privateKey, { sealed: 'secret' });
|
|
449
|
+
assert.strictEqual(await secondDb.get('account', 'privateKey'), 'secret');
|
|
450
|
+
await secondDb.dispose();
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('does not modify cache when encoding fails', async function() {
|
|
454
|
+
const driver = new JsonDriver({
|
|
455
|
+
name: 'memory-encode-failure',
|
|
456
|
+
path: testDataPath,
|
|
457
|
+
encodeForMemory(value, path) {
|
|
458
|
+
if (path.at(-1) === 'privateKey') {
|
|
459
|
+
throw new Error('Encoding failed');
|
|
460
|
+
}
|
|
461
|
+
return value;
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
const secureDb = new DeepBase(driver);
|
|
465
|
+
await secureDb.connect();
|
|
466
|
+
await secureDb.set('account', 'address', '0x123');
|
|
467
|
+
|
|
468
|
+
await assert.rejects(
|
|
469
|
+
secureDb.set('account', 'privateKey', 'secret'),
|
|
470
|
+
/Encoding failed/
|
|
471
|
+
);
|
|
472
|
+
assert.deepStrictEqual(driver.obj, { account: { address: '0x123' } });
|
|
473
|
+
await secureDb.dispose();
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('throws authentication errors instead of returning cached ciphertext', async function() {
|
|
477
|
+
const driver = new JsonDriver({
|
|
478
|
+
name: 'memory-auth-failure',
|
|
479
|
+
path: testDataPath,
|
|
480
|
+
...createMemoryTransforms()
|
|
481
|
+
});
|
|
482
|
+
const secureDb = new DeepBase(driver);
|
|
483
|
+
await secureDb.connect();
|
|
484
|
+
await secureDb.set('account', 'privateKey', 'secret');
|
|
485
|
+
driver.obj.account.privateKey = { invalid: 'ciphertext' };
|
|
486
|
+
|
|
487
|
+
await assert.rejects(
|
|
488
|
+
secureDb.get('account', 'privateKey'),
|
|
489
|
+
/Authentication failed/
|
|
490
|
+
);
|
|
491
|
+
driver.obj.account.privateKey = { sealed: 'secret' };
|
|
492
|
+
await secureDb.dispose();
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
describe('Dispose', function() {
|
|
497
|
+
it('waits for pending writes, clears memory and releases the singleton', async function() {
|
|
498
|
+
const driver = db.getDriver(0);
|
|
499
|
+
let releaseWrite;
|
|
500
|
+
let writeStarted;
|
|
501
|
+
const writeGate = new Promise(resolve => { releaseWrite = resolve; });
|
|
502
|
+
const started = new Promise(resolve => { writeStarted = resolve; });
|
|
503
|
+
const saveToFile = driver._saveToFile.bind(driver);
|
|
504
|
+
driver._saveToFile = async () => {
|
|
505
|
+
writeStarted();
|
|
506
|
+
await writeGate;
|
|
507
|
+
return saveToFile();
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const pendingWrite = db.set('account', 'address', '0x123');
|
|
511
|
+
await started;
|
|
512
|
+
const disposal = db.dispose({ clearMemory: true, releaseInstance: true });
|
|
513
|
+
|
|
514
|
+
assert.strictEqual(JsonDriver._instances[driver.fileName], driver);
|
|
515
|
+
releaseWrite();
|
|
516
|
+
await Promise.all([pendingWrite, disposal]);
|
|
517
|
+
|
|
518
|
+
assert.deepStrictEqual(driver.obj, {});
|
|
519
|
+
assert.strictEqual(JsonDriver._instances[driver.fileName], undefined);
|
|
520
|
+
assert.notStrictEqual(
|
|
521
|
+
new JsonDriver({ name: driver.name, path: driver.path }),
|
|
522
|
+
driver
|
|
523
|
+
);
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
318
527
|
describe('Queue / Stack (add + pop + shift)', function() {
|
|
319
528
|
it('should work as FIFO queue with add + shift', async function() {
|
|
320
529
|
// Enqueue items
|