indexeddbshim 17.2.2 → 17.3.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 (92) hide show
  1. package/README.md +8 -5
  2. package/dist/CFG.d.ts +1 -1
  3. package/dist/CFG.d.ts.map +1 -1
  4. package/dist/DOMException.d.ts +21 -7
  5. package/dist/DOMException.d.ts.map +1 -1
  6. package/dist/DOMStringList.d.ts.map +1 -1
  7. package/dist/Event.d.ts +12 -6
  8. package/dist/Event.d.ts.map +1 -1
  9. package/dist/IDBCursor.d.ts +125 -7
  10. package/dist/IDBCursor.d.ts.map +1 -1
  11. package/dist/IDBDatabase.d.ts +17 -10
  12. package/dist/IDBDatabase.d.ts.map +1 -1
  13. package/dist/IDBFactory.d.ts +5 -0
  14. package/dist/IDBFactory.d.ts.map +1 -1
  15. package/dist/IDBIndex.d.ts +43 -13
  16. package/dist/IDBIndex.d.ts.map +1 -1
  17. package/dist/IDBKeyRange.d.ts +6 -2
  18. package/dist/IDBKeyRange.d.ts.map +1 -1
  19. package/dist/IDBObjectStore.d.ts +66 -17
  20. package/dist/IDBObjectStore.d.ts.map +1 -1
  21. package/dist/IDBRecord.d.ts +44 -0
  22. package/dist/IDBRecord.d.ts.map +1 -0
  23. package/dist/IDBRequest.d.ts +2 -2
  24. package/dist/IDBRequest.d.ts.map +1 -1
  25. package/dist/IDBTransaction.d.ts +43 -15
  26. package/dist/IDBTransaction.d.ts.map +1 -1
  27. package/dist/IDBVersionChangeEvent.d.ts +23 -12
  28. package/dist/IDBVersionChangeEvent.d.ts.map +1 -1
  29. package/dist/Key.d.ts +7 -6
  30. package/dist/Key.d.ts.map +1 -1
  31. package/dist/Sca.d.ts +15 -0
  32. package/dist/Sca.d.ts.map +1 -1
  33. package/dist/indexeddbshim-Key.js +59 -23
  34. package/dist/indexeddbshim-Key.js.map +1 -1
  35. package/dist/indexeddbshim-Key.min.js +2 -2
  36. package/dist/indexeddbshim-Key.min.js.map +1 -1
  37. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs +3058 -1167
  38. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs.map +1 -1
  39. package/dist/indexeddbshim-UnicodeIdentifiers.js +2627 -986
  40. package/dist/indexeddbshim-UnicodeIdentifiers.js.map +1 -1
  41. package/dist/indexeddbshim-UnicodeIdentifiers.min.js +4 -4
  42. package/dist/indexeddbshim-UnicodeIdentifiers.min.js.map +1 -1
  43. package/dist/indexeddbshim-node.cjs +3058 -1167
  44. package/dist/indexeddbshim-node.cjs.map +1 -1
  45. package/dist/indexeddbshim-noninvasive.js +2627 -986
  46. package/dist/indexeddbshim-noninvasive.js.map +1 -1
  47. package/dist/indexeddbshim-noninvasive.min.js +4 -4
  48. package/dist/indexeddbshim-noninvasive.min.js.map +1 -1
  49. package/dist/indexeddbshim.js +2627 -986
  50. package/dist/indexeddbshim.js.map +1 -1
  51. package/dist/indexeddbshim.min.js +4 -4
  52. package/dist/indexeddbshim.min.js.map +1 -1
  53. package/dist/node-UnicodeIdentifiers.d.ts +2 -2
  54. package/dist/node-UnicodeIdentifiers.d.ts.map +1 -1
  55. package/dist/node.d.ts +2 -2
  56. package/dist/node.d.ts.map +1 -1
  57. package/dist/nodeSQLiteDatabase.d.ts +13 -14
  58. package/dist/nodeSQLiteDatabase.d.ts.map +1 -1
  59. package/dist/nodeWebSQL.d.ts +17 -1
  60. package/dist/nodeWebSQL.d.ts.map +1 -1
  61. package/dist/setGlobalVars.d.ts +2 -2
  62. package/dist/setGlobalVars.d.ts.map +1 -1
  63. package/dist/util.d.ts +15 -0
  64. package/dist/util.d.ts.map +1 -1
  65. package/examples/IDBKeyUtils.html +0 -1
  66. package/package.json +27 -21
  67. package/src/CFG.js +2 -1
  68. package/src/DOMException.js +38 -12
  69. package/src/DOMStringList.js +21 -2
  70. package/src/Event.js +9 -4
  71. package/src/IDBCursor.js +346 -18
  72. package/src/IDBDatabase.js +33 -16
  73. package/src/IDBFactory.js +104 -57
  74. package/src/IDBIndex.js +78 -45
  75. package/src/IDBKeyRange.js +8 -2
  76. package/src/IDBObjectStore.js +89 -65
  77. package/src/IDBRecord.js +96 -0
  78. package/src/IDBRequest.js +7 -7
  79. package/src/IDBTransaction.js +335 -67
  80. package/src/IDBVersionChangeEvent.js +37 -30
  81. package/src/Key.js +36 -18
  82. package/src/Sca.js +22 -1
  83. package/src/browser-UnicodeIdentifiers.js +3 -1
  84. package/src/browser-noninvasive.js +3 -1
  85. package/src/browser.js +3 -1
  86. package/src/node-UnicodeIdentifiers.js +1 -1
  87. package/src/node.js +1 -1
  88. package/src/nodeSQLiteDatabase.js +67 -2
  89. package/src/nodeWebSQL.js +22 -2
  90. package/src/setGlobalVars.js +5 -5
  91. package/src/util.js +29 -1
  92. package/typings/shimIndexedDB.d.ts +1 -1
