deepbase-sqlite 3.1.2 → 3.1.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepbase-sqlite",
3
- "version": "3.1.2",
3
+ "version": "3.1.6",
4
4
  "description": "⚡ DeepBase SQLite - SQLite database driver",
5
5
  "type": "module",
6
6
  "main": "src/index.cjs",
@@ -15,7 +15,7 @@
15
15
  "better-sqlite3": "^11.8.1"
16
16
  },
17
17
  "peerDependencies": {
18
- "deepbase": "^3.1.2"
18
+ "deepbase": "^3.1.6"
19
19
  },
20
20
  "scripts": {
21
21
  "test": "mocha test/test.js"
@@ -42,5 +42,5 @@
42
42
  "bugs": {
43
43
  "url": "https://github.com/clasen/DeepBase/issues"
44
44
  },
45
- "homepage": "https://github.com/clasen/DeepBase#readme"
45
+ "homepage": "https://github.com/clasen/DeepBase/tree/main/packages/driver-sqlite"
46
46
  }
@@ -57,6 +57,10 @@ export class SqliteDriver extends DeepBaseDriver {
57
57
  }
58
58
 
59
59
  async get(...args) {
60
+ return this._getSync(args);
61
+ }
62
+
63
+ _getSync(args) {
60
64
  if (args.length === 0) {
61
65
  // Get root object
62
66
  return this._getRootObject();
@@ -106,14 +110,23 @@ export class SqliteDriver extends DeepBaseDriver {
106
110
  return [];
107
111
  }
108
112
 
113
+ return this._setSync(args);
114
+ }
115
+
116
+ _setSync(args) {
109
117
  const keys = args.slice(0, -1);
110
118
  const value = args[args.length - 1];
111
119
  const key = this._pathToKey(keys);
112
120
 
113
- // Check if any parent path exists as an object that needs to be expanded
114
- this._expandParentObjects(keys);
121
+ // Use transaction to make expansion and set atomic
122
+ const transaction = this.db.transaction(() => {
123
+ // Check if any parent path exists as an object that needs to be expanded
124
+ this._expandParentObjects(keys);
125
+
126
+ this.setStmt.run(key, JSON.stringify(value));
127
+ });
115
128
 
116
- this.setStmt.run(key, JSON.stringify(value));
129
+ transaction();
117
130
  return keys;
118
131
  }
119
132
 
@@ -126,12 +139,17 @@ export class SqliteDriver extends DeepBaseDriver {
126
139
 
127
140
  const key = this._pathToKey(keys);
128
141
 
129
- // Delete the key itself
130
- this.delStmt.run(key);
142
+ // Use transaction to make deletion atomic
143
+ const transaction = this.db.transaction(() => {
144
+ // Delete the key itself
145
+ this.delStmt.run(key);
146
+
147
+ // Delete all children
148
+ const childKey = key + '.';
149
+ this.db.prepare('DELETE FROM deepbase WHERE key LIKE ?').run(childKey + '%');
150
+ });
131
151
 
132
- // Delete all children
133
- const childKey = key + '.';
134
- this.db.prepare('DELETE FROM deepbase WHERE key LIKE ?').run(childKey + '%');
152
+ transaction();
135
153
  }
136
154
 
137
155
  async inc(...args) {
@@ -153,7 +171,17 @@ export class SqliteDriver extends DeepBaseDriver {
153
171
 
154
172
  async upd(...args) {
155
173
  const func = args.pop();
156
- return this.set(...args, func(await this.get(...args)));
174
+ const keys = args;
175
+
176
+ // Use transaction to make get+set atomic
177
+ const transaction = this.db.transaction(() => {
178
+ const currentValue = this._getSync(keys);
179
+ const newValue = func(currentValue);
180
+ this._setSync([...keys, newValue]);
181
+ return keys;
182
+ });
183
+
184
+ return transaction();
157
185
  }
158
186
 
159
187
  _getRootObject() {
package/test/test.js CHANGED
@@ -500,6 +500,103 @@ describe('SqliteDriver', function() {
500
500
  });
501
501
  });
502
502
  });
