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 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 | value
145
- -----------------------|------------------
146
- users.alice.name | "Alice"
147
- users.alice.age | 30
148
- users.bob.name | "Bob"
149
- users.bob.age | 25
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.5.1",
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.5.1"
20
+ "deepbase": "^3.6.4"
21
21
  },
22
22
  "scripts": {
23
23
  "test": "mocha test/test.js"
@@ -52,14 +52,30 @@ export class SqliteDriver extends DeepBaseDriver {
52
52
  SqliteDriver._instances[this.fileName] = this;
53
53
  }
54
54
 
55
- async connect() {
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
- this.db = new Database(this.fileName);
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('INSERT OR REPLACE INTO deepbase (key, value) VALUES (?, ?)');
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("SELECT key, value FROM deepbase WHERE key LIKE ? ESCAPE '!'");
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 () {