indexeddbshim 17.3.2 → 17.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/IDBCursor.d.ts.map +1 -1
  2. package/dist/IDBFactory.d.ts.map +1 -1
  3. package/dist/IDBTransaction.d.ts +8 -6
  4. package/dist/IDBTransaction.d.ts.map +1 -1
  5. package/dist/Key.d.ts.map +1 -1
  6. package/dist/Sca.d.ts.map +1 -1
  7. package/dist/indexeddbshim-Key.js +52 -13
  8. package/dist/indexeddbshim-Key.js.map +1 -1
  9. package/dist/indexeddbshim-Key.min.js +2 -2
  10. package/dist/indexeddbshim-Key.min.js.map +1 -1
  11. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs +508 -134
  12. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs.map +1 -1
  13. package/dist/indexeddbshim-UnicodeIdentifiers.js +337 -71
  14. package/dist/indexeddbshim-UnicodeIdentifiers.js.map +1 -1
  15. package/dist/indexeddbshim-UnicodeIdentifiers.min.js +3 -3
  16. package/dist/indexeddbshim-UnicodeIdentifiers.min.js.map +1 -1
  17. package/dist/indexeddbshim-node.cjs +508 -134
  18. package/dist/indexeddbshim-node.cjs.map +1 -1
  19. package/dist/indexeddbshim-noninvasive.js +337 -71
  20. package/dist/indexeddbshim-noninvasive.js.map +1 -1
  21. package/dist/indexeddbshim-noninvasive.min.js +3 -3
  22. package/dist/indexeddbshim-noninvasive.min.js.map +1 -1
  23. package/dist/indexeddbshim.js +337 -71
  24. package/dist/indexeddbshim.js.map +1 -1
  25. package/dist/indexeddbshim.min.js +3 -3
  26. package/dist/indexeddbshim.min.js.map +1 -1
  27. package/dist/nodeSQLiteDatabase.d.ts.map +1 -1
  28. package/dist/nodeWebSQL.d.ts.map +1 -1
  29. package/package.json +4 -4
  30. package/src/IDBCursor.js +34 -4
  31. package/src/IDBFactory.js +207 -60
  32. package/src/IDBTransaction.js +23 -3
  33. package/src/Key.js +34 -12
  34. package/src/Sca.js +53 -3
  35. package/src/nodeSQLiteDatabase.js +92 -26
  36. package/src/nodeWebSQL.js +8 -1
