deepbase-json 3.10.0 → 3.11.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.md +51 -40
- package/package.json +7 -7
- package/src/JsonDriver.js +7 -3
- package/test/test.js +22 -0
package/README.md
CHANGED
|
@@ -244,6 +244,12 @@ new DeepBase(drivers, options)
|
|
|
244
244
|
- `await db.last(...path)` - Get last key at path (same order as `keys()`)
|
|
245
245
|
- `await db.values(...path)` - Get values at path
|
|
246
246
|
- `await db.entries(...path)` - Get entries at path
|
|
247
|
+
- `db.query(...path)` - Chain a query over the object at path (`where`, `orderBy`, `skip`, `take`, `select`) and run it with `toArray()`, `first()`, `count()` or `any()`
|
|
248
|
+
|
|
249
|
+
`query()` reads the object at the path with `get()` and evaluates the chain in
|
|
250
|
+
memory, so it walks the object already read: v1 pushes no filters to the driver
|
|
251
|
+
and uses no indexes. Terminals resolve to `{ id, value }` records, where `id` is
|
|
252
|
+
the property name and `value` the stored value.
|
|
247
253
|
|
|
248
254
|
### Migration Methods
|
|
249
255
|
|
|
@@ -484,58 +490,63 @@ await db.set("a", "b", "circular", "self", await db.get("a", "b"));
|
|
|
484
490
|
|
|
485
491
|
## 🔒 Secure Storage with Encryption
|
|
486
492
|
|
|
487
|
-
|
|
493
|
+
Use the built-in plugin to encrypt every value with AES-256-GCM before it
|
|
494
|
+
reaches the driver. With `JsonDriver`, values stay encrypted both on disk and
|
|
495
|
+
in its internal memory cache; no custom serialization or memory hooks are needed.
|
|
488
496
|
|
|
489
497
|
```javascript
|
|
490
|
-
import CryptoJS from 'crypto-js';
|
|
491
498
|
import DeepBase from 'deepbase';
|
|
499
|
+
import { encryptedValues } from 'deepbase/plugins/encryption';
|
|
492
500
|
import { JsonDriver } from 'deepbase-json';
|
|
493
501
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
delete opts.encryptionKey;
|
|
498
|
-
|
|
499
|
-
// Create JSON driver with encryption
|
|
500
|
-
const driver = new JsonDriver({
|
|
501
|
-
...opts,
|
|
502
|
-
stringify: (obj) => {
|
|
503
|
-
const iv = CryptoJS.lib.WordArray.random(128 / 8);
|
|
504
|
-
const encrypted = CryptoJS.AES.encrypt(
|
|
505
|
-
JSON.stringify(obj),
|
|
506
|
-
encryptionKey,
|
|
507
|
-
{ iv }
|
|
508
|
-
);
|
|
509
|
-
return iv.toString(CryptoJS.enc.Hex) + ':' + encrypted.toString();
|
|
510
|
-
},
|
|
511
|
-
parse: (encryptedData) => {
|
|
512
|
-
const [ivHex, encrypted] = encryptedData.split(':');
|
|
513
|
-
const iv = CryptoJS.enc.Hex.parse(ivHex);
|
|
514
|
-
const bytes = CryptoJS.AES.decrypt(encrypted, encryptionKey, { iv });
|
|
515
|
-
return JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
|
|
516
|
-
}
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
super(driver);
|
|
520
|
-
}
|
|
502
|
+
const encodedKey = process.env.DEEPBASE_ENCRYPTION_KEY;
|
|
503
|
+
if (!encodedKey) {
|
|
504
|
+
throw new Error('DEEPBASE_ENCRYPTION_KEY is required');
|
|
521
505
|
}
|
|
522
506
|
|
|
523
|
-
|
|
524
|
-
const
|
|
507
|
+
const encryptionKey = Buffer.from(encodedKey, 'base64');
|
|
508
|
+
const encryption = encryptedValues({
|
|
509
|
+
activeKeyId: 'primary',
|
|
510
|
+
keys: { primary: encryptionKey }
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
const driver = new JsonDriver({
|
|
525
514
|
path: '/var/lib/myapp/data',
|
|
526
|
-
name: 'secure_db'
|
|
527
|
-
encryptionKey: 'your-secret-key-here'
|
|
515
|
+
name: 'secure_db'
|
|
528
516
|
});
|
|
517
|
+
const secureDB = new DeepBase(driver).use(encryption);
|
|
518
|
+
|
|
519
|
+
try {
|
|
520
|
+
await secureDB.set('config', {
|
|
521
|
+
service: 'my-app',
|
|
522
|
+
accessToken: 'example-token',
|
|
523
|
+
retries: 0
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
await secureDB.inc('config', 'retries', 1);
|
|
527
|
+
const config = await secureDB.get('config'); // Decrypted for the application
|
|
528
|
+
} finally {
|
|
529
|
+
await secureDB.dispose({ clearMemory: true, releaseInstance: true });
|
|
530
|
+
encryptionKey.fill(0);
|
|
531
|
+
}
|
|
532
|
+
```
|
|
529
533
|
|
|
530
|
-
|
|
534
|
+
Provide a base64-encoded, randomly generated 32-byte key through your
|
|
535
|
+
application's secret manager or environment. The application reads it explicitly;
|
|
536
|
+
the plugin requires a 32-byte `Buffer` or `Uint8Array` and never supplies a
|
|
537
|
+
default key. Keep the key available under the same `keyId` to reopen the database.
|
|
531
538
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
539
|
+
Object keys, array lengths, and empty containers remain visible. Plaintext
|
|
540
|
+
exists while your application supplies or reads values, including inside
|
|
541
|
+
`upd()` callbacks; the plugin does not cache decrypted values. Disposal clears
|
|
542
|
+
the JSON driver's cache and the plugin's internal key copies.
|
|
536
543
|
|
|
537
|
-
|
|
538
|
-
|
|
544
|
+
Authentication failures, malformed envelopes, and unknown key IDs throw.
|
|
545
|
+
Existing plaintext data is encrypted when rewritten through the plugin;
|
|
546
|
+
registering it does not migrate existing data automatically.
|
|
547
|
+
|
|
548
|
+
See the [encryption plugin documentation](../core/docs/plugins/encryption.md)
|
|
549
|
+
for additional keys and key rotation.
|
|
539
550
|
|
|
540
551
|
## 🛠️ Creating Custom Drivers
|
|
541
552
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepbase-json",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.11.0",
|
|
4
4
|
"description": "⚡ DeepBase JSON - filesystem driver",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.cjs",
|
|
@@ -18,10 +18,7 @@
|
|
|
18
18
|
"steno": "^0.4.4"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
|
-
"deepbase": "^3.
|
|
22
|
-
},
|
|
23
|
-
"scripts": {
|
|
24
|
-
"test": "mocha test/test.js"
|
|
21
|
+
"deepbase": "^3.11.0"
|
|
25
22
|
},
|
|
26
23
|
"devDependencies": {
|
|
27
24
|
"mocha": "^10.8.2"
|
|
@@ -45,5 +42,8 @@
|
|
|
45
42
|
"bugs": {
|
|
46
43
|
"url": "https://github.com/clasen/DeepBase/issues"
|
|
47
44
|
},
|
|
48
|
-
"homepage": "https://github.com/clasen/DeepBase/tree/main/packages/driver-json"
|
|
49
|
-
|
|
45
|
+
"homepage": "https://github.com/clasen/DeepBase/tree/main/packages/driver-json",
|
|
46
|
+
"scripts": {
|
|
47
|
+
"test": "mocha test/test.js"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/JsonDriver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DeepBaseDriver } from 'deepbase';
|
|
1
|
+
import { DeepBaseDriver, evaluateQuery, removeKey } from 'deepbase';
|
|
2
2
|
import steno from 'steno';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import * as pathModule from 'path';
|
|
@@ -107,6 +107,11 @@ export class JsonDriver extends DeepBaseDriver {
|
|
|
107
107
|
const value = this._getRecursive(this.obj, args.slice());
|
|
108
108
|
return this._decodeFromMemory(value, args);
|
|
109
109
|
}
|
|
110
|
+
|
|
111
|
+
async query(path, steps) {
|
|
112
|
+
this._assertUsable();
|
|
113
|
+
return evaluateQuery(await this.get(...path), steps, { path });
|
|
114
|
+
}
|
|
110
115
|
|
|
111
116
|
async set(...args) {
|
|
112
117
|
return this._queueOperation(async () => {
|
|
@@ -137,8 +142,7 @@ export class JsonDriver extends DeepBaseDriver {
|
|
|
137
142
|
const key = keys.pop();
|
|
138
143
|
const parentObj = this._getRecursive(this.obj, keys.slice());
|
|
139
144
|
|
|
140
|
-
if (parentObj
|
|
141
|
-
delete parentObj[key];
|
|
145
|
+
if (removeKey(parentObj, key)) {
|
|
142
146
|
return this._saveToFile();
|
|
143
147
|
}
|
|
144
148
|
});
|
package/test/test.js
CHANGED
|
@@ -4,6 +4,8 @@ import path from 'path';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { fork } from 'child_process';
|
|
6
6
|
import { DeepBase } from '../../core/src/index.js';
|
|
7
|
+
import { arrayScenarios } from '../../core/test/array-scenario.js';
|
|
8
|
+
import { seedQueryFixture, queryScenarios } from '../../core/test/query-scenario.js';
|
|
7
9
|
import { JsonDriver } from '../src/JsonDriver.js';
|
|
8
10
|
import { SqliteDriver } from '../../driver-sqlite/src/SqliteDriver.js';
|
|
9
11
|
|
|
@@ -905,6 +907,26 @@ describe('JsonDriver', function() {
|
|
|
905
907
|
JsonDriver._instances = {};
|
|
906
908
|
});
|
|
907
909
|
});
|
|
910
|
+
|
|
911
|
+
describe('query()', function() {
|
|
912
|
+
beforeEach(async function() {
|
|
913
|
+
await seedQueryFixture(db);
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
for (const scenario of queryScenarios) {
|
|
917
|
+
it(scenario.title, async function() {
|
|
918
|
+
await scenario.run(db);
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
describe('array operations', function() {
|
|
924
|
+
for (const scenario of arrayScenarios) {
|
|
925
|
+
it(scenario.title, async function() {
|
|
926
|
+
await scenario.run(db);
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
});
|
|
908
930
|
});
|
|
909
931
|
|
|
910
932
|
describe('Multi-Driver: JsonDriver + SqliteDriver (add + pop + shift)', function() {
|