@@ -4,30 +4,41 @@ import * as util from './util.js';
4
4
  const readonlyProperties = ['oldVersion', 'newVersion'];
5
5
 
6
6
  /**
7
- * Babel apparently having a problem adding `hasInstance` to a class,
8
- * so we are redefining as a function.
9
- * @class
10
- * @param {string} type
7
+ * @typedef {number} Integer
11
8
  */
12
- function IDBVersionChangeEvent (type /* , eventInitDict */) { // eventInitDict is a IDBVersionChangeEventInit (but is not defined as a global)
13
- // @ts-expect-error It's passing only one!
14
- ShimEvent.call(this, type);
15
- this[Symbol.toStringTag] = 'IDBVersionChangeEvent';
16
- this.toString = function () {
17
- return '[object IDBVersionChangeEvent]';
18
- };
19
- // eslint-disable-next-line prefer-rest-params -- API
20
- this.__eventInitDict = arguments[1] || {};
21
- }
22
-
23
- // @ts-expect-error It's ok
24
- IDBVersionChangeEvent.prototype = Object.create(ShimEvent.prototype);
25
9
 
26
- IDBVersionChangeEvent.prototype[Symbol.toStringTag] = 'IDBVersionChangeEventPrototype';
10
+ /**
11
+ * @typedef {globalThis.Event & {
12
+ * __eventInitDict: {oldVersion?: Integer, newVersion?: Integer|null}
13
+ * }} IDBVersionChangeEventFull
14
+ */
27
15
 
28
16
  /**
29
- * @typedef {number} Integer
17
+ * `extends ShimEvent` via a real `class`, now that `Event` (`ShimEvent`)
18
+ * is itself a real, constructible class in `eventtargeter` -- the old
19
+ * `ShimEvent.call(this, type)` + `Object.create(ShimEvent.prototype)`
20
+ * pattern this used to use is illegal for a real class constructor
21
+ * (only callable via `new`/`super()`).
30
22
  */
23
+ class IDBVersionChangeEvent extends ShimEvent {
24
+ /**
25
+ * @param {string} type
26
+ */
27
+ constructor (type /* , eventInitDict */) { // eventInitDict is a IDBVersionChangeEventInit (but is not defined as a global)
28
+ super(type);
29
+ const me = /** @type {IDBVersionChangeEventFull} */ (/** @type {unknown} */ (this));
30
+ // @ts-expect-error It's ok
31
+ me[Symbol.toStringTag] = 'IDBVersionChangeEvent';
32
+ me.toString = function () {
33
+ return '[object IDBVersionChangeEvent]';
34
+ };
35
+ // eslint-disable-next-line prefer-rest-params -- API
36
+ me.__eventInitDict = arguments[1] || {};
37
+ }
38
+ }
39
+
40
+ // @ts-expect-error Not part of the class body itself
41
+ IDBVersionChangeEvent.prototype[Symbol.toStringTag] = 'IDBVersionChangeEventPrototype';
31
42
 
