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,4 +1,4 @@
1
- /*! indexeddbshim - v17.3.2 - 8/24/2026 */
1
+ /*! indexeddbshim - v17.3.4 - 8/25/2026 */
2
2
 
3
3
  (function (global, factory) {
4
4
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
@@ -3549,6 +3549,32 @@
3549
3549
  }
3550
3550
 
3551
3551
  var _templateObject, _templateObject2;
3552
+ /**
3553
+ * @param {unknown[]} arr
3554
+ * @param {number} index
3555
+ * @param {unknown} val
3556
+ * @returns {void}
3557
+ */
3558
+ var setArrayValue = function setArrayValue(arr, index, val) {
3559
+ if (Reflect.has(Array.prototype, index)) {
3560
+ Object.defineProperty(arr, index, {
3561
+ value: val,
3562
+ enumerable: true,
3563
+ writable: true,
3564
+ configurable: true
3565
+ });
3566
+ } else {
3567
+ arr[index] = val;
3568
+ }
3569
+ };
3570
+ /**
3571
+ * @param {unknown[]} arr
3572
+ * @param {unknown} val
3573
+ * @returns {void}
3574
+ */
3575
+ var safePush = function safePush(arr, val) {
3576
+ return setArrayValue(arr, arr.length, val);
3577
+ };
3552
3578
 
3553
3579
  /**
3554
3580
  * @typedef {NodeJS.TypedArray|DataView} ArrayBufferView
@@ -3812,6 +3838,7 @@
3812
3838
  * @returns {string}
3813
3839
  */
3814
3840
  encode: function encode(key) {
3841
+ /** @type {(string|null)[]} */
3815
3842
  var encoded = [];
3816
3843
  var _iterator = _createForOfIteratorHelper(key.entries()),
3817
3844
  _step;
@@ -3821,14 +3848,14 @@
3821
3848
  i = _step$value[0],
3822
3849
  item = _step$value[1];
3823
3850
  var encodedItem = _encode(item, true); // encode the array item
3824
- encoded[i] = encodedItem;
3851
+ setArrayValue(encoded, i, encodedItem);
3825
3852
  }
3826
3853
  } catch (err) {
3827
3854
  _iterator.e(err);
3828
3855
  } finally {
3829
3856
  _iterator.f();
3830
3857
  }
3831
- encoded.push(keyTypeToEncodedChar.invalid + '-'); // append an extra item, so empty arrays sort correctly
3858
+ safePush(encoded, keyTypeToEncodedChar.invalid + '-'); // append an extra item, so empty arrays sort correctly
3832
3859
  var encodedKey = JSON.stringify(encoded);
3833
3860
  if (CFG.escapeNULForSQLiteStatements === false) {
3834
3861
  encodedKey = encodedKey.replaceAll(String.raw(_templateObject || (_templateObject = _taggedTemplateLiteral(["\0"], ["\\u0000"]))), '\0');
@@ -3849,7 +3876,7 @@
3849
3876
  for (var i = 0; i < decoded.length; i++) {
3850
3877
  var item = decoded[i];
3851
3878
  var decodedItem = _decode(item, true); // decode the item
3852
- decoded[i] = decodedItem;
3879
+ setArrayValue(decoded, i, decodedItem);
3853
3880
  }
3854
3881
  return decoded;
3855
3882
  }
@@ -4147,7 +4174,7 @@
4147
4174
  // May throw (from binary)
4148
4175
  var arr = /** @type {Array<any>} */input;
4149
4176
  var len = arr.length;
4150
- seen.push(input);
4177
+ safePush(seen, input);
4151
4178
 
4152
4179
  /** @type {(KeyValueObject|Value)[]} */
4153
4180
  var keys = [];
@@ -4182,7 +4209,7 @@
4182
4209
  }) || fullKeys && keys.every(function (k) {
4183
4210
  return cmp(k, key) !== 0;
4184
4211
  })) {
4185
- keys.push(fullKeys ? key : key.value);
4212
+ safePush(keys, fullKeys ? key : key.value);
4186
4213
  }
4187
4214
  } catch (err) {
4188
4215
  if (!multiEntry) {
@@ -4342,7 +4369,7 @@
4342
4369
  if (key.failure) {
4343
4370
  return true;
4344
4371
  }
4345
- result.push(key.value);
4372
+ safePush(result, key.value);
4346
4373
  return false;
4347
4374
  }) ? {
4348
4375
  failure: true
@@ -4403,11 +4430,21 @@
4403
4430
  identifiers.forEach(function (identifier) {
4404
4431
  var hop = Object.hasOwn(value, identifier);
4405
4432
  if (!hop) {
4406
- value[identifier] = {};
4433
+ Object.defineProperty(value, identifier, {
4434
+ value: {},
4435
+ enumerable: true,
4436
+ writable: true,
4437
+ configurable: true
4438
+ });
4407
4439
  }
4408
4440
  value = value[identifier];
4409
4441
  });
4410
- value[(/** @type {string} */last)] = key; // key is already a `keyValue` in our processing so no need to convert
4442
+ Object.defineProperty(value, /** @type {string} */last, {
4443
+ value: key,
4444
+ enumerable: true,
4445
+ writable: true,
4446
+ configurable: true
4447
+ }); // key is already a `keyValue` in our processing so no need to convert
4411
4448
  }
