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/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
- describe('SqliteDriver', function() {
12
- let db;
13
- let testCounter = 0;
14
-
15
- before(function() {
16
- // Clean up test data before all tests
17
- if (fs.existsSync(testDataPath)) {
18
- fs.rmSync(testDataPath, { recursive: true, force: true });
19
- }
20
- });
11
+ const PRAGMA_MODES = ['none', 'safe', 'balanced', 'fast'];
21
12
 
22
- beforeEach(async function() {
23
- // Use unique name for each test to avoid singleton conflicts
24
- testCounter++;
25
- db = new DeepBase(new SqliteDriver({
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
- afterEach(async function() {
33
- await db.disconnect();
34
- if (fs.existsSync(testDataPath)) {
35
- fs.rmSync(testDataPath, { recursive: true, force: true });
36
- }
37
- });
38
-
39
- describe('Basic Operations', function() {
40
- it('should set and get a simple value', async function() {
41
- await db.set('key', 'value');
42
- const result = await db.get('key');
43
- assert.strictEqual(result, 'value');
18
+ before(function () {
19
+ if (fs.existsSync(testDataPath)) {
20
+ fs.rmSync(testDataPath, { recursive: true, force: true });
21
+ }
44
22
  });
45
23
 
46
- it('should set and get nested values', async function() {
47
- await db.set('user', 'name', 'Alice');
48
- await db.set('user', 'age', 30);
49
-
50
- const name = await db.get('user', 'name');
51
- const age = await db.get('user', 'age');
52
-
53
- assert.strictEqual(name, 'Alice');
54
- assert.strictEqual(age, 30);
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
- it('should get entire nested object', async function() {
58
- await db.set('user', 'name', 'Bob');
59
- await db.set('user', 'age', 25);
60
-
61
- const user = await db.get('user');
62
- assert.deepStrictEqual(user, { name: 'Bob', age: 25 });
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
- it('should return null for non-existent keys', async function() {
66
- const result = await db.get('nonexistent');
67
- assert.strictEqual(result, null);
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
- it('should handle numbers', async function() {
71
- await db.set('number', 42);
72
- const result = await db.get('number');
73
- assert.strictEqual(result, 42);
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
- it('should handle booleans', async function() {
77
- await db.set('flag', true);
78
- const result = await db.get('flag');
79
- assert.strictEqual(result, true);
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
- it('should handle arrays', async function() {
83
- await db.set('list', [1, 2, 3]);
84
- const result = await db.get('list');
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
- it('should handle complex objects', async function() {
89
- const complexObj = {
90
- name: 'Test',
91
- items: [1, 2, 3],
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
- describe('Keys with Dots', function() {
101
- it('should handle keys containing dots', async function() {
102
- const setResult = await db.set('value', 'martin.clasen@gmail.com', 1);
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
- it('should handle keys with multiple dots', async function() {
113
- await db.set('config', 'api.prod.endpoint', 'https://api.example.com');
114
- const endpoint = await db.get('config', 'api.prod.endpoint');
115
- assert.strictEqual(endpoint, 'https://api.example.com');
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
- it('should handle nested paths with dots in keys', async function() {
119
- await db.set('users', 'john.doe@example.com', 'name', 'John Doe');
120
- await db.set('users', 'john.doe@example.com', 'age', 30);
121
-
122
- const user = await db.get('users', 'john.doe@example.com');
123
- assert.deepStrictEqual(user, { name: 'John Doe', age: 30 });
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
- it('should handle dots in first level keys', async function() {
127
- await db.set('config.prod', 'value', 100);
128
- const configProd = await db.get('config.prod');
129
- assert.deepStrictEqual(configProd, { value: 100 });
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
- it('should distinguish between dots in keys and path separators', async function() {
133
- // Set a regular nested path
134
- await db.set('users', 'alice', 'email', 'alice@example.com');
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);
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
- it('should delete keys containing dots', async function() {
146
- await db.set('emails', 'user.name@domain.com', 'verified', true);
147
- await db.set('emails', 'other@email.com', 'verified', false);
148
-
149
- await db.del('emails', 'user.name@domain.com');
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
- });
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
- describe('Delete Operations', function() {
160
- it('should delete a key', async function() {
161
- await db.set('temp', 'value');
162
- await db.del('temp');
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
- it('should delete nested key', async function() {
169
- await db.set('user', 'name', 'Alice');
170
- await db.set('user', 'age', 30);
171
- await db.del('user', 'age');
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
- it('should delete parent and all children', async function() {
178
- await db.set('parent', 'child1', 'value1');
179
- await db.set('parent', 'child2', 'value2');
180
- await db.set('parent', 'nested', 'deep', 'value3');
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
- it('should clear all data', async function() {
195
- await db.set('key1', 'value1');
196
- await db.set('key2', 'value2');
197
- await db.del();
198
-
199
- const all = await db.get();
200
- assert.deepStrictEqual(all, {});
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
- describe('Add Operation', function() {
205
- it('should add item with auto-generated ID', async function() {
206
- const path = await db.add('users', { name: 'Charlie' });
207
-
208
- assert.strictEqual(path.length, 2);
209
- assert.strictEqual(path[0], 'users');
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' });
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
- it('should add multiple items with unique IDs', async function() {
218
- const path1 = await db.add('items', { value: 1 });
219
- const path2 = await db.add('items', { value: 2 });
220
-
221
- assert.notStrictEqual(path1[1], path2[1]);
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
- it('should add items at nested paths', async function() {
231
- const path = await db.add('categories', 'electronics', 'items', { name: 'Laptop' });
232
-
233
- assert.strictEqual(path.length, 4);
234
- assert.strictEqual(path[0], 'categories');
235
- assert.strictEqual(path[1], 'electronics');
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
- describe('Increment/Decrement', function() {
244
- it('should increment a value', async function() {
245
- await db.set('counter', 10);
246
- await db.inc('counter', 5);
247
-
248
- const result = await db.get('counter');
249
- assert.strictEqual(result, 15);
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
- it('should decrement a value', async function() {
253
- await db.set('counter', 20);
254
- await db.dec('counter', 8);
255
-
256
- const result = await db.get('counter');
257
- assert.strictEqual(result, 12);
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
- it('should increment nested value', async function() {
261
- await db.set('user', 'balance', 100);
262
- await db.inc('user', 'balance', 50);
263
-
264
- const balance = await db.get('user', 'balance');
265
- assert.strictEqual(balance, 150);
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
- it('should handle negative increments', async function() {
269
- await db.set('counter', 10);
270
- await db.inc('counter', -3);
271
-
272
- const result = await db.get('counter');
273
- assert.strictEqual(result, 7);
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
- describe('Update Operation', function() {
278
- it('should update value with function', async function() {
279
- await db.set('name', 'alice');
280
- await db.upd('name', name => name.toUpperCase());
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
- it('should update nested value with function', async function() {
287
- await db.set('user', 'age', 25);
288
- await db.upd('user', 'age', age => age + 1);
289
-
290
- const age = await db.get('user', 'age');
291
- assert.strictEqual(age, 26);
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
- it('should update object with function', async function() {
295
- await db.set('user', { name: 'Alice', age: 30 });
296
- await db.upd('user', user => ({ ...user, age: user.age + 1 }));
297
-
298
- const user = await db.get('user');
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
- describe('Keys, Values, Entries', function() {
304
- beforeEach(async function() {
305
- await db.set('users', 'alice', { age: 30 });
306
- await db.set('users', 'bob', { age: 25 });
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
- it('should get keys', async function() {
310
- const keys = await db.keys('users');
311
- assert.deepStrictEqual(keys.sort(), ['alice', 'bob']);
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
- it('should get values', async function() {
315
- const values = await db.values('users');
316
- assert.strictEqual(values.length, 2);
317
- assert.ok(values.some(v => v.age === 30));
318
- assert.ok(values.some(v => v.age === 25));
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
- it('should get entries', async function() {
322
- const entries = await db.entries('users');
323
- assert.strictEqual(entries.length, 2);
324
- assert.ok(entries.some(([k, v]) => k === 'alice' && v.age === 30));
325
- assert.ok(entries.some(([k, v]) => k === 'bob' && v.age === 25));
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
- it('should return empty arrays for non-object values', async function() {
329
- await db.set('simple', 'string');
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, []);
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
- describe('Persistence', function() {
342
- it('should persist data to database file', async function() {
343
- // Use a separate db for this test
344
- const persistDb = new DeepBase(new SqliteDriver({
345
- name: 'persist-test',
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
- it('should load existing data on connect', async function() {
357
- // Use a separate db for this test
358
- const db1 = new DeepBase(new SqliteDriver({
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
- it('should handle reconnection', async function() {
384
- await db.set('before', 'disconnect');
385
- await db.disconnect();
386
-
387
- await db.connect();
388
- const result = await db.get('before');
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
- it('should be a no-op when already connected', async function() {
393
- await db.set('key', 'value');
394
-
395
- // connect again while already connected
396
- await db.connect();
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
- describe('Singleton Pattern', function() {
408
- it('should return same instance for same file', function() {
409
- const driver1 = new SqliteDriver({ name: 'singleton', path: testDataPath });
410
- const driver2 = new SqliteDriver({ name: 'singleton', path: testDataPath });
411
-
412
- assert.strictEqual(driver1, driver2);
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
- it('should return different instances for different files', function() {
416
- const driver1 = new SqliteDriver({ name: 'file1', path: testDataPath });
417
- const driver2 = new SqliteDriver({ name: 'file2', path: testDataPath });
418
-
419
- assert.notStrictEqual(driver1, driver2);
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
- describe('Root Object Operations', function() {
424
- it('should set entire root object', async function() {
425
- const data = {
426
- users: { alice: { age: 30 }, bob: { age: 25 } },
427
- config: { theme: 'dark', lang: 'en' }
428
- };
429
-
430
- await db.set(data);
431
- const result = await db.get();
432
- assert.deepStrictEqual(result, data);
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
- it('should get entire root object', async function() {
436
- await db.set('key1', 'value1');
437
- await db.set('key2', 'value2');
438
- await db.set('nested', 'key', 'value');
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
- it('should replace root object on set', async function() {
449
- await db.set('old', 'data');
450
-
451
- const newData = { new: 'data' };
452
- await db.set(newData);
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);
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
- describe('Deep Nesting', function() {
463
- it('should handle deeply nested paths', async function() {
464
- await db.set('a', 'b', 'c', 'd', 'e', 'deep value');
465
- const result = await db.get('a', 'b', 'c', 'd', 'e');
466
- assert.strictEqual(result, 'deep value');
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
- it('should get partial deep objects', async function() {
470
- await db.set('a', 'b', 'c', 'value1');
471
- await db.set('a', 'b', 'd', 'value2');
472
- await db.set('a', 'e', 'value3');
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
- describe('Overwriting Object with Nested Properties', function() {
486
- it('should handle setting object first then nested properties', async function() {
487
- // This is the scenario from example 05-sqlite.js
488
- await db.set('config', { lang: 'en', theme: 'dark' });
489
- await db.set('config', 'lang', 'en');
490
- await db.set('config', 'theme', 'light');
491
-
492
- const config = await db.get('config');
493
- assert.deepStrictEqual(config, { lang: 'en', theme: 'light' });
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
- it('should prioritize nested properties over initial object', async function() {
497
- await db.set('settings', { a: 1, b: 2, c: 3 });
498
- await db.set('settings', 'b', 99);
499
- await db.set('settings', 'd', 4);
500
-
501
- const settings = await db.get('settings');
502
- assert.deepStrictEqual(settings, { a: 1, b: 99, c: 3, d: 4 });
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
- it('should handle nested object then deeper nesting', async function() {
506
- await db.set('user', { name: 'Alice', meta: { role: 'admin' } });
507
- await db.set('user', 'meta', 'role', 'user');
508
- await db.set('user', 'meta', 'active', true);
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
- describe('Object Expansion with Special Keys', function() {
519
- it('should handle object with dotted keys when expanding', async function() {
520
- // Store an object with dots in its property names
521
- await db.set('config', { 'server.port': 8080, 'server.host': 'localhost' });
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
- it('should not crash on objects with empty string keys', async function() {
533
- // This was the root cause of the production crash:
534
- // empty string key produces trailing dot in DB, leading to empty path
535
- await db.set('data', { '': 'empty_key_value', 'normal': 'ok' });
536
-
537
- // Trigger expansion
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);
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
- it('should handle nested objects with dotted keys during expansion', async function() {
547
- await db.set('replace', {
548
- '{lang}': 'es',
549
- '{first_name}': 'Martin',
550
- '@main': 'martin'
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
- it('should preserve dots in keys through set/get root object cycle', async function() {
563
- const data = {
564
- 'api.v1.endpoint': 'https://api.example.com',
565
- 'api.v2.endpoint': 'https://v2.api.example.com'
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
- describe('Keys with Underscores (SQL LIKE safety)', function() {
575
- it('should not cross-match keys with underscores', async function() {
576
- // _ is a SQL LIKE wildcard that matches any single character
577
- await db.set('chat_1', 'msg', 'hello');
578
- await db.set('chatX1', 'msg', 'world');
579
-
580
- const chat1 = await db.get('chat_1');
581
- assert.deepStrictEqual(chat1, { msg: 'hello' });
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
- it('should isolate data between similar keys with underscores', async function() {
588
- await db.set('user_42', 'name', 'Alice');
589
- await db.set('userX42', 'name', 'Bob');
590
- await db.set('user.42', 'name', 'Charlie');
591
-
592
- const u1 = await db.get('user_42');
593
- const u2 = await db.get('userX42');
594
- const u3 = await db.get('user.42');
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
- it('should delete only matching keys with underscores', async function() {
602
- await db.set('item_a', 'x', 1);
603
- await db.set('itemXa', 'x', 2);
604
-
605
- await db.del('item_a');
606
-
607
- assert.strictEqual(await db.get('item_a'), null);
608
- assert.deepStrictEqual(await db.get('itemXa'), { x: 2 });
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
- it('should handle keys with percent signs', async function() {
612
- await db.set('progress', '100%', 'done', true);
613
- await db.set('progress', 'abc', 'done', false);
614
-
615
- const p100 = await db.get('progress', '100%');
616
- assert.deepStrictEqual(p100, { done: true });
617
-
618
- const pabc = await db.get('progress', 'abc');
619
- assert.deepStrictEqual(pabc, { done: false });
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
- describe('Storybot-like Structure', function() {
624
- it('should handle dotted top-level keys with deep nesting', async function() {
625
- const sessionKey = 'stm.1769201539421.dawduxooy';
626
-
627
- await db.set(sessionKey, 'started', 'LuaGardenbot', true);
628
- await db.set(sessionKey, 'active', 'LuaGardenbot', true);
629
- await db.set(sessionKey, 'active', 'AmyWaybot', true);
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
- it('should handle replace map with special chars in keys', async function() {
639
- const sessionKey = 'stm.12345.abc';
640
-
641
- await db.set(sessionKey, 'replace', {
642
- '{lang}': 'es',
643
- '{vip_token}': '',
644
- '{first_name}': 'STM User',
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
- it('should handle node history with numeric-like keys', async function() {
657
- const sessionKey = 'stm.999.xyz';
658
-
659
- await db.set(sessionKey, 'node', 'history', 'c0', {
660
- from: 'LuaGardenbot',
661
- message: 'new contact',
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');
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
- it('should handle object expansion with replace map then set nested', async function() {
684
- const sessionKey = 'stm.12345.abc';
685
-
686
- // Store replace map as object
687
- await db.set(sessionKey, 'replace', {
688
- '{lang}': 'es',
689
- '{first_name}': 'User'
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
- it('should handle full session lifecycle without crash', async function() {
701
- const sessionKey = 'stm.1769201539421.dawduxooy';
702
-
703
- // Initial object-style set
704
- await db.set(sessionKey, 'replace', {
705
- '{lang}': 'es',
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
- it('should not mix sessions with similar dotted keys', async function() {
739
- await db.set('stm.111.aaa', 'data', 'session1');
740
- await db.set('stm.111.bbb', 'data', 'session2');
741
- await db.set('stm.222.aaa', 'data', 'session3');
742
-
743
- assert.deepStrictEqual(await db.get('stm.111.aaa'), { data: 'session1' });
744
- assert.deepStrictEqual(await db.get('stm.111.bbb'), { data: 'session2' });
745
- assert.deepStrictEqual(await db.get('stm.222.aaa'), { data: 'session3' });
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
- describe('Race Conditions', function() {
750
- it('should handle 100 concurrent increments correctly', async function() {
751
- this.timeout(5000);
752
-
753
- await db.set('counter', 0);
754
-
755
- // Run 100 concurrent increments
756
- const promises = Array.from({ length: 100 }, () => db.inc('counter', 1));
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
- });
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
- it('should handle concurrent read-modify-write operations', async function() {
764
- this.timeout(5000);
765
-
766
- await db.set('data', { counter: 0, items: [] });
767
-
768
- // Run 50 concurrent updates that read, modify, and write
769
- const promises = Array.from({ length: 50 }, (_, i) =>
770
- db.upd('data', (current) => ({
771
- counter: current.counter + 1,
772
- items: [...current.items, `item-${current.counter}`]
773
- }))
774
- );
775
-
776
- await Promise.all(promises);
777
-
778
- const finalData = await db.get('data');
779
- assert.strictEqual(finalData.counter, 50, 'Counter should be exactly 50');
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
- it('should handle concurrent sets on different keys', async function() {
788
- this.timeout(5000);
789
-
790
- // Run 50 concurrent set operations on different keys
791
- const promises = Array.from({ length: 50 }, (_, i) =>
792
- db.set('users', `user${i}`, { name: `User ${i}`, value: i })
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
- it('should handle concurrent add operations with unique IDs', async function() {
811
- this.timeout(5000);
812
-
813
- await db.set('items', {});
814
-
815
- // Run 50 concurrent add operations
816
- const promises = Array.from({ length: 50 }, (_, i) =>
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
- });
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
- it('should handle concurrent decrements correctly', async function() {
833
- this.timeout(5000);
834
-
835
- await db.set('inventory', 1000);
836
-
837
- // Run 100 concurrent decrements
838
- const promises = Array.from({ length: 100 }, () => db.dec('inventory', 5));
839
- await Promise.all(promises);
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
+ }