503
+
504
+ describe('Race Conditions', function() {
505
+ it('should handle 100 concurrent increments correctly', async function() {
506
+ this.timeout(5000);
507
+
508
+ await db.set('counter', 0);
509
+
510
+ // Run 100 concurrent increments
511
+ const promises = Array.from({ length: 100 }, () => db.inc('counter', 1));
512
+ await Promise.all(promises);
513
+
514
+ const result = await db.get('counter');
515
+ assert.strictEqual(result, 100, 'All increments should be applied atomically');
516
+ });
517
+
518
+ it('should handle concurrent read-modify-write operations', async function() {
519
+ this.timeout(5000);
520
+
521
+ await db.set('data', { counter: 0, items: [] });
522
+
523
+ // Run 50 concurrent updates that read, modify, and write
524
+ const promises = Array.from({ length: 50 }, (_, i) =>
525
+ db.upd('data', (current) => ({
526
+ counter: current.counter + 1,
527
+ items: [...current.items, `item-${current.counter}`]
528
+ }))
529
+ );
530
+
531
+ await Promise.all(promises);
532
+
533
+ const finalData = await db.get('data');
534
+ assert.strictEqual(finalData.counter, 50, 'Counter should be exactly 50');
535
+ assert.strictEqual(finalData.items.length, 50, 'Should have exactly 50 items');
536
+
537
+ // Check that all items have unique values
538
+ const uniqueItems = new Set(finalData.items);
539
+ assert.strictEqual(uniqueItems.size, 50, 'All items should be unique');
540
+ });
541
+
542
+ it('should handle concurrent sets on different keys', async function() {
543
+ this.timeout(5000);
544
+
545
+ // Run 50 concurrent set operations on different keys
546
+ const promises = Array.from({ length: 50 }, (_, i) =>
547
+ db.set('users', `user${i}`, { name: `User ${i}`, value: i })
548
+ );
549
+
550
+ await Promise.all(promises);
551
+
552
+ // Verify all keys exist and have correct values
553
+ const users = await db.get('users');
554
+ assert.strictEqual(Object.keys(users).length, 50, 'Should have 50 users');
555
+
556
+ for (let i = 0; i < 50; i++) {
557
+ assert.deepStrictEqual(
558
+ users[`user${i}`],
559
+ { name: `User ${i}`, value: i },
560
+ `User ${i} should have correct data`
561
+ );
562
+ }
563
+ });
564
+
565
+ it('should handle concurrent add operations with unique IDs', async function() {
566
+ this.timeout(5000);
567
+
568
+ await db.set('items', {});
569
+
570
+ // Run 50 concurrent add operations
571
+ const promises = Array.from({ length: 50 }, (_, i) =>
572
+ db.add('items', { value: i })
573
+ );
574
+
575
+ const results = await Promise.all(promises);
576
+
577
+ // Verify all items were added with unique IDs
578
+ const items = await db.get('items');
579
+ assert.strictEqual(Object.keys(items).length, 50, 'Should have 50 items');
580
+
581
+ // Check that all IDs are unique
582
+ const ids = results.map(r => r[r.length - 1]);
583
+ const uniqueIds = new Set(ids);
584
+ assert.strictEqual(uniqueIds.size, 50, 'All IDs should be unique');
585
+ });
586
+
587
+ it('should handle concurrent decrements correctly', async function() {
588
+ this.timeout(5000);
589
+
590
+ await db.set('inventory', 1000);
591
+
592
+ // Run 100 concurrent decrements
593
+ const promises = Array.from({ length: 100 }, () => db.dec('inventory', 5));
594
+ await Promise.all(promises);
595
+
596
+ const result = await db.get('inventory');
597
+ assert.strictEqual(result, 500, 'All decrements should be applied atomically');
598
+ });
599
+ });
503
600
  });
504
601
 
505
602