4412
4449
 
4413
4450
  /**
@@ -4485,6 +4522,7 @@
4485
4522
  * @returns {Key[]}
4486
4523
  */
4487
4524
  function findMultiEntryMatches(keyEntry, range) {
4525
+ /** @type {unknown[]} */
4488
4526
  var matches = [];
4489
4527
  if (Array.isArray(keyEntry)) {
4490
4528
  var _iterator4 = _createForOfIteratorHelper(keyEntry),
@@ -4501,13 +4539,13 @@
4501
4539
  } else {
4502
4540
  var nested = findMultiEntryMatches(key, range);
4503
4541
  if (nested.length > 0) {
4504
- matches.push(key);
4542
+ safePush(matches, key);
4505
4543
  }
4506
4544
  continue;
4507
4545
  }
4508
4546
  }
4509
4547
  if (isNullish(range) || isKeyInRange(key, range, true)) {
4510
- matches.push(key);
4548
+ safePush(matches, key);
4511
4549
  }
4512
4550
  }
4513
4551
  } catch (err) {
@@ -4516,7 +4554,7 @@
4516
4554
  _iterator4.f();
4517
4555
  }
4518
4556
  } else if (isNullish(range) || isKeyInRange(keyEntry, range, true)) {
4519
- matches.push(keyEntry);
4557
+ safePush(matches, keyEntry);
4520
4558
  }
4521
4559
  return matches;
4522
4560
  }
@@ -4538,12 +4576,13 @@
4538
4576
  }
4539
4577
  case 'array':
4540
4578
  {
4579
+ /** @type {ValueType[]} */
4541
4580
  var array = [];
4542
4581
  var len = value.length;
4543
4582
  var index = 0;
4544
4583
  while (index < len) {
4545
4584
  var entry = convertKeyToValue(value[index]);
4546
- array[index] = entry;
4585
+ setArrayValue(array, index, entry);
4547
4586
  index++;
4548
4587
  }
4549
4588
  return array;
@@ -5303,6 +5342,7 @@
5303
5342
  }
5304
5343
 
5305
5344
  var uniqueID = 0;
5345
+ var activeTransactions = new Set();
5306
5346
  var listeners$1 = ['onabort', 'oncomplete', 'onerror'];
5307
5347
  var readonlyProperties$4 = ['objectStoreNames', 'mode', 'durability', 'db', 'error'];
5308
5348
 
@@ -5434,6 +5474,7 @@
5434
5474
  me.__mode = mode;
5435
5475
  me.__durability = durability;
5436
5476
  me.__db = db;
5477
+ activeTransactions.add(me);
5437
5478
  me.__error = null;
5438
5479
  // @ts-expect-error Part of `ShimEventTarget`
5439
5480
  me.__setOptions({
@@ -5946,6 +5987,7 @@
5946
5987
  me.__errored = true;
5947
5988
  throw e;
5948
5989
  } finally {
5990
+ activeTransactions.delete(me);
5949
5991
  me.__storeHandles = {};
5950
5992
  }
5951
5993
  }
@@ -6152,13 +6194,19 @@
6152
6194
  });
6153
6195
  }
6154
6196
  me.__active = false; // Setting here and in requestsFinished for https://github.com/w3c/IndexedDB/issues/87
6155
-
6197
+ activeTransactions.delete(me);
6156
6198
  if (err !== null) {
6157
6199
  me.__error = err;
6158
6200
  }