@@ -1 +1 @@
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;AAsB9C;;;;;;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,CAmEhB;;IAxED;;;;;;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,EAqE7F;IAhEG,mBAAsB;IAkBtB,SA6CE;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,CAqFhB;;AA1OD;;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
+ {"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,CAmEhB;;IAxED;;;;;;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,EAqE7F;IAhEG,mBAAsB;IAkBtB,SA6CE;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;;AA5SD;;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 +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;AAsBrE,qHAAqI;2BAzC1G,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;AA6BrE,qHAAsK;2BAhD3I,yBAAyB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "indexeddbshim",
3
- "version": "17.3.2",
3
+ "version": "17.3.4",
4
4
  "type": "module",
5
5
  "author": "Parashuram <code@nparashuram.com>",
6
6
  "contributors": [
@@ -172,8 +172,8 @@
172
172
  "eventtargeter": "0.12.0",
173
173
  "sync-promise-expanded": "^2.0.0",
174
174
  "typeson": "10.2.0",
175
- "typeson-registry": "14.2.2",
176
- "websql-configurable": "^3.0.6"
175
+ "typeson-registry": "14.3.0",
176
+ "websql-configurable": "^3.1.0"
177
177
  },
178
178
  "devDependencies": {
179
179
  "@babel/core": "^8.0.1",
@@ -214,7 +214,7 @@
214
214
  "npm-run-all": "^4.1.5",
215
215
  "open-cli": "^9.0.0",
216
216
  "rimraf": "^6.1.3",
217
- "rollup": "^4.62.5",
217
+ "rollup": "^4.63.0",
218
218
  "rollup-plugin-filesize": "^10.0.0",
219
219
  "rollup-plugin-node-polyfills": "^0.2.1",
220
220
  "sinon": "^22.1.0",
package/src/IDBCursor.js CHANGED
@@ -260,9 +260,33 @@ IDBCursor.prototype.__findBasic = function (key, primaryKey, tx, success, error,
260
260
  // Key.convertValueToKey(key); // Already checked by `continue` or `continuePrimaryKey`
261
261
  sqlValues.push(/** @type {string} */ (Key.encode(key)));
262
262
  } else if (continueCall && me.__key !== undefined) {
263
- sql.push('AND', quotedKeyColumnName, op + ' ?');
264
263
  // Key.convertValueToKey(me.__key); // Already checked when stored
265
- sqlValues.push(/** @type {string} */ (Key.encode(me.__key)));
264
+ if (!me.__unique && me.__keyColumnName !== 'key' && me.__primaryKey !== undefined) {
265
+ // A plain `continue()` on a non-unique index cursor must find
266
+ // the next record strictly after the (key, primaryKey) pair
267
+ // this cursor last returned -- a scalar `key > lastKey` alone
268
+ // would wrongly exclude a *different*, not-yet-visited record
269
+ // that's still tied on key with the last one (e.g. another
270
+ // record that always had that same indexed value), and would
271
+ // also wrongly re-admit the *same* record forever if a
272
+ // same-transaction `update()` bumped its own key back above
273
+ // the threshold, since a scalar comparison can't distinguish
274
+ // "some other record newly tied" from "this record moved
275
+ // past its own last position." Comparing the full tuple (via
276
+ // this OR) against both key and primary key resolves both.
277
+ sql.push(
278
+ 'AND (', quotedKeyColumnName, op, '?',
279
+ 'OR (', quotedKeyColumnName, '= ?', 'AND', quotedKey, op, '?))'
280
+ );
281
+ sqlValues.push(
282
+ /** @type {string} */ (Key.encode(me.__key)),
283
+ /** @type {string} */ (Key.encode(me.__key)),
284
+ /** @type {string} */ (Key.encode(me.__primaryKey))
285
+ );
286
+ } else {
287
+ sql.push('AND', quotedKeyColumnName, op + ' ?');
288
+ sqlValues.push(/** @type {string} */ (Key.encode(me.__key)));
289
+ }
266
290
  }
267
291
 
268
292
  if (!me.__count) {
@@ -912,9 +936,15 @@ IDBCursor.prototype.update = function (valueToUpdate) {
912
936
  * @returns {void}
913
937
  */
914
938
  function addToQueue (clonedValue) {
915
- // We set the `invalidateCache` argument to `false` since the old value shouldn't be accessed
939
+ // `invalidateCache: true` so any cursor on this store (including
940
+ // this one) drops its prefetched row batch, forcing its next
941
+ // `continue()` to re-query live rather than serve rows snapshotted
942
+ // before this update -- needed for the compound-tuple
943
+ // continuation logic in `__findBasic` to see this update's
944
+ // effect on ordering (see "Modify records during cursor
945
+ // iteration" in idbcursor_update_index.any.js).
916
946
  // @ts-ignore -- API (not erring in TS 6)
917
- IDBObjectStore.__storingRecordObjectStore(request, me.__store, false, clonedValue, false, key);
947
+ IDBObjectStore.__storingRecordObjectStore(request, me.__store, true, clonedValue, false, key);
918
948
  }
919
949
  if (me.__store.keyPath !== null) {
920
950
  const [evaluatedKey, clonedValue] = me.__store.__validateKeyAndValueAndCloneValue(valueToUpdate, undefined, true);
package/src/IDBFactory.js CHANGED
@@ -156,7 +156,29 @@ function triggerAnyVersionChangeAndBlockedEvents (openConnections, req, oldVersi
156
156
  return new SyncPromise(function (resolve) {
157
157
  setTimeout(() => {
158
158
  entry.dispatchEvent(e); // No need to catch errors
159
- resolve(undefined);
159
+ // Unlike a native `Promise`, `SyncPromise#then` chains
160
+ // synchronously off `resolve()` (verified directly:
161
+ // `SyncPromise.resolve().then(cb)` runs `cb` before
162
+ // even the calling code below it finishes) -- so
163
+ // resolving immediately here would let the
164
+ // `connectionsClosed()` check below run before a
165
+ // same-task-but-microtask-later continuation of a
166
+ // `versionchange` listener above (e.g. an `await`-
167
+ // based one that then calls `db.close()`, as in
168
+ // `transaction-lifetime.any.js`) ever gets a turn,
169
+ // incorrectly firing `blocked` even though the
170
+ // connection was about to close in time. Give real
171
+ // microtask-deferred continuations a small, bounded
172
+ // number of turns first -- same pattern as
173
+ // `IDBTransaction.js`'s `checkQueueEntry`.
174
+ let attemptsLeft = 10;
175
+ (function wait () {
176
+ if (attemptsLeft-- <= 0) {
177
+ resolve(undefined);
178
+ return;
179
+ }
180
+ queueMicrotask(wait);
181
+ }());
160
182
  }, 0);
161
183
  });
162
184
  });
@@ -214,6 +236,23 @@ function triggerAnyVersionChangeAndBlockedEvents (openConnections, req, oldVersi
214
236
  */
215
237
  const websqlDBCache = {};
216
238
 
239
+ /**
240
+ * Tracks databases with a creation/upgrade currently in flight but not yet
241
+ * committed or aborted -- keyed by (unescaped) database name. The
242
+ * `dbVersions` row in `sysdb` these entries shadow is written *before*
243
+ * `upgradeneeded` is even dispatched to user code (as the success
244
+ * callback of that very `INSERT`/`UPDATE`), on the same shared `sysdb`
245
+ * connection `databases()` itself reads from -- so, absent this, a
246
+ * `databases()` call made while an upgrade is still pending would see
247
+ * the new row (or new version) immediately, rather than only once the
248
+ * corresponding `versionchange` transaction has genuinely committed, as
249
+ * required by `get-databases.any.js`'s "doesn't pick up changes that
250
+ * haven't committed" test. `IDBFactory.prototype.databases` filters
251
+ * its SQL results against this map.
252
+ * @type {Map<string, {oldVersion: Integer, newVersion: Integer}>}
253
+ */
254
+ const pendingVersionChanges = new Map();
255
+
217
256
  /** @type {import('websql-configurable/lib/websql/WebSQLDatabase.js').default} */
218
257
  let sysdb;
219
258
  let nameCounter = 0;
@@ -518,6 +557,7 @@ IDBFactory.prototype.open = function (name /* , version */) {
518
557
 
519
558
  const req = IDBOpenDBRequest.__createInstance();
520
559
  let calledDbCreateError = false;
560
+ let isRevertingSysdb = false;
521
561
 
522
562
  if (CFG.autoName && name === '') {
523
563
  // eslint-disable-next-line unicorn/no-top-level-assignment-in-function -- Necessary?
@@ -546,9 +586,22 @@ IDBFactory.prototype.open = function (name /* , version */) {
546
586
  * @returns {boolean}
547
587
  */
548
588
  function dbCreateError (tx, err) {
549
- if (calledDbCreateError) {
589
+ if (calledDbCreateError || isRevertingSysdb) {
550
590
  return false;
551
591
  }
592
+ // Defensive cleanup: `pendingVersionChanges.set(name, ...)` (see
593
+ // below) runs *before* the `dbVersions` `INSERT`/`UPDATE` it
594
+ // guards is even attempted, but only `versionSet`'s own
595
+ // `on__beforecomplete`/`on__preabort` handlers ever clear it --
596
+ // and `versionSet` never runs at all if that `INSERT`/`UPDATE`,
597
+ // or an earlier step in this same `open()` flow, fails first.
598
+ // Without this, such a failure would leave the entry orphaned
599
+ // forever, silently corrupting `databases()` for any *other*,
600
+ // unrelated later `open()` call that happens to reuse the same
601
+ // database name (common in WPT tests, e.g. generic names like
602
+ // "DB1"/"TestDatabase" reused across different test files in
603
+ // the same process). A no-op if no entry was ever set.
604
+ pendingVersionChanges.delete(name);
552
605
  const er = err ? webSQLErrback(err) : /** @type {Error} */ (tx);
553
606
  calledDbCreateError = true;
554
607
  // Re: why bubbling here (and how cancelable is only really relevant for `window.onerror`) see: https://github.com/w3c/IndexedDB/issues/86
@@ -595,46 +648,63 @@ IDBFactory.prototype.open = function (name /* , version */) {
595
648
  */
596
649
  let sysdbFinishedCb = function (systx, err, cb) {
597
650
  if (err) {
598
- try {
599
- systx.executeSql('ROLLBACK', [], cb, cb);
600
- } catch (err) {
601
- // Browser may fail with expired transaction above so
602
- // no choice but to manually revert
603
- sysdb.transaction(function (systx) {
604
- /**
605
- *
606
- * @param {string} msg
607
- * @throws {Error}
608
- * @returns {never}
609
- */
610
- function reportError (msg) {
611
- throw new Error('Unable to roll back upgrade transaction!' + (msg || ''));
651
+ /**
652
+ * @param {any} [errorToShow]
653
+ * @returns {void}
654
+ */
655
+ const manualRevert = function (errorToShow) {
656
+ /**
657
+ * @param {string} [msg]
658
+ * @throws {Error}
659
+ * @returns {never}
660
+ */
661
+ function reportError (msg) {
662
+ throw new Error('Unable to roll back upgrade transaction!' + (msg || ''));
663
+ }
664
+
665
+ sysdb.transaction(
666
+ function (systx) {
667
+ // Attempt to revert
668
+ if (oldVersion === 0) {
669
+ systx.executeSql(
670
+ 'DELETE FROM dbVersions WHERE "name" = ?',
671
+ [sqlSafeName]
672
+ );
673
+ } else {
674
+ systx.executeSql(
675
+ 'UPDATE dbVersions SET "version" = ? WHERE "name" = ?',
676
+ [oldVersion, sqlSafeName]
677
+ );
678
+ }
679
+ },
680
+ function (sqlErr) {
681
+ isRevertingSysdb = false;
682
+ cb(sqlErr); // eslint-disable-line promise/no-callback-in-promise -- Convenient
683
+ },
684
+ function () {
685
+ isRevertingSysdb = false;
686
+ cb(errorToShow || reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
612
687
  }
688
+ );
689
+ };
613
690
 
614
- // Attempt to revert
615
- if (oldVersion === 0) {
616
- systx.executeSql(
617
- 'DELETE FROM dbVersions WHERE "name" = ?',
618
- [sqlSafeName],
619
- function () {
620
- // @ts-expect-error Force to work
621
- cb(reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
622
- },
623
- // @ts-expect-error Force to work
624
- reportError
625
- );
626
- } else {
627
- systx.executeSql(
628
- 'UPDATE dbVersions SET "version" = ? WHERE "name" = ?',
629
- [
630
- oldVersion, sqlSafeName
631
- ],
632
- cb,
633
- // @ts-expect-error Force to work
634
- reportError
635
- );
691
+ try {
692
+ systx.executeSql(
693
+ 'ROLLBACK',
694
+ [],
695
+ function () {
696
+ cb(); // eslint-disable-line promise/no-callback-in-promise -- Convenient
697
+ },
698
+ function (tx, sqlErr) {
699
+ // Browser/Node may fail with expired transaction, so manually revert
700
+ manualRevert(sqlErr);
701
+ return false;
636
702
  }
637
- });
703
+ );
704
+ } catch (e) {
705
+ // Browser may fail with expired transaction above so
706
+ // no choice but to manually revert
707
+ manualRevert(e);
638
708
  }
639
709
  return;
640
710
  }
@@ -668,22 +738,43 @@ IDBFactory.prototype.open = function (name /* , version */) {
668
738
  // open/close the transaction's active-handler window itself.
669
739
  req.transaction.__handlerActive = true;
670
740
  req.dispatchEvent(e);
671
- // Give any same-tick microtask scheduled from within the
741
+ // Give any microtask-scheduled continuation of the
672
742
  // `upgradeneeded` handler (e.g. a plain
673
- // `Promise.resolve().then(...)`) a chance to run -- and still
674
- // observe the transaction as active -- before we deactivate it
675
- // again, per https://github.com/w3c/IndexedDB/issues/87. Only
676
- // the flag reset itself is deferred here -- unlike
677
- // `IDBTransaction.js`'s `advanceAfterDispatch`, `finished()`
678
- // (this transaction's own queue-advancement/completion signal)
679
- // still runs synchronously, at exactly its previous timing: an
680
- // earlier attempt at deferring `finished()` too raced against
681
- // unrelated test setup that assumed a freshly deleted/created
682
- // database's upgrade transaction had already fully completed by
683
- // the time this function returns.
684
- queueMicrotask(() => {
685
- req.transaction.__handlerActive = false;
686
- });
743
+ // `Promise.resolve().then(...)`, or an `await`-based
744
+ // continuation of a promise resolved from within the handler,
745
+ // such as testharness.js's own `EventWatcher`) a chance to
746
+ // run -- and still observe the transaction as active -- before
747
+ // we deactivate it again, per
748
+ // https://github.com/w3c/IndexedDB/issues/87. A single deferred
749
+ // tick isn't always enough: an `await`-based continuation can
750
+ // take more than one microtask turn to resume (e.g.
751
+ // `transaction-lifetime.any.js`'s `EventWatcher`-based
752
+ // `await eventWatcher.wait_for('upgradeneeded')`), so retry a
753
+ // small, bounded number of times first -- same pattern as
754
+ // `IDBTransaction.js`'s `checkQueueEntry` -- rather than
755
+ // resetting after only one. Only the flag reset itself is
756
+ // deferred here -- unlike `IDBTransaction.js`'s
757
+ // `advanceAfterDispatch`, `finished()` (this transaction's own
758
+ // queue-advancement/completion signal) still runs
759
+ // synchronously, at exactly its previous timing: an earlier
760
+ // attempt at deferring `finished()` too raced against unrelated
761
+ // test setup that assumed a freshly deleted/created database's
762
+ // upgrade transaction had already fully completed by the time
763
+ // this function returns.
764
+ /**
765
+ * @param {Integer} attemptsLeft
766
+ * @returns {void}
767
+ */
768
+ function deferHandlerActiveReset (attemptsLeft) {
769
+ if (attemptsLeft <= 0) {
770
+ req.transaction.__handlerActive = false;
771
+ return;
772
+ }
773
+ queueMicrotask(() => {
774
+ deferHandlerActiveReset(attemptsLeft - 1);
775
+ });
776
+ }
777
+ deferHandlerActiveReset(10);
687
778
 
688
779
  if (e.__legacyOutputDidListenersThrowError) {
689
780
  logError('Error', 'An error occurred in an upgradeneeded handler attached to request chain', /** @type {Error} */ (e.__legacyOutputDidListenersThrowError)); // We do nothing else with this error as per spec
@@ -701,6 +792,7 @@ IDBFactory.prototype.open = function (name /* , version */) {
701
792
  * @returns {void}
702
793
  */
703
794
  function (ev) {
795
+ pendingVersionChanges.delete(name);
704
796
  connection.__upgradeTransaction = null;
705
797
  /** @type {import('./IDBDatabase.js').IDBDatabaseFull} */ (
706
798
  req.__result
@@ -716,6 +808,8 @@ IDBFactory.prototype.open = function (name /* , version */) {
716
808
 
717
809
  // eslint-disable-next-line camelcase -- Clear API
718
810
  req.transaction.on__preabort = function () {
811
+ isRevertingSysdb = true;
812
+ pendingVersionChanges.delete(name);
719
813
  connection.__upgradeTransaction = null;
720
814
  // We ensure any cache is deleted before any request error events fire and try to reopen
721
815
  if (useDatabaseCache) {
@@ -733,6 +827,7 @@ IDBFactory.prototype.open = function (name /* , version */) {
733
827
  req.__done = false;
734
828
 
735
829
  connection.close();
830
+ isRevertingSysdb = true;
736
831
  setTimeout(() => {
737
832
  const err = createDOMException('AbortError', 'The upgrade transaction was aborted.');
738
833
  sysdbFinishedCb(systx, err, function (reportError) {
@@ -789,6 +884,7 @@ IDBFactory.prototype.open = function (name /* , version */) {
789
884
  };
790
885
  }
791
886
 
887
+ pendingVersionChanges.set(name, {oldVersion, newVersion: version});
792
888
  if (oldVersion === 0) {
793
889
  systx.executeSql('INSERT INTO dbVersions VALUES (?,?)', [sqlSafeName, version], versionSet, dbCreateError);
794
890
  } else {
@@ -800,7 +896,36 @@ IDBFactory.prototype.open = function (name /* , version */) {
800
896
  }
801
897
  sysdbFinishedCb = function (systx, err, cb) {
802
898
  if (err) {
803
- rollback(err, cb);
899
+ rollback(err,
900
+ /**
901
+ * @param {Error} [reportError]
902
+ * @returns {void}
903
+ */
904
+ function (reportError) {
905
+ sysdb.transaction(
906
+ function (systx) {
907
+ if (oldVersion === 0) {
908
+ systx.executeSql(
909
+ 'DELETE FROM dbVersions WHERE "name" = ?',
910
+ [sqlSafeName]
911
+ );
912
+ } else {
913
+ systx.executeSql(
914
+ 'UPDATE dbVersions SET "version" = ? WHERE "name" = ?',
915
+ [oldVersion, sqlSafeName]
916
+ );
917
+ }
918
+ },
919
+ function (sqlErr) {
920
+ isRevertingSysdb = false;
921
+ cb(sqlErr); // eslint-disable-line promise/no-callback-in-promise -- Convenient
922
+ },
923
+ function () {
924
+ isRevertingSysdb = false;
925
+ cb(reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
926
+ }
927
+ );
928
+ });
804
929
  } else {
805
930
  commit(cb);
806
931
  }
@@ -1102,6 +1227,15 @@ IDBFactory.prototype.cmp = function (key1, key2) {
1102
1227
  IDBFactory.prototype.databases = function () {
1103
1228
  const me = this;
1104
1229
  let calledDbCreateError = false;
1230
+ // Snapshotted *now*, synchronously, at call time -- not read later
1231
+ // from inside the SQL query's callback below, which runs on a
1232
+ // deferred macrotask (see `nodeSQLiteDatabase.js`'s `exec`) and so
1233
+ // could otherwise race against (and lose to) an in-flight upgrade's
1234
+ // own commit/abort handler clearing its `pendingVersionChanges`
1235
+ // entry in the meantime -- which would make this method incorrectly
1236
+ // reflect a since-committed change that hadn't committed yet when
1237
+ // it was actually called.
1238
+ const pendingVersionChangesSnapshot = new Map(pendingVersionChanges);
1105
1239
  return new Promise(function (resolve, reject) { // eslint-disable-line promise/avoid-new -- Own polyfill
1106
1240
  if (!(me instanceof IDBFactory)) {
1107
1241
  throw new TypeError('Illegal invocation');
@@ -1129,11 +1263,24 @@ IDBFactory.prototype.databases = function () {
1129
1263
  sysReadTx.executeSql('SELECT "name", "version" FROM dbVersions', [], function (sysReadTx, data) {
1130
1264
  const dbNames = [];
1131
1265
  for (let i = 0; i < data.rows.length; i++) {
1132
- const {name, version} = /** @type {{name: string, version: Integer}} */ (data.rows.item(i));
1133
- dbNames.push({
1134
- name: util.unescapeSQLiteResponse(name),
1135
- version
1136
- });
1266
+ const {name: encodedName, version} = /** @type {{name: string, version: Integer}} */ (data.rows.item(i));
1267
+ const name = util.unescapeSQLiteResponse(encodedName);
1268
+ // A row for a database whose creation/upgrade hasn't
1269
+ // committed yet (see `pendingVersionChanges`) must
1270
+ // not reflect that in-flight change: a brand new
1271
+ // database (`oldVersion === 0`) isn't reported at
1272
+ // all until its creation commits, and an existing
1273
+ // database being upgraded is still reported, but
1274
+ // with its pre-upgrade version.
1275
+ const pending = pendingVersionChangesSnapshot.get(name);
1276
+ if (pending) {
1277
+ if (pending.oldVersion === 0) {
1278
+ continue;
1279
+ }
1280
+ dbNames.push({name, version: pending.oldVersion});
1281
+ continue;
1282
+ }
1283
+ dbNames.push({name, version});
1137
1284
  }
1138
1285
  resolve(dbNames);
1139
1286
  }, dbGetDatabaseNamesError);
@@ -8,6 +8,7 @@ import IDBObjectStore from './IDBObjectStore.js';
8
8
  import CFG from './CFG.js';
9
9
 
10
10
  let uniqueID = 0;
11
+ const activeTransactions = new Set();
11
12
  const listeners = ['onabort', 'oncomplete', 'onerror'];
12
13
  const readonlyProperties = ['objectStoreNames', 'mode', 'durability', 'db', 'error'];
13
14
 
@@ -137,6 +138,7 @@ IDBTransaction.__createInstance = function (db, storeNames, mode, durability = '
137
138
  me.__mode = mode;
138
139
  me.__durability = durability;
139
140
  me.__db = db;
141
+ activeTransactions.add(me);
140
142
  me.__error = null;
141
143
  // @ts-expect-error Part of `ShimEventTarget`
142
144
  me.__setOptions({
@@ -641,6 +643,7 @@ IDBTransaction.prototype.__executeRequests = function () {
641
643
  me.__errored = true;
642
644
  throw e;
643
645
  } finally {
646
+ activeTransactions.delete(me);
644
647
  me.__storeHandles = {};
645
648
  }
646
649
  }
@@ -855,14 +858,21 @@ IDBTransaction.prototype.__abortTransaction = function (err) {
855
858
  });
856
859
  }
857
860
  me.__active = false; // Setting here and in requestsFinished for https://github.com/w3c/IndexedDB/issues/87
861
+ activeTransactions.delete(me);
858
862
 
859
863
  if (err !== null) {
860
864
  me.__error = err;
861
865
  }
862
866
 
863
- if (me.__requestsFinished) {
867
+ if (me.__requestsFinished && err !== null) {
864
868
  // The transaction has already completed, so we can't call "onerror" or "onabort".
865
- // So throw the error instead.
869
+ // So throw the error instead. `err` is only ever `null` here via
870
+ // `IDBTransaction.prototype.abort`'s own `__abortTransaction(null)`
871
+ // call, which now checks `__requestsFinished` itself first and
872
+ // throws `InvalidStateError` synchronously before ever reaching
873
+ // this point -- so this guard is just defense in depth against
874
+ // `err` somehow being `null` some other way, not something this
875
+ // path should see in practice.
866
876
  setTimeout(() => {
867
877
  throw err;
868
878
  }, 0);
@@ -970,6 +980,14 @@ IDBTransaction.prototype.abort = function () {
970
980
  if (me.__committed) {
971
981
  throw createDOMException('InvalidStateError', 'The transaction has already been committed');
972
982
  }
983
+ if (me.__requestsFinished) {
984
+ // All requests have already finished and the transaction is
985
+ // auto-committing (the async SQL commit round trip just hasn't
986
+ // resolved yet) -- too late to abort per spec, even though
987
+ // `__committed` itself isn't set until that round trip actually
988
+ // finishes.
989
+ throw createDOMException('InvalidStateError', 'The transaction is already committing');
990
+ }
973
991
  me.__abortTransaction(null);
974
992
  };
975
993
 
@@ -1050,7 +1068,7 @@ IDBTransaction.__assertNotFinishedObjectStoreMethod = function (tx) {
1050
1068
  * @returns {void}
1051
1069
  */
1052
1070
  IDBTransaction.__assertActive = function (tx) {
1053
- if (!tx || !tx.__active || tx.__committed) {
1071
+ if (!tx || !tx.__active || !tx.__handlerActive || tx.__committed) {
1054
1072
  throw createDOMException('TransactionInactiveError', 'A request was placed against a transaction which is currently not active, or which is finished');
1055
1073
  }
1056
1074
  };
@@ -1081,4 +1099,6 @@ Object.defineProperty(IDBTransaction, 'prototype', {
1081
1099
  });
1082
1100
  /* eslint-enable unicorn/no-top-level-side-effects -- Would be good */
1083
1101
 
1102
+ IDBTransaction.activeTransactions = activeTransactions;
1103
+
1084
1104
  export default IDBTransaction;