32
43
  /* eslint-disable unicorn/no-top-level-side-effects -- Would be good */
33
44
  readonlyProperties.forEach((prop) => {
@@ -40,7 +51,10 @@ readonlyProperties.forEach((prop) => {
40
51
  if (!(this instanceof IDBVersionChangeEvent)) {
41
52
  throw new TypeError('Illegal invocation');
42
53
  }
43
- return (this.__eventInitDict && this.__eventInitDict[prop]) || (prop === 'oldVersion' ? 0 : null);
54
+ const me = /** @type {IDBVersionChangeEventFull} */ (/** @type {unknown} */ (this));
55
+ return (me.__eventInitDict && me.__eventInitDict[
56
+ /** @type {keyof IDBVersionChangeEventFull['__eventInitDict']} */ (prop)
57
+ ]) || (prop === 'oldVersion' ? 0 : null);
44
58
  }
45
59
  };
46
60
  const desc = /** @type {PropertyDescriptor} */ (
@@ -64,16 +78,9 @@ Object.defineProperty(IDBVersionChangeEvent, Symbol.hasInstance, {
64
78
  'defaultPrevented' in obj && typeof obj.defaultPrevented === 'boolean'
65
79
  });
66
80
 
67
- Object.defineProperty(IDBVersionChangeEvent.prototype, 'constructor', {
68
- enumerable: false,
69
- writable: true,
70
- configurable: true,
71
- value: IDBVersionChangeEvent
72
- });
73
-
74
- Object.defineProperty(IDBVersionChangeEvent, 'prototype', {
75
- writable: false
76
- });
81
+ // A real class's own `.prototype` (and its `.constructor` back-reference)
82
+ // is already set up correctly and non-writable/non-configurable per
83
+ // spec, so no explicit setup is needed here.
77
84
  /* eslint-enable unicorn/no-top-level-side-effects -- Would be good */
78
85
 
79
86
  export default IDBVersionChangeEvent;
package/src/Key.js CHANGED
@@ -15,6 +15,10 @@ import CFG from './CFG.js';
15
15
  * @typedef {"number"|"date"|"string"|"binary"|"array"} KeyType
16
16
  */
17
17
 
18
+ /**
19
+ * @typedef {import('websql-configurable/lib/websql/WebSQLTransaction.js').default} WebSQLTransaction
20
+ */
21
+
18
22
  /**
19
23
  * @typedef {any} Value
20
24
  */
@@ -553,12 +557,22 @@ function convertValueToKeyValueDecoded (input, seen, multiEntry, fullKeys) {
553
557
  return /** @type {{type: KeyType; value: Value}} */ (ret);
554
558
  } case 'string': {
555
559
  return /** @type {{type: KeyType; value: Value}} */ (ret);
556
- } case 'binary': { // May throw (if detached)
560
+ } case 'binary': {
557
561
  // Get a copy of the bytes held by the buffer source
558
562
  // https://heycam.github.io/webidl/#ref-for-dfn-get-buffer-source-copy-2
559
- const octets = getCopyBytesHeldByBufferSource(
560
- /** @type {BufferSource} */ (input)
561
- );
563
+ // A detached buffer/view can't be read -- the engine itself throws
564
+ // (e.g. "Cannot perform Construct on a detached ArrayBuffer")
565
+ // rather than us being able to detect this up front, so treat any
566
+ // such failure the same as any other invalid key rather than
567
+ // letting that native error escape uncaught.
568
+ let octets;
569
+ try {
570
+ octets = getCopyBytesHeldByBufferSource(
571
+ /** @type {BufferSource} */ (input)
572
+ );
573
+ } catch {
574
+ return {type, invalid: true, message: 'Could not read the buffer source (it may be detached)'};
575
+ }
562
576
  return {type: 'binary', value: octets};
563
577
  } case 'array': { // May throw (from binary)
564
578
  const arr = /** @type {Array<any>} */ (input);
@@ -992,7 +1006,7 @@ const MAX_ALLOWED_CURRENT_NUMBER = 9007199254740992; // 2 ^ 53 (Also equal to `N
992
1006
 
993
1007
  /**
994
1008
  *
995
- * @param {SQLTransaction} tx
1009
+ * @param {WebSQLTransaction} tx
996
1010
  * @param {import('./IDBObjectStore.js').IDBObjectStoreFull} store
997
1011
  * @param {CurrentNumberCallback} func
998
1012
  * @param {SQLFailureCallback} sqlFailCb
@@ -1005,7 +1019,7 @@ function getCurrentNumber (tx, store, func, sqlFailCb) {
1005
1019
  if (data.rows.length !== 1) {
1006
1020
  func(1);
1007
1021
  } else {
1008
- func(data.rows.item(0).currNum);
1022
+ func(/** @type {{currNum: number}} */ (data.rows.item(0)).currNum);
1009
1023
  }
1010
1024
  }, function (tx, error) {
1011
1025
  sqlFailCb(createDOMException(
@@ -1019,7 +1033,7 @@ function getCurrentNumber (tx, store, func, sqlFailCb) {
1019
1033
 
1020
1034
  /**
1021
1035
  *
1022
- * @param {SQLTransaction} tx
1036
+ * @param {WebSQLTransaction} tx
1023
1037
  * @param {import('./IDBObjectStore.js').IDBObjectStoreFull} store
1024
1038
  * @param {Integer} num
1025
1039
  * @param {CurrentNumberCallback} successCb
@@ -1042,7 +1056,7 @@ function assignCurrentNumber (tx, store, num, successCb, failCb) {
1042
1056
  * Bump up the auto-inc counter if the key path-resolved value is valid
1043
1057
  * (greater than old value and >=1) OR if a manually passed in key is
1044
1058
  * valid (numeric and >= 1) and >= any primaryKey.
1045
- * @param {SQLTransaction} tx
1059
+ * @param {WebSQLTransaction} tx
1046
1060
  * @param {import('./IDBObjectStore.js').IDBObjectStoreFull} store
1047
1061
  * @param {Integer} num
1048
1062
  * @param {CurrentNumberCallback} successCb
@@ -1050,14 +1064,18 @@ function assignCurrentNumber (tx, store, num, successCb, failCb) {
1050
1064
  * @returns {void}
1051
1065
  */
1052
1066
  function setCurrentNumber (tx, store, num, successCb, failCb) {
1053
- num = 1 + (num === MAX_ALLOWED_CURRENT_NUMBER
1054
- // Since incrementing by one will have no effect in JavaScript on this
1055
- // unsafe max, we represent the max as a number incremented by two.
1056
- // The getting of the current number is never returned to the user and
1057
- // is only used in safe comparisons, so it is safe for us to represent
1058
- // it in this manner
1059
- ? num + 1
1060
- : num);
1067
+ // Since incrementing by one has no effect in JavaScript on this unsafe
1068
+ // max (`MAX_ALLOWED_CURRENT_NUMBER + 1 === MAX_ALLOWED_CURRENT_NUMBER`
1069
+ // in IEEE 754 double precision), we represent the max as a number
1070
+ // incremented by two instead -- computed as a single `+ 2` rather
1071
+ // than two chained `+ 1`s, as the latter *also* rounds back to the
1072
+ // original value (each addition independently hits the same
1073
+ // unrepresentable `+ 1` and rounds down, so the two `+ 1`s don't
1074
+ // compose into a `+ 2`).
1075
+ // The getting of the current number is never returned to the user and
1076
+ // is only used in safe comparisons, so it is safe for us to represent
1077
+ // it in this manner
1078
+ num += num === MAX_ALLOWED_CURRENT_NUMBER ? 2 : 1;
1061
1079
  return assignCurrentNumber(tx, store, num, successCb, failCb);
1062
1080
  }
1063
1081
 
@@ -1071,7 +1089,7 @@ function setCurrentNumber (tx, store, num, successCb, failCb) {
1071
1089
 
1072
1090
  /**
1073
1091
  *
1074
- * @param {SQLTransaction} tx
1092
+ * @param {WebSQLTransaction} tx
1075
1093
  * @param {import('./IDBObjectStore.js').IDBObjectStoreFull} store
1076
1094
  * @param {KeyForStoreCallback} cb
1077
1095
  * @param {SQLFailureCallback} sqlFailCb
@@ -1101,7 +1119,7 @@ function generateKeyForStore (tx, store, cb, sqlFailCb) {
1101
1119
  // so we do not return a key
1102
1120
  /**
1103
1121
  *
1104
- * @param {SQLTransaction} tx
1122
+ * @param {WebSQLTransaction} tx
1105
1123
  * @param {import('./IDBObjectStore.js').IDBObjectStoreFull} store
1106
1124
  * @param {Key} key
1107
1125
  * @param {(num?: Integer) => void} successCb
package/src/Sca.js CHANGED
@@ -73,4 +73,25 @@ function clone (val) {
73
73
  return decode(encode(val));
74
74
  }
75
75
 
76
- export {encode, decode, clone, register};
76
+ /**
77
+ * Per spec, a transaction must appear inactive to any code that runs
78
+ * reentrantly *during* the structured clone of a value passed to
79
+ * `add()`/`put()`/`IDBCursor#update()` (e.g. a getter on the value being
80
+ * stored) -- even though the transaction is otherwise active for the
81
+ * duration of that same call. `__active` is restored in a `finally` so a
82
+ * `DataCloneError` (or any other throw from `clone()`) can't leave the
83
+ * transaction permanently stuck inactive.
84
+ * @param {{__active: boolean}} transaction
85
+ * @param {AnyValue} val
86
+ * @returns {AnyValue}
87
+ */
88
+ function cloneWithInactiveTransaction (transaction, val) {
89
+ transaction.__active = false;
90
+ try {
91
+ return clone(val);
92
+ } finally {
93
+ transaction.__active = true;
94
+ }
95
+ }
96
+
97
+ export {encode, decode, clone, cloneWithInactiveTransaction, register};
@@ -5,7 +5,9 @@ import * as UnicodeIdentifiers from './UnicodeIdentifiers.js';
5
5
  import setGlobalVars from './setGlobalVars.js';
6
6
  import CFG from './CFG.js';
7
7
 
8
- CFG.win = typeof window !== 'undefined' ? window : self; // For Web Workers
8
+ CFG.win = /** @type {import('./CFG.js').ConfigValues['win']} */ (
9
+ /** @type {unknown} */ (typeof window !== 'undefined' ? window : self)
10
+ ); // For Web Workers
9
11
 
10
12
  setGlobalVars();
11
13
  // END: Same code as in browser.js
@@ -4,7 +4,9 @@ import {UnicodeIDStart, UnicodeIDContinue} from './UnicodeIdentifiers.js';
4
4
  import setGlobalVars from './setGlobalVars.js';
5
5
  import CFG from './CFG.js';
6
6
 
7
- CFG.win = typeof window !== 'undefined' ? window : self; // For Web Workers
7
+ CFG.win = /** @type {import('./CFG.js').ConfigValues['win']} */ (
8
+ /** @type {unknown} */ (typeof window !== 'undefined' ? window : self)
9
+ ); // For Web Workers
8
10
  // END: Same code as in browser.js
9
11
 
10
12
  CFG.UnicodeIDStart = UnicodeIDStart;
package/src/browser.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import setGlobalVars from './setGlobalVars.js';
2
2
  import CFG from './CFG.js';
3
3
 
4
- CFG.win = typeof window !== 'undefined' ? window : self; // For Web Workers
4
+ CFG.win = /** @type {import('./CFG.js').ConfigValues['win']} */ (
5
+ /** @type {unknown} */ (typeof window !== 'undefined' ? window : self)
6
+ ); // For Web Workers
5
7
  setGlobalVars();
@@ -9,7 +9,7 @@ CFG.win = {openDatabase: nodeWebSQL};
9
9
 
10
10
  /**
11
11
  * @param {import('./setGlobalVars.js').ShimmedObject} idb
12
- * @param {import('./CFG.js').default} initialConfig
12
+ * @param {Partial<import('./CFG.js').ConfigValues>} initialConfig
13
13
  * @returns {import('./setGlobalVars.js').ShimmedObject|Window}
14
14
  */
15
15
  const __setGlobalVars = function (idb, initialConfig = {}) {
package/src/node.js CHANGED
@@ -8,7 +8,7 @@ CFG.win = {openDatabase: nodeWebSQL};
8
8
 
9
9
  /**
10
10
  * @param {import('./setGlobalVars.js').ShimmedObject} idb
11
- * @param {import('./CFG.js').default} initialConfig
11
+ * @param {Partial<import('./CFG.js').ConfigValues>} initialConfig
12
12
  * @returns {import('./setGlobalVars.js').ShimmedObject|Window}
13
13
  */
14
14
  const __setGlobalVars = function (idb, initialConfig = {}) {
@@ -30,12 +30,34 @@ const READ_ONLY_ERROR = new Error(
30
30
  * @typedef {((sql: string) => void)|undefined} SQLTraceCallback
31
31
  */
32
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 `setTimeout` below, which can never
40
+ // fire while that retry loop is blocking the same thread -- so instead of
41
+ // waiting, the second connection's write just fails with "database is
42
+ // locked". Serialize writes per file path instead, so a second
43
+ // connection's `BEGIN` waits for the first connection's transaction to
44
+ // actually finish rather than colliding with it.
45
+ /** @type {Map<string, {_db: any, _qFilePath: string}>} */
46
+ const fileLockOwners = new Map();
47
+ /** @type {Map<string, (() => void)[]>} */
48
+ const fileLockWaiters = new Map();
49
+ const beginRe = /^\s*BEGIN\b/iu;
50
+ const endRe = /^\s*(END|COMMIT|ROLLBACK)\b/iu;
51
+
33
52
  /**
53
+ * @class
34
54
  * @param {string} name
35
55
  * @param {{busyTimeout?: number, trace?: (sql: string) => void, profile?: SQLProfileCallback}} [opts]
56
+ * @this {{_db: any, _qFilePath: string}}
36
57
  * @returns {void}
37
58
  */
38
59
  function SQLiteDatabase (name, opts = {}) {
60
+ this._qFilePath = name;
39
61
  /** @type {import('better-sqlite3').Database} */
40
62
  const db = new Database(name);
41
63
 
@@ -130,6 +152,22 @@ function runNonSelect (db, sql, args) {
130
152
  * @returns {void}
131
153
  */
132
154
  SQLiteDatabase.prototype.exec = function exec (queries, readOnly, callback) {
155
+ // `:memory:` (SQLite/`better-sqlite3`'s own sentinel for an isolated,
156
+ // non-shared in-memory database) can never actually collide with a
157
+ // different connection -- each `new Database(':memory:')` call is
158
+ // already fully independent -- so there is nothing to serialize.
159
+ const filePath = this._qFilePath === ':memory:' ? null : this._qFilePath;
160
+ if (filePath && queries[0] && beginRe.test(queries[0].sql)) {
161
+ const owner = fileLockOwners.get(filePath);
162
+ if (owner && owner !== this) {
163
+ const waiters = fileLockWaiters.get(filePath) || [];
164
+ waiters.push(() => this.exec(queries, readOnly, callback));
165
+ fileLockWaiters.set(filePath, waiters);
166
+ return;
167
+ }
168
+ fileLockOwners.set(filePath, this);
169
+ }
170
+
133
171
  const db = this._db._db;
134
172
  const len = queries.length;
135
173
  const results = Array.from({length: len});
@@ -166,9 +204,36 @@ SQLiteDatabase.prototype.exec = function exec (queries, readOnly, callback) {
166
204
  }
167
205
  }
168
206
  }
169
- queueMicrotask(() => {
207
+ // A real timer (not `queueMicrotask`) so this yields to the macrotask
208
+ // queue: code that synchronously issues a new request from within
209
+ // each request's callback (e.g. to keep a transaction alive) would
210
+ // otherwise chain microtask to microtask forever, starving out any
211
+ // `setTimeout`-based code (including IndexedDB's own internal request
212
+ // scheduling) that never gets a turn to run.
213
+ setTimeout(() => {
214
+ // Release the file lock (if held) and hand it to the next waiting
215
+ // connection, if any, only once this transaction has genuinely
216
+ // finished -- and only here, on its own turn, so a resumed
217
+ // waiter's own `exec` call can't run reentrantly inside this one.
218
+ // Checked across the whole batch, not just `queries[0]`: when a
219
+ // transaction's `BEGIN` and its later `ROLLBACK`/`COMMIT` are both
220
+ // queued synchronously before `websql-configurable`'s async flush
221
+ // gets a turn to run (e.g. an immediate `transaction.abort()`
222
+ // inside `onupgradeneeded`), they arrive coalesced into a single
223
+ // batch/`exec()` call, with the terminal statement anywhere but
224
+ // first -- so only checking `queries[0]` would acquire the lock
225
+ // and never see the matching release, deadlocking every later
226
+ // connection to the same file.
227
+ if (filePath && queries.some((q) => endRe.test(q.sql))) {
228
+ fileLockOwners.delete(filePath);
229
+ const waiters = fileLockWaiters.get(filePath);
230
+ const nextWaitingExec = waiters && waiters.shift();
231
+ if (nextWaitingExec) {
232
+ nextWaitingExec();
233
+ }
234
+ }
170
235
  callback(null, results);
171
- });
236
+ }, 0);
172
237
  };
173
238
 
174
239
  export default SQLiteDatabase;
package/src/nodeWebSQL.js CHANGED
@@ -2,11 +2,31 @@ import customOpenDatabase from 'websql-configurable/custom/index.js';
2
2
  import SQLiteDatabase from './nodeSQLiteDatabase.js';
3
3
  import CFG from './CFG.js';
4
4
 
5
+ /**
6
+ * @typedef {{
7
+ * _db: any,
8
+ * exec: typeof SQLiteDatabase['prototype']['exec']
9
+ * }} SQLiteDatabaseInstance
10
+ */
11
+
12
+ /**
13
+ * `websql-configurable`'s `customOpenDatabase` always calls this via `new`
14
+ * (see its `custom.js`: `new SQLiteDatabase(dbName, sqliteOpts)`), so this
15
+ * is typed with a construct signature even though `wrappedSQLiteDatabase`
16
+ * below is written as a plain function -- that's a deliberate, working JS
17
+ * idiom (a plain function invoked with `new` that explicitly `return`s an
18
+ * object yields that object as the `new` expression's result, overriding
19
+ * the default "return `this`" behavior), not something TypeScript can
20
+ * verify structurally.
21
+ * @typedef {new (name: string, opts?: any) => SQLiteDatabaseInstance} SQLiteDatabaseConstructor
22
+ */
23
+
5
24
  /**
6
25
  * @param {string} name
7
- * @returns {SQLiteDatabase}
26
+ * @returns {SQLiteDatabaseInstance}
8
27
  */
9
28
  function wrappedSQLiteDatabase (name) {
29
+ // @ts-ignore It's ok; needed under some TS versions
10
30
  const db = new SQLiteDatabase(name, {});
11
31
  if (CFG.sqlBusyTimeout) {
12
32
  db._db.configure('busyTimeout', /** @type {number} */ (CFG.sqlBusyTimeout)); // Default is 1000
@@ -20,5 +40,5 @@ function wrappedSQLiteDatabase (name) {
20
40
  return db;
21
41
  }
22
42
 
23
- const nodeWebSQL = customOpenDatabase(wrappedSQLiteDatabase, {});
43
+ const nodeWebSQL = customOpenDatabase(/** @type {SQLiteDatabaseConstructor} */ (/** @type {unknown} */ (wrappedSQLiteDatabase)), {});
24
44
  export default nodeWebSQL;
@@ -1,6 +1,6 @@
1
- import {setPrototypeOfCustomEvent} from 'eventtargeter';
2
1
  import shimIDBVersionChangeEvent from './IDBVersionChangeEvent.js';
3
2
  import {IDBCursor as shimIDBCursor, IDBCursorWithValue as shimIDBCursorWithValue} from './IDBCursor.js';
3
+ import shimIDBRecord from './IDBRecord.js';
4
4
  import {IDBRequest as shimIDBRequest, IDBOpenDBRequest as shimIDBOpenDBRequest} from './IDBRequest.js';
5
5
  import {createDOMException, ShimDOMException} from './DOMException.js';
6
6
  import {shimIndexedDB, IDBFactory, setFS} from './IDBFactory.js';
@@ -104,7 +104,7 @@ function setConfig (prop, val) {
104
104
  /**
105
105
  *
106
106
  * @param {ShimmedObject} [idb]
107
- * @param {import('./CFG.js').ConfigValues} [initialConfig]
107
+ * @param {Partial<import('./CFG.js').ConfigValues>} [initialConfig]
108
108
  * @returns {ShimmedObject}
109
109
  */
110
110
  function setGlobalVars (idb, initialConfig) {
@@ -249,6 +249,7 @@ function setGlobalVars (idb, initialConfig) {
249
249
  ['IDBTransaction', shimIDBTransaction],
250
250
  ['IDBCursor', shimIDBCursor],
251
251
  ['IDBCursorWithValue', shimIDBCursorWithValue],
252
+ ['IDBRecord', shimIDBRecord],
252
253
  ['IDBKeyRange', shimIDBKeyRange],
253
254
  ['IDBRequest', shimIDBRequest],
254
255
  ['IDBOpenDBRequest', shimIDBOpenDBRequest],
@@ -275,7 +276,6 @@ function setGlobalVars (idb, initialConfig) {
275
276
  Object.setPrototypeOf(shimIDBVersionChangeEvent, ShimEvent);
276
277
  Object.setPrototypeOf(ShimDOMException, Error);
277
278
  Object.setPrototypeOf(ShimDOMException.prototype, Error.prototype);
278
- setPrototypeOfCustomEvent();
279
279
  }
280
280
  if (IDB.indexedDB && !IDB.indexedDB.toString().includes('[native code]')) {
281
281
  if (CFG.addNonIDBGlobals) {
@@ -296,9 +296,9 @@ function setGlobalVars (idb, initialConfig) {
296
296
  }
297
297
  };
298
298
 
299
- IDB.shimIndexedDB.__debug = function (val) {
299
+ IDB.shimIndexedDB.__debug = /** @type {(val: boolean) => void} */ (function (val) {
300
300
  CFG.DEBUG = val;
301
- };
301
+ });
302
302
  IDB.shimIndexedDB.__setConfig = setConfig;
303
303
 
304
304
  /** @type {GetConfig} */
package/src/util.js CHANGED
@@ -377,7 +377,6 @@ function defineListenerProperties (obj, listeners) {
377
377
  },
378
378
  /**
379
379
  * @param {AnyValue} val
380
- * @returns {void}
381
380
  */
382
381
  set [listener] (val) {
383
382
  obj['__' + listener] = val;
@@ -434,6 +433,34 @@ function defineReadonlyProperties (obj, props, getter = null) {
434
433
  });
435
434
  }
436
435
 
436
+ /**
437
+ * `X.prototype.method = function (...) {}` (an assignment to a
438
+ * `MemberExpression`, as used throughout this codebase for IDB
439
+ * interface operations) does not get its `name` inferred by the
440
+ * engine the way object-literal method shorthand or a plain variable
441
+ * assignment would, so it is left as `''`. Per Web IDL, an operation's
442
+ * function object must have its `name` set to the operation's
443
+ * identifier, so call this once all of an interface's own-property
444
+ * operations have been assigned onto its prototype (or, for static
445
+ * operations, onto the constructor itself) to patch any still-empty
446
+ * names in place.
447
+ * @param {object} obj
448
+ * @returns {void}
449
+ */
450
+ function setOperationNames (obj) {
451
+ Object.getOwnPropertyNames(obj).forEach((key) => {
452
+ if (key === 'constructor') {
453
+ return;
454
+ }
455
+ const desc = /** @type {PropertyDescriptor} */ (
456
+ Object.getOwnPropertyDescriptor(obj, key)
457
+ );
458
+ if (typeof desc.value === 'function' && desc.value.name === '') {
459
+ Object.defineProperty(desc.value, 'name', {value: key, configurable: true});
460
+ }
461
+ });
462
+ }
463
+
437
464
  /**
438
465
  *
439
466
  * @param {string} item
@@ -615,6 +642,7 @@ export {escapeSQLiteStatement, unescapeSQLiteResponse,
615
642
  isObj, isDate, isBlob, isRegExp, isFile, isBinary, isIterable,
616
643
  defineOuterInterface, defineReadonlyOuterInterface,
617
644
  defineListenerProperties, defineReadonlyProperties,
645
+ setOperationNames,
618
646
  isValidKeyPath, enforceRange,
619
647
  convertToDOMString, convertToSequenceDOMString,
620
648
  isNullish, runContinuationSafely};
@@ -1,4 +1,4 @@
1
- declare var shimIndexedDB: import('../src/IDBFactory.js').IDBFactory & WindowDatabase & {
1
+ declare var shimIndexedDB: import('../src/IDBFactory.js').IDBFactoryFull & WindowDatabase & {
2
2
  __useShim: () => void
3
3
  __debug: (val: boolean) => void
4
4
  __setConfig: (prop: string, val: import('../src/CFG.js').ConfigValue) => void