indexeddbshim 17.0.0 → 17.2.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.
Files changed (58) hide show
  1. package/README.md +62 -0
  2. package/badges/licenses-badge-dev.svg +1 -1
  3. package/badges/licenses-badge.svg +1 -1
  4. package/dist/CFG.d.ts +1 -0
  5. package/dist/CFG.d.ts.map +1 -1
  6. package/dist/DOMException.d.ts.map +1 -1
  7. package/dist/IDBCursor.d.ts +25 -18
  8. package/dist/IDBCursor.d.ts.map +1 -1
  9. package/dist/IDBFactory.d.ts +19 -19
  10. package/dist/IDBFactory.d.ts.map +1 -1
  11. package/dist/IDBIndex.d.ts.map +1 -1
  12. package/dist/IDBKeyRange.d.ts +5 -5
  13. package/dist/IDBKeyRange.d.ts.map +1 -1
  14. package/dist/IDBTransaction.d.ts +24 -3
  15. package/dist/IDBTransaction.d.ts.map +1 -1
  16. package/dist/Key.d.ts +41 -41
  17. package/dist/Key.d.ts.map +1 -1
  18. package/dist/indexeddbshim-Key.js +88 -69
  19. package/dist/indexeddbshim-Key.js.map +1 -1
  20. package/dist/indexeddbshim-Key.min.js +2 -2
  21. package/dist/indexeddbshim-Key.min.js.map +1 -1
  22. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs +1682 -1056
  23. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs.map +1 -1
  24. package/dist/indexeddbshim-UnicodeIdentifiers.js +886 -663
  25. package/dist/indexeddbshim-UnicodeIdentifiers.js.map +1 -1
  26. package/dist/indexeddbshim-UnicodeIdentifiers.min.js +4 -4
  27. package/dist/indexeddbshim-UnicodeIdentifiers.min.js.map +1 -1
  28. package/dist/indexeddbshim-node.cjs +1682 -1056
  29. package/dist/indexeddbshim-node.cjs.map +1 -1
  30. package/dist/indexeddbshim-noninvasive.js +886 -663
  31. package/dist/indexeddbshim-noninvasive.js.map +1 -1
  32. package/dist/indexeddbshim-noninvasive.min.js +4 -4
  33. package/dist/indexeddbshim-noninvasive.min.js.map +1 -1
  34. package/dist/indexeddbshim.js +890 -660
  35. package/dist/indexeddbshim.js.map +1 -1
  36. package/dist/indexeddbshim.min.js +4 -4
  37. package/dist/indexeddbshim.min.js.map +1 -1
  38. package/dist/node-UnicodeIdentifiers.d.ts.map +1 -1
  39. package/dist/node.d.ts.map +1 -1
  40. package/dist/nodeSQLiteDatabase.d.ts +65 -0
  41. package/dist/nodeSQLiteDatabase.d.ts.map +1 -0
  42. package/dist/nodeWebSQL.d.ts.map +1 -1
  43. package/dist/util.d.ts +5 -0
  44. package/dist/util.d.ts.map +1 -1
  45. package/package.json +22 -18
  46. package/src/CFG.js +3 -0
  47. package/src/DOMException.js +10 -10
  48. package/src/IDBCursor.js +152 -102
  49. package/src/IDBFactory.js +30 -26
  50. package/src/IDBIndex.js +1 -2
  51. package/src/IDBKeyRange.js +5 -5
  52. package/src/IDBTransaction.js +35 -5
  53. package/src/Key.js +63 -55
  54. package/src/node-UnicodeIdentifiers.js +5 -1
  55. package/src/node.js +5 -1
  56. package/src/nodeSQLiteDatabase.js +174 -0
  57. package/src/nodeWebSQL.js +1 -3
  58. package/src/util.js +62 -3
package/src/IDBFactory.js CHANGED
@@ -388,7 +388,7 @@ function IDBFactory () {
388
388
  * __connections: {
389
389
  * [key: string]: import('./IDBDatabase.js').IDBDatabaseFull[]
390
390
  * }
391
- * }} IDBFactoryFull
391
+ * }} IDBFactoryFull
392
392
  */
393
393
 
394
394
  const IDBFactoryAlias = IDBFactory;
@@ -593,7 +593,7 @@ IDBFactory.prototype.open = function (name /* , version */) {
593
593
  req.__result
594
594
  ).__versionTransaction = null;
