@homeofthings/sqlite3 6.3.0 → 6.4.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 CHANGED
@@ -20,7 +20,7 @@ Asynchronous, non-blocking [SQLite3](https://sqlite.org/) bindings for [Node.js]
20
20
  - [Extension support](https://github.com/gms1/node-sqlite3/wiki/API#databaseloadextensionpath-callback), including bundled support for the [json1 extension](https://www.sqlite.org/json1.html)
21
21
  - Big test suite
22
22
  - Written in modern C++ and tested for memory leaks
23
- - Bundles SQLite v3.51.3, or you can build using a local SQLite
23
+ - Bundles SQLite v3.53.0, or you can build using a local SQLite
24
24
 
25
25
  # Installing
26
26
 
@@ -245,6 +245,34 @@ npm install @homeofthings/sqlite3 --build-from-source --sqlite_libname=sqlcipher
245
245
  npm test
246
246
  ```
247
247
 
248
+ # Benchmarks
249
+
250
+ ## Driver Comparison
251
+
252
+ The `tools/benchmark-drivers` directory contains a comprehensive benchmark suite comparing different SQLite drivers for Node.js:
253
+
254
+ ```bash
255
+ cd tools/benchmark-drivers
256
+ npm install
257
+ node index.js
258
+ ```
259
+
260
+ This compares `@homeofthings/sqlite3` against other popular SQLite drivers:
261
+ - `better-sqlite3` - Synchronous, high-performance
262
+ - `node:sqlite` - Built-in Node.js SQLite (v22.6.0+)
263
+
264
+ See [tools/benchmark-drivers/README.md](tools/benchmark-drivers/README.md) for details.
265
+
266
+ **Key insight**: Async drivers like `@homeofthings/sqlite3` show lower raw throughput but provide better event loop availability, allowing other operations to proceed concurrently. Sync drivers block the event loop completely.
267
+
268
+ ## Internal Benchmarks
269
+
270
+ Internal performance benchmarks are available in `tools/benchmark-internal`:
271
+
272
+ ```bash
273
+ node tools/benchmark-internal/run.js
274
+ ```
275
+
248
276
  # Contributors
249
277
 
250
278
  * [Daniel Lockyer](https://github.com/daniellockyer)
@@ -269,7 +297,9 @@ Thanks to [Orlando Vazquez](https://github.com/orlandov),
269
297
  [Eric Fredricksen](https://github.com/grumdrig) and
270
298
  [Ryan Dahl](https://github.com/ry) for their SQLite bindings for node, and to mraleph on Freenode's #v8 for answering questions.
271
299
 
272
- This module was originally created by [Mapbox](https://mapbox.com/) & is now maintained by [Ghost](https://ghost.org).
300
+ This module was originally created by [Mapbox](https://mapbox.com/), then it was taken over by [Ghost](https://ghost.org), but was then deprecated without prior notice, so that the original is no longer maintained. See [TryGhost/node-sqlite3](https://github.com/TryGhost/node-sqlite3)
301
+
302
+ I still hope that it will eventually be taken over by a larger organization, but in the meantime, I'm trying to maintain this fork here.
273
303
 
274
304
  # Changelog
275
305
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  'variables': {
3
- 'sqlite_version%':'3510300',
3
+ 'sqlite_version%':'3530000',
4
4
  "toolset%":'',
5
5
  },
6
6
  'target_defaults': {
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Promise-based wrapper for the Backup class from node-sqlite3
3
+ * @module promise/backup
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ /**
9
+ * A thin wrapper for the 'Backup' class from 'node-sqlite3' using Promises
10
+ * instead of callbacks
11
+ *
12
+ * @see https://github.com/mapbox/node-sqlite3/wiki/API
13
+ */
14
+ class SqliteBackup {
15
+ /**
16
+ * Creates an instance of SqliteBackup.
17
+ *
18
+ * @param {import('../sqlite3.js').Backup} backup - The underlying Backup instance
19
+ */
20
+ constructor(backup) {
21
+ /**
22
+ * @type {import('../sqlite3.js').Backup}
23
+ * @private
24
+ */
25
+ this._backup = backup;
26
+ }
27
+
28
+ /**
29
+ * Returns true if the backup is idle (not actively copying)
30
+ *
31
+ * @returns {boolean}
32
+ */
33
+ get idle() {
34
+ return this._backup.idle;
35
+ }
36
+
37
+ /**
38
+ * Returns true if the backup is completed
39
+ *
40
+ * @returns {boolean}
41
+ */
42
+ get completed() {
43
+ return this._backup.completed;
44
+ }
45
+
46
+ /**
47
+ * Returns true if the backup has failed
48
+ *
49
+ * @returns {boolean}
50
+ */
51
+ get failed() {
52
+ return this._backup.failed;
53
+ }
54
+
55
+ /**
56
+ * Returns the remaining number of pages left to copy
57
+ * Returns -1 if `step` not yet called
58
+ *
59
+ * @returns {number}
60
+ */
61
+ get remaining() {
62
+ return this._backup.remaining;
63
+ }
64
+
65
+ /**
66
+ * Returns the total number of pages
67
+ * Returns -1 if `step` not yet called
68
+ *
69
+ * @returns {number}
70
+ */
71
+ get pageCount() {
72
+ return this._backup.pageCount;
73
+ }
74
+
75
+ /**
76
+ * Returns the progress (percentage completion)
77
+ *
78
+ * @returns {number} Progress as percentage (0-100)
79
+ */
80
+ get progress() {
81
+ const pageCount = this.pageCount;
82
+ const remaining = this.remaining;
83
+ if (pageCount === -1 || remaining === -1) {
84
+ return 0;
85
+ }
86
+ return pageCount === 0 ? 100 : ((pageCount - remaining) / pageCount) * 100;
87
+ }
88
+
89
+ /**
90
+ * Copy the next page or all remaining pages of the backup
91
+ *
92
+ * @param {number} [pages=-1] - Number of pages to copy (-1 for all remaining)
93
+ * @returns {Promise<void>}
94
+ */
95
+ step(pages) {
96
+ return new Promise((resolve, reject) => {
97
+ if (!this._backup) {
98
+ reject(new Error('backup handle not open'));
99
+ return;
100
+ }
101
+ this._backup.step(pages === undefined ? -1 : pages, (err) => {
102
+ if (err) {
103
+ reject(err);
104
+ } else {
105
+ resolve();
106
+ }
107
+ });
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Finish the backup (synchronous)
113
+ */
114
+ finish() {
115
+ if (this._backup) {
116
+ this._backup.finish();
117
+ }
118
+ }
119
+ }
120
+
121
+ module.exports = { SqliteBackup };
@@ -0,0 +1,517 @@
1
+ /**
2
+ * Promise-based wrapper for the Database class from node-sqlite3
3
+ * @module promise/database
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const { Database } = require('../sqlite3.js');
9
+ const { SqliteStatement } = require('./statement');
10
+ const { SqliteBackup } = require('./backup');
11
+
12
+ /**
13
+ * Result object returned by run() operations
14
+ * @typedef {Object} SqlRunResult
15
+ * @property {number} lastID - The ID of the last inserted row
16
+ * @property {number} changes - The number of rows affected
17
+ */
18
+
19
+ /**
20
+ * @typedef {import('../sqlite3-binding.js').Database} Database
21
+ */
22
+
23
+ /**
24
+ * A thin wrapper for the 'Database' class from 'node-sqlite3' using Promises
25
+ * instead of callbacks
26
+ *
27
+ * @see https://github.com/mapbox/node-sqlite3/wiki/API
28
+ */
29
+ class SqliteDatabase {
30
+ /**
31
+ * Creates a new SqliteDatabase instance (without opening a database).
32
+ * Use open() to open a database connection, or use the static factory method open().
33
+ *
34
+ * @example
35
+ * // Using constructor + open
36
+ * const db = new SqliteDatabase();
37
+ * await db.open(':memory:');
38
+ *
39
+ * // Using static factory method
40
+ * const db = await SqliteDatabase.open(':memory:');
41
+ */
42
+ constructor() {
43
+ /**
44
+ * @type {Database | undefined}
45
+ * @protected
46
+ */
47
+ this.db = undefined;
48
+ }
49
+
50
+ /**
51
+ * Static factory method to create and open a database connection.
52
+ *
53
+ * @param {string} filename - The path to the database file or ':memory:' for in-memory
54
+ * @param {number} [mode] - Optional mode flags (OPEN_READONLY, OPEN_READWRITE, OPEN_CREATE)
55
+ * @returns {Promise<SqliteDatabase>} A promise that resolves to the opened database
56
+ * @example
57
+ * const db = await SqliteDatabase.open(':memory:');
58
+ * const db = await SqliteDatabase.open('mydb.sqlite', sqlite3.OPEN_READONLY);
59
+ */
60
+ static async open(filename, mode) {
61
+ const db = new SqliteDatabase();
62
+ await db.open(filename, mode);
63
+ return db;
64
+ }
65
+
66
+ /**
67
+ * Open a database connection
68
+ *
69
+ * @param {string} filename - The path to the database file or ':memory:' for in-memory
70
+ * @param {number} [mode] - Optional mode flags (OPEN_READONLY, OPEN_READWRITE, OPEN_CREATE)
71
+ * @returns {Promise<void>}
72
+ */
73
+ open(filename, mode) {
74
+ return new Promise((resolve, reject) => {
75
+ // Default mode: OPEN_READWRITE | OPEN_CREATE
76
+ const defaultMode = 0x00000002 | 0x00000004; // OPEN_READWRITE | OPEN_CREATE
77
+ const db = new Database(filename, mode === undefined ? defaultMode : mode, (err) => {
78
+ if (err) {
79
+ reject(err);
80
+ } else {
81
+ this.db = db;
82
+ resolve();
83
+ }
84
+ });
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Close the database connection
90
+ *
91
+ * @returns {Promise<void>}
92
+ */
93
+ close() {
94
+ return new Promise((resolve, reject) => {
95
+ if (!this.db) {
96
+ resolve();
97
+ return;
98
+ }
99
+ const db = this.db;
100
+ this.db = undefined;
101
+ db.close((err) => {
102
+ db.removeAllListeners();
103
+ if (err) {
104
+ reject(err);
105
+ } else {
106
+ resolve();
107
+ }
108
+ });
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Test if a connection is open
114
+ *
115
+ * @returns {boolean}
116
+ */
117
+ isOpen() {
118
+ return !!this.db;
119
+ }
120
+
121
+ /**
122
+ * Runs a SQL statement with the specified parameters
123
+ *
124
+ * @param {string} sql - The SQL statement
125
+ * @param {any} [params] - The parameters referenced in the statement
126
+ * @returns {Promise<SqlRunResult>} A promise that resolves to the result object
127
+ */
128
+ run(sql, params) {
129
+ return new Promise((resolve, reject) => {
130
+ if (!this.db) {
131
+ reject(new Error('database connection not open'));
132
+ return;
133
+ }
134
+ // Use function() to get 'this' context with lastID and changes
135
+ this.db.run(sql, params, function (err) {
136
+ if (err) {
137
+ reject(err);
138
+ } else {
139
+ resolve({
140
+ lastID: this.lastID,
141
+ changes: this.changes
142
+ });
143
+ }
144
+ });
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Runs a SQL query with the specified parameters, fetching only the first row
150
+ *
151
+ * @template T
152
+ * @param {string} sql - The DQL statement
153
+ * @param {any} [params] - The parameters referenced in the statement
154
+ * @returns {Promise<T | undefined>} A promise that resolves to the row or undefined
155
+ */
156
+ get(sql, params) {
157
+ return new Promise((resolve, reject) => {
158
+ if (!this.db) {
159
+ reject(new Error('database connection not open'));
160
+ return;
161
+ }
162
+ this.db.get(sql, params, (err, row) => {
163
+ if (err) {
164
+ reject(err);
165
+ } else {
166
+ resolve(row);
167
+ }
168
+ });
169
+ });
170
+ }
171
+
172
+ /**
173
+ * Runs a SQL query with the specified parameters, fetching all rows
174
+ *
175
+ * @template T
176
+ * @param {string} sql - The DQL statement
177
+ * @param {any} [params] - The parameters referenced in the statement
178
+ * @returns {Promise<T[]>} A promise that resolves to an array of rows
179
+ */
180
+ all(sql, params) {
181
+ return new Promise((resolve, reject) => {
182
+ if (!this.db) {
183
+ reject(new Error('database connection not open'));
184
+ return;
185
+ }
186
+ this.db.all(sql, params, (err, rows) => {
187
+ if (err) {
188
+ reject(err);
189
+ } else {
190
+ resolve(rows);
191
+ }
192
+ });
193
+ });
194
+ }
195
+
196
+ /**
197
+ * Runs a SQL query with the specified parameters, fetching all rows
198
+ * using a callback for each row
199
+ *
200
+ * @param {string} sql - The DQL statement
201
+ * @param {any} [params] - The parameters referenced in the statement
202
+ * @param {(err: Error | null, row: any) => void} [callback] - The callback function for each row
203
+ * @returns {Promise<number>} A promise that resolves to the number of rows retrieved
204
+ */
205
+ each(sql, params, callback) {
206
+ // Handle case where params is actually the callback (no params provided)
207
+ if (typeof params === 'function') {
208
+ callback = params;
209
+ params = undefined;
210
+ }
211
+ return new Promise((resolve, reject) => {
212
+ if (!this.db) {
213
+ reject(new Error('database connection not open'));
214
+ return;
215
+ }
216
+ this.db.each(sql, params, callback, (err, count) => {
217
+ if (err) {
218
+ reject(err);
219
+ } else {
220
+ resolve(count);
221
+ }
222
+ });
223
+ });
224
+ }
225
+
226
+ /**
227
+ * Execute a SQL statement
228
+ *
229
+ * @param {string} sql - The SQL statement
230
+ * @returns {Promise<void>}
231
+ */
232
+ exec(sql) {
233
+ return new Promise((resolve, reject) => {
234
+ if (!this.db) {
235
+ reject(new Error('database connection not open'));
236
+ return;
237
+ }
238
+ this.db.exec(sql, (err) => {
239
+ if (err) {
240
+ reject(err);
241
+ } else {
242
+ resolve();
243
+ }
244
+ });
245
+ });
246
+ }
247
+
248
+ /**
249
+ * Prepare a SQL statement
250
+ *
251
+ * @param {string} sql - The SQL statement
252
+ * @param {any} [params] - The parameters referenced in the statement
253
+ * @returns {Promise<SqliteStatement>} A promise that resolves to the prepared statement
254
+ */
255
+ prepare(sql, params) {
256
+ return new Promise((resolve, reject) => {
257
+ if (!this.db) {
258
+ reject(new Error('database connection not open'));
259
+ return;
260
+ }
261
+ const stmt = this.db.prepare(sql, params, (err) => {
262
+ if (err) {
263
+ reject(err);
264
+ } else {
265
+ resolve(new SqliteStatement(stmt));
266
+ }
267
+ });
268
+ });
269
+ }
270
+
271
+ /**
272
+ * Initiate online backup
273
+ *
274
+ * @param {string} filename - The database file to backup from or to
275
+ * @param {boolean} [filenameIsDest=true] - Whether filename is destination
276
+ * @param {string} [destName='main'] - The destination database name
277
+ * @param {string} [sourceName='main'] - The source database name
278
+ * @returns {Promise<SqliteBackup>} A promise that resolves to the backup object
279
+ */
280
+ backup(filename, filenameIsDest = true, destName = 'main', sourceName = 'main') {
281
+ return new Promise((resolve, reject) => {
282
+ if (!this.db) {
283
+ reject(new Error('database connection not open'));
284
+ return;
285
+ }
286
+ const backup = this.db.backup(filename, destName, sourceName, filenameIsDest, (err) => {
287
+ if (err) {
288
+ reject(err);
289
+ } else {
290
+ resolve(new SqliteBackup(backup));
291
+ }
292
+ });
293
+ });
294
+ }
295
+
296
+ /**
297
+ * Serialized sqlite3 calls
298
+ * If callback is provided, run callback in serialized mode
299
+ * Otherwise, switch connection to serialized mode
300
+ *
301
+ * @param {() => void} [callback] - Optional callback to run in serialized mode
302
+ */
303
+ serialize(callback) {
304
+ if (!this.db) {
305
+ throw new Error('database connection not open');
306
+ }
307
+ this.db.serialize(callback);
308
+ }
309
+
310
+ /**
311
+ * Parallelized sqlite3 calls
312
+ * If callback is provided, run callback in parallel mode
313
+ * Otherwise, switch connection to parallel mode
314
+ *
315
+ * @param {() => void} [callback] - Optional callback to run in parallel mode
316
+ */
317
+ parallelize(callback) {
318
+ if (!this.db) {
319
+ throw new Error('database connection not open');
320
+ }
321
+ this.db.parallelize(callback);
322
+ }
323
+
324
+ /**
325
+ * Run callback inside a database transaction
326
+ *
327
+ * IMPORTANT: SQLite provides isolation between different database connections,
328
+ * but NOT between operations on the same connection. If you need concurrent
329
+ * transactions with proper isolation, use separate SqliteDatabase instances.
330
+ *
331
+ * Note: This uses BEGIN IMMEDIATE TRANSACTION which acquires a write lock
332
+ * immediately. If another connection holds a write lock, this will fail
333
+ * with SQLITE_BUSY error.
334
+ *
335
+ * @template T
336
+ * @param {() => Promise<T>} callback - The callback to run in transaction
337
+ * @returns {Promise<T>} A promise that resolves to the callback result
338
+ * @see https://www.sqlite.org/isolation.html
339
+ */
340
+ async transactionalize(callback) {
341
+ await this.beginTransaction();
342
+ try {
343
+ const result = await callback();
344
+ await this.commitTransaction();
345
+ return result;
346
+ } catch (err) {
347
+ await this.rollbackTransaction();
348
+ throw err;
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Begin a transaction using BEGIN IMMEDIATE TRANSACTION
354
+ *
355
+ * This acquires a write lock immediately, preventing deadlocks by
356
+ * failing fast if another connection holds the lock (SQLITE_BUSY).
357
+ *
358
+ * @returns {Promise<SqlRunResult>}
359
+ */
360
+ beginTransaction() {
361
+ return this.run('BEGIN IMMEDIATE TRANSACTION');
362
+ }
363
+
364
+ /**
365
+ * Commit a transaction
366
+ *
367
+ * @returns {Promise<SqlRunResult>}
368
+ */
369
+ commitTransaction() {
370
+ return this.run('COMMIT TRANSACTION');
371
+ }
372
+
373
+ /**
374
+ * Rollback a transaction
375
+ *
376
+ * @returns {Promise<SqlRunResult>}
377
+ */
378
+ rollbackTransaction() {
379
+ return this.run('ROLLBACK TRANSACTION');
380
+ }
381
+
382
+ /**
383
+ * End a transaction (commit or rollback)
384
+ *
385
+ * @param {boolean} commit - Whether to commit (true) or rollback (false)
386
+ * @returns {Promise<void>}
387
+ */
388
+ endTransaction(commit) {
389
+ const sql = commit ? 'COMMIT TRANSACTION' : 'ROLLBACK TRANSACTION';
390
+ return new Promise((resolve, reject) => {
391
+ if (!this.db) {
392
+ reject(new Error('database connection not open'));
393
+ return;
394
+ }
395
+ this.db.run(sql, (err) => {
396
+ // Ignore "no transaction" errors
397
+ if (err && !err.message.includes('no transaction')) {
398
+ reject(err);
399
+ } else {
400
+ resolve();
401
+ }
402
+ });
403
+ });
404
+ }
405
+
406
+ /**
407
+ * Load an extension
408
+ *
409
+ * @param {string} filename - The path to the extension
410
+ * @returns {Promise<void>}
411
+ */
412
+ loadExtension(filename) {
413
+ return new Promise((resolve, reject) => {
414
+ if (!this.db) {
415
+ reject(new Error('database connection not open'));
416
+ return;
417
+ }
418
+ this.db.loadExtension(filename, (err) => {
419
+ if (err) {
420
+ reject(err);
421
+ } else {
422
+ resolve();
423
+ }
424
+ });
425
+ });
426
+ }
427
+
428
+ /**
429
+ * Wait for the database to be ready
430
+ *
431
+ * @returns {Promise<void>}
432
+ */
433
+ wait() {
434
+ return new Promise((resolve, reject) => {
435
+ if (!this.db) {
436
+ reject(new Error('database connection not open'));
437
+ return;
438
+ }
439
+ this.db.wait((err) => {
440
+ if (err) {
441
+ reject(err);
442
+ } else {
443
+ resolve();
444
+ }
445
+ });
446
+ });
447
+ }
448
+
449
+ /**
450
+ * Interrupt a running query
451
+ */
452
+ interrupt() {
453
+ if (!this.db) {
454
+ throw new Error('database connection not open');
455
+ }
456
+ this.db.interrupt();
457
+ }
458
+
459
+ /**
460
+ * Configure database options
461
+ *
462
+ * @param {string} option - The option name
463
+ * @param {number} [value] - The option value
464
+ */
465
+ configure(option, value) {
466
+ if (!this.db) {
467
+ throw new Error('database connection not open');
468
+ }
469
+ this.db.configure(option, value);
470
+ }
471
+
472
+ /**
473
+ * Register an event listener
474
+ *
475
+ * @param {'trace' | 'profile' | 'error' | 'close' | 'open' | 'change'} event - The event name
476
+ * @param {Function} listener - The listener function
477
+ * @returns {this}
478
+ */
479
+ on(event, listener) {
480
+ if (!this.db) {
481
+ throw new Error('database connection not open');
482
+ }
483
+ this.db.on(event, listener);
484
+ return this;
485
+ }
486
+
487
+ /**
488
+ * Remove an event listener
489
+ *
490
+ * @param {string} event - The event name
491
+ * @param {Function} listener - The listener function
492
+ * @returns {this}
493
+ */
494
+ off(event, listener) {
495
+ if (!this.db) {
496
+ throw new Error('database connection not open');
497
+ }
498
+ this.db.off(event, listener);
499
+ return this;
500
+ }
501
+
502
+ /**
503
+ * Remove all event listeners for an event
504
+ *
505
+ * @param {string} [event] - The event name
506
+ * @returns {this}
507
+ */
508
+ removeAllListeners(event) {
509
+ if (!this.db) {
510
+ throw new Error('database connection not open');
511
+ }
512
+ this.db.removeAllListeners(event);
513
+ return this;
514
+ }
515
+ }
516
+
517
+ module.exports = { SqliteDatabase };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Promise-based wrappers for node-sqlite3
3
+ * @module promise
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const { SqliteDatabase } = require('./database');
9
+ const { SqliteStatement } = require('./statement');
10
+ const { SqliteBackup } = require('./backup');
11
+
12
+ module.exports = {
13
+ SqliteDatabase,
14
+ SqliteStatement,
15
+ SqliteBackup
16
+ };
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Promise-based wrapper for the Statement class from node-sqlite3
3
+ * @module promise/statement
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ /**
9
+ * Result object returned by run() operations
10
+ * @typedef {Object} SqlRunResult
11
+ * @property {number} lastID - The ID of the last inserted row
12
+ * @property {number} changes - The number of rows affected
13
+ */
14
+
15
+ /**
16
+ * A thin wrapper for the 'Statement' class from 'node-sqlite3' using Promises
17
+ * instead of callbacks
18
+ *
19
+ * @see https://github.com/mapbox/node-sqlite3/wiki/API
20
+ */
21
+ class SqliteStatement {
22
+ /**
23
+ * Creates an instance of SqliteStatement.
24
+ *
25
+ * @param {import('../sqlite3.js').Statement} stmt - The underlying Statement instance
26
+ */
27
+ constructor(stmt) {
28
+ /**
29
+ * @type {import('../sqlite3.js').Statement}
30
+ * @private
31
+ */
32
+ this._stmt = stmt;
33
+ }
34
+
35
+ /**
36
+ * Bind the given parameters to the prepared statement
37
+ *
38
+ * @param {...any} params - The parameters to bind
39
+ * @returns {this} Returns this for chaining
40
+ */
41
+ bind(...params) {
42
+ this._stmt.bind(params);
43
+ return this;
44
+ }
45
+
46
+ /**
47
+ * Reset a open cursor of the prepared statement preserving the parameter binding
48
+ * Allows re-execute of the same query
49
+ *
50
+ * @returns {Promise<void>}
51
+ */
52
+ reset() {
53
+ return new Promise((resolve) => {
54
+ this._stmt.reset(() => {
55
+ resolve();
56
+ });
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Finalizes a prepared statement (freeing any resource used by this statement)
62
+ *
63
+ * IMPORTANT: You MUST finalize all prepared statements before closing the database.
64
+ * If you attempt to close a database with unfinalized statements, you will get:
65
+ * SQLITE_BUSY: unable to close due to unfinalised statements
66
+ *
67
+ * @returns {Promise<void>}
68
+ */
69
+ finalize() {
70
+ return new Promise((resolve, reject) => {
71
+ this._stmt.finalize((err) => {
72
+ if (err) {
73
+ reject(err);
74
+ } else {
75
+ resolve();
76
+ }
77
+ });
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Runs a prepared statement with the specified parameters
83
+ *
84
+ * @param {any} [params] - The parameters referenced in the statement
85
+ * @returns {Promise<SqlRunResult>} A promise that resolves to the result object
86
+ */
87
+ run(params) {
88
+ return new Promise((resolve, reject) => {
89
+ // Use function() to get 'this' context with lastID and changes
90
+ const callback = function(err) {
91
+ if (err) {
92
+ reject(err);
93
+ } else {
94
+ resolve({
95
+ lastID: this.lastID,
96
+ changes: this.changes
97
+ });
98
+ }
99
+ };
100
+ // Only pass params if provided - otherwise use bound parameters
101
+ if (params === undefined) {
102
+ this._stmt.run(callback);
103
+ } else {
104
+ this._stmt.run(params, callback);
105
+ }
106
+ });
107
+ }
108
+
109
+ /**
110
+ * Runs a prepared statement with the specified parameters, fetching only the first row
111
+ *
112
+ * @template T
113
+ * @param {any} [params] - The parameters referenced in the statement
114
+ * @returns {Promise<T | undefined>} A promise that resolves to the row or undefined
115
+ */
116
+ get(params) {
117
+ return new Promise((resolve, reject) => {
118
+ const callback = (err, row) => {
119
+ if (err) {
120
+ reject(err);
121
+ } else {
122
+ resolve(row);
123
+ }
124
+ };
125
+ // Only pass params if provided - otherwise use bound parameters
126
+ if (params === undefined) {
127
+ this._stmt.get(callback);
128
+ } else {
129
+ this._stmt.get(params, callback);
130
+ }
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Runs a prepared statement with the specified parameters, fetching all rows
136
+ *
137
+ * @template T
138
+ * @param {any} [params] - The parameters referenced in the statement
139
+ * @returns {Promise<T[]>} A promise that resolves to an array of rows
140
+ */
141
+ all(params) {
142
+ return new Promise((resolve, reject) => {
143
+ const callback = (err, rows) => {
144
+ if (err) {
145
+ reject(err);
146
+ } else {
147
+ resolve(rows);
148
+ }
149
+ };
150
+ // Only pass params if provided - otherwise use bound parameters
151
+ if (params === undefined) {
152
+ this._stmt.all(callback);
153
+ } else {
154
+ this._stmt.all(params, callback);
155
+ }
156
+ });
157
+ }
158
+
159
+ /**
160
+ * Runs a prepared statement with the specified parameters, fetching all rows
161
+ * using a callback for each row
162
+ *
163
+ * @param {any} [params] - The parameters referenced in the statement
164
+ * @param {(err: Error | null, row: any) => void} [callback] - The callback function for each row
165
+ * @returns {Promise<number>} A promise that resolves to the number of rows retrieved
166
+ */
167
+ each(params, callback) {
168
+ // Handle case where params is actually the callback (no params provided)
169
+ if (typeof params === 'function') {
170
+ callback = params;
171
+ params = undefined;
172
+ }
173
+ return new Promise((resolve, reject) => {
174
+ this._stmt.each(params, callback, (err, count) => {
175
+ if (err) {
176
+ reject(err);
177
+ } else {
178
+ resolve(count);
179
+ }
180
+ });
181
+ });
182
+ }
183
+ }
184
+
185
+ module.exports = { SqliteStatement };
package/lib/sqlite3.d.ts CHANGED
@@ -265,7 +265,22 @@ export class SqliteDatabase {
265
265
  serialize(callback?: () => void): void;
266
266
  parallelize(callback?: () => void): void;
267
267
 
268
+ /**
269
+ * Run callback inside a database transaction.
270
+ *
271
+ * IMPORTANT: SQLite provides isolation between different database connections,
272
+ * but NOT between operations on the same connection. For concurrent transactions
273
+ * with proper isolation, use separate SqliteDatabase instances.
274
+ *
275
+ * Uses BEGIN IMMEDIATE TRANSACTION which acquires a write lock immediately.
276
+ * @see https://www.sqlite.org/isolation.html
277
+ */
268
278
  transactionalize<T>(callback: () => Promise<T>): Promise<T>;
279
+
280
+ /**
281
+ * Begin a transaction using BEGIN IMMEDIATE TRANSACTION.
282
+ * Acquires write lock immediately - fails with SQLITE_BUSY if lock unavailable.
283
+ */
269
284
  beginTransaction(): Promise<void>;
270
285
  commitTransaction(): Promise<void>;
271
286
  rollbackTransaction(): Promise<void>;
@@ -300,6 +315,11 @@ export class SqliteStatement {
300
315
  callback?: (err: Error | null, row: T) => void,
301
316
  ): Promise<number>;
302
317
 
318
+ /**
319
+ * Finalizes the statement.
320
+ * IMPORTANT: You MUST finalize all prepared statements before closing the database.
321
+ * Otherwise you will get SQLITE_BUSY: unable to close due to unfinalised statements
322
+ */
303
323
  finalize(): Promise<void>;
304
324
  }
305
325
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@homeofthings/sqlite3",
3
3
  "description": "Asynchronous, non-blocking SQLite3 bindings",
4
- "version": "6.3.0",
4
+ "version": "6.4.0",
5
5
  "homepage": "https://github.com/gms1/node-sqlite3",
6
6
  "author": {
7
7
  "name": "Mapbox",
@@ -35,8 +35,8 @@
35
35
  "files": [
36
36
  "binding.gyp",
37
37
  "deps/",
38
- "lib/*.js",
39
- "lib/*.d.ts",
38
+ "lib/**/*.js",
39
+ "lib/**/*.d.ts",
40
40
  "src/"
41
41
  ],
42
42
  "repository": {
@@ -52,7 +52,7 @@
52
52
  "devDependencies": {
53
53
  "@eslint/js": "^10.0.1",
54
54
  "eslint": "^10.2.0",
55
- "globals": "^17.4.0",
55
+ "globals": "^17.5.0",
56
56
  "mocha": "11.7.5",
57
57
  "nyc": "^18.0.0",
58
58
  "prebuild": "13.0.1",
Binary file