6159
- if (me.__requestsFinished) {
6201
+ if (me.__requestsFinished && err !== null) {
6160
6202
  // The transaction has already completed, so we can't call "onerror" or "onabort".
6161
- // So throw the error instead.
6203
+ // So throw the error instead. `err` is only ever `null` here via
6204
+ // `IDBTransaction.prototype.abort`'s own `__abortTransaction(null)`
6205
+ // call, which now checks `__requestsFinished` itself first and
6206
+ // throws `InvalidStateError` synchronously before ever reaching
6207
+ // this point -- so this guard is just defense in depth against
6208
+ // `err` somehow being `null` some other way, not something this
6209
+ // path should see in practice.
6162
6210
  setTimeout(function () {
6163
6211
  throw err;
6164
6212
  }, 0);
@@ -6278,6 +6326,14 @@
6278
6326
  if (me.__committed) {
6279
6327
  throw createDOMException('InvalidStateError', 'The transaction has already been committed');
6280
6328
  }
6329
+ if (me.__requestsFinished) {
6330
+ // All requests have already finished and the transaction is
6331
+ // auto-committing (the async SQL commit round trip just hasn't
6332
+ // resolved yet) -- too late to abort per spec, even though
6333
+ // `__committed` itself isn't set until that round trip actually
6334
+ // finishes.
6335
+ throw createDOMException('InvalidStateError', 'The transaction is already committing');
6336
+ }
6281
6337
  me.__abortTransaction(null);
6282
6338
  };
6283
6339
 
@@ -6359,7 +6415,7 @@
6359
6415
  * @returns {void}
6360
6416
  */
6361
6417
  IDBTransaction.__assertActive = function (tx) {
6362
- if (!tx || !tx.__active || tx.__committed) {
6418
+ if (!tx || !tx.__active || !tx.__handlerActive || tx.__committed) {
6363
6419
  throw createDOMException('TransactionInactiveError', 'A request was placed against a transaction which is currently not active, or which is finished');
6364
6420
  }
6365
6421
  };
@@ -6386,6 +6442,9 @@
6386
6442
  Object.defineProperty(IDBTransaction, 'prototype', {
6387
6443
  writable: false
6388
6444
  });
6445
+ /* eslint-enable unicorn/no-top-level-side-effects -- Would be good */
6446
+
6447
+ IDBTransaction.activeTransactions = activeTransactions;
6389
6448
 
6390
6449
  var TypesonPromise = /*#__PURE__*/_createClass(function TypesonPromise(e) {
6391
6450
  _classCallCheck(this, TypesonPromise);
@@ -7772,7 +7831,7 @@
7772
7831
  }
7773
7832
  }
7774
7833
  },
7775
- k = {
7834
+ D = {
7776
7835
  map: {
7777
7836
  test: function test(e) {
7778
7837
  return "Map" === toStringTag(e);
@@ -7785,7 +7844,7 @@
7785
7844
  }
7786
7845
  }
7787
7846
  },
7788
- D = {
7847
+ R = {
7789
7848
  nan: {
7790
7849
  test: function test(e) {
7791
7850
  return Number.isNaN(e);
@@ -8134,8 +8193,8 @@
8134
8193
  revive: function revive() {}
8135
8194
  }
8136
8195
  }],
8137
- ne = [D, M, L, F],
8138
- ce = [Z, Y, re, K, ne, O, z, U, _, B, I, h, N, C].concat("function" == typeof Map ? k : [], "function" == typeof Set ? H : [], "function" == typeof ArrayBuffer ? p : [], "function" == typeof Uint8Array ? X : [], "function" == typeof DataView ? w : [], "undefined" != typeof crypto ? v : [], "undefined" != typeof BigInt ? [m, d] : [], "undefined" != typeof DOMException ? A : [], "undefined" != typeof QuotaExceededError ? W : [], "undefined" != typeof WebTransportError ? te : [], "undefined" != typeof DOMRect ? x : [], "undefined" != typeof DOMPoint ? S : [], "undefined" != typeof DOMQuad ? P : [], "undefined" != typeof DOMMatrix ? T : [], "undefined" != typeof AudioData ? f : [], "undefined" != typeof EncodedAudioChunk ? E : [], "undefined" != typeof EncodedVideoChunk ? j : [], "undefined" != typeof VideoFrame ? ee : []);
8196
+ ne = [R, M, L, F],
8197
+ ce = [Z, Y, re, K, ne, O, z, U, _, B, I, h, N, C].concat("function" == typeof Map ? D : [], "function" == typeof Set ? H : [], "function" == typeof ArrayBuffer ? p : [], "function" == typeof Uint8Array ? X : [], "function" == typeof DataView ? w : [], "undefined" != typeof crypto ? v : [], "undefined" != typeof BigInt ? [m, d] : [], "undefined" != typeof DOMException ? A : [], "undefined" != typeof QuotaExceededError ? W : [], "undefined" != typeof WebTransportError ? te : [], "undefined" != typeof DOMRect ? x : [], "undefined" != typeof DOMPoint ? S : [], "undefined" != typeof DOMQuad ? P : [], "undefined" != typeof DOMMatrix ? T : [], "undefined" != typeof AudioData ? f : [], "undefined" != typeof EncodedAudioChunk ? E : [], "undefined" != typeof EncodedVideoChunk ? j : [], "undefined" != typeof VideoFrame ? ee : []);
8139
8198
  var ue = ce.concat({
8140
8199
  checkDataCloneException: {
8141
8200
  test: function test(e) {
@@ -8152,11 +8211,56 @@
8152
8211
  return false;
8153
8212
  }
8154
8213
  }
8214
+ }),
8215
+ ye = ue.concat({
8216
+ checkSharedArrayBufferException: {
8217
+ test: function test(e) {
8218
+ if ("SharedArrayBuffer" === {}.toString.call(e).slice(8, -1)) throw new DOMException("The object cannot be cloned.", "DataCloneError");
8219
+ return false;
8220
+ }
8221
+ }
8155
8222
  });
8156
8223
 
8157
8224
  // See: https://stackoverflow.com/questions/42170826/categories-for-rejection-by-the-structured-cloning-algorithm
8158
8225
 
8159
- var typeson = new Typeson().register(ue);
8226
+ // Although typeson-registry already has a FileList type in its structured cloning presets,
8227
+ // we need to override it so it works with our tests
8228
+
8229
+ var specSet = ye.flatMap(function (preset) {
8230
+ return Array.isArray(preset) ? preset : [preset];
8231
+ }).find(function (preset) {
8232
+ return preset && !Array.isArray(preset) && 'filelist' in preset;
8233
+ });
8234
+ var origFileList = specSet && !Array.isArray(specSet) && 'filelist' in specSet ? specSet.filelist : undefined;
8235
+ var origTest = origFileList && _typeof(origFileList) === 'object' && 'test' in origFileList && typeof origFileList.test === 'function' ? origFileList.test : undefined;
8236
+ var origRevive = origFileList && _typeof(origFileList) === 'object' && 'revive' in origFileList && typeof origFileList.revive === 'function' ? origFileList.revive : undefined;
8237
+ var customFileList = origFileList ? _objectSpread2(_objectSpread2({}, origFileList), {}, {
8238
+ /**
8239
+ * @param {unknown} x
8240
+ * @param {import('typeson').StateObject} state
8241
+ * @returns {boolean}
8242
+ */
8243
+ test: function test(x, state) {
8244
+ if (typeof FileList !== 'undefined') {
8245
+ return x instanceof FileList;
8246
+ }
8247
+ return typeof origTest === 'function' ? origTest(x, state) : false;
8248
+ },
8249
+ /**
8250
+ * @param {unknown} x
8251
+ * @param {import('typeson').StateObject} state
8252
+ * @returns {unknown}
8253
+ */
8254
+ revive: function revive(x, state) {
8255
+ if (typeof FileList !== 'undefined') {
8256
+ return Reflect.construct(FileList, [x]);
8257
+ }
8258
+ return typeof origRevive === 'function' ? origRevive(x, state) : undefined;
8259
+ }
8260
+ }) : undefined;
8261
+ var typeson = new Typeson().register([ye, customFileList ? {
8262
+ filelist: customFileList
8263
+ } : {}]);
8160
8264
 
8161
8265
  /**
8162
8266
  * @param {(preset: import('typeson-registry').Preset) =>
@@ -8165,7 +8269,7 @@
8165
8269
  */
8166
8270
  function register(func) {
8167
8271
  // eslint-disable-next-line unicorn/no-top-level-assignment-in-function -- Should be one-time cache
8168
- typeson = new Typeson().register(func(ue));
8272
+ typeson = new Typeson().register(func(ye));
8169
8273
  }
8170
8274
 
8171
8275
  /**
@@ -11092,7 +11196,29 @@
11092
11196
  return new SyncPromise(function (resolve) {
11093
11197
  setTimeout(function () {
11094
11198
  entry.dispatchEvent(e); // No need to catch errors
11095
- resolve(undefined);
11199
+ // Unlike a native `Promise`, `SyncPromise#then` chains
11200
+ // synchronously off `resolve()` (verified directly:
11201
+ // `SyncPromise.resolve().then(cb)` runs `cb` before
11202
+ // even the calling code below it finishes) -- so
11203
+ // resolving immediately here would let the
11204
+ // `connectionsClosed()` check below run before a
11205
+ // same-task-but-microtask-later continuation of a
11206
+ // `versionchange` listener above (e.g. an `await`-
11207
+ // based one that then calls `db.close()`, as in
11208
+ // `transaction-lifetime.any.js`) ever gets a turn,
11209
+ // incorrectly firing `blocked` even though the
11210
+ // connection was about to close in time. Give real
11211
+ // microtask-deferred continuations a small, bounded
11212
+ // number of turns first -- same pattern as
11213
+ // `IDBTransaction.js`'s `checkQueueEntry`.
11214
+ var attemptsLeft = 10;
11215
+ (function wait() {
11216
+ if (attemptsLeft-- <= 0) {
11217
+ resolve(undefined);
11218
+ return;
11219
+ }
11220
+ queueMicrotask(wait);
11221
+ })();
11096
11222
  }, 0);
11097
11223
  });
11098
11224
  });
@@ -11152,6 +11278,23 @@
11152
11278
  */
11153
11279
  var websqlDBCache = {};
11154
11280
 
11281
+ /**
11282
+ * Tracks databases with a creation/upgrade currently in flight but not yet
11283
+ * committed or aborted -- keyed by (unescaped) database name. The
11284
+ * `dbVersions` row in `sysdb` these entries shadow is written *before*
11285
+ * `upgradeneeded` is even dispatched to user code (as the success
11286
+ * callback of that very `INSERT`/`UPDATE`), on the same shared `sysdb`
11287
+ * connection `databases()` itself reads from -- so, absent this, a
11288
+ * `databases()` call made while an upgrade is still pending would see
11289
+ * the new row (or new version) immediately, rather than only once the
11290
+ * corresponding `versionchange` transaction has genuinely committed, as
11291
+ * required by `get-databases.any.js`'s "doesn't pick up changes that
11292
+ * haven't committed" test. `IDBFactory.prototype.databases` filters
11293
+ * its SQL results against this map.
11294
+ * @type {Map<string, {oldVersion: Integer, newVersion: Integer}>}
11295
+ */
11296
+ var pendingVersionChanges = new Map();
11297
+
11155
11298
  /** @type {import('websql-configurable/lib/websql/WebSQLDatabase.js').default} */
11156
11299
  var sysdb;
11157
11300
  var nameCounter = 0;
@@ -11425,6 +11568,7 @@
11425
11568
  }
11426
11569
  var req = IDBOpenDBRequest.__createInstance();
11427
11570
  var calledDbCreateError = false;
11571
+ var isRevertingSysdb = false;
11428
11572
  if (CFG.autoName && name === '') {
11429
11573
  // eslint-disable-next-line unicorn/no-top-level-assignment-in-function -- Necessary?
11430
11574
  name = 'autoNamedDatabase_' + nameCounter++;
@@ -11451,9 +11595,22 @@
11451
11595
  * @returns {boolean}
11452
11596
  */
11453
11597
  function dbCreateError(tx, err) {
11454
- if (calledDbCreateError) {
11598
+ if (calledDbCreateError || isRevertingSysdb) {
11455
11599
  return false;
11456
11600
  }
11601
+ // Defensive cleanup: `pendingVersionChanges.set(name, ...)` (see
11602
+ // below) runs *before* the `dbVersions` `INSERT`/`UPDATE` it
11603
+ // guards is even attempted, but only `versionSet`'s own
11604
+ // `on__beforecomplete`/`on__preabort` handlers ever clear it --
11605
+ // and `versionSet` never runs at all if that `INSERT`/`UPDATE`,
11606
+ // or an earlier step in this same `open()` flow, fails first.
11607
+ // Without this, such a failure would leave the entry orphaned
11608
+ // forever, silently corrupting `databases()` for any *other*,
11609
+ // unrelated later `open()` call that happens to reuse the same
11610
+ // database name (common in WPT tests, e.g. generic names like
11611
+ // "DB1"/"TestDatabase" reused across different test files in
11612
+ // the same process). A no-op if no entry was ever set.
11613
+ pendingVersionChanges.delete(name);
11457
11614
  var er = err ? webSQLErrback(err) : (/** @type {Error} */tx);
11458
11615
  calledDbCreateError = true;
11459
11616
  // Re: why bubbling here (and how cancelable is only really relevant for `window.onerror`) see: https://github.com/w3c/IndexedDB/issues/86
@@ -11502,36 +11659,46 @@
11502
11659
  */
11503
11660
  var sysdbFinishedCb = function sysdbFinishedCb(systx, err, cb) {
11504
11661
  if (err) {
11505
- try {
11506
- systx.executeSql('ROLLBACK', [], cb, cb);
11507
- } catch (err) {
11508
- // Browser may fail with expired transaction above so
11509
- // no choice but to manually revert
11662
+ /**
11663
+ * @param {any} [errorToShow]
11664
+ * @returns {void}
11665
+ */
11666
+ var manualRevert = function manualRevert(errorToShow) {
11667
+ /**
11668
+ * @param {string} [msg]
11669
+ * @throws {Error}
11670
+ * @returns {never}
11671
+ */
11672
+ function reportError(msg) {
11673
+ throw new Error('Unable to roll back upgrade transaction!' + (msg || ''));
11674
+ }
11510
11675
  sysdb.transaction(function (systx) {
11511
- /**
11512
- *
11513
- * @param {string} msg
11514
- * @throws {Error}
11515
- * @returns {never}
11516
- */
11517
- function reportError(msg) {
11518
- throw new Error('Unable to roll back upgrade transaction!' + (msg || ''));
11519
- }
11520
-
11521
11676
  // Attempt to revert
11522
11677
  if (oldVersion === 0) {
11523
- systx.executeSql('DELETE FROM dbVersions WHERE "name" = ?', [sqlSafeName], function () {
11524
- // @ts-expect-error Force to work
11525
- cb(reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11526
- },
11527
- // @ts-expect-error Force to work
11528
- reportError);
11678
+ systx.executeSql('DELETE FROM dbVersions WHERE "name" = ?', [sqlSafeName]);
11529
11679
  } else {
11530
- systx.executeSql('UPDATE dbVersions SET "version" = ? WHERE "name" = ?', [oldVersion, sqlSafeName], cb,
11531
- // @ts-expect-error Force to work
11532
- reportError);
11680
+ systx.executeSql('UPDATE dbVersions SET "version" = ? WHERE "name" = ?', [oldVersion, sqlSafeName]);
11533
11681
  }
11682
+ }, function (sqlErr) {
11683
+ isRevertingSysdb = false;
11684
+ cb(sqlErr); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11685
+ }, function () {
11686
+ isRevertingSysdb = false;
11687
+ cb(errorToShow || reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11534
11688
  });
11689
+ };
11690
+ try {
11691
+ systx.executeSql('ROLLBACK', [], function () {
11692
+ cb(); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11693
+ }, function (tx, sqlErr) {
11694
+ // Browser/Node may fail with expired transaction, so manually revert
11695
+ manualRevert(sqlErr);
11696
+ return false;
11697
+ });
11698
+ } catch (e) {
11699
+ // Browser may fail with expired transaction above so
11700
+ // no choice but to manually revert
11701
+ manualRevert(e);
11535
11702
  }
11536
11703
  return;
11537
11704
  }
@@ -11565,22 +11732,43 @@
11565
11732
  // open/close the transaction's active-handler window itself.
11566
11733
  req.transaction.__handlerActive = true;
11567
11734
  req.dispatchEvent(e);
11568
- // Give any same-tick microtask scheduled from within the
11735
+ // Give any microtask-scheduled continuation of the
11569
11736
  // `upgradeneeded` handler (e.g. a plain
11570
- // `Promise.resolve().then(...)`) a chance to run -- and still
11571
- // observe the transaction as active -- before we deactivate it
11572
- // again, per https://github.com/w3c/IndexedDB/issues/87. Only
11573
- // the flag reset itself is deferred here -- unlike
11574
- // `IDBTransaction.js`'s `advanceAfterDispatch`, `finished()`
11575
- // (this transaction's own queue-advancement/completion signal)
11576
- // still runs synchronously, at exactly its previous timing: an
11577
- // earlier attempt at deferring `finished()` too raced against
11578
- // unrelated test setup that assumed a freshly deleted/created
11579
- // database's upgrade transaction had already fully completed by
11580
- // the time this function returns.
11581
- queueMicrotask(function () {
11582
- req.transaction.__handlerActive = false;
11583
- });
11737
+ // `Promise.resolve().then(...)`, or an `await`-based
11738
+ // continuation of a promise resolved from within the handler,
11739
+ // such as testharness.js's own `EventWatcher`) a chance to
11740
+ // run -- and still observe the transaction as active -- before
11741
+ // we deactivate it again, per
11742
+ // https://github.com/w3c/IndexedDB/issues/87. A single deferred
11743
+ // tick isn't always enough: an `await`-based continuation can
11744
+ // take more than one microtask turn to resume (e.g.
11745
+ // `transaction-lifetime.any.js`'s `EventWatcher`-based
11746
+ // `await eventWatcher.wait_for('upgradeneeded')`), so retry a
11747
+ // small, bounded number of times first -- same pattern as
11748
+ // `IDBTransaction.js`'s `checkQueueEntry` -- rather than
11749
+ // resetting after only one. Only the flag reset itself is
11750
+ // deferred here -- unlike `IDBTransaction.js`'s
11751
+ // `advanceAfterDispatch`, `finished()` (this transaction's own
11752
+ // queue-advancement/completion signal) still runs
11753
+ // synchronously, at exactly its previous timing: an earlier
11754
+ // attempt at deferring `finished()` too raced against unrelated
11755
+ // test setup that assumed a freshly deleted/created database's
11756
+ // upgrade transaction had already fully completed by the time
11757
+ // this function returns.
11758
+ /**
11759
+ * @param {Integer} attemptsLeft
11760
+ * @returns {void}
11761
+ */
11762
+ function deferHandlerActiveReset(attemptsLeft) {
11763
+ if (attemptsLeft <= 0) {
11764
+ req.transaction.__handlerActive = false;
11765
+ return;
11766
+ }
11767
+ queueMicrotask(function () {
11768
+ deferHandlerActiveReset(attemptsLeft - 1);
11769
+ });
11770
+ }
11771
+ deferHandlerActiveReset(10);
11584
11772
  if (e.__legacyOutputDidListenersThrowError) {
11585
11773
  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
11586
11774
  req.transaction.__abortTransaction(createDOMException('AbortError', 'A request was aborted.'));
@@ -11596,6 +11784,7 @@
11596
11784
  * @returns {void}
11597
11785
  */
11598
11786
  function (ev) {
11787
+ pendingVersionChanges.delete(name);
11599
11788
  connection.__upgradeTransaction = null;
11600
11789
  /** @type {import('./IDBDatabase.js').IDBDatabaseFull} */
11601
11790
  req.__result.__versionTransaction = null;
@@ -11610,6 +11799,8 @@
11610
11799
 
11611
11800
  // eslint-disable-next-line camelcase -- Clear API
11612
11801
  req.transaction.on__preabort = function () {
11802
+ isRevertingSysdb = true;
11803
+ pendingVersionChanges.delete(name);
11613
11804
  connection.__upgradeTransaction = null;
11614
11805
  // We ensure any cache is deleted before any request error events fire and try to reopen
11615
11806
  if (useDatabaseCache) {
@@ -11626,6 +11817,7 @@
11626
11817
  req.__result = undefined;
11627
11818
  req.__done = false;
11628
11819
  connection.close();
11820
+ isRevertingSysdb = true;
11629
11821
  setTimeout(function () {
11630
11822
  var err = createDOMException('AbortError', 'The upgrade transaction was aborted.');
11631
11823
  sysdbFinishedCb(systx, err, function (reportError) {
@@ -11672,6 +11864,10 @@
11672
11864
  // });
11673
11865
  };
11674
11866
  }
11867
+ pendingVersionChanges.set(name, {
11868
+ oldVersion: oldVersion,
11869
+ newVersion: version
11870
+ });
11675
11871
  if (oldVersion === 0) {
11676
11872
  systx.executeSql('INSERT INTO dbVersions VALUES (?,?)', [sqlSafeName, version], versionSet, dbCreateError);
11677
11873
  } else {
@@ -11683,7 +11879,26 @@
11683
11879
  }
11684
11880
  sysdbFinishedCb = function sysdbFinishedCb(systx, err, cb) {
11685
11881
  if (err) {
11686
- rollback(err, cb);
11882
+ rollback(err,
11883
+ /**
11884
+ * @param {Error} [reportError]
11885
+ * @returns {void}
11886
+ */
11887
+ function (reportError) {
11888
+ sysdb.transaction(function (systx) {
11889
+ if (oldVersion === 0) {
11890
+ systx.executeSql('DELETE FROM dbVersions WHERE "name" = ?', [sqlSafeName]);
11891
+ } else {
11892
+ systx.executeSql('UPDATE dbVersions SET "version" = ? WHERE "name" = ?', [oldVersion, sqlSafeName]);
11893
+ }
11894
+ }, function (sqlErr) {
11895
+ isRevertingSysdb = false;
11896
+ cb(sqlErr); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11897
+ }, function () {
11898
+ isRevertingSysdb = false;
11899
+ cb(reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11900
+ });
11901
+ });
11687
11902
  } else {
11688
11903
  commit(cb);
11689
11904
  }
@@ -11974,6 +12189,15 @@
11974
12189
  IDBFactory.prototype.databases = function () {
11975
12190
  var me = this;
11976
12191
  var calledDbCreateError = false;
12192
+ // Snapshotted *now*, synchronously, at call time -- not read later
12193
+ // from inside the SQL query's callback below, which runs on a
12194
+ // deferred macrotask (see `nodeSQLiteDatabase.js`'s `exec`) and so
12195
+ // could otherwise race against (and lose to) an in-flight upgrade's
12196
+ // own commit/abort handler clearing its `pendingVersionChanges`
12197
+ // entry in the meantime -- which would make this method incorrectly
12198
+ // reflect a since-committed change that hadn't committed yet when
12199
+ // it was actually called.
12200
+ var pendingVersionChangesSnapshot = new Map(pendingVersionChanges);
11977
12201
  return new Promise(function (resolve, reject) {
11978
12202
  // eslint-disable-line promise/avoid-new -- Own polyfill
11979
12203
  if (!(me instanceof IDBFactory)) {
@@ -12003,10 +12227,29 @@
12003
12227
  var dbNames = [];
12004
12228
  for (var i = 0; i < data.rows.length; i++) {
12005
12229
  var _data$rows$item2 = /** @type {{name: string, version: Integer}} */data.rows.item(i),
12006
- name = _data$rows$item2.name,
12230
+ encodedName = _data$rows$item2.name,
12007
12231
  version = _data$rows$item2.version;
12232
+ var name = unescapeSQLiteResponse(encodedName);
12233
+ // A row for a database whose creation/upgrade hasn't
12234
+ // committed yet (see `pendingVersionChanges`) must
12235
+ // not reflect that in-flight change: a brand new
12236
+ // database (`oldVersion === 0`) isn't reported at
12237
+ // all until its creation commits, and an existing
12238
+ // database being upgraded is still reported, but
12239
+ // with its pre-upgrade version.
12240
+ var pending = pendingVersionChangesSnapshot.get(name);
12241
+ if (pending) {
12242
+ if (pending.oldVersion === 0) {
12243
+ continue;
12244
+ }
12245
+ dbNames.push({
12246
+ name: name,
12247
+ version: pending.oldVersion
12248
+ });
12249
+ continue;
12250
+ }
12008
12251
  dbNames.push({
12009
- name: unescapeSQLiteResponse(name),
12252
+ name: name,
12010
12253
  version: version
12011
12254
  });
12012
12255
  }
@@ -12426,9 +12669,26 @@
12426
12669
  // Key.convertValueToKey(key); // Already checked by `continue` or `continuePrimaryKey`
12427
12670
  sqlValues.push(/** @type {string} */_encode(key));
12428
12671
  } else if (continueCall && me.__key !== undefined) {
12429
- sql.push('AND', quotedKeyColumnName, op + ' ?');
12430
12672
  // Key.convertValueToKey(me.__key); // Already checked when stored
12431
- sqlValues.push(/** @type {string} */_encode(me.__key));
12673
+ if (!me.__unique && me.__keyColumnName !== 'key' && me.__primaryKey !== undefined) {
12674
+ // A plain `continue()` on a non-unique index cursor must find
12675
+ // the next record strictly after the (key, primaryKey) pair
12676
+ // this cursor last returned -- a scalar `key > lastKey` alone
12677
+ // would wrongly exclude a *different*, not-yet-visited record
12678
+ // that's still tied on key with the last one (e.g. another
12679
+ // record that always had that same indexed value), and would
12680
+ // also wrongly re-admit the *same* record forever if a
12681
+ // same-transaction `update()` bumped its own key back above
12682
+ // the threshold, since a scalar comparison can't distinguish
12683
+ // "some other record newly tied" from "this record moved
12684
+ // past its own last position." Comparing the full tuple (via
12685
+ // this OR) against both key and primary key resolves both.
12686
+ sql.push('AND (', quotedKeyColumnName, op, '?', 'OR (', quotedKeyColumnName, '= ?', 'AND', quotedKey, op, '?))');
12687
+ sqlValues.push(/** @type {string} */_encode(me.__key), /** @type {string} */_encode(me.__key), /** @type {string} */_encode(me.__primaryKey));
12688
+ } else {
12689
+ sql.push('AND', quotedKeyColumnName, op + ' ?');
12690
+ sqlValues.push(/** @type {string} */_encode(me.__key));
12691
+ }
12432
12692
  }
12433
12693
  if (!me.__count) {
12434
12694
  // 1. Sort by key
@@ -13058,9 +13318,15 @@
13058
13318
  * @returns {void}
13059
13319
  */
13060
13320
  function addToQueue(clonedValue) {
13061
- // We set the `invalidateCache` argument to `false` since the old value shouldn't be accessed
13321
+ // `invalidateCache: true` so any cursor on this store (including
13322
+ // this one) drops its prefetched row batch, forcing its next
13323
+ // `continue()` to re-query live rather than serve rows snapshotted
13324
+ // before this update -- needed for the compound-tuple
13325
+ // continuation logic in `__findBasic` to see this update's
13326
+ // effect on ordering (see "Modify records during cursor
13327
+ // iteration" in idbcursor_update_index.any.js).
13062
13328
  // @ts-ignore -- API (not erring in TS 6)
13063
- IDBObjectStore.__storingRecordObjectStore(request, me.__store, false, clonedValue, false, key);
13329
+ IDBObjectStore.__storingRecordObjectStore(request, me.__store, true, clonedValue, false, key);
13064
13330
  }
13065
13331
  if (me.__store.keyPath !== null) {
13066
13332
  var _me$__store$__validat = me.__store.__validateKeyAndValueAndCloneValue(valueToUpdate, undefined, true),