deepbase-sqlite 3.5.1 → 3.6.4
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 +60 -7
- package/package.json +2 -2
- package/src/SqliteDriver.js +53 -6
- package/test/test.js +15 -0
package/README.md
CHANGED
|
@@ -5,9 +5,14 @@ SQLite driver for DeepBase.
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install deepbase deepbase-sqlite
|
|
8
|
+
npm install deepbase deepbase-sqlite --ignore-scripts=false
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
> `deepbase-sqlite` depends on `better-sqlite3`, a native module. Its install
|
|
12
|
+
> script downloads a prebuilt binary (or compiles from source as a fallback).
|
|
13
|
+
> If your environment disables npm lifecycle scripts, see
|
|
14
|
+
> [Troubleshooting](#troubleshooting) below.
|
|
15
|
+
|
|
11
16
|
## Description
|
|
12
17
|
|
|
13
18
|
Stores data in SQLite database files. Perfect for:
|
|
@@ -77,6 +82,8 @@ Efficiently stores nested objects using a key-value schema:
|
|
|
77
82
|
- Values are stored as JSON
|
|
78
83
|
- Fast lookups for both exact keys and partial paths
|
|
79
84
|
|
|
85
|
+
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`).
|
|
86
|
+
|
|
80
87
|
### ACID Compliance
|
|
81
88
|
|
|
82
89
|
SQLite provides:
|
|
@@ -141,12 +148,12 @@ CREATE TABLE deepbase (
|
|
|
141
148
|
Example data:
|
|
142
149
|
|
|
143
150
|
```
|
|
144
|
-
key
|
|
145
|
-
|
|
146
|
-
users.alice.name
|
|
147
|
-
users.alice.age
|
|
148
|
-
users.bob.name
|
|
149
|
-
users.bob.age
|
|
151
|
+
key | value
|
|
152
|
+
----------------------|------------------
|
|
153
|
+
users.alice.name | "Alice"
|
|
154
|
+
users.alice.age | 30
|
|
155
|
+
users.bob.name | "Bob"
|
|
156
|
+
users.bob.age | 25
|
|
150
157
|
config.theme | "dark"
|
|
151
158
|
config.lang | "en"
|
|
152
159
|
```
|
|
@@ -262,6 +269,52 @@ await db.set('users', userId, 'profile', data);
|
|
|
262
269
|
await db.set(`user_${userId}_profile`, data);
|
|
263
270
|
```
|
|
264
271
|
|
|
272
|
+
## Troubleshooting
|
|
273
|
+
|
|
274
|
+
### `Could not locate the bindings file` / `better_sqlite3.node` missing
|
|
275
|
+
|
|
276
|
+
This error means `better-sqlite3`'s native binding was never fetched or built.
|
|
277
|
+
It almost always comes from npm install scripts being disabled, which prevents
|
|
278
|
+
`better-sqlite3`'s `install` script from downloading the prebuilt binary.
|
|
279
|
+
|
|
280
|
+
Check whether scripts are disabled:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
npm config get ignore-scripts # should be "false"
|
|
284
|
+
cat ~/.npmrc 2>/dev/null # look for `ignore-scripts=true`
|
|
285
|
+
cat .npmrc 2>/dev/null
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Fix — rebuild the native binding in the project that uses `deepbase-sqlite`:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
npm rebuild better-sqlite3 --ignore-scripts=false
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
If `npm rebuild` still doesn't fetch a prebuild (common when `ignore-scripts`
|
|
295
|
+
is persisted in `.npmrc`), do a clean reinstall of just that package:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
rm -rf node_modules/better-sqlite3
|
|
299
|
+
npm i --ignore-scripts=false
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Verify the binding landed:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
ls node_modules/better-sqlite3/build/Release/better_sqlite3.node
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Notes:
|
|
309
|
+
|
|
310
|
+
- `npm i <pkg> --ignore-scripts=false` only runs scripts if the install
|
|
311
|
+
actually changes `node_modules`. When npm reports `up to date`, no install
|
|
312
|
+
scripts run — use `npm rebuild` or remove the package folder first.
|
|
313
|
+
- In monorepos or CI, prefer setting `ignore-scripts=false` for the install
|
|
314
|
+
step rather than passing it as a one-off flag.
|
|
315
|
+
- When this happens at runtime, `deepbase-sqlite` will throw an error with
|
|
316
|
+
code `DEEPBASE_SQLITE_BINDING_MISSING` and a pointer back to this section.
|
|
317
|
+
|
|
265
318
|
## License
|
|
266
319
|
|
|
267
320
|
MIT - Copyright (c) Martin Clasen
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepbase-sqlite",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.4",
|
|
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.4"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"test": "mocha test/test.js"
|
package/src/SqliteDriver.js
CHANGED
|
@@ -52,14 +52,30 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
52
52
|
SqliteDriver._instances[this.fileName] = this;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
_connectSync() {
|
|
56
56
|
if (this._connected) return;
|
|
57
57
|
|
|
58
58
|
if (!fs.existsSync(this.path)) {
|
|
59
59
|
fs.mkdirSync(this.path, { recursive: true });
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
try {
|
|
63
|
+
this.db = new Database(this.fileName);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
if (this._isMissingNativeBinding(err)) {
|
|
66
|
+
const hint =
|
|
67
|
+
'deepbase-sqlite: the native binding for "better-sqlite3" is missing. ' +
|
|
68
|
+
'This usually means npm install scripts were disabled (e.g. `ignore-scripts=true` in your .npmrc). ' +
|
|
69
|
+
'Fix it with: `npm rebuild better-sqlite3 --ignore-scripts=false`, ' +
|
|
70
|
+
'or reinstall with `npm i --ignore-scripts=false`. ' +
|
|
71
|
+
'See https://github.com/clasen/DeepBase/tree/main/packages/driver-sqlite#troubleshooting';
|
|
72
|
+
const wrapped = new Error(hint);
|
|
73
|
+
wrapped.cause = err;
|
|
74
|
+
wrapped.code = 'DEEPBASE_SQLITE_BINDING_MISSING';
|
|
75
|
+
throw wrapped;
|
|
76
|
+
}
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
63
79
|
|
|
64
80
|
const cfg = PRAGMA[this.pragma];
|
|
65
81
|
if (cfg) {
|
|
@@ -75,15 +91,27 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
75
91
|
this.db.exec(`
|
|
76
92
|
CREATE TABLE IF NOT EXISTS deepbase (
|
|
77
93
|
key TEXT PRIMARY KEY,
|
|
78
|
-
value TEXT NOT NULL
|
|
94
|
+
value TEXT NOT NULL,
|
|
95
|
+
seq INTEGER NOT NULL DEFAULT 0
|
|
79
96
|
)${withoutRowid}
|
|
80
97
|
`);
|
|
81
98
|
|
|
99
|
+
const tableCols = this.db.prepare('PRAGMA table_info(deepbase)').all();
|
|
100
|
+
if (!tableCols.some((c) => c.name === 'seq')) {
|
|
101
|
+
this.db.exec('ALTER TABLE deepbase ADD COLUMN seq INTEGER NOT NULL DEFAULT 0');
|
|
102
|
+
}
|
|
103
|
+
|
|
82
104
|
this.getStmt = this.db.prepare('SELECT value FROM deepbase WHERE key = ?');
|
|
83
|
-
this.setStmt = this.db.prepare(
|
|
105
|
+
this.setStmt = this.db.prepare(`
|
|
106
|
+
INSERT INTO deepbase (key, value, seq)
|
|
107
|
+
VALUES (?, ?, (SELECT IFNULL(MAX(seq), 0) + 1 FROM deepbase))
|
|
108
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
109
|
+
`);
|
|
84
110
|
this.delStmt = this.db.prepare('DELETE FROM deepbase WHERE key = ?');
|
|
85
|
-
this.getAllStmt = this.db.prepare('SELECT key, value FROM deepbase');
|
|
86
|
-
this.getKeysLikeStmt = this.db.prepare(
|
|
111
|
+
this.getAllStmt = this.db.prepare('SELECT key, value FROM deepbase ORDER BY seq, key');
|
|
112
|
+
this.getKeysLikeStmt = this.db.prepare(
|
|
113
|
+
"SELECT key, value FROM deepbase WHERE key LIKE ? ESCAPE '!' ORDER BY seq, key",
|
|
114
|
+
);
|
|
87
115
|
this.delChildrenStmt = this.db.prepare("DELETE FROM deepbase WHERE key LIKE ? ESCAPE '!'");
|
|
88
116
|
this.hasChildrenStmt = this.db.prepare("SELECT 1 FROM deepbase WHERE key LIKE ? ESCAPE '!' LIMIT 1");
|
|
89
117
|
|
|
@@ -116,6 +144,10 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
116
144
|
this._connected = true;
|
|
117
145
|
}
|
|
118
146
|
|
|
147
|
+
async connect() {
|
|
148
|
+
this._connectSync();
|
|
149
|
+
}
|
|
150
|
+
|
|
119
151
|
async disconnect() {
|
|
120
152
|
if (this.db) {
|
|
121
153
|
this.db.close();
|
|
@@ -128,6 +160,11 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
128
160
|
return this._getSync(args);
|
|
129
161
|
}
|
|
130
162
|
|
|
163
|
+
getSync(...args) {
|
|
164
|
+
this._connectSync();
|
|
165
|
+
return this._getSync(args);
|
|
166
|
+
}
|
|
167
|
+
|
|
131
168
|
_getSync(args) {
|
|
132
169
|
if (args.length === 0) {
|
|
133
170
|
return this._getRootObject();
|
|
@@ -236,6 +273,16 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
236
273
|
return result;
|
|
237
274
|
}
|
|
238
275
|
|
|
276
|
+
_isMissingNativeBinding(err) {
|
|
277
|
+
if (!err) return false;
|
|
278
|
+
const msg = String(err.message || '');
|
|
279
|
+
return (
|
|
280
|
+
err.code === 'MODULE_NOT_FOUND' ||
|
|
281
|
+
msg.includes('Could not locate the bindings file') ||
|
|
282
|
+
msg.includes('better_sqlite3.node')
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
239
286
|
_escapeLikePattern(str) {
|
|
240
287
|
return str.replace(/[!%_]/g, '!$&');
|
|
241
288
|
}
|
package/test/test.js
CHANGED
|
@@ -85,6 +85,21 @@ for (const pragma of PRAGMA_MODES) {
|
|
|
85
85
|
await db.set('complex', complexObj);
|
|
86
86
|
assert.deepStrictEqual(await db.get('complex'), complexObj);
|
|
87
87
|
});
|
|
88
|
+
|
|
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
|
+
});
|
|
97
|
+
|
|
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
|
+
});
|
|
88
103
|
});
|
|
89
104
|
|
|
90
105
|
describe('Keys with Dots', function () {
|