deepbase-sqlite 3.4.12 → 3.6.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 +67 -1
- package/package.json +2 -2
- package/src/SqliteDriver.js +166 -150
- package/src/index.d.ts +3 -0
- package/src/index.js +1 -2
- package/test/test.js +538 -746
package/test/test.js
CHANGED
|
@@ -8,840 +8,632 @@ import { SqliteDriver } from '../src/SqliteDriver.js';
|
|
|
8
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
const testDataPath = path.join(__dirname, 'test-data');
|
|
10
10
|
|
|
11
|
-
|
|
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
|
-
});
|
|
11
|
+
const PRAGMA_MODES = ['none', 'safe', 'balanced', 'fast'];
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
name: `test-${testCounter}`,
|
|
27
|
-
path: testDataPath
|
|
28
|
-
}));
|
|
29
|
-
await db.connect();
|
|
30
|
-
});
|
|
13
|
+
for (const pragma of PRAGMA_MODES) {
|
|
14
|
+
describe(`SqliteDriver [pragma=${pragma}]`, function () {
|
|
15
|
+
let db;
|
|
16
|
+
let testCounter = 0;
|
|
31
17
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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');
|
|
18
|
+
before(function () {
|
|
19
|
+
if (fs.existsSync(testDataPath)) {
|
|
20
|
+
fs.rmSync(testDataPath, { recursive: true, force: true });
|
|
21
|
+
}
|
|
44
22
|
});
|
|
45
23
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
assert.strictEqual(age, 30);
|
|
24
|
+
beforeEach(async function () {
|
|
25
|
+
testCounter++;
|
|
26
|
+
db = new DeepBase(new SqliteDriver({
|
|
27
|
+
name: `test-${pragma}-${testCounter}`,
|
|
28
|
+
path: testDataPath,
|
|
29
|
+
pragma,
|
|
30
|
+
}));
|
|
31
|
+
await db.connect();
|
|
55
32
|
});
|
|
56
33
|
|
|
57
|
-
|
|
58
|
-
await db.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
assert.deepStrictEqual(user, { name: 'Bob', age: 25 });
|
|
34
|
+
afterEach(async function () {
|
|
35
|
+
await db.disconnect();
|
|
36
|
+
if (fs.existsSync(testDataPath)) {
|
|
37
|
+
fs.rmSync(testDataPath, { recursive: true, force: true });
|
|
38
|
+
}
|
|
63
39
|
});
|
|
64
40
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
41
|
+
describe('Basic Operations', function () {
|
|
42
|
+
it('should set and get a simple value', async function () {
|
|
43
|
+
await db.set('key', 'value');
|
|
44
|
+
assert.strictEqual(await db.get('key'), 'value');
|
|
45
|
+
});
|
|
69
46
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
47
|
+
it('should set and get nested values', async function () {
|
|
48
|
+
await db.set('user', 'name', 'Alice');
|
|
49
|
+
await db.set('user', 'age', 30);
|
|
50
|
+
assert.strictEqual(await db.get('user', 'name'), 'Alice');
|
|
51
|
+
assert.strictEqual(await db.get('user', 'age'), 30);
|
|
52
|
+
});
|
|
75
53
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
54
|
+
it('should get entire nested object', async function () {
|
|
55
|
+
await db.set('user', 'name', 'Bob');
|
|
56
|
+
await db.set('user', 'age', 25);
|
|
57
|
+
assert.deepStrictEqual(await db.get('user'), { name: 'Bob', age: 25 });
|
|
58
|
+
});
|
|
81
59
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
assert.deepStrictEqual(result, [1, 2, 3]);
|
|
86
|
-
});
|
|
60
|
+
it('should return null for non-existent keys', async function () {
|
|
61
|
+
assert.strictEqual(await db.get('nonexistent'), null);
|
|
62
|
+
});
|
|
87
63
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
meta: { created: '2024-01-01', active: true }
|
|
93
|
-
};
|
|
94
|
-
await db.set('complex', complexObj);
|
|
95
|
-
const result = await db.get('complex');
|
|
96
|
-
assert.deepStrictEqual(result, complexObj);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
64
|
+
it('should handle numbers', async function () {
|
|
65
|
+
await db.set('number', 42);
|
|
66
|
+
assert.strictEqual(await db.get('number'), 42);
|
|
67
|
+
});
|
|
99
68
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
assert.strictEqual(setResult.length, 2);
|
|
105
|
-
assert.strictEqual(setResult[0], 'value');
|
|
106
|
-
assert.strictEqual(setResult[1], 'martin.clasen@gmail.com');
|
|
107
|
-
|
|
108
|
-
const getValue = await db.get('value');
|
|
109
|
-
assert.deepStrictEqual(getValue, { 'martin.clasen@gmail.com': 1 });
|
|
110
|
-
});
|
|
69
|
+
it('should handle booleans', async function () {
|
|
70
|
+
await db.set('flag', true);
|
|
71
|
+
assert.strictEqual(await db.get('flag'), true);
|
|
72
|
+
});
|
|
111
73
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
74
|
+
it('should handle arrays', async function () {
|
|
75
|
+
await db.set('list', [1, 2, 3]);
|
|
76
|
+
assert.deepStrictEqual(await db.get('list'), [1, 2, 3]);
|
|
77
|
+
});
|
|
117
78
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
79
|
+
it('should handle complex objects', async function () {
|
|
80
|
+
const complexObj = {
|
|
81
|
+
name: 'Test',
|
|
82
|
+
items: [1, 2, 3],
|
|
83
|
+
meta: { created: '2024-01-01', active: true },
|
|
84
|
+
};
|
|
85
|
+
await db.set('complex', complexObj);
|
|
86
|
+
assert.deepStrictEqual(await db.get('complex'), complexObj);
|
|
87
|
+
});
|
|
125
88
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
89
|
+
it('getSync returns same value as get (sync API)', async function () {
|
|
90
|
+
await db.set('syncKey', 'syncValue');
|
|
91
|
+
await db.set('syncNested', 'a', 1);
|
|
92
|
+
const driver = db.getDriver(0);
|
|
93
|
+
assert.strictEqual(driver.getSync('syncKey'), 'syncValue');
|
|
94
|
+
assert.strictEqual(driver.getSync('syncNested', 'a'), 1);
|
|
95
|
+
assert.strictEqual(driver.getSync('nonexistent'), null);
|
|
96
|
+
});
|
|
131
97
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
await db.set('users', 'john.doe@company.com', 'email', 'john@work.com');
|
|
138
|
-
|
|
139
|
-
const users = await db.get('users');
|
|
140
|
-
assert.strictEqual(users.alice.email, 'alice@example.com');
|
|
141
|
-
assert.strictEqual(users['john.doe@company.com'].email, 'john@work.com');
|
|
142
|
-
assert.strictEqual(Object.keys(users).length, 2);
|
|
98
|
+
it('getSync lazy-connects when not connected', function () {
|
|
99
|
+
const driver = new SqliteDriver({ name: 'getSync-lazy', path: testDataPath });
|
|
100
|
+
assert.strictEqual(driver.getSync('key'), null);
|
|
101
|
+
assert.ok(driver._connected);
|
|
102
|
+
});
|
|
143
103
|
});
|
|
144
104
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
assert.strictEqual(email1, null);
|
|
155
|
-
assert.deepStrictEqual(email2, { verified: false });
|
|
156
|
-
});
|
|
157
|
-
});
|
|
105
|
+
describe('Keys with Dots', function () {
|
|
106
|
+
it('should handle keys containing dots', async function () {
|
|
107
|
+
const setResult = await db.set('value', 'martin.clasen@gmail.com', 1);
|
|
108
|
+
assert.strictEqual(setResult.length, 2);
|
|
109
|
+
assert.strictEqual(setResult[0], 'value');
|
|
110
|
+
assert.strictEqual(setResult[1], 'martin.clasen@gmail.com');
|
|
111
|
+
assert.deepStrictEqual(await db.get('value'), { 'martin.clasen@gmail.com': 1 });
|
|
112
|
+
});
|
|
158
113
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const result = await db.get('temp');
|
|
165
|
-
assert.strictEqual(result, null);
|
|
166
|
-
});
|
|
114
|
+
it('should handle keys with multiple dots', async function () {
|
|
115
|
+
await db.set('config', 'api.prod.endpoint', 'https://api.example.com');
|
|
116
|
+
assert.strictEqual(await db.get('config', 'api.prod.endpoint'), 'https://api.example.com');
|
|
117
|
+
});
|
|
167
118
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const user = await db.get('user');
|
|
174
|
-
assert.deepStrictEqual(user, { name: 'Alice' });
|
|
175
|
-
});
|
|
119
|
+
it('should handle nested paths with dots in keys', async function () {
|
|
120
|
+
await db.set('users', 'john.doe@example.com', 'name', 'John Doe');
|
|
121
|
+
await db.set('users', 'john.doe@example.com', 'age', 30);
|
|
122
|
+
assert.deepStrictEqual(await db.get('users', 'john.doe@example.com'), { name: 'John Doe', age: 30 });
|
|
123
|
+
});
|
|
176
124
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
await db.del('parent');
|
|
183
|
-
|
|
184
|
-
const result = await db.get('parent');
|
|
185
|
-
assert.strictEqual(result, null);
|
|
186
|
-
|
|
187
|
-
const child1 = await db.get('parent', 'child1');
|
|
188
|
-
assert.strictEqual(child1, null);
|
|
189
|
-
|
|
190
|
-
const nested = await db.get('parent', 'nested', 'deep');
|
|
191
|
-
assert.strictEqual(nested, null);
|
|
192
|
-
});
|
|
125
|
+
it('should handle dots in first level keys', async function () {
|
|
126
|
+
await db.set('config.prod', 'value', 100);
|
|
127
|
+
assert.deepStrictEqual(await db.get('config.prod'), { value: 100 });
|
|
128
|
+
});
|
|
193
129
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
});
|
|
130
|
+
it('should distinguish between dots in keys and path separators', async function () {
|
|
131
|
+
await db.set('users', 'alice', 'email', 'alice@example.com');
|
|
132
|
+
await db.set('users', 'john.doe@company.com', 'email', 'john@work.com');
|
|
133
|
+
const users = await db.get('users');
|
|
134
|
+
assert.strictEqual(users.alice.email, 'alice@example.com');
|
|
135
|
+
assert.strictEqual(users['john.doe@company.com'].email, 'john@work.com');
|
|
136
|
+
assert.strictEqual(Object.keys(users).length, 2);
|
|
137
|
+
});
|
|
203
138
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
assert.strictEqual(path[1].length, 10);
|
|
212
|
-
|
|
213
|
-
const user = await db.get(...path);
|
|
214
|
-
assert.deepStrictEqual(user, { name: 'Charlie' });
|
|
139
|
+
it('should delete keys containing dots', async function () {
|
|
140
|
+
await db.set('emails', 'user.name@domain.com', 'verified', true);
|
|
141
|
+
await db.set('emails', 'other@email.com', 'verified', false);
|
|
142
|
+
await db.del('emails', 'user.name@domain.com');
|
|
143
|
+
assert.strictEqual(await db.get('emails', 'user.name@domain.com'), null);
|
|
144
|
+
assert.deepStrictEqual(await db.get('emails', 'other@email.com'), { verified: false });
|
|
145
|
+
});
|
|
215
146
|
});
|
|
216
147
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const item1 = await db.get(...path1);
|
|
224
|
-
const item2 = await db.get(...path2);
|
|
225
|
-
|
|
226
|
-
assert.deepStrictEqual(item1, { value: 1 });
|
|
227
|
-
assert.deepStrictEqual(item2, { value: 2 });
|
|
228
|
-
});
|
|
148
|
+
describe('Delete Operations', function () {
|
|
149
|
+
it('should delete a key', async function () {
|
|
150
|
+
await db.set('temp', 'value');
|
|
151
|
+
await db.del('temp');
|
|
152
|
+
assert.strictEqual(await db.get('temp'), null);
|
|
153
|
+
});
|
|
229
154
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
assert.strictEqual(path[2], 'items');
|
|
237
|
-
|
|
238
|
-
const item = await db.get(...path);
|
|
239
|
-
assert.deepStrictEqual(item, { name: 'Laptop' });
|
|
240
|
-
});
|
|
241
|
-
});
|
|
155
|
+
it('should delete nested key', async function () {
|
|
156
|
+
await db.set('user', 'name', 'Alice');
|
|
157
|
+
await db.set('user', 'age', 30);
|
|
158
|
+
await db.del('user', 'age');
|
|
159
|
+
assert.deepStrictEqual(await db.get('user'), { name: 'Alice' });
|
|
160
|
+
});
|
|
242
161
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
162
|
+
it('should delete parent and all children', async function () {
|
|
163
|
+
await db.set('parent', 'child1', 'value1');
|
|
164
|
+
await db.set('parent', 'child2', 'value2');
|
|
165
|
+
await db.set('parent', 'nested', 'deep', 'value3');
|
|
166
|
+
await db.del('parent');
|
|
167
|
+
assert.strictEqual(await db.get('parent'), null);
|
|
168
|
+
assert.strictEqual(await db.get('parent', 'child1'), null);
|
|
169
|
+
assert.strictEqual(await db.get('parent', 'nested', 'deep'), null);
|
|
170
|
+
});
|
|
251
171
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
172
|
+
it('should clear all data', async function () {
|
|
173
|
+
await db.set('key1', 'value1');
|
|
174
|
+
await db.set('key2', 'value2');
|
|
175
|
+
await db.del();
|
|
176
|
+
assert.deepStrictEqual(await db.get(), {});
|
|
177
|
+
});
|
|
258
178
|
});
|
|
259
179
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
180
|
+
describe('Add Operation', function () {
|
|
181
|
+
it('should add item with auto-generated ID', async function () {
|
|
182
|
+
const p = await db.add('users', { name: 'Charlie' });
|
|
183
|
+
assert.strictEqual(p.length, 2);
|
|
184
|
+
assert.strictEqual(p[0], 'users');
|
|
185
|
+
assert.strictEqual(typeof p[1], 'string');
|
|
186
|
+
assert.strictEqual(p[1].length, 10);
|
|
187
|
+
assert.deepStrictEqual(await db.get(...p), { name: 'Charlie' });
|
|
188
|
+
});
|
|
267
189
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
});
|
|
190
|
+
it('should add multiple items with unique IDs', async function () {
|
|
191
|
+
const p1 = await db.add('items', { value: 1 });
|
|
192
|
+
const p2 = await db.add('items', { value: 2 });
|
|
193
|
+
assert.notStrictEqual(p1[1], p2[1]);
|
|
194
|
+
assert.deepStrictEqual(await db.get(...p1), { value: 1 });
|
|
195
|
+
assert.deepStrictEqual(await db.get(...p2), { value: 2 });
|
|
196
|
+
});
|
|
276
197
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const result = await db.get('name');
|
|
283
|
-
assert.strictEqual(result, 'ALICE');
|
|
198
|
+
it('should add items at nested paths', async function () {
|
|
199
|
+
const p = await db.add('categories', 'electronics', 'items', { name: 'Laptop' });
|
|
200
|
+
assert.strictEqual(p.length, 4);
|
|
201
|
+
assert.deepStrictEqual(await db.get(...p), { name: 'Laptop' });
|
|
202
|
+
});
|
|
284
203
|
});
|
|
285
204
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
});
|
|
205
|
+
describe('Increment/Decrement', function () {
|
|
206
|
+
it('should increment a value', async function () {
|
|
207
|
+
await db.set('counter', 10);
|
|
208
|
+
await db.inc('counter', 5);
|
|
209
|
+
assert.strictEqual(await db.get('counter'), 15);
|
|
210
|
+
});
|
|
293
211
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
assert.deepStrictEqual(user, { name: 'Alice', age: 31 });
|
|
300
|
-
});
|
|
301
|
-
});
|
|
212
|
+
it('should decrement a value', async function () {
|
|
213
|
+
await db.set('counter', 20);
|
|
214
|
+
await db.dec('counter', 8);
|
|
215
|
+
assert.strictEqual(await db.get('counter'), 12);
|
|
216
|
+
});
|
|
302
217
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
218
|
+
it('should increment nested value', async function () {
|
|
219
|
+
await db.set('user', 'balance', 100);
|
|
220
|
+
await db.inc('user', 'balance', 50);
|
|
221
|
+
assert.strictEqual(await db.get('user', 'balance'), 150);
|
|
222
|
+
});
|
|
308
223
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
224
|
+
it('should handle negative increments', async function () {
|
|
225
|
+
await db.set('counter', 10);
|
|
226
|
+
await db.inc('counter', -3);
|
|
227
|
+
assert.strictEqual(await db.get('counter'), 7);
|
|
228
|
+
});
|
|
312
229
|
});
|
|
313
230
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
231
|
+
describe('Update Operation', function () {
|
|
232
|
+
it('should update value with function', async function () {
|
|
233
|
+
await db.set('name', 'alice');
|
|
234
|
+
await db.upd('name', name => name.toUpperCase());
|
|
235
|
+
assert.strictEqual(await db.get('name'), 'ALICE');
|
|
236
|
+
});
|
|
320
237
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
});
|
|
238
|
+
it('should update nested value with function', async function () {
|
|
239
|
+
await db.set('user', 'age', 25);
|
|
240
|
+
await db.upd('user', 'age', age => age + 1);
|
|
241
|
+
assert.strictEqual(await db.get('user', 'age'), 26);
|
|
242
|
+
});
|
|
327
243
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const entries = await db.entries('simple');
|
|
334
|
-
|
|
335
|
-
assert.deepStrictEqual(keys, []);
|
|
336
|
-
assert.deepStrictEqual(values, []);
|
|
337
|
-
assert.deepStrictEqual(entries, []);
|
|
244
|
+
it('should update object with function', async function () {
|
|
245
|
+
await db.set('user', { name: 'Alice', age: 30 });
|
|
246
|
+
await db.upd('user', user => ({ ...user, age: user.age + 1 }));
|
|
247
|
+
assert.deepStrictEqual(await db.get('user'), { name: 'Alice', age: 31 });
|
|
248
|
+
});
|
|
338
249
|
});
|
|
339
|
-
});
|
|
340
250
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
path: testDataPath
|
|
347
|
-
}));
|
|
348
|
-
await persistDb.connect();
|
|
349
|
-
await persistDb.set('persistent', 'data');
|
|
350
|
-
await persistDb.disconnect();
|
|
351
|
-
|
|
352
|
-
const filePath = path.join(testDataPath, 'persist-test.db');
|
|
353
|
-
assert.ok(fs.existsSync(filePath));
|
|
354
|
-
});
|
|
251
|
+
describe('Keys, Values, Entries', function () {
|
|
252
|
+
beforeEach(async function () {
|
|
253
|
+
await db.set('users', 'alice', { age: 30 });
|
|
254
|
+
await db.set('users', 'bob', { age: 25 });
|
|
255
|
+
});
|
|
355
256
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
name: 'reload-test',
|
|
360
|
-
path: testDataPath
|
|
361
|
-
}));
|
|
362
|
-
await db1.connect();
|
|
363
|
-
await db1.set('existing', 'value');
|
|
364
|
-
await db1.set('nested', 'key', 'data');
|
|
365
|
-
await db1.disconnect();
|
|
366
|
-
|
|
367
|
-
// Create new instance pointing to same file
|
|
368
|
-
const db2 = new DeepBase(new SqliteDriver({
|
|
369
|
-
name: 'reload-test',
|
|
370
|
-
path: testDataPath
|
|
371
|
-
}));
|
|
372
|
-
await db2.connect();
|
|
373
|
-
|
|
374
|
-
const result = await db2.get('existing');
|
|
375
|
-
assert.strictEqual(result, 'value');
|
|
376
|
-
|
|
377
|
-
const nested = await db2.get('nested', 'key');
|
|
378
|
-
assert.strictEqual(nested, 'data');
|
|
379
|
-
|
|
380
|
-
await db2.disconnect();
|
|
381
|
-
});
|
|
257
|
+
it('should get keys', async function () {
|
|
258
|
+
assert.deepStrictEqual((await db.keys('users')).sort(), ['alice', 'bob']);
|
|
259
|
+
});
|
|
382
260
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
assert.strictEqual(result, 'disconnect');
|
|
390
|
-
});
|
|
261
|
+
it('should get values', async function () {
|
|
262
|
+
const values = await db.values('users');
|
|
263
|
+
assert.strictEqual(values.length, 2);
|
|
264
|
+
assert.ok(values.some(v => v.age === 30));
|
|
265
|
+
assert.ok(values.some(v => v.age === 25));
|
|
266
|
+
});
|
|
391
267
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
// data and operations should be unaffected
|
|
399
|
-
const result = await db.get('key');
|
|
400
|
-
assert.strictEqual(result, 'value');
|
|
401
|
-
|
|
402
|
-
await db.set('key2', 'value2');
|
|
403
|
-
assert.strictEqual(await db.get('key2'), 'value2');
|
|
404
|
-
});
|
|
405
|
-
});
|
|
268
|
+
it('should get entries', async function () {
|
|
269
|
+
const entries = await db.entries('users');
|
|
270
|
+
assert.strictEqual(entries.length, 2);
|
|
271
|
+
assert.ok(entries.some(([k, v]) => k === 'alice' && v.age === 30));
|
|
272
|
+
assert.ok(entries.some(([k, v]) => k === 'bob' && v.age === 25));
|
|
273
|
+
});
|
|
406
274
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
275
|
+
it('should return empty arrays for non-object values', async function () {
|
|
276
|
+
await db.set('simple', 'string');
|
|
277
|
+
assert.deepStrictEqual(await db.keys('simple'), []);
|
|
278
|
+
assert.deepStrictEqual(await db.values('simple'), []);
|
|
279
|
+
assert.deepStrictEqual(await db.entries('simple'), []);
|
|
280
|
+
});
|
|
413
281
|
});
|
|
414
282
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
283
|
+
describe('Persistence', function () {
|
|
284
|
+
it('should persist data to database file', async function () {
|
|
285
|
+
const persistDb = new DeepBase(new SqliteDriver({
|
|
286
|
+
name: `persist-${pragma}`,
|
|
287
|
+
path: testDataPath,
|
|
288
|
+
pragma,
|
|
289
|
+
}));
|
|
290
|
+
await persistDb.connect();
|
|
291
|
+
await persistDb.set('persistent', 'data');
|
|
292
|
+
await persistDb.disconnect();
|
|
293
|
+
assert.ok(fs.existsSync(path.join(testDataPath, `persist-${pragma}.db`)));
|
|
294
|
+
});
|
|
422
295
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
296
|
+
it('should load existing data on connect', async function () {
|
|
297
|
+
const db1 = new DeepBase(new SqliteDriver({
|
|
298
|
+
name: `reload-${pragma}`,
|
|
299
|
+
path: testDataPath,
|
|
300
|
+
pragma,
|
|
301
|
+
}));
|
|
302
|
+
await db1.connect();
|
|
303
|
+
await db1.set('existing', 'value');
|
|
304
|
+
await db1.set('nested', 'key', 'data');
|
|
305
|
+
await db1.disconnect();
|
|
306
|
+
|
|
307
|
+
const db2 = new DeepBase(new SqliteDriver({
|
|
308
|
+
name: `reload-${pragma}`,
|
|
309
|
+
path: testDataPath,
|
|
310
|
+
pragma,
|
|
311
|
+
}));
|
|
312
|
+
await db2.connect();
|
|
313
|
+
assert.strictEqual(await db2.get('existing'), 'value');
|
|
314
|
+
assert.strictEqual(await db2.get('nested', 'key'), 'data');
|
|
315
|
+
await db2.disconnect();
|
|
316
|
+
});
|
|
434
317
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const result = await db.get();
|
|
441
|
-
assert.deepStrictEqual(result, {
|
|
442
|
-
key1: 'value1',
|
|
443
|
-
key2: 'value2',
|
|
444
|
-
nested: { key: 'value' }
|
|
318
|
+
it('should handle reconnection', async function () {
|
|
319
|
+
await db.set('before', 'disconnect');
|
|
320
|
+
await db.disconnect();
|
|
321
|
+
await db.connect();
|
|
322
|
+
assert.strictEqual(await db.get('before'), 'disconnect');
|
|
445
323
|
});
|
|
446
|
-
});
|
|
447
324
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
assert.deepStrictEqual(result, newData);
|
|
456
|
-
|
|
457
|
-
const old = await db.get('old');
|
|
458
|
-
assert.strictEqual(old, null);
|
|
325
|
+
it('should be a no-op when already connected', async function () {
|
|
326
|
+
await db.set('key', 'value');
|
|
327
|
+
await db.connect();
|
|
328
|
+
assert.strictEqual(await db.get('key'), 'value');
|
|
329
|
+
await db.set('key2', 'value2');
|
|
330
|
+
assert.strictEqual(await db.get('key2'), 'value2');
|
|
331
|
+
});
|
|
459
332
|
});
|
|
460
|
-
});
|
|
461
333
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
334
|
+
describe('Singleton Pattern', function () {
|
|
335
|
+
it('should return same instance for same file', function () {
|
|
336
|
+
const d1 = new SqliteDriver({ name: `singleton-${pragma}`, path: testDataPath, pragma });
|
|
337
|
+
const d2 = new SqliteDriver({ name: `singleton-${pragma}`, path: testDataPath, pragma });
|
|
338
|
+
assert.strictEqual(d1, d2);
|
|
339
|
+
});
|
|
468
340
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
const resultB = await db.get('a', 'b');
|
|
475
|
-
assert.deepStrictEqual(resultB, { c: 'value1', d: 'value2' });
|
|
476
|
-
|
|
477
|
-
const resultA = await db.get('a');
|
|
478
|
-
assert.deepStrictEqual(resultA, {
|
|
479
|
-
b: { c: 'value1', d: 'value2' },
|
|
480
|
-
e: 'value3'
|
|
341
|
+
it('should return different instances for different files', function () {
|
|
342
|
+
const d1 = new SqliteDriver({ name: `file1-${pragma}`, path: testDataPath, pragma });
|
|
343
|
+
const d2 = new SqliteDriver({ name: `file2-${pragma}`, path: testDataPath, pragma });
|
|
344
|
+
assert.notStrictEqual(d1, d2);
|
|
481
345
|
});
|
|
482
346
|
});
|
|
483
|
-
});
|
|
484
347
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
});
|
|
348
|
+
describe('Root Object Operations', function () {
|
|
349
|
+
it('should set entire root object', async function () {
|
|
350
|
+
const data = {
|
|
351
|
+
users: { alice: { age: 30 }, bob: { age: 25 } },
|
|
352
|
+
config: { theme: 'dark', lang: 'en' },
|
|
353
|
+
};
|
|
354
|
+
await db.set(data);
|
|
355
|
+
assert.deepStrictEqual(await db.get(), data);
|
|
356
|
+
});
|
|
495
357
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
358
|
+
it('should get entire root object', async function () {
|
|
359
|
+
await db.set('key1', 'value1');
|
|
360
|
+
await db.set('key2', 'value2');
|
|
361
|
+
await db.set('nested', 'key', 'value');
|
|
362
|
+
assert.deepStrictEqual(await db.get(), {
|
|
363
|
+
key1: 'value1',
|
|
364
|
+
key2: 'value2',
|
|
365
|
+
nested: { key: 'value' },
|
|
366
|
+
});
|
|
367
|
+
});
|
|
504
368
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const user = await db.get('user');
|
|
511
|
-
assert.deepStrictEqual(user, {
|
|
512
|
-
name: 'Alice',
|
|
513
|
-
meta: { role: 'user', active: true }
|
|
369
|
+
it('should replace root object on set', async function () {
|
|
370
|
+
await db.set('old', 'data');
|
|
371
|
+
await db.set({ new: 'data' });
|
|
372
|
+
assert.deepStrictEqual(await db.get(), { new: 'data' });
|
|
373
|
+
assert.strictEqual(await db.get('old'), null);
|
|
514
374
|
});
|
|
515
375
|
});
|
|
516
|
-
});
|
|
517
376
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
// Trigger expansion by setting a nested property
|
|
524
|
-
await db.set('config', 'debug', true);
|
|
525
|
-
|
|
526
|
-
const config = await db.get('config');
|
|
527
|
-
assert.strictEqual(config['server.port'], 8080);
|
|
528
|
-
assert.strictEqual(config['server.host'], 'localhost');
|
|
529
|
-
assert.strictEqual(config.debug, true);
|
|
530
|
-
});
|
|
377
|
+
describe('Deep Nesting', function () {
|
|
378
|
+
it('should handle deeply nested paths', async function () {
|
|
379
|
+
await db.set('a', 'b', 'c', 'd', 'e', 'deep value');
|
|
380
|
+
assert.strictEqual(await db.get('a', 'b', 'c', 'd', 'e'), 'deep value');
|
|
381
|
+
});
|
|
531
382
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
assert.strictEqual(data.normal, 'ok');
|
|
543
|
-
assert.strictEqual(data.extra, 123);
|
|
383
|
+
it('should get partial deep objects', async function () {
|
|
384
|
+
await db.set('a', 'b', 'c', 'value1');
|
|
385
|
+
await db.set('a', 'b', 'd', 'value2');
|
|
386
|
+
await db.set('a', 'e', 'value3');
|
|
387
|
+
assert.deepStrictEqual(await db.get('a', 'b'), { c: 'value1', d: 'value2' });
|
|
388
|
+
assert.deepStrictEqual(await db.get('a'), {
|
|
389
|
+
b: { c: 'value1', d: 'value2' },
|
|
390
|
+
e: 'value3',
|
|
391
|
+
});
|
|
392
|
+
});
|
|
544
393
|
});
|
|
545
394
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
'{lang
|
|
549
|
-
'
|
|
550
|
-
'
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
// Trigger expansion
|
|
554
|
-
await db.set('replace', '{lang}', 'en');
|
|
555
|
-
|
|
556
|
-
const result = await db.get('replace');
|
|
557
|
-
assert.strictEqual(result['{lang}'], 'en');
|
|
558
|
-
assert.strictEqual(result['{first_name}'], 'Martin');
|
|
559
|
-
assert.strictEqual(result['@main'], 'martin');
|
|
560
|
-
});
|
|
395
|
+
describe('Overwriting Object with Nested Properties', function () {
|
|
396
|
+
it('should handle setting object first then nested properties', async function () {
|
|
397
|
+
await db.set('config', { lang: 'en', theme: 'dark' });
|
|
398
|
+
await db.set('config', 'lang', 'en');
|
|
399
|
+
await db.set('config', 'theme', 'light');
|
|
400
|
+
assert.deepStrictEqual(await db.get('config'), { lang: 'en', theme: 'light' });
|
|
401
|
+
});
|
|
561
402
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
await db.set(data);
|
|
569
|
-
const result = await db.get();
|
|
570
|
-
assert.deepStrictEqual(result, data);
|
|
571
|
-
});
|
|
572
|
-
});
|
|
403
|
+
it('should prioritize nested properties over initial object', async function () {
|
|
404
|
+
await db.set('settings', { a: 1, b: 2, c: 3 });
|
|
405
|
+
await db.set('settings', 'b', 99);
|
|
406
|
+
await db.set('settings', 'd', 4);
|
|
407
|
+
assert.deepStrictEqual(await db.get('settings'), { a: 1, b: 99, c: 3, d: 4 });
|
|
408
|
+
});
|
|
573
409
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
const chatX1 = await db.get('chatX1');
|
|
584
|
-
assert.deepStrictEqual(chatX1, { msg: 'world' });
|
|
410
|
+
it('should handle nested object then deeper nesting', async function () {
|
|
411
|
+
await db.set('user', { name: 'Alice', meta: { role: 'admin' } });
|
|
412
|
+
await db.set('user', 'meta', 'role', 'user');
|
|
413
|
+
await db.set('user', 'meta', 'active', true);
|
|
414
|
+
assert.deepStrictEqual(await db.get('user'), {
|
|
415
|
+
name: 'Alice',
|
|
416
|
+
meta: { role: 'user', active: true },
|
|
417
|
+
});
|
|
418
|
+
});
|
|
585
419
|
});
|
|
586
420
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
assert.deepStrictEqual(u1, { name: 'Alice' });
|
|
597
|
-
assert.deepStrictEqual(u2, { name: 'Bob' });
|
|
598
|
-
assert.deepStrictEqual(u3, { name: 'Charlie' });
|
|
599
|
-
});
|
|
421
|
+
describe('Object Expansion with Special Keys', function () {
|
|
422
|
+
it('should handle object with dotted keys when expanding', async function () {
|
|
423
|
+
await db.set('config', { 'server.port': 8080, 'server.host': 'localhost' });
|
|
424
|
+
await db.set('config', 'debug', true);
|
|
425
|
+
const config = await db.get('config');
|
|
426
|
+
assert.strictEqual(config['server.port'], 8080);
|
|
427
|
+
assert.strictEqual(config['server.host'], 'localhost');
|
|
428
|
+
assert.strictEqual(config.debug, true);
|
|
429
|
+
});
|
|
600
430
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
});
|
|
431
|
+
it('should not crash on objects with empty string keys', async function () {
|
|
432
|
+
await db.set('data', { '': 'empty_key_value', normal: 'ok' });
|
|
433
|
+
await db.set('data', 'extra', 123);
|
|
434
|
+
const data = await db.get('data');
|
|
435
|
+
assert.strictEqual(data[''], 'empty_key_value');
|
|
436
|
+
assert.strictEqual(data.normal, 'ok');
|
|
437
|
+
assert.strictEqual(data.extra, 123);
|
|
438
|
+
});
|
|
610
439
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
440
|
+
it('should handle nested objects with dotted keys during expansion', async function () {
|
|
441
|
+
await db.set('replace', {
|
|
442
|
+
'{lang}': 'es',
|
|
443
|
+
'{first_name}': 'Martin',
|
|
444
|
+
'@main': 'martin',
|
|
445
|
+
});
|
|
446
|
+
await db.set('replace', '{lang}', 'en');
|
|
447
|
+
const result = await db.get('replace');
|
|
448
|
+
assert.strictEqual(result['{lang}'], 'en');
|
|
449
|
+
assert.strictEqual(result['{first_name}'], 'Martin');
|
|
450
|
+
assert.strictEqual(result['@main'], 'martin');
|
|
451
|
+
});
|
|
622
452
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const session = await db.get(sessionKey);
|
|
632
|
-
assert.deepStrictEqual(session, {
|
|
633
|
-
started: { LuaGardenbot: true },
|
|
634
|
-
active: { LuaGardenbot: true, AmyWaybot: true }
|
|
453
|
+
it('should preserve dots in keys through set/get root object cycle', async function () {
|
|
454
|
+
const data = {
|
|
455
|
+
'api.v1.endpoint': 'https://api.example.com',
|
|
456
|
+
'api.v2.endpoint': 'https://v2.api.example.com',
|
|
457
|
+
};
|
|
458
|
+
await db.set(data);
|
|
459
|
+
assert.deepStrictEqual(await db.get(), data);
|
|
635
460
|
});
|
|
636
461
|
});
|
|
637
462
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
'{
|
|
643
|
-
'{
|
|
644
|
-
|
|
645
|
-
'{last_name}': '',
|
|
646
|
-
'{crypto_name}': 'kkerfhkkzgf',
|
|
647
|
-
'@main': 'martin'
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
const replace = await db.get(sessionKey, 'replace');
|
|
651
|
-
assert.strictEqual(replace['{lang}'], 'es');
|
|
652
|
-
assert.strictEqual(replace['@main'], 'martin');
|
|
653
|
-
assert.strictEqual(replace['{first_name}'], 'STM User');
|
|
654
|
-
});
|
|
463
|
+
describe('Keys with Underscores (SQL LIKE safety)', function () {
|
|
464
|
+
it('should not cross-match keys with underscores', async function () {
|
|
465
|
+
await db.set('chat_1', 'msg', 'hello');
|
|
466
|
+
await db.set('chatX1', 'msg', 'world');
|
|
467
|
+
assert.deepStrictEqual(await db.get('chat_1'), { msg: 'hello' });
|
|
468
|
+
assert.deepStrictEqual(await db.get('chatX1'), { msg: 'world' });
|
|
469
|
+
});
|
|
655
470
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
});
|
|
664
|
-
await db.set(sessionKey, 'node', 'history', '0-2760293686', {
|
|
665
|
-
from: 'LuaGardenbot',
|
|
666
|
-
message: 'Hey, I\'m Lua',
|
|
667
|
-
timestamp: 1771012956863
|
|
668
|
-
});
|
|
669
|
-
await db.set(sessionKey, 'node', 'history', '0-4151942372-o', {
|
|
670
|
-
from: 'main',
|
|
671
|
-
to: 'LuaGardenbot',
|
|
672
|
-
message: 'hola',
|
|
673
|
-
timestamp: 1771012960058
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
const history = await db.get(sessionKey, 'node', 'history');
|
|
677
|
-
assert.strictEqual(Object.keys(history).length, 3);
|
|
678
|
-
assert.strictEqual(history['c0'].from, 'LuaGardenbot');
|
|
679
|
-
assert.strictEqual(history['0-2760293686'].message, 'Hey, I\'m Lua');
|
|
680
|
-
assert.strictEqual(history['0-4151942372-o'].to, 'LuaGardenbot');
|
|
681
|
-
});
|
|
471
|
+
it('should isolate data between similar keys with underscores', async function () {
|
|
472
|
+
await db.set('user_42', 'name', 'Alice');
|
|
473
|
+
await db.set('userX42', 'name', 'Bob');
|
|
474
|
+
await db.set('user.42', 'name', 'Charlie');
|
|
475
|
+
assert.deepStrictEqual(await db.get('user_42'), { name: 'Alice' });
|
|
476
|
+
assert.deepStrictEqual(await db.get('userX42'), { name: 'Bob' });
|
|
477
|
+
assert.deepStrictEqual(await db.get('user.42'), { name: 'Charlie' });
|
|
478
|
+
});
|
|
682
479
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
'{
|
|
689
|
-
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
// Now update a single key — triggers _expandParentObjects
|
|
693
|
-
await db.set(sessionKey, 'replace', '{lang}', 'en');
|
|
694
|
-
|
|
695
|
-
const replace = await db.get(sessionKey, 'replace');
|
|
696
|
-
assert.strictEqual(replace['{lang}'], 'en');
|
|
697
|
-
assert.strictEqual(replace['{first_name}'], 'User');
|
|
698
|
-
});
|
|
480
|
+
it('should delete only matching keys with underscores', async function () {
|
|
481
|
+
await db.set('item_a', 'x', 1);
|
|
482
|
+
await db.set('itemXa', 'x', 2);
|
|
483
|
+
await db.del('item_a');
|
|
484
|
+
assert.strictEqual(await db.get('item_a'), null);
|
|
485
|
+
assert.deepStrictEqual(await db.get('itemXa'), { x: 2 });
|
|
486
|
+
});
|
|
699
487
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
'{vip_token}': '',
|
|
707
|
-
'{first_name}': 'STM User'
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
await db.set(sessionKey, 'config', { country: '', gender: 'M' });
|
|
711
|
-
await db.set(sessionKey, 'tokens', { input: 4554, output: 229, cost: 0.0079825 });
|
|
712
|
-
|
|
713
|
-
// Nested individual sets
|
|
714
|
-
await db.set(sessionKey, 'started', 'LuaGardenbot', true);
|
|
715
|
-
await db.set(sessionKey, 'active', 'LuaGardenbot', true);
|
|
716
|
-
await db.set(sessionKey, 'active', 'AmyWaybot', true);
|
|
717
|
-
await db.set(sessionKey, 'node', 'current', 'chapter', 2);
|
|
718
|
-
await db.set(sessionKey, 'node', 'current', 'id', '50-326833713');
|
|
719
|
-
|
|
720
|
-
// Update inside previously stored object — triggers expansion
|
|
721
|
-
await db.set(sessionKey, 'replace', '{lang}', 'en');
|
|
722
|
-
await db.set(sessionKey, 'config', 'country', 'AR');
|
|
723
|
-
|
|
724
|
-
// Verify everything
|
|
725
|
-
const session = await db.get(sessionKey);
|
|
726
|
-
|
|
727
|
-
assert.strictEqual(session.replace['{lang}'], 'en');
|
|
728
|
-
assert.strictEqual(session.replace['{first_name}'], 'STM User');
|
|
729
|
-
assert.strictEqual(session.config.country, 'AR');
|
|
730
|
-
assert.strictEqual(session.config.gender, 'M');
|
|
731
|
-
assert.strictEqual(session.tokens.cost, 0.0079825);
|
|
732
|
-
assert.strictEqual(session.started.LuaGardenbot, true);
|
|
733
|
-
assert.strictEqual(session.active.AmyWaybot, true);
|
|
734
|
-
assert.strictEqual(session.node.current.chapter, 2);
|
|
735
|
-
assert.strictEqual(session.node.current.id, '50-326833713');
|
|
488
|
+
it('should handle keys with percent signs', async function () {
|
|
489
|
+
await db.set('progress', '100%', 'done', true);
|
|
490
|
+
await db.set('progress', 'abc', 'done', false);
|
|
491
|
+
assert.deepStrictEqual(await db.get('progress', '100%'), { done: true });
|
|
492
|
+
assert.deepStrictEqual(await db.get('progress', 'abc'), { done: false });
|
|
493
|
+
});
|
|
736
494
|
});
|
|
737
495
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
496
|
+
describe('Storybot-like Structure', function () {
|
|
497
|
+
it('should handle dotted top-level keys with deep nesting', async function () {
|
|
498
|
+
const sk = 'stm.1769201539421.dawduxooy';
|
|
499
|
+
await db.set(sk, 'started', 'LuaGardenbot', true);
|
|
500
|
+
await db.set(sk, 'active', 'LuaGardenbot', true);
|
|
501
|
+
await db.set(sk, 'active', 'AmyWaybot', true);
|
|
502
|
+
assert.deepStrictEqual(await db.get(sk), {
|
|
503
|
+
started: { LuaGardenbot: true },
|
|
504
|
+
active: { LuaGardenbot: true, AmyWaybot: true },
|
|
505
|
+
});
|
|
506
|
+
});
|
|
748
507
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
508
|
+
it('should handle replace map with special chars in keys', async function () {
|
|
509
|
+
const sk = 'stm.12345.abc';
|
|
510
|
+
await db.set(sk, 'replace', {
|
|
511
|
+
'{lang}': 'es',
|
|
512
|
+
'{vip_token}': '',
|
|
513
|
+
'{first_name}': 'STM User',
|
|
514
|
+
'{last_name}': '',
|
|
515
|
+
'{crypto_name}': 'kkerfhkkzgf',
|
|
516
|
+
'@main': 'martin',
|
|
517
|
+
});
|
|
518
|
+
const replace = await db.get(sk, 'replace');
|
|
519
|
+
assert.strictEqual(replace['{lang}'], 'es');
|
|
520
|
+
assert.strictEqual(replace['@main'], 'martin');
|
|
521
|
+
assert.strictEqual(replace['{first_name}'], 'STM User');
|
|
522
|
+
});
|
|
762
523
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
})
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
assert.strictEqual(finalData.items.length, 50, 'Should have exactly 50 items');
|
|
781
|
-
|
|
782
|
-
// Check that all items have unique values
|
|
783
|
-
const uniqueItems = new Set(finalData.items);
|
|
784
|
-
assert.strictEqual(uniqueItems.size, 50, 'All items should be unique');
|
|
785
|
-
});
|
|
524
|
+
it('should handle node history with numeric-like keys', async function () {
|
|
525
|
+
const sk = 'stm.999.xyz';
|
|
526
|
+
await db.set(sk, 'node', 'history', 'c0', {
|
|
527
|
+
from: 'LuaGardenbot', message: 'new contact', timestamp: 1771012955839,
|
|
528
|
+
});
|
|
529
|
+
await db.set(sk, 'node', 'history', '0-2760293686', {
|
|
530
|
+
from: 'LuaGardenbot', message: 'Hey, I\'m Lua', timestamp: 1771012956863,
|
|
531
|
+
});
|
|
532
|
+
await db.set(sk, 'node', 'history', '0-4151942372-o', {
|
|
533
|
+
from: 'main', to: 'LuaGardenbot', message: 'hola', timestamp: 1771012960058,
|
|
534
|
+
});
|
|
535
|
+
const history = await db.get(sk, 'node', 'history');
|
|
536
|
+
assert.strictEqual(Object.keys(history).length, 3);
|
|
537
|
+
assert.strictEqual(history['c0'].from, 'LuaGardenbot');
|
|
538
|
+
assert.strictEqual(history['0-2760293686'].message, 'Hey, I\'m Lua');
|
|
539
|
+
assert.strictEqual(history['0-4151942372-o'].to, 'LuaGardenbot');
|
|
540
|
+
});
|
|
786
541
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
await Promise.all(promises);
|
|
796
|
-
|
|
797
|
-
// Verify all keys exist and have correct values
|
|
798
|
-
const users = await db.get('users');
|
|
799
|
-
assert.strictEqual(Object.keys(users).length, 50, 'Should have 50 users');
|
|
800
|
-
|
|
801
|
-
for (let i = 0; i < 50; i++) {
|
|
802
|
-
assert.deepStrictEqual(
|
|
803
|
-
users[`user${i}`],
|
|
804
|
-
{ name: `User ${i}`, value: i },
|
|
805
|
-
`User ${i} should have correct data`
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
});
|
|
542
|
+
it('should handle object expansion with replace map then set nested', async function () {
|
|
543
|
+
const sk = 'stm.12345.abc';
|
|
544
|
+
await db.set(sk, 'replace', { '{lang}': 'es', '{first_name}': 'User' });
|
|
545
|
+
await db.set(sk, 'replace', '{lang}', 'en');
|
|
546
|
+
const replace = await db.get(sk, 'replace');
|
|
547
|
+
assert.strictEqual(replace['{lang}'], 'en');
|
|
548
|
+
assert.strictEqual(replace['{first_name}'], 'User');
|
|
549
|
+
});
|
|
809
550
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
db.
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
551
|
+
it('should handle full session lifecycle without crash', async function () {
|
|
552
|
+
const sk = 'stm.1769201539421.dawduxooy';
|
|
553
|
+
await db.set(sk, 'replace', { '{lang}': 'es', '{vip_token}': '', '{first_name}': 'STM User' });
|
|
554
|
+
await db.set(sk, 'config', { country: '', gender: 'M' });
|
|
555
|
+
await db.set(sk, 'tokens', { input: 4554, output: 229, cost: 0.0079825 });
|
|
556
|
+
await db.set(sk, 'started', 'LuaGardenbot', true);
|
|
557
|
+
await db.set(sk, 'active', 'LuaGardenbot', true);
|
|
558
|
+
await db.set(sk, 'active', 'AmyWaybot', true);
|
|
559
|
+
await db.set(sk, 'node', 'current', 'chapter', 2);
|
|
560
|
+
await db.set(sk, 'node', 'current', 'id', '50-326833713');
|
|
561
|
+
await db.set(sk, 'replace', '{lang}', 'en');
|
|
562
|
+
await db.set(sk, 'config', 'country', 'AR');
|
|
563
|
+
const session = await db.get(sk);
|
|
564
|
+
assert.strictEqual(session.replace['{lang}'], 'en');
|
|
565
|
+
assert.strictEqual(session.replace['{first_name}'], 'STM User');
|
|
566
|
+
assert.strictEqual(session.config.country, 'AR');
|
|
567
|
+
assert.strictEqual(session.config.gender, 'M');
|
|
568
|
+
assert.strictEqual(session.tokens.cost, 0.0079825);
|
|
569
|
+
assert.strictEqual(session.started.LuaGardenbot, true);
|
|
570
|
+
assert.strictEqual(session.active.AmyWaybot, true);
|
|
571
|
+
assert.strictEqual(session.node.current.chapter, 2);
|
|
572
|
+
assert.strictEqual(session.node.current.id, '50-326833713');
|
|
573
|
+
});
|
|
831
574
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
const result = await db.get('inventory');
|
|
842
|
-
assert.strictEqual(result, 500, 'All decrements should be applied atomically');
|
|
575
|
+
it('should not mix sessions with similar dotted keys', async function () {
|
|
576
|
+
await db.set('stm.111.aaa', 'data', 'session1');
|
|
577
|
+
await db.set('stm.111.bbb', 'data', 'session2');
|
|
578
|
+
await db.set('stm.222.aaa', 'data', 'session3');
|
|
579
|
+
assert.deepStrictEqual(await db.get('stm.111.aaa'), { data: 'session1' });
|
|
580
|
+
assert.deepStrictEqual(await db.get('stm.111.bbb'), { data: 'session2' });
|
|
581
|
+
assert.deepStrictEqual(await db.get('stm.222.aaa'), { data: 'session3' });
|
|
582
|
+
});
|
|
843
583
|
});
|
|
844
|
-
});
|
|
845
|
-
});
|
|
846
584
|
|
|
585
|
+
describe('Race Conditions', function () {
|
|
586
|
+
it('should handle 100 concurrent increments correctly', async function () {
|
|
587
|
+
this.timeout(5000);
|
|
588
|
+
await db.set('counter', 0);
|
|
589
|
+
await Promise.all(Array.from({ length: 100 }, () => db.inc('counter', 1)));
|
|
590
|
+
assert.strictEqual(await db.get('counter'), 100);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it('should handle concurrent read-modify-write operations', async function () {
|
|
594
|
+
this.timeout(5000);
|
|
595
|
+
await db.set('data', { counter: 0, items: [] });
|
|
596
|
+
await Promise.all(Array.from({ length: 50 }, () =>
|
|
597
|
+
db.upd('data', cur => ({
|
|
598
|
+
counter: cur.counter + 1,
|
|
599
|
+
items: [...cur.items, `item-${cur.counter}`],
|
|
600
|
+
})),
|
|
601
|
+
));
|
|
602
|
+
const final = await db.get('data');
|
|
603
|
+
assert.strictEqual(final.counter, 50);
|
|
604
|
+
assert.strictEqual(final.items.length, 50);
|
|
605
|
+
assert.strictEqual(new Set(final.items).size, 50);
|
|
606
|
+
});
|
|
847
607
|
|
|
608
|
+
it('should handle concurrent sets on different keys', async function () {
|
|
609
|
+
this.timeout(5000);
|
|
610
|
+
await Promise.all(Array.from({ length: 50 }, (_, i) =>
|
|
611
|
+
db.set('users', `user${i}`, { name: `User ${i}`, value: i }),
|
|
612
|
+
));
|
|
613
|
+
const users = await db.get('users');
|
|
614
|
+
assert.strictEqual(Object.keys(users).length, 50);
|
|
615
|
+
for (let i = 0; i < 50; i++) {
|
|
616
|
+
assert.deepStrictEqual(users[`user${i}`], { name: `User ${i}`, value: i });
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('should handle concurrent add operations with unique IDs', async function () {
|
|
621
|
+
this.timeout(5000);
|
|
622
|
+
await db.set('items', {});
|
|
623
|
+
const results = await Promise.all(Array.from({ length: 50 }, (_, i) =>
|
|
624
|
+
db.add('items', { value: i }),
|
|
625
|
+
));
|
|
626
|
+
const items = await db.get('items');
|
|
627
|
+
assert.strictEqual(Object.keys(items).length, 50);
|
|
628
|
+
assert.strictEqual(new Set(results.map(r => r[r.length - 1])).size, 50);
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
it('should handle concurrent decrements correctly', async function () {
|
|
632
|
+
this.timeout(5000);
|
|
633
|
+
await db.set('inventory', 1000);
|
|
634
|
+
await Promise.all(Array.from({ length: 100 }, () => db.dec('inventory', 5)));
|
|
635
|
+
assert.strictEqual(await db.get('inventory'), 500);
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
}
|