indexeddbshim 17.4.3 → 17.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.
@@ -15,5 +15,5 @@ export type SQLiteDatabaseInstance = {
15
15
  */
16
16
  export type SQLiteDatabaseConstructor = new (name: string, opts?: any) => SQLiteDatabaseInstance;
17
17
  declare const nodeWebSQL: (...args: unknown[]) => import("websql-configurable/lib/websql/WebSQLDatabase.js").default;
18
- import SQLiteDatabase from './nodeSQLiteDatabase.js';
18
+ import SQLiteDatabase from 'websql-configurable/lib/sqlite/SQLiteDatabase.js';
19
19
  //# sourceMappingURL=nodeWebSQL.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"nodeWebSQL.d.ts","sourceRoot":"","sources":["../src/nodeWebSQL.js"],"names":[],"mappings":";qCAKa;IACR,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,CAAA,OAAO,cAAc,EAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;CACjD;;;;;;;;;;;wCAYS,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,KAAK,sBAAsB;AAgCrE,qHAAsK;2BAnD3I,yBAAyB"}
1
+ {"version":3,"file":"nodeWebSQL.d.ts","sourceRoot":"","sources":["../src/nodeWebSQL.js"],"names":[],"mappings":";qCAKa;IACR,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,CAAA,OAAO,cAAc,EAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;CACjD;;;;;;;;;;;wCAYS,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,KAAK,sBAAsB;AAgCrE,qHAAsK;2BAnD3I,kDAAkD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "indexeddbshim",
3
- "version": "17.4.3",
3
+ "version": "17.6.0",
4
4
  "type": "module",
5
5
  "author": "Parashuram <code@nparashuram.com>",
6
6
  "contributors": [
@@ -55,7 +55,6 @@
55
55
  "fix-permissions": "chmod +x node_modules/npm-run-all/bin/*/index.js",
56
56
  "lint": "eslint .",
57
57
  "eslint": "npm run lint --",
58
- "sqlite-rebuild": "cd node_modules/sqlite3 && npm i",
59
58
  "w3c-build-only": "node test-support/node-buildjs",
60
59
  "w3c-build": "run-s clean-w3c clean-w3c-built w3c-build-only",
61
60
  "fake": "run-s clean-fake coverage:fake clean-fake",
@@ -168,13 +167,12 @@
168
167
  },
169
168
  "dependencies": {
170
169
  "@node-static/node-static": "^0.9.1",
171
- "better-sqlite3": "^13.0.3",
172
170
  "canvas": "^3.2.3",
173
171
  "eventtargeter": "0.12.0",
174
172
  "sync-promise-expanded": "^2.0.0",
175
173
  "typeson": "10.2.0",
176
174
  "typeson-registry": "14.3.0",
177
- "websql-configurable": "^3.1.0"
175
+ "websql-configurable": "^4.0.0"
178
176
  },
179
177
  "devDependencies": {
180
178
  "@arethetypeswrong/cli": "^0.18.5",
@@ -187,7 +185,6 @@
187
185
  "@rollup/plugin-json": "^6.1.0",
188
186
  "@rollup/plugin-node-resolve": "^16.0.3",
189
187
  "@rollup/plugin-terser": "^1.0.0",
190
- "@types/better-sqlite3": "^9.6.0",
191
188
  "@types/node": "^26.4.0",
192
189
  "@types/websql": "^0.0.30",
193
190
  "@unicode/unicode-17.0.0": "^2.0.2",
@@ -202,7 +199,7 @@
202
199
  "coveradge": "^0.8.2",
203
200
  "delay-cli": "^3.0.0",
204
201
  "eslint": "^10.9.1",
205
- "eslint-config-ash-nazg": "^42.6.0",
202
+ "eslint-config-ash-nazg": "^43.1.0",
206
203
  "globals": "^17.11.0",
207
204
  "is-date-object": "1.1.0",
208
205
  "isomorphic-fetch": "^3.0.0",
@@ -215,7 +212,7 @@
215
212
  "npm-run-all": "^4.1.5",
216
213
  "open-cli": "^9.0.0",
217
214
  "rimraf": "^6.1.3",
218
- "rollup": "^4.63.0",
215
+ "rollup": "^4.63.1",
219
216
  "rollup-plugin-filesize": "^10.0.0",
220
217
  "rollup-plugin-node-polyfills": "^0.2.1",
221
218
  "sinon": "^22.1.0",
@@ -227,7 +224,6 @@
227
224
  "allowScripts": {
228
225
  "es5-ext@0.10.64": true,
229
226
  "fsevents@2.3.3": true,
230
- "sqlite3@5.1.7": true,
231
227
  "canvas@3.2.3": true,
232
228
  "unrs-resolver@1.12.2": true,
233
229
  "better-sqlite3@13.0.3": true,
package/src/IDBFactory.js CHANGED
@@ -220,9 +220,7 @@ function triggerAnyVersionChangeAndBlockedEvents (openConnections, req, oldVersi
220
220
  /**
221
221
  * @typedef {import('websql-configurable/lib/websql/WebSQLDatabase.js').default & {
222
222
  * _db: {
223
- * _db: {
224
- * close: (errBack: (err: Error) => void) => void
225
- * }
223
+ * close: (errBack: (err: Error) => void) => void
226
224
  * }
227
225
  * }} DatabaseFull
228
226
  */
@@ -304,7 +302,7 @@ function closeCachedWebSQLConnections (name, cb) {
304
302
  return;
305
303
  }
306
304
  dbs.forEach((db) => {
307
- const sqliteDB = db._db && db._db._db;
305
+ const sqliteDB = db._db;
308
306
  if (!sqliteDB || !sqliteDB.close) {
309
307
  remaining--;
310
308
  if (remaining === 0) {
@@ -347,9 +345,9 @@ function cleanupDatabaseResources (__openDatabase, name, escapedDatabaseName, da
347
345
  databaseDeleted();
348
346
  return;
349
347
  }
350
- const sqliteDB = latestSQLiteDBCached._db && latestSQLiteDBCached._db._db;
348
+ const sqliteDB = latestSQLiteDBCached._db;
351
349
  if (!sqliteDB || !sqliteDB.close) {
352
- console.error('The `openDatabase` implementation does not have the expected `._db._db.close` method for closing the database');
350
+ console.error('The `openDatabase` implementation does not have the expected `._db.close` method for closing the database');
353
351
  return;
354
352
  }
355
353
  sqliteDB.close(
@@ -1229,7 +1227,7 @@ IDBFactory.prototype.databases = function () {
1229
1227
  let calledDbCreateError = false;
1230
1228
  // Snapshotted *now*, synchronously, at call time -- not read later
1231
1229
  // from inside the SQL query's callback below, which runs on a
1232
- // deferred macrotask (see `nodeSQLiteDatabase.js`'s `exec`) and so
1230
+ // deferred macrotask (see `websql-configurable`'s `SQLiteDatabase#exec`) and so
1233
1231
  // could otherwise race against (and lose to) an in-flight upgrade's
1234
1232
  // own commit/abort handler clearing its `pendingVersionChanges`
1235
1233
  // entry in the meantime -- which would make this method incorrectly
@@ -56,7 +56,7 @@ const readonlyProperties = ['objectStoreNames', 'mode', 'durability', 'db', 'err
56
56
  * __requestsFinished: boolean,
57
57
  * __transFinishedCb: (err: boolean, cb: ((bool?: boolean) => void)) => void,
58
58
  * __callTransFinishedCb: (err: boolean, cb: ((bool?: boolean) => void)) => void,
59
- * __transactionEndCallback: () => void,
59
+ * __transactionEndCallback: (() => void)|undefined,
60
60
  * __transactionFinished: boolean,
61
61
  * __completed: boolean,
62
62
  * __transFinishedCbFired: boolean,
@@ -91,6 +91,19 @@ const readonlyProperties = ['objectStoreNames', 'mode', 'durability', 'db', 'err
91
91
  * }} IDBTransactionFull
92
92
  */
93
93
 
94
+ /**
95
+ * @param {IDBTransactionFull} tx
96
+ * @returns {void}
97
+ */
98
+ function releaseFinishedTransaction (tx) {
99
+ const pos = tx.db.__transactions.indexOf(tx);
100
+ if (pos !== -1) {
101
+ tx.db.__transactions.splice(pos, 1);
102
+ }
103
+ tx.__transactionEndCallback = undefined;
104
+ tx.__requests = [];
105
+ }
106
+
94
107
  /**
95
108
  * The IndexedDB Transaction.
96
109
  * @see https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBTransaction
@@ -645,6 +658,7 @@ IDBTransaction.prototype.__executeRequests = function () {
645
658
  } finally {
646
659
  activeTransactions.delete(me);
647
660
  me.__storeHandles = {};
661
+ releaseFinishedTransaction(me);
648
662
  }
649
663
  }
650
664
  if (me.mode === 'readwrite' || me.mode === 'readonly') {
@@ -928,6 +942,7 @@ IDBTransaction.prototype.__abortTransaction = function (err) {
928
942
  me.dispatchEvent(evt);
929
943
  me.__storeHandles = {};
930
944
  me.dispatchEvent(createEvent('__abort'));
945
+ releaseFinishedTransaction(me);
931
946
  }, 0);
932
947
  return undefined;
933
948
  }).catch((err) => {
package/src/nodeWebSQL.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import customOpenDatabase from 'websql-configurable/custom/index.js';
2
- import SQLiteDatabase from './nodeSQLiteDatabase.js';
2
+ import SQLiteDatabase from 'websql-configurable/lib/sqlite/SQLiteDatabase.js';
3
3
  import CFG from './CFG.js';
4
4
 
5
5
  /**
@@ -29,16 +29,16 @@ function wrappedSQLiteDatabase (name) {
29
29
  // @ts-ignore It's ok; needed under some TS versions
30
30
  const db = new SQLiteDatabase(name, {});
31
31
  if (CFG.sqlBusyTimeout) {
32
- db._db.configure('busyTimeout', /** @type {number} */ (CFG.sqlBusyTimeout)); // Default is 1000
32
+ db.configure('busyTimeout', /** @type {number} */ (CFG.sqlBusyTimeout)); // Default is 1000
33
33
  }
34
34
  if (CFG.sqlTrace) {
35
- db._db.configure('trace', CFG.sqlTrace);
35
+ db.configure('trace', CFG.sqlTrace);
36
36
  }
37
37
  if (CFG.sqlProfile) {
38
- db._db.configure('profile', CFG.sqlProfile);
38
+ db.configure('profile', CFG.sqlProfile);
39
39
  }
40
40
  if (CFG.sqlMemoryQuota) {
41
- db._db.configure('memoryQuota', /** @type {number} */ (CFG.sqlMemoryQuota));
41
+ db.configure('memoryQuota', /** @type {number} */ (CFG.sqlMemoryQuota));
42
42
  }
43
43
  return db;
44
44
  }
@@ -1,64 +0,0 @@
1
- export default SQLiteDatabase;
2
- export type SQLProfileCallback = (sql: string, duration: number) => void;
3
- export type SQLTraceCallback = ((sql: string) => void) | undefined;
4
- /**
5
- * @class
6
- * @param {string} name
7
- * @param {{busyTimeout?: number, trace?: (sql: string) => void, profile?: SQLProfileCallback}} [opts]
8
- * @this {{_db: any, _qFilePath: string}}
9
- * @returns {void}
10
- */
11
- declare function SQLiteDatabase(this: {
12
- _db: any;
13
- _qFilePath: string;
14
- }, name: string, opts?: {
15
- busyTimeout?: number;
16
- trace?: (sql: string) => void;
17
- profile?: SQLProfileCallback;
18
- }): void;
19
- declare class SQLiteDatabase {
20
- /**
21
- * @class
22
- * @param {string} name
23
- * @param {{busyTimeout?: number, trace?: (sql: string) => void, profile?: SQLProfileCallback}} [opts]
24
- * @this {{_db: any, _qFilePath: string}}
25
- * @returns {void}
26
- */
27
- constructor(this: {
28
- _db: any;
29
- _qFilePath: string;
30
- }, name: string, opts?: {
31
- busyTimeout?: number;
32
- trace?: (sql: string) => void;
33
- profile?: SQLProfileCallback;
34
- });
35
- _qFilePath: string;
36
- _db: any;
37
- /**
38
- * @param {{sql: string, args: unknown[]}[]} queries
39
- * @param {boolean} readOnly
40
- * @param {(err: Error|null, results?: SQLiteResult[]) => void} callback
41
- * @returns {void}
42
- */
43
- exec(queries: {
44
- sql: string;
45
- args: unknown[];
46
- }[], readOnly: boolean, callback: (err: Error | null, results?: SQLiteResult[]) => void): void;
47
- }
48
- /**
49
- *
50
- */
51
- declare class SQLiteResult {
52
- /**
53
- * @param {Error|null|undefined} error
54
- * @param {number|undefined} [insertId]
55
- * @param {number} [rowsAffected]
56
- * @param {object[]} [rows]
57
- */
58
- constructor(error: Error | null | undefined, insertId?: number | undefined, rowsAffected?: number, rows?: object[]);
59
- error: Error | null | undefined;
60
- insertId: number | undefined;
61
- rowsAffected: number | undefined;
62
- rows: object[] | undefined;
63
- }
64
- //# sourceMappingURL=nodeSQLiteDatabase.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"nodeSQLiteDatabase.d.ts","sourceRoot":"","sources":["../src/nodeSQLiteDatabase.js"],"names":[],"mappings":";iCAyBa,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI;+BAIvC,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAC,SAAS;AAkC9C;;;;;;GAMG;AACH;SAHgB,GAAG;gBAAc,MAAM;SAF5B,MAAM,SACN;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,kBAAkB,CAAA;CAAC,GAEjF,IAAI,CA4EhB;;IAjFD;;;;;;OAMG;IACH;aAHgB,GAAG;oBAAc,MAAM;aAF5B,MAAM,SACN;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,OAAO,CAAC,EAAE,kBAAkB,CAAA;KAAC,EA8E7F;IAzEG,mBAAsB;IAkBtB,SAsDE;IAyBN;;;;;OAKG;IACH,cALW;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,EAAE,CAAA;KAAC,EAAE,YAChC,OAAO,YACP,CAAC,GAAG,EAAE,KAAK,GAAC,IAAI,EAAE,OAAO,CAAC,EAAE,YAAY,EAAE,KAAK,IAAI,GACjD,IAAI,CA2IhB;;AArTD;;GAEG;AACH;IACI;;;;;OAKG;IACH,mBALW,KAAK,GAAC,IAAI,GAAC,SAAS,aACpB,MAAM,GAAC,SAAS,iBAChB,MAAM,SACN,MAAM,EAAE,EAOlB;IAJG,gCAAkB;IAClB,6BAAwB;IACxB,iCAAgC;IAChC,2BAAgB;CAEvB"}
@@ -1,314 +0,0 @@
1
- import Database from 'better-sqlite3';
2
-
3
- /**
4
- *
5
- */
6
- class SQLiteResult {
7
- /**
8
- * @param {Error|null|undefined} error
9
- * @param {number|undefined} [insertId]
10
- * @param {number} [rowsAffected]
11
- * @param {object[]} [rows]
12
- */
13
- constructor (error, insertId, rowsAffected, rows) {
14
- this.error = error;
15
- this.insertId = insertId;
16
- this.rowsAffected = rowsAffected;
17
- this.rows = rows;
18
- }
19
- }
20
-
21
- const READ_ONLY_ERROR = new Error(
22
- 'could not prepare statement (23 not authorized)'
23
- );
24
-
25
- /**
26
- * @typedef {(sql: string, duration: number) => void} SQLProfileCallback
27
- */
28
-
29
- /**
30
- * @typedef {((sql: string) => void)|undefined} SQLTraceCallback
31
- */
32
-
33
- // Two separate `better-sqlite3` connections (i.e., two separate
34
- // `SQLiteDatabase` instances) can legitimately point at the same
35
- // underlying file at once -- e.g. an old, about-to-close IndexedDB
36
- // connection and a newly-opened one during an upgrade. `PRAGMA
37
- // busy_timeout` can't safely arbitrate between them here: it blocks the
38
- // single JS thread synchronously while it retries, but the lock holder's
39
- // own release is itself scheduled via `setImmediate` below, which can
40
- // never fire while that retry loop is blocking the same thread -- so
41
- // instead of waiting, a second connection's write just fails with
42
- // "database is locked".
43
- //
44
- // `fileReaders`/`fileWriter` implement a simple (not starvation-proof --
45
- // see the release loop below) reader/writer lock per file path instead:
46
- // any number of `readonly` transactions may hold the file concurrently,
47
- // matching what SQLite itself already natively supports (multiple
48
- // readers never need to exclude each other, only a writer needs
49
- // exclusivity), while a non-`readonly` transaction still waits for
50
- // exclusive access -- no concurrent readers, no concurrent writer. A
51
- // simple single-owner mutex (this file's prior implementation) would
52
- // otherwise serialize even purely-concurrent-reader scenarios that
53
- // never touch a writer at all, for no reason `better-sqlite3`/SQLite
54
- // itself requires.
55
- /** @type {Map<string, Set<{_db: any, _qFilePath: string}>>} */
56
- const fileReaders = new Map();
57
- /** @type {Map<string, {_db: any, _qFilePath: string}>} */
58
- const fileWriter = new Map();
59
- /** @type {Map<string, {isReader: boolean, resume: () => void}[]>} */
60
- const fileWaiters = new Map();
61
- const beginRe = /^\s*BEGIN\b/iu;
62
- const endRe = /^\s*(END|COMMIT|ROLLBACK)\b/iu;
63
-
64
- /**
65
- * @class
66
- * @param {string} name
67
- * @param {{busyTimeout?: number, trace?: (sql: string) => void, profile?: SQLProfileCallback}} [opts]
68
- * @this {{_db: any, _qFilePath: string}}
69
- * @returns {void}
70
- */
71
- function SQLiteDatabase (name, opts = {}) {
72
- this._qFilePath = name;
73
- /** @type {import('better-sqlite3').Database} */
74
- const db = new Database(name);
75
-
76
- /** @type {SQLTraceCallback} */
77
- // eslint-disable-next-line prefer-destructuring -- TS
78
- let trace = opts.trace;
79
- /** @type {SQLProfileCallback|undefined} */
80
- // eslint-disable-next-line prefer-destructuring -- TS
81
- let profile = opts.profile;
82
-
83
- if (opts.busyTimeout) {
84
- db.pragma('busy_timeout = ' + Number(opts.busyTimeout));
85
- }
86
-
87
- // Kept untyped (rather than the better-sqlite3 `Database` type) since that
88
- // type is internal to `@types/better-sqlite3` and can't be named in this
89
- // file's emitted declaration.
90
- this._db = /** @type {any} */ ({
91
- _db: db,
92
- /**
93
- * Compatibility with node-sqlite3's configure API.
94
- * @param {'busyTimeout'|'trace'|'profile'|'memoryQuota'} option
95
- * @param {number|((sql: string, duration?: number) => void)} value
96
- * @returns {void}
97
- */
98
- configure (option, value) {
99
- if (option === 'busyTimeout') {
100
- db.pragma('busy_timeout = ' + Number(/** @type {number} */ (value)));
101
- return;
102
- }
103
- if (option === 'trace') {
104
- trace = /** @type {(sql: string) => void} */ (value);
105
- return;
106
- }
107
- if (option === 'profile') {
108
- profile = /** @type {SQLProfileCallback} */ (value);
109
- return;
110
- }
111
- if (option === 'memoryQuota') {
112
- const bytes = Number(value);
113
- // `better-sqlite3` doesn't have a direct `sqlite3_quota_set` binding,
114
- // but we can limit the max pages:
115
- const pageSizeObj = db.pragma('page_size', {simple: true});
116
- const pageSize = Number(pageSizeObj);
117
- db.pragma('max_page_count = ' + Math.ceil(bytes / pageSize));
118
- }
119
- },
120
- /**
121
- * Compatibility with callback-oriented close semantics.
122
- * @param {(err?: Error|null) => void} [cb]
123
- * @returns {void}
124
- */
125
- close (cb) {
126
- try {
127
- db.close();
128
- if (cb) {
129
- return cb(null);
130
- }
131
- } catch (err) {
132
- if (cb) {
133
- return cb(/** @type {Error} */ (err));
134
- }
135
- }
136
- return undefined;
137
- },
138
- getTrace () {
139
- return trace;
140
- },
141
- getProfile () {
142
- return profile;
143
- }
144
- });
145
- }
146
-
147
- /**
148
- * @param {import('better-sqlite3').Database} db
149
- * @param {string} sql
150
- * @param {unknown[]} args
151
- * @returns {object[]}
152
- */
153
- function runSelect (db, sql, args) {
154
- const stmt = db.prepare(sql);
155
- return stmt.reader ? /** @type {object[]} */ (stmt.all(...args)) : [];
156
- }
157
-
158
- /**
159
- * @param {import('better-sqlite3').Database} db
160
- * @param {string} sql
161
- * @param {unknown[]} args
162
- * @returns {import('better-sqlite3').RunResult}
163
- */
164
- function runNonSelect (db, sql, args) {
165
- const stmt = db.prepare(sql);
166
- return stmt.run(...args);
167
- }
168
-
169
- /**
170
- * @param {{sql: string, args: unknown[]}[]} queries
171
- * @param {boolean} readOnly
172
- * @param {(err: Error|null, results?: SQLiteResult[]) => void} callback
173
- * @returns {void}
174
- */
175
- SQLiteDatabase.prototype.exec = function exec (queries, readOnly, callback) {
176
- // `:memory:` (SQLite/`better-sqlite3`'s own sentinel for an isolated,
177
- // non-shared in-memory database) can never actually collide with a
178
- // different connection -- each `new Database(':memory:')` call is
179
- // already fully independent -- so there is nothing to serialize.
180
- const filePath = this._qFilePath === ':memory:' ? null : this._qFilePath;
181
- if (filePath && queries[0] && beginRe.test(queries[0].sql)) {
182
- const activeReaders = fileReaders.get(filePath);
183
- const hasActiveReaders = Boolean(activeReaders && activeReaders.size);
184
- const activeWriter = fileWriter.get(filePath);
185
- const blockedByWriter = Boolean(activeWriter && activeWriter !== this);
186
- const blocked = blockedByWriter || (!readOnly && hasActiveReaders);
187
- if (blocked) {
188
- const waiters = fileWaiters.get(filePath) || [];
189
- waiters.push({
190
- isReader: readOnly,
191
- resume: () => this.exec(queries, readOnly, callback)
192
- });
193
- fileWaiters.set(filePath, waiters);
194
- return;
195
- }
196
- if (readOnly) {
197
- if (activeReaders) {
198
- activeReaders.add(this);
199
- } else {
200
- fileReaders.set(filePath, new Set([this]));
201
- }
202
- } else {
203
- fileWriter.set(filePath, this);
204
- }
205
- }
206
-
207
- const db = this._db._db;
208
- const len = queries.length;
209
- const results = Array.from({length: len});
210
-
211
- for (let i = 0; i < len; i++) {
212
- const query = queries[i];
213
- const {sql, args} = query;
214
- const isSelect = (/^\s*SELECT\b/iu).test(sql);
215
- if (readOnly && !isSelect) {
216
- results[i] = new SQLiteResult(READ_ONLY_ERROR);
217
- continue;
218
- }
219
- const trace = this._db.getTrace();
220
- const profile = this._db.getProfile();
221
- // eslint-disable-next-line unicorn/prefer-bigint-literals -- `0n` needs ES2020+ target for tsc
222
- const start = profile ? process.hrtime.bigint() : BigInt(0);
223
- try {
224
- if (trace) {
225
- trace(sql);
226
- }
227
- if (isSelect) {
228
- const rows = runSelect(db, sql, args);
229
- results[i] = new SQLiteResult(null, undefined, 0, rows);
230
- } else {
231
- const executionResult = runNonSelect(db, sql, args);
232
- const insertId = Number(executionResult.lastInsertRowid);
233
- results[i] = new SQLiteResult(null, insertId, executionResult.changes, []);
234
- }
235
- } catch (err) {
236
- results[i] = new SQLiteResult(/** @type {Error} */ (err));
237
- } finally {
238
- if (profile) {
239
- profile(sql, Number(process.hrtime.bigint() - start));
240
- }
241
- }
242
- }
243
- // A real macrotask (not `queueMicrotask`) so this yields properly:
244
- // code that synchronously issues a new request from within each
245
- // request's callback (e.g. to keep a transaction alive) would
246
- // otherwise chain microtask to microtask forever, starving out any
247
- // `setTimeout`-based code (including IndexedDB's own internal request
248
- // scheduling) that never gets a turn to run. `setImmediate` (Node's
249
- // "check" phase) still yields the same way `setTimeout(..., 0)` did,
250
- // but runs sooner in Node's event loop.
251
- setImmediate(() => {
252
- // Release the file lock (if held) and hand it to the next waiting
253
- // connection, if any, only once this transaction has genuinely
254
- // finished -- and only here, on its own turn, so a resumed
255
- // waiter's own `exec` call can't run reentrantly inside this one.
256
- // Checked across the whole batch, not just `queries[0]`: when a
257
- // transaction's `BEGIN` and its later `ROLLBACK`/`COMMIT` are both
258
- // queued synchronously before `websql-configurable`'s async flush
259
- // gets a turn to run (e.g. an immediate `transaction.abort()`
260
- // inside `onupgradeneeded`), they arrive coalesced into a single
261
- // batch/`exec()` call, with the terminal statement anywhere but
262
- // first -- so only checking `queries[0]` would acquire the lock
263
- // and never see the matching release, deadlocking every later
264
- // connection to the same file.
265
- if (filePath && queries.some((q) => endRe.test(q.sql))) {
266
- const activeReaders = fileReaders.get(filePath);
267
- if (activeReaders) {
268
- activeReaders.delete(this);
269
- if (!activeReaders.size) {
270
- fileReaders.delete(filePath);
271
- }
272
- }
273
- if (fileWriter.get(filePath) === this) {
274
- fileWriter.delete(filePath);
275
- }
276
- // Resume as many waiters as the lock now genuinely allows:
277
- // any leading run of readers (concurrent reads are always
278
- // fine), then at most one writer, since a writer needs
279
- // exclusivity -- stop there rather than looking past it.
280
- // Not starvation-proof: a steady stream of arriving readers
281
- // could in principle keep a waiting writer waiting
282
- // indefinitely, but that's an acceptable tradeoff here over
283
- // the added complexity of tracking arrival order across
284
- // reader/writer kinds.
285
- const waiters = fileWaiters.get(filePath);
286
- if (waiters) {
287
- while (waiters.length) {
288
- if (fileWriter.has(filePath)) {
289
- break;
290
- }
291
- const next = waiters[0];
292
- const stillHasReaders = fileReaders.get(filePath);
293
- if (!next.isReader && stillHasReaders && stillHasReaders.size) {
294
- break;
295
- }
296
- waiters.shift();
297
- next.resume();
298
- if (!next.isReader) {
299
- // A writer just took exclusive ownership (inside
300
- // `resume()`, synchronously, via the acquire
301
- // logic above) -- nothing else may proceed now.
302
- break;
303
- }
304
- }
305
- if (!waiters.length) {
306
- fileWaiters.delete(filePath);
307
- }
308
- }
309
- }
310
- callback(null, results);
311
- });
312
- };
313
-
314
- export default SQLiteDatabase;