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/README.md +67 -1
- package/package.json +2 -2
- package/src/SqliteDriver.js +166 -150
- package/src/index.d.ts +3 -0
- package/src/index.js +1 -2
- package/test/test.js +538 -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
|
})
|
|
@@ -75,6 +77,8 @@ Efficiently stores nested objects using a key-value schema:
|
|
|
75
77
|
- Values are stored as JSON
|
|
76
78
|
- Fast lookups for both exact keys and partial paths
|
|
77
79
|
|
|
80
|
+
Each row also stores a monotonic `seq` so reads that rebuild objects use `ORDER BY seq, key`. That matches JavaScript insertion order for sibling keys and keeps `shift()` / `pop()` (via `DeepBase.keys()`) aligned with `JsonDriver`. Existing databases pick up `seq` via `ALTER TABLE` on connect (legacy rows default to `0`, then tie-break by `key`).
|
|
81
|
+
|
|
78
82
|
### ACID Compliance
|
|
79
83
|
|
|
80
84
|
SQLite provides:
|
|
@@ -84,15 +88,56 @@ SQLite provides:
|
|
|
84
88
|
- **Isolation**: Concurrent operations don't interfere
|
|
85
89
|
- **Durability**: Committed data persists even after crashes
|
|
86
90
|
|
|
91
|
+
## Pragma Modes
|
|
92
|
+
|
|
93
|
+
`SqliteDriver` ships with four configurable performance profiles via the `pragma` option:
|
|
94
|
+
|
|
95
|
+
| Mode | `synchronous` | `cache_size` | `mmap_size` | `WITHOUT ROWID` | Use case |
|
|
96
|
+
|------|--------------|-------------|-------------|-----------------|----------|
|
|
97
|
+
| **none** | — | — | — | No | Backward-compatible with databases created by older versions |
|
|
98
|
+
| **safe** | FULL | 2 MB | off | Yes | Durability first, WAL + full fsync |
|
|
99
|
+
| **balanced** *(default)* | NORMAL | 8 MB | 256 MB | Yes | Best mix of speed and safety for most apps |
|
|
100
|
+
| **fast** | OFF | 16 MB | 256 MB | Yes | Maximum throughput — data may be lost on OS crash |
|
|
101
|
+
|
|
102
|
+
All WAL modes use `journal_mode=WAL`, `temp_store=MEMORY`, and `busy_timeout=5000ms`.
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
// Backward-compatible (no PRAGMAs, no WITHOUT ROWID)
|
|
106
|
+
new SqliteDriver({ name: 'mydb', pragma: 'none' })
|
|
107
|
+
|
|
108
|
+
// Maximum durability
|
|
109
|
+
new SqliteDriver({ name: 'mydb', pragma: 'safe' })
|
|
110
|
+
|
|
111
|
+
// Recommended (default)
|
|
112
|
+
new SqliteDriver({ name: 'mydb', pragma: 'balanced' })
|
|
113
|
+
|
|
114
|
+
// Maximum throughput
|
|
115
|
+
new SqliteDriver({ name: 'mydb', pragma: 'fast' })
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`SqliteFastDriver` is kept as a named alias for backward compatibility:
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
import { SqliteFastDriver } from 'deepbase-sqlite';
|
|
122
|
+
// SqliteFastDriver === SqliteDriver (same class, same options)
|
|
123
|
+
```
|
|
124
|
+
|
|
87
125
|
## Database Structure
|
|
88
126
|
|
|
89
127
|
Data is stored in a simple key-value table:
|
|
90
128
|
|
|
91
129
|
```sql
|
|
130
|
+
-- pragma: 'none' (legacy-compatible)
|
|
92
131
|
CREATE TABLE deepbase (
|
|
93
132
|
key TEXT PRIMARY KEY,
|
|
94
133
|
value TEXT NOT NULL
|
|
95
134
|
)
|
|
135
|
+
|
|
136
|
+
-- pragma: 'safe' | 'balanced' | 'fast' (optimized)
|
|
137
|
+
CREATE TABLE deepbase (
|
|
138
|
+
key TEXT PRIMARY KEY,
|
|
139
|
+
value TEXT NOT NULL
|
|
140
|
+
) WITHOUT ROWID
|
|
96
141
|
```
|
|
97
142
|
|
|
98
143
|
Example data:
|
|
@@ -167,6 +212,27 @@ data/
|
|
|
167
212
|
| Reliability | 💪 Very High | ⚠️ File corruption risk |
|
|
168
213
|
| Debugging | 🔧 SQL tools | 👁️ Easy to inspect |
|
|
169
214
|
|
|
215
|
+
## Benchmark — pragma modes
|
|
216
|
+
|
|
217
|
+
Median of 3 runs, 1 000 iterations per operation. `balanced` vs `none`:
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
Operation none safe balanced fast Bal vs None
|
|
221
|
+
─────────────────────────────────────────────────────────────────────────
|
|
222
|
+
Sequential Write 4,525 17,296 84,701 98,238 +1772%
|
|
223
|
+
Sequential Read 20,705 22,871 23,405 23,040 +13%
|
|
224
|
+
Update 2,689 10,666 17,764 18,857 +561%
|
|
225
|
+
Increment 4,291 14,129 25,734 26,364 +500%
|
|
226
|
+
Delete 3,806 12,010 20,884 21,074 +449%
|
|
227
|
+
Batch Write 3,813 17,763 87,209 83,045 +2187%
|
|
228
|
+
Concurrent Write 4,449 22,196 72,613 102,458 +1532%
|
|
229
|
+
Deep Write (5-lvl) 4,311 26,740 53,957 91,312 +1152%
|
|
230
|
+
Obj Expansion 1,997 365 34,633 47,318 +1634%
|
|
231
|
+
Session Lifecycle 572 2,465 3,700 3,792 +546%
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Disk usage is **29 % smaller** after compaction compared to `none`. All correctness checks pass on every mode.
|
|
235
|
+
|
|
170
236
|
## Best Practices
|
|
171
237
|
|
|
172
238
|
### 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.6.0",
|
|
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.6.0"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"test": "mocha test/test.js"
|
package/src/SqliteDriver.js
CHANGED
|
@@ -3,54 +3,135 @@ 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
|
-
|
|
27
|
-
|
|
54
|
+
|
|
55
|
+
_connectSync() {
|
|
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
|
-
value TEXT NOT NULL
|
|
41
|
-
|
|
78
|
+
value TEXT NOT NULL,
|
|
79
|
+
seq INTEGER NOT NULL DEFAULT 0
|
|
80
|
+
)${withoutRowid}
|
|
42
81
|
`);
|
|
43
|
-
|
|
44
|
-
|
|
82
|
+
|
|
83
|
+
const tableCols = this.db.prepare('PRAGMA table_info(deepbase)').all();
|
|
84
|
+
if (!tableCols.some((c) => c.name === 'seq')) {
|
|
85
|
+
this.db.exec('ALTER TABLE deepbase ADD COLUMN seq INTEGER NOT NULL DEFAULT 0');
|
|
86
|
+
}
|
|
87
|
+
|
|
45
88
|
this.getStmt = this.db.prepare('SELECT value FROM deepbase WHERE key = ?');
|
|
46
|
-
this.setStmt = this.db.prepare(
|
|
89
|
+
this.setStmt = this.db.prepare(`
|
|
90
|
+
INSERT INTO deepbase (key, value, seq)
|
|
91
|
+
VALUES (?, ?, (SELECT IFNULL(MAX(seq), 0) + 1 FROM deepbase))
|
|
92
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
93
|
+
`);
|
|
47
94
|
this.delStmt = this.db.prepare('DELETE FROM deepbase WHERE key = ?');
|
|
48
|
-
this.getAllStmt = this.db.prepare('SELECT key, value FROM deepbase');
|
|
49
|
-
this.getKeysLikeStmt = this.db.prepare(
|
|
50
|
-
|
|
95
|
+
this.getAllStmt = this.db.prepare('SELECT key, value FROM deepbase ORDER BY seq, key');
|
|
96
|
+
this.getKeysLikeStmt = this.db.prepare(
|
|
97
|
+
"SELECT key, value FROM deepbase WHERE key LIKE ? ESCAPE '!' ORDER BY seq, key",
|
|
98
|
+
);
|
|
99
|
+
this.delChildrenStmt = this.db.prepare("DELETE FROM deepbase WHERE key LIKE ? ESCAPE '!'");
|
|
100
|
+
this.hasChildrenStmt = this.db.prepare("SELECT 1 FROM deepbase WHERE key LIKE ? ESCAPE '!' LIMIT 1");
|
|
101
|
+
|
|
102
|
+
this._setTxn = this.db.transaction((key, jsonValue, keys) => {
|
|
103
|
+
this._expandParentObjects(keys);
|
|
104
|
+
this.setStmt.run(key, jsonValue);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
this._delTxn = this.db.transaction((key, likePattern) => {
|
|
108
|
+
this.delStmt.run(key);
|
|
109
|
+
this.delChildrenStmt.run(likePattern);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
this._updTxn = this.db.transaction((keys, func) => {
|
|
113
|
+
const currentValue = this._getSync(keys);
|
|
114
|
+
const newValue = func(currentValue);
|
|
115
|
+
const key = this._pathToKey(keys);
|
|
116
|
+
this._expandParentObjects(keys);
|
|
117
|
+
this.setStmt.run(key, JSON.stringify(newValue));
|
|
118
|
+
return keys;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
this._setRootTxn = this.db.transaction((entries) => {
|
|
122
|
+
this.db.exec('DELETE FROM deepbase');
|
|
123
|
+
for (const [key, value] of entries) {
|
|
124
|
+
this.setStmt.run(key, JSON.stringify(value));
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
51
128
|
this._connected = true;
|
|
52
129
|
}
|
|
53
|
-
|
|
130
|
+
|
|
131
|
+
async connect() {
|
|
132
|
+
this._connectSync();
|
|
133
|
+
}
|
|
134
|
+
|
|
54
135
|
async disconnect() {
|
|
55
136
|
if (this.db) {
|
|
56
137
|
this.db.close();
|
|
@@ -58,178 +139,124 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
58
139
|
}
|
|
59
140
|
this._connected = false;
|
|
60
141
|
}
|
|
61
|
-
|
|
142
|
+
|
|
62
143
|
async get(...args) {
|
|
63
144
|
return this._getSync(args);
|
|
64
145
|
}
|
|
65
|
-
|
|
146
|
+
|
|
147
|
+
getSync(...args) {
|
|
148
|
+
this._connectSync();
|
|
149
|
+
return this._getSync(args);
|
|
150
|
+
}
|
|
151
|
+
|
|
66
152
|
_getSync(args) {
|
|
67
153
|
if (args.length === 0) {
|
|
68
|
-
// Get root object
|
|
69
154
|
return this._getRootObject();
|
|
70
155
|
}
|
|
71
|
-
|
|
156
|
+
|
|
72
157
|
const key = this._pathToKey(args);
|
|
73
158
|
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
|
|
159
|
+
const likePattern = this._likePrefix(key);
|
|
160
|
+
|
|
84
161
|
if (row) {
|
|
162
|
+
if (this.hasChildrenStmt.get(likePattern)) {
|
|
163
|
+
return this._buildObjectFromChildren(key, likePattern);
|
|
164
|
+
}
|
|
85
165
|
return JSON.parse(row.value);
|
|
86
166
|
}
|
|
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
|
-
}
|
|
167
|
+
|
|
168
|
+
if (this.hasChildrenStmt.get(likePattern)) {
|
|
169
|
+
return this._buildObjectFromChildren(key, likePattern);
|
|
99
170
|
}
|
|
100
|
-
|
|
101
|
-
return
|
|
171
|
+
|
|
172
|
+
return this._getFromParent(args);
|
|
102
173
|
}
|
|
103
|
-
|
|
174
|
+
|
|
104
175
|
async set(...args) {
|
|
105
176
|
if (args.length === 0) {
|
|
106
177
|
throw new Error('set() requires at least one argument');
|
|
107
178
|
}
|
|
108
|
-
|
|
179
|
+
|
|
109
180
|
if (args.length === 1) {
|
|
110
|
-
// Setting root object
|
|
111
181
|
await this._setRootObject(args[0]);
|
|
112
182
|
return [];
|
|
113
183
|
}
|
|
114
|
-
|
|
184
|
+
|
|
115
185
|
return this._setSync(args);
|
|
116
186
|
}
|
|
117
|
-
|
|
187
|
+
|
|
118
188
|
_setSync(args) {
|
|
119
189
|
const keys = args.slice(0, -1);
|
|
120
190
|
const value = args[args.length - 1];
|
|
121
191
|
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();
|
|
192
|
+
this._setTxn(key, JSON.stringify(value), keys);
|
|
132
193
|
return keys;
|
|
133
194
|
}
|
|
134
|
-
|
|
195
|
+
|
|
135
196
|
async del(...keys) {
|
|
136
197
|
if (keys.length === 0) {
|
|
137
|
-
// Delete everything
|
|
138
198
|
this.db.exec('DELETE FROM deepbase');
|
|
139
199
|
return;
|
|
140
200
|
}
|
|
141
|
-
|
|
201
|
+
|
|
142
202
|
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();
|
|
203
|
+
this._delTxn(key, this._likePrefix(key));
|
|
154
204
|
}
|
|
155
|
-
|
|
205
|
+
|
|
156
206
|
async inc(...args) {
|
|
157
207
|
const i = args.pop();
|
|
158
208
|
return this.upd(...args, n => n + i);
|
|
159
209
|
}
|
|
160
|
-
|
|
210
|
+
|
|
161
211
|
async dec(...args) {
|
|
162
212
|
const i = args.pop();
|
|
163
213
|
return this.upd(...args, n => n - i);
|
|
164
214
|
}
|
|
165
|
-
|
|
215
|
+
|
|
166
216
|
async add(...keys) {
|
|
167
217
|
const value = keys.pop();
|
|
168
218
|
const id = this.nanoid();
|
|
169
219
|
await this.set(...[...keys, id], value);
|
|
170
220
|
return [...keys, id];
|
|
171
221
|
}
|
|
172
|
-
|
|
222
|
+
|
|
173
223
|
async upd(...args) {
|
|
174
224
|
const func = args.pop();
|
|
175
225
|
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();
|
|
226
|
+
return this._updTxn(keys, func);
|
|
186
227
|
}
|
|
187
|
-
|
|
228
|
+
|
|
188
229
|
_getRootObject() {
|
|
189
230
|
const rows = this.getAllStmt.all();
|
|
190
231
|
const result = {};
|
|
191
|
-
|
|
192
232
|
for (const row of rows) {
|
|
193
233
|
const path = this._keyToPath(row.key);
|
|
194
234
|
const value = JSON.parse(row.value);
|
|
195
235
|
this._setNestedValue(result, path, value);
|
|
196
236
|
}
|
|
197
|
-
|
|
198
237
|
return result;
|
|
199
238
|
}
|
|
200
|
-
|
|
239
|
+
|
|
201
240
|
async _setRootObject(obj) {
|
|
202
|
-
// Clear existing data
|
|
203
|
-
this.db.exec('DELETE FROM deepbase');
|
|
204
|
-
|
|
205
|
-
// Flatten and insert
|
|
206
241
|
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);
|
|
242
|
+
this._setRootTxn(entries);
|
|
214
243
|
}
|
|
215
|
-
|
|
216
|
-
_buildObjectFromChildren(parentKey) {
|
|
217
|
-
const rows = this.getKeysLikeStmt.all(parentKey ? this._likePrefix(parentKey) : '%');
|
|
244
|
+
|
|
245
|
+
_buildObjectFromChildren(parentKey, likePattern) {
|
|
246
|
+
const rows = this.getKeysLikeStmt.all(likePattern || (parentKey ? this._likePrefix(parentKey) : '%'));
|
|
218
247
|
const result = {};
|
|
219
|
-
|
|
248
|
+
const parentPathLen = parentKey ? this._keyToPath(parentKey).length : 0;
|
|
249
|
+
|
|
220
250
|
for (const row of rows) {
|
|
221
251
|
const fullPath = this._keyToPath(row.key);
|
|
222
|
-
const relativePath =
|
|
223
|
-
? fullPath.slice(this._keyToPath(parentKey).length)
|
|
224
|
-
: fullPath;
|
|
225
|
-
|
|
252
|
+
const relativePath = fullPath.slice(parentPathLen);
|
|
226
253
|
const value = JSON.parse(row.value);
|
|
227
254
|
this._setNestedValue(result, relativePath, value);
|
|
228
255
|
}
|
|
229
|
-
|
|
256
|
+
|
|
230
257
|
return result;
|
|
231
258
|
}
|
|
232
|
-
|
|
259
|
+
|
|
233
260
|
_escapeLikePattern(str) {
|
|
234
261
|
return str.replace(/[!%_]/g, '!$&');
|
|
235
262
|
}
|
|
@@ -240,85 +267,76 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
240
267
|
|
|
241
268
|
_setNestedValue(obj, path, value) {
|
|
242
269
|
if (path.length === 0) return;
|
|
243
|
-
|
|
270
|
+
|
|
244
271
|
if (path.length === 1) {
|
|
245
272
|
obj[path[0]] = value;
|
|
246
273
|
return;
|
|
247
274
|
}
|
|
248
|
-
|
|
275
|
+
|
|
249
276
|
const key = path[0];
|
|
250
|
-
if (!obj.hasOwnProperty(key) || typeof obj[key] !==
|
|
277
|
+
if (!obj.hasOwnProperty(key) || typeof obj[key] !== 'object') {
|
|
251
278
|
obj[key] = {};
|
|
252
279
|
}
|
|
253
|
-
|
|
280
|
+
|
|
254
281
|
this._setNestedValue(obj[key], path.slice(1), value);
|
|
255
282
|
}
|
|
256
|
-
|
|
283
|
+
|
|
257
284
|
_flattenObject(obj, prefix = '') {
|
|
258
285
|
const entries = [];
|
|
259
|
-
|
|
286
|
+
|
|
260
287
|
for (const [key, value] of Object.entries(obj)) {
|
|
261
288
|
const escapedKey = this._escapeDots(String(key));
|
|
262
289
|
const fullKey = prefix ? `${prefix}.${escapedKey}` : escapedKey;
|
|
263
|
-
|
|
290
|
+
|
|
264
291
|
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
265
292
|
entries.push(...this._flattenObject(value, fullKey));
|
|
266
293
|
} else {
|
|
267
294
|
entries.push([fullKey, value]);
|
|
268
295
|
}
|
|
269
296
|
}
|
|
270
|
-
|
|
297
|
+
|
|
271
298
|
return entries;
|
|
272
299
|
}
|
|
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']
|
|
300
|
+
|
|
301
|
+
_getFromParent(path) {
|
|
278
302
|
for (let i = path.length - 1; i > 0; i--) {
|
|
279
303
|
const parentPath = path.slice(0, i);
|
|
280
304
|
const parentKey = this._pathToKey(parentPath);
|
|
281
305
|
const row = this.getStmt.get(parentKey);
|
|
282
306
|
if (row) {
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return parentPath;
|
|
307
|
+
const parentValue = JSON.parse(row.value);
|
|
308
|
+
if (parentValue !== null && typeof parentValue === 'object') {
|
|
309
|
+
return this._getFromObject(parentValue, path.slice(i));
|
|
287
310
|
}
|
|
288
311
|
}
|
|
289
312
|
}
|
|
290
313
|
return null;
|
|
291
314
|
}
|
|
292
|
-
|
|
315
|
+
|
|
293
316
|
_getFromObject(obj, path) {
|
|
294
317
|
if (path.length === 0) return obj;
|
|
295
318
|
if (path.length === 1) {
|
|
296
319
|
return obj === null || obj[path[0]] === undefined ? null : obj[path[0]];
|
|
297
320
|
}
|
|
298
|
-
|
|
321
|
+
|
|
299
322
|
const key = path[0];
|
|
300
323
|
if (!obj.hasOwnProperty(key)) return null;
|
|
301
|
-
|
|
324
|
+
|
|
302
325
|
return this._getFromObject(obj[key], path.slice(1));
|
|
303
326
|
}
|
|
304
|
-
|
|
327
|
+
|
|
305
328
|
_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
329
|
for (let i = 1; i < path.length; i++) {
|
|
309
330
|
const parentPath = path.slice(0, i);
|
|
310
331
|
const parentKey = this._pathToKey(parentPath);
|
|
311
332
|
const parentRow = this.getStmt.get(parentKey);
|
|
312
|
-
|
|
333
|
+
|
|
313
334
|
if (parentRow) {
|
|
314
335
|
const parentValue = JSON.parse(parentRow.value);
|
|
315
|
-
|
|
316
|
-
// If it's an object (not array or primitive), expand it
|
|
336
|
+
|
|
317
337
|
if (parentValue !== null && typeof parentValue === 'object' && !Array.isArray(parentValue)) {
|
|
318
|
-
// Delete the parent key
|
|
319
338
|
this.delStmt.run(parentKey);
|
|
320
|
-
|
|
321
|
-
// Insert all properties as individual keys
|
|
339
|
+
|
|
322
340
|
const entries = this._flattenObject(parentValue, parentKey);
|
|
323
341
|
for (const [key, value] of entries) {
|
|
324
342
|
this.setStmt.run(key, JSON.stringify(value));
|
|
@@ -330,5 +348,3 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
330
348
|
}
|
|
331
349
|
|
|
332
350
|
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;
|