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