deepbase-sqlite 3.4.11 → 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 +143 -145
- package/src/index.d.ts +3 -0
- package/src/index.js +1 -2
- package/test/test.js +524 -733
package/README.md
CHANGED
|
@@ -27,7 +27,8 @@ import SqliteDriver from 'deepbase-sqlite';
|
|
|
27
27
|
|
|
28
28
|
const db = new DeepBase(new SqliteDriver({
|
|
29
29
|
path: './data',
|
|
30
|
-
name: 'mydb'
|
|
30
|
+
name: 'mydb',
|
|
31
|
+
pragma: 'balanced' // default — omit for same result
|
|
31
32
|
}));
|
|
32
33
|
|
|
33
34
|
await db.connect();
|
|
@@ -42,6 +43,7 @@ const alice = await db.get('users', 'alice');
|
|
|
42
43
|
new SqliteDriver({
|
|
43
44
|
path: './data', // Directory to store database files
|
|
44
45
|
name: 'default', // Database filename (without .db)
|
|
46
|
+
pragma: 'balanced', // Performance profile: 'none' | 'safe' | 'balanced' | 'fast'
|
|
45
47
|
nidAlphabet: 'ABC...', // Alphabet for ID generation
|
|
46
48
|
nidLength: 10 // Length of generated IDs
|
|
47
49
|
})
|
|
@@ -84,15 +86,56 @@ SQLite provides:
|
|
|
84
86
|
- **Isolation**: Concurrent operations don't interfere
|
|
85
87
|
- **Durability**: Committed data persists even after crashes
|
|
86
88
|
|
|
89
|
+
## Pragma Modes
|
|
90
|
+
|
|
91
|
+
`SqliteDriver` ships with four configurable performance profiles via the `pragma` option:
|
|
92
|
+
|
|
93
|
+
| Mode | `synchronous` | `cache_size` | `mmap_size` | `WITHOUT ROWID` | Use case |
|
|
94
|
+
|------|--------------|-------------|-------------|-----------------|----------|
|
|
95
|
+
| **none** | — | — | — | No | Backward-compatible with databases created by older versions |
|
|
96
|
+
| **safe** | FULL | 2 MB | off | Yes | Durability first, WAL + full fsync |
|
|
97
|
+
| **balanced** *(default)* | NORMAL | 8 MB | 256 MB | Yes | Best mix of speed and safety for most apps |
|
|
98
|
+
| **fast** | OFF | 16 MB | 256 MB | Yes | Maximum throughput — data may be lost on OS crash |
|
|
99
|
+
|
|
100
|
+
All WAL modes use `journal_mode=WAL`, `temp_store=MEMORY`, and `busy_timeout=5000ms`.
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
// Backward-compatible (no PRAGMAs, no WITHOUT ROWID)
|
|
104
|
+
new SqliteDriver({ name: 'mydb', pragma: 'none' })
|
|
105
|
+
|
|
106
|
+
// Maximum durability
|
|
107
|
+
new SqliteDriver({ name: 'mydb', pragma: 'safe' })
|
|
108
|
+
|
|
109
|
+
// Recommended (default)
|
|
110
|
+
new SqliteDriver({ name: 'mydb', pragma: 'balanced' })
|
|
111
|
+
|
|
112
|
+
// Maximum throughput
|
|
113
|
+
new SqliteDriver({ name: 'mydb', pragma: 'fast' })
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`SqliteFastDriver` is kept as a named alias for backward compatibility:
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
import { SqliteFastDriver } from 'deepbase-sqlite';
|
|
120
|
+
// SqliteFastDriver === SqliteDriver (same class, same options)
|
|
121
|
+
```
|
|
122
|
+
|
|
87
123
|
## Database Structure
|
|
88
124
|
|
|
89
125
|
Data is stored in a simple key-value table:
|
|
90
126
|
|
|
91
127
|
```sql
|
|
128
|
+
-- pragma: 'none' (legacy-compatible)
|
|
92
129
|
CREATE TABLE deepbase (
|
|
93
130
|
key TEXT PRIMARY KEY,
|
|
94
131
|
value TEXT NOT NULL
|
|
95
132
|
)
|
|
133
|
+
|
|
134
|
+
-- pragma: 'safe' | 'balanced' | 'fast' (optimized)
|
|
135
|
+
CREATE TABLE deepbase (
|
|
136
|
+
key TEXT PRIMARY KEY,
|
|
137
|
+
value TEXT NOT NULL
|
|
138
|
+
) WITHOUT ROWID
|
|
96
139
|
```
|
|
97
140
|
|
|
98
141
|
Example data:
|
|
@@ -167,6 +210,27 @@ data/
|
|
|
167
210
|
| Reliability | 💪 Very High | ⚠️ File corruption risk |
|
|
168
211
|
| Debugging | 🔧 SQL tools | 👁️ Easy to inspect |
|
|
169
212
|
|
|
213
|
+
## Benchmark — pragma modes
|
|
214
|
+
|
|
215
|
+
Median of 3 runs, 1 000 iterations per operation. `balanced` vs `none`:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Operation none safe balanced fast Bal vs None
|
|
219
|
+
─────────────────────────────────────────────────────────────────────────
|
|
220
|
+
Sequential Write 4,525 17,296 84,701 98,238 +1772%
|
|
221
|
+
Sequential Read 20,705 22,871 23,405 23,040 +13%
|
|
222
|
+
Update 2,689 10,666 17,764 18,857 +561%
|
|
223
|
+
Increment 4,291 14,129 25,734 26,364 +500%
|
|
224
|
+
Delete 3,806 12,010 20,884 21,074 +449%
|
|
225
|
+
Batch Write 3,813 17,763 87,209 83,045 +2187%
|
|
226
|
+
Concurrent Write 4,449 22,196 72,613 102,458 +1532%
|
|
227
|
+
Deep Write (5-lvl) 4,311 26,740 53,957 91,312 +1152%
|
|
228
|
+
Obj Expansion 1,997 365 34,633 47,318 +1634%
|
|
229
|
+
Session Lifecycle 572 2,465 3,700 3,792 +546%
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Disk usage is **29 % smaller** after compaction compared to `none`. All correctness checks pass on every mode.
|
|
233
|
+
|
|
170
234
|
## Best Practices
|
|
171
235
|
|
|
172
236
|
### Use Transactions for Bulk Operations
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepbase-sqlite",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.1",
|
|
4
4
|
"description": "⚡ DeepBase SQLite - SQLite database driver",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.cjs",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"better-sqlite3": "^11.8.1"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"deepbase": "^3.
|
|
20
|
+
"deepbase": "^3.5.1"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"test": "mocha test/test.js"
|
package/src/SqliteDriver.js
CHANGED
|
@@ -3,230 +3,239 @@ import Database from 'better-sqlite3';
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import * as pathModule from 'path';
|
|
5
5
|
|
|
6
|
+
const PRAGMA = {
|
|
7
|
+
none: null,
|
|
8
|
+
safe: {
|
|
9
|
+
journal_mode: 'WAL',
|
|
10
|
+
synchronous: 'FULL',
|
|
11
|
+
temp_store: 'MEMORY',
|
|
12
|
+
cache_size: -2000,
|
|
13
|
+
busy_timeout: 5000,
|
|
14
|
+
mmap_size: 0,
|
|
15
|
+
},
|
|
16
|
+
balanced: {
|
|
17
|
+
journal_mode: 'WAL',
|
|
18
|
+
synchronous: 'NORMAL',
|
|
19
|
+
temp_store: 'MEMORY',
|
|
20
|
+
cache_size: -8000,
|
|
21
|
+
busy_timeout: 5000,
|
|
22
|
+
mmap_size: 268435456,
|
|
23
|
+
},
|
|
24
|
+
fast: {
|
|
25
|
+
journal_mode: 'WAL',
|
|
26
|
+
synchronous: 'OFF',
|
|
27
|
+
temp_store: 'MEMORY',
|
|
28
|
+
cache_size: -16000,
|
|
29
|
+
busy_timeout: 5000,
|
|
30
|
+
mmap_size: 268435456,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
6
34
|
export class SqliteDriver extends DeepBaseDriver {
|
|
7
35
|
static _instances = {};
|
|
8
|
-
|
|
9
|
-
constructor({name, path, ...opts} = {}) {
|
|
36
|
+
|
|
37
|
+
constructor({ name, path, pragma, ...opts } = {}) {
|
|
10
38
|
super(opts);
|
|
11
|
-
|
|
12
|
-
this.name = name ||
|
|
39
|
+
|
|
40
|
+
this.name = name || 'default';
|
|
13
41
|
this.path = path || new URL('../../../db', import.meta.url).pathname;
|
|
14
|
-
|
|
42
|
+
this.pragma = pragma || 'balanced';
|
|
43
|
+
|
|
15
44
|
this.path = pathModule.resolve(this.path);
|
|
16
45
|
this.fileName = pathModule.join(this.path, `${this.name}.db`);
|
|
17
|
-
|
|
18
|
-
// Singleton pattern per file
|
|
46
|
+
|
|
19
47
|
if (SqliteDriver._instances[this.fileName]) {
|
|
20
48
|
return SqliteDriver._instances[this.fileName];
|
|
21
49
|
}
|
|
22
|
-
|
|
50
|
+
|
|
23
51
|
this.db = null;
|
|
24
52
|
SqliteDriver._instances[this.fileName] = this;
|
|
25
53
|
}
|
|
26
|
-
|
|
54
|
+
|
|
27
55
|
async connect() {
|
|
56
|
+
if (this._connected) return;
|
|
57
|
+
|
|
28
58
|
if (!fs.existsSync(this.path)) {
|
|
29
59
|
fs.mkdirSync(this.path, { recursive: true });
|
|
30
60
|
}
|
|
31
|
-
|
|
61
|
+
|
|
32
62
|
this.db = new Database(this.fileName);
|
|
33
|
-
|
|
34
|
-
|
|
63
|
+
|
|
64
|
+
const cfg = PRAGMA[this.pragma];
|
|
65
|
+
if (cfg) {
|
|
66
|
+
this.db.pragma(`journal_mode = ${cfg.journal_mode}`);
|
|
67
|
+
this.db.pragma(`synchronous = ${cfg.synchronous}`);
|
|
68
|
+
this.db.pragma(`temp_store = ${cfg.temp_store}`);
|
|
69
|
+
this.db.pragma(`cache_size = ${cfg.cache_size}`);
|
|
70
|
+
this.db.pragma(`busy_timeout = ${cfg.busy_timeout}`);
|
|
71
|
+
this.db.pragma(`mmap_size = ${cfg.mmap_size}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const withoutRowid = cfg ? ' WITHOUT ROWID' : '';
|
|
35
75
|
this.db.exec(`
|
|
36
76
|
CREATE TABLE IF NOT EXISTS deepbase (
|
|
37
77
|
key TEXT PRIMARY KEY,
|
|
38
78
|
value TEXT NOT NULL
|
|
39
|
-
)
|
|
79
|
+
)${withoutRowid}
|
|
40
80
|
`);
|
|
41
|
-
|
|
42
|
-
// Prepare statements for better performance
|
|
81
|
+
|
|
43
82
|
this.getStmt = this.db.prepare('SELECT value FROM deepbase WHERE key = ?');
|
|
44
83
|
this.setStmt = this.db.prepare('INSERT OR REPLACE INTO deepbase (key, value) VALUES (?, ?)');
|
|
45
84
|
this.delStmt = this.db.prepare('DELETE FROM deepbase WHERE key = ?');
|
|
46
85
|
this.getAllStmt = this.db.prepare('SELECT key, value FROM deepbase');
|
|
47
|
-
this.getKeysLikeStmt = this.db.prepare(
|
|
48
|
-
|
|
86
|
+
this.getKeysLikeStmt = this.db.prepare("SELECT key, value FROM deepbase WHERE key LIKE ? ESCAPE '!'");
|
|
87
|
+
this.delChildrenStmt = this.db.prepare("DELETE FROM deepbase WHERE key LIKE ? ESCAPE '!'");
|
|
88
|
+
this.hasChildrenStmt = this.db.prepare("SELECT 1 FROM deepbase WHERE key LIKE ? ESCAPE '!' LIMIT 1");
|
|
89
|
+
|
|
90
|
+
this._setTxn = this.db.transaction((key, jsonValue, keys) => {
|
|
91
|
+
this._expandParentObjects(keys);
|
|
92
|
+
this.setStmt.run(key, jsonValue);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
this._delTxn = this.db.transaction((key, likePattern) => {
|
|
96
|
+
this.delStmt.run(key);
|
|
97
|
+
this.delChildrenStmt.run(likePattern);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
this._updTxn = this.db.transaction((keys, func) => {
|
|
101
|
+
const currentValue = this._getSync(keys);
|
|
102
|
+
const newValue = func(currentValue);
|
|
103
|
+
const key = this._pathToKey(keys);
|
|
104
|
+
this._expandParentObjects(keys);
|
|
105
|
+
this.setStmt.run(key, JSON.stringify(newValue));
|
|
106
|
+
return keys;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
this._setRootTxn = this.db.transaction((entries) => {
|
|
110
|
+
this.db.exec('DELETE FROM deepbase');
|
|
111
|
+
for (const [key, value] of entries) {
|
|
112
|
+
this.setStmt.run(key, JSON.stringify(value));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
49
116
|
this._connected = true;
|
|
50
117
|
}
|
|
51
|
-
|
|
118
|
+
|
|
52
119
|
async disconnect() {
|
|
53
120
|
if (this.db) {
|
|
54
121
|
this.db.close();
|
|
55
122
|
this.db = null;
|
|
56
123
|
}
|
|
124
|
+
this._connected = false;
|
|
57
125
|
}
|
|
58
|
-
|
|
126
|
+
|
|
59
127
|
async get(...args) {
|
|
60
128
|
return this._getSync(args);
|
|
61
129
|
}
|
|
62
|
-
|
|
130
|
+
|
|
63
131
|
_getSync(args) {
|
|
64
132
|
if (args.length === 0) {
|
|
65
|
-
// Get root object
|
|
66
133
|
return this._getRootObject();
|
|
67
134
|
}
|
|
68
|
-
|
|
135
|
+
|
|
69
136
|
const key = this._pathToKey(args);
|
|
70
137
|
const row = this.getStmt.get(key);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const children = this.getKeysLikeStmt.all(this._likePrefix(key));
|
|
74
|
-
|
|
75
|
-
// If there are children, build object from them
|
|
76
|
-
if (children.length > 0) {
|
|
77
|
-
return this._buildObjectFromChildren(key);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// If direct key exists and no children, return it
|
|
138
|
+
const likePattern = this._likePrefix(key);
|
|
139
|
+
|
|
81
140
|
if (row) {
|
|
141
|
+
if (this.hasChildrenStmt.get(likePattern)) {
|
|
142
|
+
return this._buildObjectFromChildren(key, likePattern);
|
|
143
|
+
}
|
|
82
144
|
return JSON.parse(row.value);
|
|
83
145
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const parentPath = this._findParentWithValue(args);
|
|
88
|
-
if (parentPath) {
|
|
89
|
-
const parentKey = this._pathToKey(parentPath);
|
|
90
|
-
const parentRow = this.getStmt.get(parentKey);
|
|
91
|
-
if (parentRow) {
|
|
92
|
-
const parentValue = JSON.parse(parentRow.value);
|
|
93
|
-
const relativePath = args.slice(parentPath.length);
|
|
94
|
-
return this._getFromObject(parentValue, relativePath);
|
|
95
|
-
}
|
|
146
|
+
|
|
147
|
+
if (this.hasChildrenStmt.get(likePattern)) {
|
|
148
|
+
return this._buildObjectFromChildren(key, likePattern);
|
|
96
149
|
}
|
|
97
|
-
|
|
98
|
-
return
|
|
150
|
+
|
|
151
|
+
return this._getFromParent(args);
|
|
99
152
|
}
|
|
100
|
-
|
|
153
|
+
|
|
101
154
|
async set(...args) {
|
|
102
155
|
if (args.length === 0) {
|
|
103
156
|
throw new Error('set() requires at least one argument');
|
|
104
157
|
}
|
|
105
|
-
|
|
158
|
+
|
|
106
159
|
if (args.length === 1) {
|
|
107
|
-
// Setting root object
|
|
108
160
|
await this._setRootObject(args[0]);
|
|
109
161
|
return [];
|
|
110
162
|
}
|
|
111
|
-
|
|
163
|
+
|
|
112
164
|
return this._setSync(args);
|
|
113
165
|
}
|
|
114
|
-
|
|
166
|
+
|
|
115
167
|
_setSync(args) {
|
|
116
168
|
const keys = args.slice(0, -1);
|
|
117
169
|
const value = args[args.length - 1];
|
|
118
170
|
const key = this._pathToKey(keys);
|
|
119
|
-
|
|
120
|
-
// Use transaction to make expansion and set atomic
|
|
121
|
-
const transaction = this.db.transaction(() => {
|
|
122
|
-
// Check if any parent path exists as an object that needs to be expanded
|
|
123
|
-
this._expandParentObjects(keys);
|
|
124
|
-
|
|
125
|
-
this.setStmt.run(key, JSON.stringify(value));
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
transaction();
|
|
171
|
+
this._setTxn(key, JSON.stringify(value), keys);
|
|
129
172
|
return keys;
|
|
130
173
|
}
|
|
131
|
-
|
|
174
|
+
|
|
132
175
|
async del(...keys) {
|
|
133
176
|
if (keys.length === 0) {
|
|
134
|
-
// Delete everything
|
|
135
177
|
this.db.exec('DELETE FROM deepbase');
|
|
136
178
|
return;
|
|
137
179
|
}
|
|
138
|
-
|
|
180
|
+
|
|
139
181
|
const key = this._pathToKey(keys);
|
|
140
|
-
|
|
141
|
-
// Use transaction to make deletion atomic
|
|
142
|
-
const transaction = this.db.transaction(() => {
|
|
143
|
-
// Delete the key itself
|
|
144
|
-
this.delStmt.run(key);
|
|
145
|
-
|
|
146
|
-
// Delete all children
|
|
147
|
-
this.db.prepare('DELETE FROM deepbase WHERE key LIKE ? ESCAPE \'!\'').run(this._likePrefix(key));
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
transaction();
|
|
182
|
+
this._delTxn(key, this._likePrefix(key));
|
|
151
183
|
}
|
|
152
|
-
|
|
184
|
+
|
|
153
185
|
async inc(...args) {
|
|
154
186
|
const i = args.pop();
|
|
155
187
|
return this.upd(...args, n => n + i);
|
|
156
188
|
}
|
|
157
|
-
|
|
189
|
+
|
|
158
190
|
async dec(...args) {
|
|
159
191
|
const i = args.pop();
|
|
160
192
|
return this.upd(...args, n => n - i);
|
|
161
193
|
}
|
|
162
|
-
|
|
194
|
+
|
|
163
195
|
async add(...keys) {
|
|
164
196
|
const value = keys.pop();
|
|
165
197
|
const id = this.nanoid();
|
|
166
198
|
await this.set(...[...keys, id], value);
|
|
167
199
|
return [...keys, id];
|
|
168
200
|
}
|
|
169
|
-
|
|
201
|
+
|
|
170
202
|
async upd(...args) {
|
|
171
203
|
const func = args.pop();
|
|
172
204
|
const keys = args;
|
|
173
|
-
|
|
174
|
-
// Use transaction to make get+set atomic
|
|
175
|
-
const transaction = this.db.transaction(() => {
|
|
176
|
-
const currentValue = this._getSync(keys);
|
|
177
|
-
const newValue = func(currentValue);
|
|
178
|
-
this._setSync([...keys, newValue]);
|
|
179
|
-
return keys;
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
return transaction();
|
|
205
|
+
return this._updTxn(keys, func);
|
|
183
206
|
}
|
|
184
|
-
|
|
207
|
+
|
|
185
208
|
_getRootObject() {
|
|
186
209
|
const rows = this.getAllStmt.all();
|
|
187
210
|
const result = {};
|
|
188
|
-
|
|
189
211
|
for (const row of rows) {
|
|
190
212
|
const path = this._keyToPath(row.key);
|
|
191
213
|
const value = JSON.parse(row.value);
|
|
192
214
|
this._setNestedValue(result, path, value);
|
|
193
215
|
}
|
|
194
|
-
|
|
195
216
|
return result;
|
|
196
217
|
}
|
|
197
|
-
|
|
218
|
+
|
|
198
219
|
async _setRootObject(obj) {
|
|
199
|
-
// Clear existing data
|
|
200
|
-
this.db.exec('DELETE FROM deepbase');
|
|
201
|
-
|
|
202
|
-
// Flatten and insert
|
|
203
220
|
const entries = this._flattenObject(obj);
|
|
204
|
-
|
|
205
|
-
for (const [key, value] of entries) {
|
|
206
|
-
this.setStmt.run(key, JSON.stringify(value));
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
insertMany(entries);
|
|
221
|
+
this._setRootTxn(entries);
|
|
211
222
|
}
|
|
212
|
-
|
|
213
|
-
_buildObjectFromChildren(parentKey) {
|
|
214
|
-
const rows = this.getKeysLikeStmt.all(parentKey ? this._likePrefix(parentKey) : '%');
|
|
223
|
+
|
|
224
|
+
_buildObjectFromChildren(parentKey, likePattern) {
|
|
225
|
+
const rows = this.getKeysLikeStmt.all(likePattern || (parentKey ? this._likePrefix(parentKey) : '%'));
|
|
215
226
|
const result = {};
|
|
216
|
-
|
|
227
|
+
const parentPathLen = parentKey ? this._keyToPath(parentKey).length : 0;
|
|
228
|
+
|
|
217
229
|
for (const row of rows) {
|
|
218
230
|
const fullPath = this._keyToPath(row.key);
|
|
219
|
-
const relativePath =
|
|
220
|
-
? fullPath.slice(this._keyToPath(parentKey).length)
|
|
221
|
-
: fullPath;
|
|
222
|
-
|
|
231
|
+
const relativePath = fullPath.slice(parentPathLen);
|
|
223
232
|
const value = JSON.parse(row.value);
|
|
224
233
|
this._setNestedValue(result, relativePath, value);
|
|
225
234
|
}
|
|
226
|
-
|
|
235
|
+
|
|
227
236
|
return result;
|
|
228
237
|
}
|
|
229
|
-
|
|
238
|
+
|
|
230
239
|
_escapeLikePattern(str) {
|
|
231
240
|
return str.replace(/[!%_]/g, '!$&');
|
|
232
241
|
}
|
|
@@ -237,85 +246,76 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
237
246
|
|
|
238
247
|
_setNestedValue(obj, path, value) {
|
|
239
248
|
if (path.length === 0) return;
|
|
240
|
-
|
|
249
|
+
|
|
241
250
|
if (path.length === 1) {
|
|
242
251
|
obj[path[0]] = value;
|
|
243
252
|
return;
|
|
244
253
|
}
|
|
245
|
-
|
|
254
|
+
|
|
246
255
|
const key = path[0];
|
|
247
|
-
if (!obj.hasOwnProperty(key) || typeof obj[key] !==
|
|
256
|
+
if (!obj.hasOwnProperty(key) || typeof obj[key] !== 'object') {
|
|
248
257
|
obj[key] = {};
|
|
249
258
|
}
|
|
250
|
-
|
|
259
|
+
|
|
251
260
|
this._setNestedValue(obj[key], path.slice(1), value);
|
|
252
261
|
}
|
|
253
|
-
|
|
262
|
+
|
|
254
263
|
_flattenObject(obj, prefix = '') {
|
|
255
264
|
const entries = [];
|
|
256
|
-
|
|
265
|
+
|
|
257
266
|
for (const [key, value] of Object.entries(obj)) {
|
|
258
267
|
const escapedKey = this._escapeDots(String(key));
|
|
259
268
|
const fullKey = prefix ? `${prefix}.${escapedKey}` : escapedKey;
|
|
260
|
-
|
|
269
|
+
|
|
261
270
|
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
262
271
|
entries.push(...this._flattenObject(value, fullKey));
|
|
263
272
|
} else {
|
|
264
273
|
entries.push([fullKey, value]);
|
|
265
274
|
}
|
|
266
275
|
}
|
|
267
|
-
|
|
276
|
+
|
|
268
277
|
return entries;
|
|
269
278
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
// Try to find a parent path that has a stored value
|
|
273
|
-
// For example: if looking for ['users', 'abc', 'name']
|
|
274
|
-
// and 'users.abc' exists as a stored JSON object, return ['users', 'abc']
|
|
279
|
+
|
|
280
|
+
_getFromParent(path) {
|
|
275
281
|
for (let i = path.length - 1; i > 0; i--) {
|
|
276
282
|
const parentPath = path.slice(0, i);
|
|
277
283
|
const parentKey = this._pathToKey(parentPath);
|
|
278
284
|
const row = this.getStmt.get(parentKey);
|
|
279
285
|
if (row) {
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
return parentPath;
|
|
286
|
+
const parentValue = JSON.parse(row.value);
|
|
287
|
+
if (parentValue !== null && typeof parentValue === 'object') {
|
|
288
|
+
return this._getFromObject(parentValue, path.slice(i));
|
|
284
289
|
}
|
|
285
290
|
}
|
|
286
291
|
}
|
|
287
292
|
return null;
|
|
288
293
|
}
|
|
289
|
-
|
|
294
|
+
|
|
290
295
|
_getFromObject(obj, path) {
|
|
291
296
|
if (path.length === 0) return obj;
|
|
292
297
|
if (path.length === 1) {
|
|
293
298
|
return obj === null || obj[path[0]] === undefined ? null : obj[path[0]];
|
|
294
299
|
}
|
|
295
|
-
|
|
300
|
+
|
|
296
301
|
const key = path[0];
|
|
297
302
|
if (!obj.hasOwnProperty(key)) return null;
|
|
298
|
-
|
|
303
|
+
|
|
299
304
|
return this._getFromObject(obj[key], path.slice(1));
|
|
300
305
|
}
|
|
301
|
-
|
|
306
|
+
|
|
302
307
|
_expandParentObjects(path) {
|
|
303
|
-
// Check each parent level (not including the path itself) to see if it exists as an object that needs expanding
|
|
304
|
-
// For example, if path is ['config', 'theme'], check if 'config' exists as an object
|
|
305
308
|
for (let i = 1; i < path.length; i++) {
|
|
306
309
|
const parentPath = path.slice(0, i);
|
|
307
310
|
const parentKey = this._pathToKey(parentPath);
|
|
308
311
|
const parentRow = this.getStmt.get(parentKey);
|
|
309
|
-
|
|
312
|
+
|
|
310
313
|
if (parentRow) {
|
|
311
314
|
const parentValue = JSON.parse(parentRow.value);
|
|
312
|
-
|
|
313
|
-
// If it's an object (not array or primitive), expand it
|
|
315
|
+
|
|
314
316
|
if (parentValue !== null && typeof parentValue === 'object' && !Array.isArray(parentValue)) {
|
|
315
|
-
// Delete the parent key
|
|
316
317
|
this.delStmt.run(parentKey);
|
|
317
|
-
|
|
318
|
-
// Insert all properties as individual keys
|
|
318
|
+
|
|
319
319
|
const entries = this._flattenObject(parentValue, parentKey);
|
|
320
320
|
for (const [key, value] of entries) {
|
|
321
321
|
this.setStmt.run(key, JSON.stringify(value));
|
|
@@ -327,5 +327,3 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
export default SqliteDriver;
|
|
330
|
-
|
|
331
|
-
|
package/src/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { DeepBaseDriver, DeepBaseDriverOptions } from 'deepbase';
|
|
|
3
3
|
export interface SqliteDriverOptions extends DeepBaseDriverOptions {
|
|
4
4
|
name?: string;
|
|
5
5
|
path?: string;
|
|
6
|
+
pragma?: 'none' | 'safe' | 'balanced' | 'fast';
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export class SqliteDriver extends DeepBaseDriver {
|
|
@@ -11,6 +12,8 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
11
12
|
name: string;
|
|
12
13
|
path: string;
|
|
13
14
|
fileName: string;
|
|
15
|
+
pragma: string;
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
export { SqliteDriver as SqliteFastDriver };
|
|
16
19
|
export default SqliteDriver;
|