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/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,54 +3,119 @@ 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() {
|
|
28
56
|
if (this._connected) return;
|
|
29
|
-
|
|
57
|
+
|
|
30
58
|
if (!fs.existsSync(this.path)) {
|
|
31
59
|
fs.mkdirSync(this.path, { recursive: true });
|
|
32
60
|
}
|
|
33
|
-
|
|
61
|
+
|
|
34
62
|
this.db = new Database(this.fileName);
|
|
35
|
-
|
|
36
|
-
|
|
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' : '';
|
|
37
75
|
this.db.exec(`
|
|
38
76
|
CREATE TABLE IF NOT EXISTS deepbase (
|
|
39
77
|
key TEXT PRIMARY KEY,
|
|
40
78
|
value TEXT NOT NULL
|
|
41
|
-
)
|
|
79
|
+
)${withoutRowid}
|
|
42
80
|
`);
|
|
43
|
-
|
|
44
|
-
// Prepare statements for better performance
|
|
81
|
+
|
|
45
82
|
this.getStmt = this.db.prepare('SELECT value FROM deepbase WHERE key = ?');
|
|
46
83
|
this.setStmt = this.db.prepare('INSERT OR REPLACE INTO deepbase (key, value) VALUES (?, ?)');
|
|
47
84
|
this.delStmt = this.db.prepare('DELETE FROM deepbase WHERE key = ?');
|
|
48
85
|
this.getAllStmt = this.db.prepare('SELECT key, value FROM deepbase');
|
|
49
|
-
this.getKeysLikeStmt = this.db.prepare(
|
|
50
|
-
|
|
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
|
+
|
|
51
116
|
this._connected = true;
|
|
52
117
|
}
|
|
53
|
-
|
|
118
|
+
|
|
54
119
|
async disconnect() {
|
|
55
120
|
if (this.db) {
|
|
56
121
|
this.db.close();
|
|
@@ -58,178 +123,119 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
58
123
|
}
|
|
59
124
|
this._connected = false;
|
|
60
125
|
}
|
|
61
|
-
|
|
126
|
+
|
|
62
127
|
async get(...args) {
|
|
63
128
|
return this._getSync(args);
|
|
64
129
|
}
|
|
65
|
-
|
|
130
|
+
|
|
66
131
|
_getSync(args) {
|
|
67
132
|
if (args.length === 0) {
|
|
68
|
-
// Get root object
|
|
69
133
|
return this._getRootObject();
|
|
70
134
|
}
|
|
71
|
-
|
|
135
|
+
|
|
72
136
|
const key = this._pathToKey(args);
|
|
73
137
|
const row = this.getStmt.get(key);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const children = this.getKeysLikeStmt.all(this._likePrefix(key));
|
|
77
|
-
|
|
78
|
-
// If there are children, build object from them
|
|
79
|
-
if (children.length > 0) {
|
|
80
|
-
return this._buildObjectFromChildren(key);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// If direct key exists and no children, return it
|
|
138
|
+
const likePattern = this._likePrefix(key);
|
|
139
|
+
|
|
84
140
|
if (row) {
|
|
141
|
+
if (this.hasChildrenStmt.get(likePattern)) {
|
|
142
|
+
return this._buildObjectFromChildren(key, likePattern);
|
|
143
|
+
}
|
|
85
144
|
return JSON.parse(row.value);
|
|
86
145
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const parentPath = this._findParentWithValue(args);
|
|
91
|
-
if (parentPath) {
|
|
92
|
-
const parentKey = this._pathToKey(parentPath);
|
|
93
|
-
const parentRow = this.getStmt.get(parentKey);
|
|
94
|
-
if (parentRow) {
|
|
95
|
-
const parentValue = JSON.parse(parentRow.value);
|
|
96
|
-
const relativePath = args.slice(parentPath.length);
|
|
97
|
-
return this._getFromObject(parentValue, relativePath);
|
|
98
|
-
}
|
|
146
|
+
|
|
147
|
+
if (this.hasChildrenStmt.get(likePattern)) {
|
|
148
|
+
return this._buildObjectFromChildren(key, likePattern);
|
|
99
149
|
}
|
|
100
|
-
|
|
101
|
-
return
|
|
150
|
+
|
|
151
|
+
return this._getFromParent(args);
|
|
102
152
|
}
|
|
103
|
-
|
|
153
|
+
|
|
104
154
|
async set(...args) {
|
|
105
155
|
if (args.length === 0) {
|
|
106
156
|
throw new Error('set() requires at least one argument');
|
|
107
157
|
}
|
|
108
|
-
|
|
158
|
+
|
|
109
159
|
if (args.length === 1) {
|
|
110
|
-
// Setting root object
|
|
111
160
|
await this._setRootObject(args[0]);
|
|
112
161
|
return [];
|
|
113
162
|
}
|
|
114
|
-
|
|
163
|
+
|
|
115
164
|
return this._setSync(args);
|
|
116
165
|
}
|
|
117
|
-
|
|
166
|
+
|
|
118
167
|
_setSync(args) {
|
|
119
168
|
const keys = args.slice(0, -1);
|
|
120
169
|
const value = args[args.length - 1];
|
|
121
170
|
const key = this._pathToKey(keys);
|
|
122
|
-
|
|
123
|
-
// Use transaction to make expansion and set atomic
|
|
124
|
-
const transaction = this.db.transaction(() => {
|
|
125
|
-
// Check if any parent path exists as an object that needs to be expanded
|
|
126
|
-
this._expandParentObjects(keys);
|
|
127
|
-
|
|
128
|
-
this.setStmt.run(key, JSON.stringify(value));
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
transaction();
|
|
171
|
+
this._setTxn(key, JSON.stringify(value), keys);
|
|
132
172
|
return keys;
|
|
133
173
|
}
|
|
134
|
-
|
|
174
|
+
|
|
135
175
|
async del(...keys) {
|
|
136
176
|
if (keys.length === 0) {
|
|
137
|
-
// Delete everything
|
|
138
177
|
this.db.exec('DELETE FROM deepbase');
|
|
139
178
|
return;
|
|
140
179
|
}
|
|
141
|
-
|
|
180
|
+
|
|
142
181
|
const key = this._pathToKey(keys);
|
|
143
|
-
|
|
144
|
-
// Use transaction to make deletion atomic
|
|
145
|
-
const transaction = this.db.transaction(() => {
|
|
146
|
-
// Delete the key itself
|
|
147
|
-
this.delStmt.run(key);
|
|
148
|
-
|
|
149
|
-
// Delete all children
|
|
150
|
-
this.db.prepare('DELETE FROM deepbase WHERE key LIKE ? ESCAPE \'!\'').run(this._likePrefix(key));
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
transaction();
|
|
182
|
+
this._delTxn(key, this._likePrefix(key));
|
|
154
183
|
}
|
|
155
|
-
|
|
184
|
+
|
|
156
185
|
async inc(...args) {
|
|
157
186
|
const i = args.pop();
|
|
158
187
|
return this.upd(...args, n => n + i);
|
|
159
188
|
}
|
|
160
|
-
|
|
189
|
+
|
|
161
190
|
async dec(...args) {
|
|
162
191
|
const i = args.pop();
|
|
163
192
|
return this.upd(...args, n => n - i);
|
|
164
193
|
}
|
|
165
|
-
|
|
194
|
+
|
|
166
195
|
async add(...keys) {
|
|
167
196
|
const value = keys.pop();
|
|
168
197
|
const id = this.nanoid();
|
|
169
198
|
await this.set(...[...keys, id], value);
|
|
170
199
|
return [...keys, id];
|
|
171
200
|
}
|
|
172
|
-
|
|
201
|
+
|
|
173
202
|
async upd(...args) {
|
|
174
203
|
const func = args.pop();
|
|
175
204
|
const keys = args;
|
|
176
|
-
|
|
177
|
-
// Use transaction to make get+set atomic
|
|
178
|
-
const transaction = this.db.transaction(() => {
|
|
179
|
-
const currentValue = this._getSync(keys);
|
|
180
|
-
const newValue = func(currentValue);
|
|
181
|
-
this._setSync([...keys, newValue]);
|
|
182
|
-
return keys;
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
return transaction();
|
|
205
|
+
return this._updTxn(keys, func);
|
|
186
206
|
}
|
|
187
|
-
|
|
207
|
+
|
|
188
208
|
_getRootObject() {
|
|
189
209
|
const rows = this.getAllStmt.all();
|
|
190
210
|
const result = {};
|
|
191
|
-
|
|
192
211
|
for (const row of rows) {
|
|
193
212
|
const path = this._keyToPath(row.key);
|
|
194
213
|
const value = JSON.parse(row.value);
|
|
195
214
|
this._setNestedValue(result, path, value);
|
|
196
215
|
}
|
|
197
|
-
|
|
198
216
|
return result;
|
|
199
217
|
}
|
|
200
|
-
|
|
218
|
+
|
|
201
219
|
async _setRootObject(obj) {
|
|
202
|
-
// Clear existing data
|
|
203
|
-
this.db.exec('DELETE FROM deepbase');
|
|
204
|
-
|
|
205
|
-
// Flatten and insert
|
|
206
220
|
const entries = this._flattenObject(obj);
|
|
207
|
-
|
|
208
|
-
for (const [key, value] of entries) {
|
|
209
|
-
this.setStmt.run(key, JSON.stringify(value));
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
insertMany(entries);
|
|
221
|
+
this._setRootTxn(entries);
|
|
214
222
|
}
|
|
215
|
-
|
|
216
|
-
_buildObjectFromChildren(parentKey) {
|
|
217
|
-
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) : '%'));
|
|
218
226
|
const result = {};
|
|
219
|
-
|
|
227
|
+
const parentPathLen = parentKey ? this._keyToPath(parentKey).length : 0;
|
|
228
|
+
|
|
220
229
|
for (const row of rows) {
|
|
221
230
|
const fullPath = this._keyToPath(row.key);
|
|
222
|
-
const relativePath =
|
|
223
|
-
? fullPath.slice(this._keyToPath(parentKey).length)
|
|
224
|
-
: fullPath;
|
|
225
|
-
|
|
231
|
+
const relativePath = fullPath.slice(parentPathLen);
|
|
226
232
|
const value = JSON.parse(row.value);
|
|
227
233
|
this._setNestedValue(result, relativePath, value);
|
|
228
234
|
}
|
|
229
|
-
|
|
235
|
+
|
|
230
236
|
return result;
|
|
231
237
|
}
|
|
232
|
-
|
|
238
|
+
|
|
233
239
|
_escapeLikePattern(str) {
|
|
234
240
|
return str.replace(/[!%_]/g, '!$&');
|
|
235
241
|
}
|
|
@@ -240,85 +246,76 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
240
246
|
|
|
241
247
|
_setNestedValue(obj, path, value) {
|
|
242
248
|
if (path.length === 0) return;
|
|
243
|
-
|
|
249
|
+
|
|
244
250
|
if (path.length === 1) {
|
|
245
251
|
obj[path[0]] = value;
|
|
246
252
|
return;
|
|
247
253
|
}
|
|
248
|
-
|
|
254
|
+
|
|
249
255
|
const key = path[0];
|
|
250
|
-
if (!obj.hasOwnProperty(key) || typeof obj[key] !==
|
|
256
|
+
if (!obj.hasOwnProperty(key) || typeof obj[key] !== 'object') {
|
|
251
257
|
obj[key] = {};
|
|
252
258
|
}
|
|
253
|
-
|
|
259
|
+
|
|
254
260
|
this._setNestedValue(obj[key], path.slice(1), value);
|
|
255
261
|
}
|
|
256
|
-
|
|
262
|
+
|
|
257
263
|
_flattenObject(obj, prefix = '') {
|
|
258
264
|
const entries = [];
|
|
259
|
-
|
|
265
|
+
|
|
260
266
|
for (const [key, value] of Object.entries(obj)) {
|
|
261
267
|
const escapedKey = this._escapeDots(String(key));
|
|
262
268
|
const fullKey = prefix ? `${prefix}.${escapedKey}` : escapedKey;
|
|
263
|
-
|
|
269
|
+
|
|
264
270
|
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
265
271
|
entries.push(...this._flattenObject(value, fullKey));
|
|
266
272
|
} else {
|
|
267
273
|
entries.push([fullKey, value]);
|
|
268
274
|
}
|
|
269
275
|
}
|
|
270
|
-
|
|
276
|
+
|
|
271
277
|
return entries;
|
|
272
278
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
// Try to find a parent path that has a stored value
|
|
276
|
-
// For example: if looking for ['users', 'abc', 'name']
|
|
277
|
-
// and 'users.abc' exists as a stored JSON object, return ['users', 'abc']
|
|
279
|
+
|
|
280
|
+
_getFromParent(path) {
|
|
278
281
|
for (let i = path.length - 1; i > 0; i--) {
|
|
279
282
|
const parentPath = path.slice(0, i);
|
|
280
283
|
const parentKey = this._pathToKey(parentPath);
|
|
281
284
|
const row = this.getStmt.get(parentKey);
|
|
282
285
|
if (row) {
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return parentPath;
|
|
286
|
+
const parentValue = JSON.parse(row.value);
|
|
287
|
+
if (parentValue !== null && typeof parentValue === 'object') {
|
|
288
|
+
return this._getFromObject(parentValue, path.slice(i));
|
|
287
289
|
}
|
|
288
290
|
}
|
|
289
291
|
}
|
|
290
292
|
return null;
|
|
291
293
|
}
|
|
292
|
-
|
|
294
|
+
|
|
293
295
|
_getFromObject(obj, path) {
|
|
294
296
|
if (path.length === 0) return obj;
|
|
295
297
|
if (path.length === 1) {
|
|
296
298
|
return obj === null || obj[path[0]] === undefined ? null : obj[path[0]];
|
|
297
299
|
}
|
|
298
|
-
|
|
300
|
+
|
|
299
301
|
const key = path[0];
|
|
300
302
|
if (!obj.hasOwnProperty(key)) return null;
|
|
301
|
-
|
|
303
|
+
|
|
302
304
|
return this._getFromObject(obj[key], path.slice(1));
|
|
303
305
|
}
|
|
304
|
-
|
|
306
|
+
|
|
305
307
|
_expandParentObjects(path) {
|
|
306
|
-
// Check each parent level (not including the path itself) to see if it exists as an object that needs expanding
|
|
307
|
-
// For example, if path is ['config', 'theme'], check if 'config' exists as an object
|
|
308
308
|
for (let i = 1; i < path.length; i++) {
|
|
309
309
|
const parentPath = path.slice(0, i);
|
|
310
310
|
const parentKey = this._pathToKey(parentPath);
|
|
311
311
|
const parentRow = this.getStmt.get(parentKey);
|
|
312
|
-
|
|
312
|
+
|
|
313
313
|
if (parentRow) {
|
|
314
314
|
const parentValue = JSON.parse(parentRow.value);
|
|
315
|
-
|
|
316
|
-
// If it's an object (not array or primitive), expand it
|
|
315
|
+
|
|
317
316
|
if (parentValue !== null && typeof parentValue === 'object' && !Array.isArray(parentValue)) {
|
|
318
|
-
// Delete the parent key
|
|
319
317
|
this.delStmt.run(parentKey);
|
|
320
|
-
|
|
321
|
-
// Insert all properties as individual keys
|
|
318
|
+
|
|
322
319
|
const entries = this._flattenObject(parentValue, parentKey);
|
|
323
320
|
for (const [key, value] of entries) {
|
|
324
321
|
this.setStmt.run(key, JSON.stringify(value));
|
|
@@ -330,5 +327,3 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
330
327
|
}
|
|
331
328
|
|
|
332
329
|
export default SqliteDriver;
|
|
333
|
-
|
|
334
|
-
|
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;
|