595
595
  sysdbFinishedCb(systx, false, function () {
596
- req.transaction.__transFinishedCb(false, function () {
596
+ req.transaction.__callTransFinishedCb(false, function () {
597
597
  ev.complete();
598
598
  req.__transaction = null;
599
599
  });
@@ -716,6 +716,13 @@ IDBFactory.prototype.open = function (name /* , version */) {
716
716
  function openDB (oldVersion) {
717
717
  /** @type {DatabaseFull} */
718
718
  let db;
719
+ if (version === undefined) {
720
+ // Resolve before use as a cache key below, or a `open(name)` call
721
+ // (no explicit version) would cache/look up under `undefined`
722
+ // instead of the actual version, causing a second, separate
723
+ // connection to be opened for the same database on reopen.
724
+ version = oldVersion || 1;
725
+ }
719
726
  if ((useMemoryDatabase || useDatabaseCache) && Object.hasOwn(websqlDBCache, name) && Object.hasOwn(websqlDBCache[name], version)) {
720
727
  db = websqlDBCache[name][version];
721
728
  } else {
@@ -733,9 +740,6 @@ IDBFactory.prototype.open = function (name /* , version */) {
733
740
  }
734
741
  }
735
742
 
736
- if (version === undefined) {
737
- version = oldVersion || 1;
738
- }
739
743
  if (oldVersion > version) {
740
744
  const err = createDOMException('VersionError', 'An attempt was made to open a database using a lower version than the existing version.', version);
741
745
  if (useDatabaseCache) {
@@ -905,7 +909,7 @@ IDBFactory.prototype.deleteDatabase = function (name) {
905
909
  ({version} = data.rows.item(0));
906
910
 
907
911
  const openConnections = me.__connections[name] || [];
908
- triggerAnyVersionChangeAndBlockedEvents(openConnections, req, version, null).then(function () { // eslint-disable-line promise/catch-or-return -- Sync promise
912
+ triggerAnyVersionChangeAndBlockedEvents(openConnections, req, version, null).then(function () {
909
913
  // Since we need two databases which can't be in a single transaction, we
910
914
  // do this deleting from `dbVersions` first since the `__sys__` deleting
911
915
  // only impacts file memory whereas this one is critical for avoiding it
@@ -934,7 +938,7 @@ IDBFactory.prototype.deleteDatabase = function (name) {
934
938
  });
935
939
  return undefined;
936
940
  // @ts-expect-error It's ok
937
- }, dbError);
941
+ }).catch(dbError);
938
942
  return undefined;
939
943
  }, dbError);
940
944
  });
@@ -966,14 +970,14 @@ IDBFactory.prototype.cmp = function (key1, key2) {
966
970
  };
967
971
 
968
972
  /**
969
- * May return outdated information if a database has since been deleted.
970
- * @see https://github.com/w3c/IndexedDB/pull/240/files
971
- * @this {IDBFactoryFull}
972
- * @returns {Promise<{
973
- * name: string,
974
- * version: Integer
975
- * }[]>}
976
- */
973
+ * May return outdated information if a database has since been deleted.
974
+ * @see https://github.com/w3c/IndexedDB/pull/240/files
975
+ * @this {IDBFactoryFull}
976
+ * @returns {Promise<{
977
+ * name: string,
978
+ * version: Integer
979
+ * }[]>}
980
+ */
977
981
  IDBFactory.prototype.databases = function () {
978
982
  const me = this;
979
983
  let calledDbCreateError = false;
@@ -1018,17 +1022,17 @@ IDBFactory.prototype.databases = function () {
1018
1022
  };
1019
1023
 
1020
1024
  /**
1021
- * @todo forceClose: Test
1022
- * This is provided to facilitate unit-testing of the
1023
- * closing of a database connection with a forced flag:
1024
- * <https://w3c.github.io/IndexedDB/#steps-for-closing-a-database-connection>
1025
- * @param {string} dbName
1026
- * @param {Integer} connIdx
1027
- * @param {string} msg
1028
- * @throws {TypeError}
1029
- * @this {IDBFactoryFull}
1030
- * @returns {void}
1031
- */
1025
+ * @todo forceClose: Test
1026
+ * This is provided to facilitate unit-testing of the
1027
+ * closing of a database connection with a forced flag:
1028
+ * <https://w3c.github.io/IndexedDB/#steps-for-closing-a-database-connection>
1029
+ * @param {string} dbName
1030
+ * @param {Integer} connIdx
1031
+ * @param {string} msg
1032
+ * @throws {TypeError}
1033
+ * @this {IDBFactoryFull}
1034
+ * @returns {void}
1035
+ */
1032
1036
  IDBFactory.prototype.__forceClose = function (dbName, connIdx, msg) {
1033
1037
  const me = this;
1034
1038
  /**
package/src/IDBIndex.js CHANGED
@@ -733,8 +733,7 @@ IDBIndex.prototype.__renameIndex = function (store, oldName, newName, colInfoToP
733
733
  );
734
734
  })
735
735
  );
736
- SyncPromise.all(indexCreations).then(
737
- finish,
736
+ SyncPromise.all(indexCreations).then(finish).catch(
738
737
  /** @type {(reason: any) => PromiseLike<never>} */
739
738
  (error)
740
739
  ).catch((err) => {
@@ -6,11 +6,11 @@ const readonlyProperties = /** @type {const} */ (['lower', 'upper', 'lowerOpen',
6
6
 
7
7
  /**
8
8
  * @typedef {globalThis.IDBKeyRange & {
9
- * __lowerCached: string|null|false,
10
- * __upperCached: string|null|false,
11
- * __lowerOpen: boolean,
12
- * }} IDBKeyRangeFull
13
- */
9
+ * __lowerCached: string|null|false,
10
+ * __upperCached: string|null|false,
11
+ * __lowerOpen: boolean,
12
+ * }} IDBKeyRangeFull
13
+ */
14
14
 
15
15
  /**
16
16
  * The IndexedDB KeyRange object.
@@ -50,6 +50,7 @@ const readonlyProperties = ['objectStoreNames', 'mode', 'db', 'error'];
50
50
  * },
51
51
  * __requestsFinished: boolean,
52
52
  * __transFinishedCb: (err: boolean, cb: ((bool?: boolean) => void)) => void,
53
+ * __callTransFinishedCb: (err: boolean, cb: ((bool?: boolean) => void)) => void,
53
54
  * __transactionEndCallback: () => void,
54
55
  * __transactionFinished: boolean,
55
56
  * __completed: boolean,
@@ -157,6 +158,35 @@ IDBTransaction.prototype = EventTargetFactory.createInstance({
157
158
  IDBTransaction.prototype.__transFinishedCb = function (err, cb) {
158
159
  cb(Boolean(err));
159
160
  };
161
+
162
+ /**
163
+ * In Node, the real (SQL-commit-capable) `__transFinishedCb` is only
164
+ * installed once the underlying WebSQL driver's own SQL-queue-idle check
165
+ * has fired at least once for this transaction (asynchronously, via the
166
+ * `nonstandardTransCb` passed to `db.transaction`/`.readTransaction`).
167
+ * Since our own request processing can now finish synchronously (e.g., a
168
+ * trivial upgrade using a synchronous SQL driver), it is possible to reach
169
+ * transaction completion here before that has happened, in which case
170
+ * `__transFinishedCb` is still the non-committing default above. Calling
171
+ * that default directly would silently skip the actual SQL commit and
172
+ * leave the underlying WebSQL transaction "running" forever, hanging any
173
+ * later transaction on that same database connection. So, if the real
174
+ * callback isn't installed yet, defer and retry until it is.
175
+ * @this {IDBTransactionFull}
176
+ * @param {boolean} err
177
+ * @param {(bool?: boolean) => void} cb
178
+ * @returns {void}
179
+ */
180
+ IDBTransaction.prototype.__callTransFinishedCb = function (err, cb) {
181
+ const me = this;
182
+ if (me.__transFinishedCb === IDBTransaction.prototype.__transFinishedCb) {
183
+ setTimeout(() => {
184
+ me.__callTransFinishedCb(err, cb);
185
+ }, 0);
186
+ return;
187
+ }
188
+ me.__transFinishedCb(err, cb);
189
+ };
160
190
  /**
161
191
  * @this {IDBTransactionFull}
162
192
  * @returns {void}
@@ -214,7 +244,7 @@ IDBTransaction.prototype.__executeRequests = function () {
214
244
  me.__abortTransaction(createDOMException('AbortError', 'A request was aborted (in user handler after success).'));
215
245
  return;
216
246
  }
217
- executeNextRequest();
247
+ util.runContinuationSafely(executeNextRequest);
218
248
  }
219
249
 
220
250
  /**
@@ -282,7 +312,7 @@ IDBTransaction.prototype.__executeRequests = function () {
282
312
  try {
283
313
  q = me.__requests[i];
284
314
  if (!q.req) {
285
- q.op(tx, q.args, executeNextRequest, error);
315
+ q.op(tx, q.args, () => util.runContinuationSafely(executeNextRequest), error);
286
316
  return;
287
317
  }
288
318
  if (q.req.__done) { // Avoid continuing with aborted requests
@@ -746,10 +776,10 @@ IDBTransaction.__assertActive = function (tx) {
746
776
  };
747
777
 
748
778
  /**
749
- * Used by our `EventTarget.prototype` library to implement bubbling/capturing.
779
+ * Used by our `EventTarget.prototype` library to implement bubbling/capturing.
750
780
  * @this {IDBTransactionFull}
751
- * @returns {import('./IDBDatabase.js').IDBDatabaseFull}
752
- */
781
+ * @returns {import('./IDBDatabase.js').IDBDatabaseFull}
782
+ */
753
783
  IDBTransaction.prototype.__getParent = function () {
754
784
  return this.db;
755
785
  };
package/src/Key.js CHANGED
@@ -32,14 +32,14 @@ import CFG from './CFG.js';
32
32
  */
33
33
 
34
34
  /**
35
- * @typedef {object} KeyValueObject
36
- * @property {KeyType|"NaN"|"null"|"undefined"|"boolean"|"object"|"symbol"|
37
- * "function"|"bigint"} type If not `KeyType`, indicates invalid value
38
- * @property {Value} [value]
39
- * @property {boolean} [invalid]
40
- * @property {string} [message]
41
- * @todo Specify acceptable `value` more precisely
42
- */
35
+ * @typedef {object} KeyValueObject
36
+ * @property {KeyType|"NaN"|"null"|"undefined"|"boolean"|"object"|"symbol"|
37
+ * "function"|"bigint"} type If not `KeyType`, indicates invalid value
38
+ * @property {Value} [value]
39
+ * @property {boolean} [invalid]
40
+ * @property {string} [message]
41
+ * @todo Specify acceptable `value` more precisely
42
+ */
43
43
 
44
44
  /**
45
45
  * @typedef {number|string|Date|ArrayBuffer} ValueTypePrimitive
@@ -283,14 +283,22 @@ const types = {
283
283
  encoded[i] = encodedItem;
284
284
  }
285
285
  encoded.push(keyTypeToEncodedChar.invalid + '-'); // append an extra item, so empty arrays sort correctly
286
- return keyTypeToEncodedChar.array + '-' + JSON.stringify(encoded);
286
+ let encodedKey = JSON.stringify(encoded);
287
+ if (CFG.escapeNULForSQLiteStatements === false) {
288
+ encodedKey = encodedKey.replaceAll(String.raw`\u0000`, '\0');
289
+ }
290
+ return keyTypeToEncodedChar.array + '-' + encodedKey;
287
291
  },
288
292
  /**
289
293
  * @param {string} key
290
294
  * @returns {ValueTypeArray}
291
295
  */
292
296
  decode (key) {
293
- const decoded = JSON.parse(key.slice(2));
297
+ let decodedKey = key.slice(2);
298
+ if (CFG.escapeNULForSQLiteStatements === false) {
299
+ decodedKey = decodedKey.replaceAll('\0', String.raw`\u0000`);
300
+ }
301
+ const decoded = JSON.parse(decodedKey);
294
302
  decoded.pop(); // remove the extra item
295
303
  for (let i = 0; i < decoded.length; i++) {
296
304
  const item = decoded[i];
@@ -466,10 +474,10 @@ function convertValueToKey (input, seen) {
466
474
  }
467
475
 
468
476
  /**
469
- * Currently not in use.
470
- * @param {Value} input
471
- * @returns {KeyValueObject}
472
- */
477
+ * Currently not in use.
478
+ * @param {Value} input
479
+ * @returns {KeyValueObject}
480
+ */
473
481
  function convertValueToMultiEntryKey (input) {
474
482
  return convertValueToKeyValueDecoded(input, null, true, true);
475
483
  }
@@ -508,17 +516,17 @@ function getCopyBytesHeldByBufferSource (O) {
508
516
  }
509
517
 
510
518
  /**
511
- * Shortcut utility to avoid returning full keys from `convertValueToKey`
512
- * and subsequent need to process in calling code unless `fullKeys` is
513
- * set; may throw.
514
- * @param {Value} input
515
- * @param {Value[]|null} [seen]
516
- * @param {boolean} [multiEntry]
517
- * @param {boolean} [fullKeys]
518
- * @throws {TypeError} See `getCopyBytesHeldByBufferSource`
519
- * @todo Document other allowable `input`
520
- * @returns {KeyValueObject}
521
- */
519
+ * Shortcut utility to avoid returning full keys from `convertValueToKey`
520
+ * and subsequent need to process in calling code unless `fullKeys` is
521
+ * set; may throw.
522
+ * @param {Value} input
523
+ * @param {Value[]|null} [seen]
524
+ * @param {boolean} [multiEntry]
525
+ * @param {boolean} [fullKeys]
526
+ * @throws {TypeError} See `getCopyBytesHeldByBufferSource`
527
+ * @todo Document other allowable `input`
528
+ * @returns {KeyValueObject}
529
+ */
522
530
  function convertValueToKeyValueDecoded (input, seen, multiEntry, fullKeys) {
523
531
  seen ||= [];
524
532
  if (seen.includes(input)) {
@@ -615,12 +623,12 @@ function convertValueToMultiEntryKeyDecoded (key, fullKeys) {
615
623
  }
616
624
 
617
625
  /**
618
- * An internal utility.
619
- * @param {Value} input
620
- * @param {Value[]|null|undefined} [seen]
621
- * @throws {DOMException} `DataError`
622
- * @returns {KeyValueObject}
623
- */
626
+ * An internal utility.
627
+ * @param {Value} input
628
+ * @param {Value[]|null|undefined} [seen]
629
+ * @throws {DOMException} `DataError`
630
+ * @returns {KeyValueObject}
631
+ */
624
632
  function convertValueToKeyRethrowingAndIfInvalid (input, seen) {
625
633
  const key = convertValueToKey(input, seen);
626
634
  if (key.invalid) {
@@ -641,26 +649,26 @@ function extractKeyFromValueUsingKeyPath (value, keyPath, multiEntry) {
641
649
  return extractKeyValueDecodedFromValueUsingKeyPath(value, keyPath, multiEntry, true);
642
650
  }
643
651
  /**
644
- * Not currently in use.
645
- * @param {Value} value
646
- * @param {KeyPath} keyPath
647
- * @param {boolean} multiEntry
648
- * @returns {KeyPathEvaluateValue}
649
- */
652
+ * Not currently in use.
653
+ * @param {Value} value
654
+ * @param {KeyPath} keyPath
655
+ * @param {boolean} multiEntry
656
+ * @returns {KeyPathEvaluateValue}
657
+ */
650
658
  function evaluateKeyPathOnValue (value, keyPath, multiEntry) {
651
659
  return evaluateKeyPathOnValueToDecodedValue(value, keyPath, multiEntry, true);
652
660
  }
653
661
 
654
662
  /**
655
- * May throw, return `{failure: true}` (e.g., non-object on keyPath resolution)
656
- * or `{invalid: true}` (e.g., `NaN`).
657
- * @param {Value} value
658
- * @param {KeyPath} keyPath
659
- * @param {boolean} [multiEntry]
660
- * @param {boolean} [fullKeys]
661
- * @returns {KeyValueObject|KeyPathEvaluateValue}
662
- * @todo Document other possible return?
663
- */
663
+ * May throw, return `{failure: true}` (e.g., non-object on keyPath resolution)
664
+ * or `{invalid: true}` (e.g., `NaN`).
665
+ * @param {Value} value
666
+ * @param {KeyPath} keyPath
667
+ * @param {boolean} [multiEntry]
668
+ * @param {boolean} [fullKeys]
669
+ * @returns {KeyValueObject|KeyPathEvaluateValue}
670
+ * @todo Document other possible return?
671
+ */
664
672
  function extractKeyValueDecodedFromValueUsingKeyPath (value, keyPath, multiEntry, fullKeys) {
665
673
  const r = evaluateKeyPathOnValueToDecodedValue(value, keyPath, multiEntry, fullKeys);
666
674
  if (r.failure) {
@@ -891,11 +899,11 @@ function findMultiEntryMatches (keyEntry, range) {
891
899
  }
892
900
 
893
901
  /**
894
- * Not currently in use but keeping for spec parity.
895
- * @param {Key} key
896
- * @throws {Error} Upon a "bad key"
897
- * @returns {ValueType}
898
- */
902
+ * Not currently in use but keeping for spec parity.
903
+ * @param {Key} key
904
+ * @throws {Error} Upon a "bad key"
905
+ * @returns {ValueType}
906
+ */
899
907
  function convertKeyToValue (key) {
900
908
  const {type, value} = key;
901
909
  switch (type) {
@@ -977,10 +985,10 @@ const MAX_ALLOWED_CURRENT_NUMBER = 9007199254740992; // 2 ^ 53 (Also equal to `N
977
985
  */
978
986
 
979
987
  /**
980
- * @callback SQLFailureCallback
981
- * @param {DOMException|Error} exception
982
- * @returns {void}
983
- */
988
+ * @callback SQLFailureCallback
989
+ * @param {DOMException|Error} exception
990
+ * @returns {void}
991
+ */
984
992
 
985
993
  /**
986
994
  *
@@ -13,7 +13,11 @@ CFG.win = {openDatabase: nodeWebSQL};
13
13
  * @returns {import('./setGlobalVars.js').ShimmedObject|Window}
14
14
  */
15
15
  const __setGlobalVars = function (idb, initialConfig = {}) {
16
- const obj = setGlobalVars(idb, {fs, ...initialConfig});
16
+ const obj = setGlobalVars(idb, {
17
+ fs,
18
+ escapeNULForSQLiteStatements: false,
19
+ ...initialConfig
20
+ });
17
21
  /* istanbul ignore next -- TS guard */
18
22
  if (!obj.shimIndexedDB) {
19
23
  return obj;
package/src/node.js CHANGED
@@ -12,7 +12,11 @@ CFG.win = {openDatabase: nodeWebSQL};
12
12
  * @returns {import('./setGlobalVars.js').ShimmedObject|Window}
13
13
  */
14
14
  const __setGlobalVars = function (idb, initialConfig = {}) {
15
- return setGlobalVars(idb, {fs, ...initialConfig});
15
+ return setGlobalVars(idb, {
16
+ fs,
17
+ escapeNULForSQLiteStatements: false,
18
+ ...initialConfig
19
+ });
16
20
  };
17
21
 
18
22
  export default __setGlobalVars;
@@ -0,0 +1,174 @@
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
+ /**
34
+ * @param {string} name
35
+ * @param {{busyTimeout?: number, trace?: (sql: string) => void, profile?: SQLProfileCallback}} [opts]
36
+ * @returns {void}
37
+ */
38
+ function SQLiteDatabase (name, opts = {}) {
39
+ /** @type {import('better-sqlite3').Database} */
40
+ const db = new Database(name);
41
+
42
+ /** @type {SQLTraceCallback} */
43
+ // eslint-disable-next-line prefer-destructuring -- TS
44
+ let trace = opts.trace;
45
+ /** @type {SQLProfileCallback|undefined} */
46
+ // eslint-disable-next-line prefer-destructuring -- TS
47
+ let profile = opts.profile;
48
+
49
+ if (opts.busyTimeout) {
50
+ db.pragma('busy_timeout = ' + Number(opts.busyTimeout));
51
+ }
52
+
53
+ // Kept untyped (rather than the better-sqlite3 `Database` type) since that
54
+ // type is internal to `@types/better-sqlite3` and can't be named in this
55
+ // file's emitted declaration.
56
+ this._db = /** @type {any} */ ({
57
+ _db: db,
58
+ /**
59
+ * Compatibility with node-sqlite3's configure API.
60
+ * @param {'busyTimeout'|'trace'|'profile'} option
61
+ * @param {number|((sql: string, duration?: number) => void)} value
62
+ * @returns {void}
63
+ */
64
+ configure (option, value) {
65
+ if (option === 'busyTimeout') {
66
+ db.pragma('busy_timeout = ' + Number(/** @type {number} */ (value)));
67
+ return;
68
+ }
69
+ if (option === 'trace') {
70
+ trace = /** @type {(sql: string) => void} */ (value);
71
+ return;
72
+ }
73
+ if (option === 'profile') {
74
+ profile = /** @type {SQLProfileCallback} */ (value);
75
+ }
76
+ },
77
+ /**
78
+ * Compatibility with callback-oriented close semantics.
79
+ * @param {(err?: Error|null) => void} [cb]
80
+ * @returns {void}
81
+ */
82
+ close (cb) {
83
+ try {
84
+ db.close();
85
+ if (cb) {
86
+ return cb(null);
87
+ }
88
+ } catch (err) {
89
+ if (cb) {
90
+ return cb(/** @type {Error} */ (err));
91
+ }
92
+ }
93
+ return undefined;
94
+ },
95
+ getTrace () {
96
+ return trace;
97
+ },
98
+ getProfile () {
99
+ return profile;
100
+ }
101
+ });
102
+ }
103
+
104
+ /**
105
+ * @param {import('better-sqlite3').Database} db
106
+ * @param {string} sql
107
+ * @param {unknown[]} args
108
+ * @returns {object[]}
109
+ */
110
+ function runSelect (db, sql, args) {
111
+ const stmt = db.prepare(sql);
112
+ return stmt.reader ? /** @type {object[]} */ (stmt.all(...args)) : [];
113
+ }
114
+
115
+ /**
116
+ * @param {import('better-sqlite3').Database} db
117
+ * @param {string} sql
118
+ * @param {unknown[]} args
119
+ * @returns {import('better-sqlite3').RunResult}
120
+ */
121
+ function runNonSelect (db, sql, args) {
122
+ const stmt = db.prepare(sql);
123
+ return stmt.run(...args);
124
+ }
125
+
126
+ /**
127
+ * @param {{sql: string, args: unknown[]}[]} queries
128
+ * @param {boolean} readOnly
129
+ * @param {(err: Error|null, results?: SQLiteResult[]) => void} callback
130
+ * @returns {void}
131
+ */
132
+ SQLiteDatabase.prototype.exec = function exec (queries, readOnly, callback) {
133
+ const db = this._db._db;
134
+ const len = queries.length;
135
+ const results = Array.from({length: len});
136
+
137
+ for (let i = 0; i < len; i++) {
138
+ const query = queries[i];
139
+ const {sql, args} = query;
140
+ const isSelect = (/^\s*SELECT\b/iu).test(sql);
141
+ if (readOnly && !isSelect) {
142
+ results[i] = new SQLiteResult(READ_ONLY_ERROR);
143
+ continue;
144
+ }
145
+ const trace = this._db.getTrace();
146
+ const profile = this._db.getProfile();
147
+ // eslint-disable-next-line unicorn/prefer-bigint-literals -- `0n` needs ES2020+ target for tsc
148
+ const start = profile ? process.hrtime.bigint() : BigInt(0);
149
+ try {
150
+ if (trace) {
151
+ trace(sql);
152
+ }
153
+ if (isSelect) {
154
+ const rows = runSelect(db, sql, args);
155
+ results[i] = new SQLiteResult(null, undefined, 0, rows);
156
+ } else {
157
+ const executionResult = runNonSelect(db, sql, args);
158
+ const insertId = Number(executionResult.lastInsertRowid);
159
+ results[i] = new SQLiteResult(null, insertId, executionResult.changes, []);
160
+ }
161
+ } catch (err) {
162
+ results[i] = new SQLiteResult(/** @type {Error} */ (err));
163
+ } finally {
164
+ if (profile) {
165
+ profile(sql, Number(process.hrtime.bigint() - start));
166
+ }
167
+ }
168
+ }
169
+ queueMicrotask(() => {
170
+ callback(null, results);
171
+ });
172
+ };
173
+
174
+ export default SQLiteDatabase;
package/src/nodeWebSQL.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import customOpenDatabase from 'websql-configurable/custom/index.js';
2
- import SQLiteDatabase from 'websql-configurable/lib/sqlite/SQLiteDatabase.js';
2
+ import SQLiteDatabase from './nodeSQLiteDatabase.js';
3
3
  import CFG from './CFG.js';
4
4
 
5
5
  /**
@@ -12,11 +12,9 @@ function wrappedSQLiteDatabase (name) {
12
12
  db._db.configure('busyTimeout', /** @type {number} */ (CFG.sqlBusyTimeout)); // Default is 1000
13
13
  }
14
14
  if (CFG.sqlTrace) {
15
- // @ts-expect-error native API?
16
15
  db._db.configure('trace', CFG.sqlTrace);
17
16
  }
18
17
  if (CFG.sqlProfile) {
19
- // @ts-expect-error native API?
20
18
  db._db.configure('profile', CFG.sqlProfile);
21
19
  }
22
20
  return db;