deepbase-json 3.0.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/LICENSE +21 -0
- package/README.md +140 -0
- package/package.json +46 -0
- package/src/JsonDriver.js +141 -0
- package/src/index.js +4 -0
- package/test/test.js +248 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 mclasen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# deepbase-json
|
|
2
|
+
|
|
3
|
+
JSON filesystem driver for DeepBase.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install deepbase
|
|
9
|
+
# deepbase-json is included automatically as a dependency
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Description
|
|
13
|
+
|
|
14
|
+
Stores data in JSON files on the filesystem. Perfect for:
|
|
15
|
+
|
|
16
|
+
- ✅ Development and testing
|
|
17
|
+
- ✅ Small to medium datasets
|
|
18
|
+
- ✅ Human-readable data
|
|
19
|
+
- ✅ No external dependencies needed
|
|
20
|
+
- ✅ Version control friendly
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
import DeepBase, { JsonDriver } from 'deepbase';
|
|
26
|
+
|
|
27
|
+
const db = new DeepBase(new JsonDriver({
|
|
28
|
+
path: './data',
|
|
29
|
+
name: 'mydb'
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
await db.connect();
|
|
33
|
+
|
|
34
|
+
await db.set('users', 'alice', { name: 'Alice', age: 30 });
|
|
35
|
+
const alice = await db.get('users', 'alice');
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Options
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
new JsonDriver({
|
|
42
|
+
path: './data', // Directory to store JSON files
|
|
43
|
+
name: 'default', // Filename (without .json)
|
|
44
|
+
nidAlphabet: 'ABC...', // Alphabet for ID generation
|
|
45
|
+
nidLength: 10, // Length of generated IDs
|
|
46
|
+
stringify: JSON.stringify, // Custom JSON serializer
|
|
47
|
+
parse: JSON.parse // Custom JSON parser
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
### Singleton Pattern
|
|
54
|
+
|
|
55
|
+
Multiple instances pointing to the same file will share the same data:
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
const db1 = new DeepBase(new JsonDriver({ name: 'mydb' }));
|
|
59
|
+
const db2 = new DeepBase(new JsonDriver({ name: 'mydb' }));
|
|
60
|
+
// Both use the same underlying data
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Custom Serialization
|
|
64
|
+
|
|
65
|
+
Support for circular references and custom serialization:
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
import CircularJSON from 'circular-json';
|
|
69
|
+
|
|
70
|
+
const db = new DeepBase(new JsonDriver({
|
|
71
|
+
stringify: (obj) => CircularJSON.stringify(obj, null, 4),
|
|
72
|
+
parse: CircularJSON.parse
|
|
73
|
+
}));
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Atomic Writes
|
|
77
|
+
|
|
78
|
+
Uses `steno` for atomic file writes, preventing data corruption.
|
|
79
|
+
|
|
80
|
+
## File Structure
|
|
81
|
+
|
|
82
|
+
Data is stored as JSON files:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
data/
|
|
86
|
+
mydb.json
|
|
87
|
+
users.json
|
|
88
|
+
config.json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Example Data
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"users": {
|
|
96
|
+
"alice": {
|
|
97
|
+
"name": "Alice",
|
|
98
|
+
"age": 30
|
|
99
|
+
},
|
|
100
|
+
"bob": {
|
|
101
|
+
"name": "Bob",
|
|
102
|
+
"age": 25
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"config": {
|
|
106
|
+
"theme": "dark",
|
|
107
|
+
"lang": "en"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Use Cases
|
|
113
|
+
|
|
114
|
+
- **Development**: Quick prototyping without database setup
|
|
115
|
+
- **Testing**: Easy to inspect and modify test data
|
|
116
|
+
- **Small Apps**: Perfect for configuration and small datasets
|
|
117
|
+
- **Backup**: Use as secondary driver for data backup
|
|
118
|
+
- **Version Control**: Human-readable, git-friendly format
|
|
119
|
+
|
|
120
|
+
## Migration
|
|
121
|
+
|
|
122
|
+
Easy to migrate from JSON to other drivers:
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
import DeepBase, { JsonDriver } from 'deepbase';
|
|
126
|
+
import MongoDriver from 'deepbase-mongodb';
|
|
127
|
+
|
|
128
|
+
const db = new DeepBase([
|
|
129
|
+
new JsonDriver({ path: './data' }),
|
|
130
|
+
new MongoDriver({ url: 'mongodb://localhost:27017' })
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
await db.connect();
|
|
134
|
+
await db.migrate(0, 1); // Migrate JSON to MongoDB
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
MIT - Copyright (c) Martin Clasen
|
|
140
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "deepbase-json",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "⚡ DeepBase JSON - filesystem driver",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"module": "src/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./src/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"steno": "^0.4.4"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"deepbase": "^3.0.0"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "mocha test/test.js"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"mocha": "^10.8.2"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/clasen/DeepBase.git"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"deepbase",
|
|
31
|
+
"json",
|
|
32
|
+
"filesystem",
|
|
33
|
+
"driver",
|
|
34
|
+
"database",
|
|
35
|
+
"persist",
|
|
36
|
+
"nested",
|
|
37
|
+
"objects"
|
|
38
|
+
],
|
|
39
|
+
"author": "Martin Clasen",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/clasen/DeepBase/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/clasen/DeepBase#readme"
|
|
45
|
+
}
|
|
46
|
+
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { DeepBaseDriver } from 'deepbase';
|
|
2
|
+
import steno from 'steno';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import * as pathModule from 'path';
|
|
5
|
+
|
|
6
|
+
export class JsonDriver extends DeepBaseDriver {
|
|
7
|
+
static _instances = {};
|
|
8
|
+
|
|
9
|
+
constructor({name, path, stringify, parse, ...opts} = {}) {
|
|
10
|
+
super(opts);
|
|
11
|
+
|
|
12
|
+
this.name = name || "default";
|
|
13
|
+
this.path = path || './db';
|
|
14
|
+
this.stringify = stringify || ((obj) => JSON.stringify(obj, null, 4));
|
|
15
|
+
this.parse = parse || JSON.parse;
|
|
16
|
+
|
|
17
|
+
this.path = pathModule.resolve(this.path);
|
|
18
|
+
this.fileName = pathModule.join(this.path, `${this.name}.json`);
|
|
19
|
+
|
|
20
|
+
// Singleton pattern per file
|
|
21
|
+
if (JsonDriver._instances[this.fileName]) {
|
|
22
|
+
return JsonDriver._instances[this.fileName];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.obj = {};
|
|
26
|
+
JsonDriver._instances[this.fileName] = this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async connect() {
|
|
30
|
+
if (!fs.existsSync(this.path)) {
|
|
31
|
+
fs.mkdirSync(this.path, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (fs.existsSync(this.fileName)) {
|
|
35
|
+
const fileContent = fs.readFileSync(this.fileName, "utf8");
|
|
36
|
+
this.obj = fileContent ? this.parse(fileContent) : {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async disconnect() {
|
|
41
|
+
await this._saveToFile();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async get(...args) {
|
|
45
|
+
const value = this._getRecursive(this.obj, args.slice());
|
|
46
|
+
return typeof value === 'object' && value !== null
|
|
47
|
+
? this.parse(this.stringify(value))
|
|
48
|
+
: value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async set(...args) {
|
|
52
|
+
if (args.length < 2) {
|
|
53
|
+
this.obj = args[0];
|
|
54
|
+
await this._saveToFile();
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const keys = args.slice(0, -1);
|
|
59
|
+
const value = args[args.length - 1];
|
|
60
|
+
|
|
61
|
+
this._setRecursive(this.obj, keys, value);
|
|
62
|
+
await this._saveToFile();
|
|
63
|
+
return keys;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async del(...keys) {
|
|
67
|
+
if (keys.length === 0) {
|
|
68
|
+
this.obj = {};
|
|
69
|
+
return this._saveToFile();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const key = keys.pop();
|
|
73
|
+
const parentObj = this._getRecursive(this.obj, keys.slice());
|
|
74
|
+
|
|
75
|
+
if (parentObj && parentObj.hasOwnProperty(key)) {
|
|
76
|
+
delete parentObj[key];
|
|
77
|
+
return this._saveToFile();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async inc(...args) {
|
|
82
|
+
const i = args.pop();
|
|
83
|
+
return this.upd(...args, n => n + i);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async dec(...args) {
|
|
87
|
+
const i = args.pop();
|
|
88
|
+
return this.upd(...args, n => n - i);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async add(...keys) {
|
|
92
|
+
const value = keys.pop();
|
|
93
|
+
const id = this.nanoid();
|
|
94
|
+
await this.set(...[...keys, id], value);
|
|
95
|
+
return [...keys, id];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async upd(...args) {
|
|
99
|
+
const func = args.pop();
|
|
100
|
+
return this.set(...args, func(await this.get(...args)));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
_setRecursive(obj, keys, value) {
|
|
104
|
+
if (keys.length === 1) {
|
|
105
|
+
obj[keys[0]] = value;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const key = keys.shift();
|
|
110
|
+
if (!obj.hasOwnProperty(key) || typeof obj[key] !== "object") {
|
|
111
|
+
obj[key] = {};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this._setRecursive(obj[key], keys, value);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_getRecursive(obj, keys) {
|
|
118
|
+
if (keys.length === 0) return obj;
|
|
119
|
+
if (keys.length === 1) {
|
|
120
|
+
return obj === null || obj[keys[0]] === undefined ? null : obj[keys[0]];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const key = keys.shift();
|
|
124
|
+
if (!obj.hasOwnProperty(key)) return null;
|
|
125
|
+
|
|
126
|
+
return this._getRecursive(obj[key], keys);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async _saveToFile() {
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
const serializedData = this.stringify(this.obj);
|
|
132
|
+
steno.writeFile(this.fileName, serializedData, err => {
|
|
133
|
+
if (err) reject(err);
|
|
134
|
+
else resolve();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export default JsonDriver;
|
|
141
|
+
|
package/src/index.js
ADDED
package/test/test.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { DeepBase } from '../../core/src/index.js';
|
|
6
|
+
import { JsonDriver } from '../src/JsonDriver.js';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const testDataPath = path.join(__dirname, 'test-data');
|
|
10
|
+
|
|
11
|
+
describe('JsonDriver', function() {
|
|
12
|
+
let db;
|
|
13
|
+
let testCounter = 0;
|
|
14
|
+
|
|
15
|
+
before(function() {
|
|
16
|
+
// Clean up test data before all tests
|
|
17
|
+
if (fs.existsSync(testDataPath)) {
|
|
18
|
+
fs.rmSync(testDataPath, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
beforeEach(async function() {
|
|
23
|
+
// Use unique name for each test to avoid singleton conflicts
|
|
24
|
+
testCounter++;
|
|
25
|
+
db = new DeepBase(new JsonDriver({
|
|
26
|
+
name: `test-${testCounter}`,
|
|
27
|
+
path: testDataPath
|
|
28
|
+
}));
|
|
29
|
+
await db.connect();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(async function() {
|
|
33
|
+
await db.disconnect();
|
|
34
|
+
if (fs.existsSync(testDataPath)) {
|
|
35
|
+
fs.rmSync(testDataPath, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('Basic Operations', function() {
|
|
40
|
+
it('should set and get a simple value', async function() {
|
|
41
|
+
await db.set('key', 'value');
|
|
42
|
+
const result = await db.get('key');
|
|
43
|
+
assert.strictEqual(result, 'value');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should set and get nested values', async function() {
|
|
47
|
+
await db.set('user', 'name', 'Alice');
|
|
48
|
+
await db.set('user', 'age', 30);
|
|
49
|
+
|
|
50
|
+
const name = await db.get('user', 'name');
|
|
51
|
+
const age = await db.get('user', 'age');
|
|
52
|
+
|
|
53
|
+
assert.strictEqual(name, 'Alice');
|
|
54
|
+
assert.strictEqual(age, 30);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should get entire nested object', async function() {
|
|
58
|
+
await db.set('user', 'name', 'Bob');
|
|
59
|
+
await db.set('user', 'age', 25);
|
|
60
|
+
|
|
61
|
+
const user = await db.get('user');
|
|
62
|
+
assert.deepStrictEqual(user, { name: 'Bob', age: 25 });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should return null for non-existent keys', async function() {
|
|
66
|
+
const result = await db.get('nonexistent');
|
|
67
|
+
assert.strictEqual(result, null);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('Delete Operations', function() {
|
|
72
|
+
it('should delete a key', async function() {
|
|
73
|
+
await db.set('temp', 'value');
|
|
74
|
+
await db.del('temp');
|
|
75
|
+
|
|
76
|
+
const result = await db.get('temp');
|
|
77
|
+
assert.strictEqual(result, null);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should delete nested key', async function() {
|
|
81
|
+
await db.set('user', 'name', 'Alice');
|
|
82
|
+
await db.set('user', 'age', 30);
|
|
83
|
+
await db.del('user', 'age');
|
|
84
|
+
|
|
85
|
+
const user = await db.get('user');
|
|
86
|
+
assert.deepStrictEqual(user, { name: 'Alice' });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should clear all data', async function() {
|
|
90
|
+
await db.set('key1', 'value1');
|
|
91
|
+
await db.set('key2', 'value2');
|
|
92
|
+
await db.del();
|
|
93
|
+
|
|
94
|
+
const all = await db.get();
|
|
95
|
+
assert.deepStrictEqual(all, {});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('Add Operation', function() {
|
|
100
|
+
it('should add item with auto-generated ID', async function() {
|
|
101
|
+
const path = await db.add('users', { name: 'Charlie' });
|
|
102
|
+
|
|
103
|
+
assert.strictEqual(path.length, 2);
|
|
104
|
+
assert.strictEqual(path[0], 'users');
|
|
105
|
+
assert.strictEqual(typeof path[1], 'string');
|
|
106
|
+
assert.strictEqual(path[1].length, 10);
|
|
107
|
+
|
|
108
|
+
const user = await db.get(...path);
|
|
109
|
+
assert.deepStrictEqual(user, { name: 'Charlie' });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should add multiple items with unique IDs', async function() {
|
|
113
|
+
const path1 = await db.add('items', { value: 1 });
|
|
114
|
+
const path2 = await db.add('items', { value: 2 });
|
|
115
|
+
|
|
116
|
+
assert.notStrictEqual(path1[1], path2[1]);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('Increment/Decrement', function() {
|
|
121
|
+
it('should increment a value', async function() {
|
|
122
|
+
await db.set('counter', 10);
|
|
123
|
+
await db.inc('counter', 5);
|
|
124
|
+
|
|
125
|
+
const result = await db.get('counter');
|
|
126
|
+
assert.strictEqual(result, 15);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should decrement a value', async function() {
|
|
130
|
+
await db.set('counter', 20);
|
|
131
|
+
await db.dec('counter', 8);
|
|
132
|
+
|
|
133
|
+
const result = await db.get('counter');
|
|
134
|
+
assert.strictEqual(result, 12);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should increment nested value', async function() {
|
|
138
|
+
await db.set('user', 'balance', 100);
|
|
139
|
+
await db.inc('user', 'balance', 50);
|
|
140
|
+
|
|
141
|
+
const balance = await db.get('user', 'balance');
|
|
142
|
+
assert.strictEqual(balance, 150);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('Update Operation', function() {
|
|
147
|
+
it('should update value with function', async function() {
|
|
148
|
+
await db.set('name', 'alice');
|
|
149
|
+
await db.upd('name', name => name.toUpperCase());
|
|
150
|
+
|
|
151
|
+
const result = await db.get('name');
|
|
152
|
+
assert.strictEqual(result, 'ALICE');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should update nested value with function', async function() {
|
|
156
|
+
await db.set('user', 'age', 25);
|
|
157
|
+
await db.upd('user', 'age', age => age + 1);
|
|
158
|
+
|
|
159
|
+
const age = await db.get('user', 'age');
|
|
160
|
+
assert.strictEqual(age, 26);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('Keys, Values, Entries', function() {
|
|
165
|
+
beforeEach(async function() {
|
|
166
|
+
await db.set('users', 'alice', { age: 30 });
|
|
167
|
+
await db.set('users', 'bob', { age: 25 });
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should get keys', async function() {
|
|
171
|
+
const keys = await db.keys('users');
|
|
172
|
+
assert.deepStrictEqual(keys.sort(), ['alice', 'bob']);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should get values', async function() {
|
|
176
|
+
const values = await db.values('users');
|
|
177
|
+
assert.strictEqual(values.length, 2);
|
|
178
|
+
assert.ok(values.some(v => v.age === 30));
|
|
179
|
+
assert.ok(values.some(v => v.age === 25));
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should get entries', async function() {
|
|
183
|
+
const entries = await db.entries('users');
|
|
184
|
+
assert.strictEqual(entries.length, 2);
|
|
185
|
+
assert.ok(entries.some(([k, v]) => k === 'alice' && v.age === 30));
|
|
186
|
+
assert.ok(entries.some(([k, v]) => k === 'bob' && v.age === 25));
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('Persistence', function() {
|
|
191
|
+
it('should persist data to file', async function() {
|
|
192
|
+
// Use a separate db for this test
|
|
193
|
+
const persistDb = new DeepBase(new JsonDriver({
|
|
194
|
+
name: 'persist-test',
|
|
195
|
+
path: testDataPath
|
|
196
|
+
}));
|
|
197
|
+
await persistDb.connect();
|
|
198
|
+
await persistDb.set('persistent', 'data');
|
|
199
|
+
await persistDb.disconnect();
|
|
200
|
+
|
|
201
|
+
const filePath = path.join(testDataPath, 'persist-test.json');
|
|
202
|
+
assert.ok(fs.existsSync(filePath));
|
|
203
|
+
|
|
204
|
+
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
205
|
+
assert.deepStrictEqual(content, { persistent: 'data' });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should load existing data on connect', async function() {
|
|
209
|
+
// Use a separate db for this test
|
|
210
|
+
const db1 = new DeepBase(new JsonDriver({
|
|
211
|
+
name: 'reload-test',
|
|
212
|
+
path: testDataPath
|
|
213
|
+
}));
|
|
214
|
+
await db1.connect();
|
|
215
|
+
await db1.set('existing', 'value');
|
|
216
|
+
await db1.disconnect();
|
|
217
|
+
|
|
218
|
+
// Create new instance pointing to same file
|
|
219
|
+
const db2 = new DeepBase(new JsonDriver({
|
|
220
|
+
name: 'reload-test',
|
|
221
|
+
path: testDataPath
|
|
222
|
+
}));
|
|
223
|
+
await db2.connect();
|
|
224
|
+
|
|
225
|
+
const result = await db2.get('existing');
|
|
226
|
+
assert.strictEqual(result, 'value');
|
|
227
|
+
|
|
228
|
+
await db2.disconnect();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('Singleton Pattern', function() {
|
|
233
|
+
it('should return same instance for same file', function() {
|
|
234
|
+
const driver1 = new JsonDriver({ name: 'singleton', path: testDataPath });
|
|
235
|
+
const driver2 = new JsonDriver({ name: 'singleton', path: testDataPath });
|
|
236
|
+
|
|
237
|
+
assert.strictEqual(driver1, driver2);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should return different instances for different files', function() {
|
|
241
|
+
const driver1 = new JsonDriver({ name: 'file1', path: testDataPath });
|
|
242
|
+
const driver2 = new JsonDriver({ name: 'file2', path: testDataPath });
|
|
243
|
+
|
|
244
|
+
assert.notStrictEqual(driver1, driver2);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|