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 (factory) {
4
4
  typeof define === 'function' && define.amd ? define(factory) :
@@ -3554,6 +3554,32 @@
3554
3554
  }
3555
3555
 
3556
3556
  var _templateObject, _templateObject2;
3557
+ /**
3558
+ * @param {unknown[]} arr
3559
+ * @param {number} index
3560
+ * @param {unknown} val
3561
+ * @returns {void}
3562
+ */
3563
+ var setArrayValue = function setArrayValue(arr, index, val) {
3564
+ if (Reflect.has(Array.prototype, index)) {
3565
+ Object.defineProperty(arr, index, {
3566
+ value: val,
3567
+ enumerable: true,
3568
+ writable: true,
3569
+ configurable: true
3570
+ });
3571
+ } else {
3572
+ arr[index] = val;
3573
+ }
3574
+ };
3575
+ /**
3576
+ * @param {unknown[]} arr
3577
+ * @param {unknown} val
3578
+ * @returns {void}
3579
+ */
3580
+ var safePush = function safePush(arr, val) {
3581
+ return setArrayValue(arr, arr.length, val);
3582
+ };
3557
3583
 
3558
3584
  /**
3559
3585
  * @typedef {NodeJS.TypedArray|DataView} ArrayBufferView
@@ -3817,6 +3843,7 @@
3817
3843
  * @returns {string}
3818
3844
  */
3819
3845
  encode: function encode(key) {
3846
+ /** @type {(string|null)[]} */
3820
3847
  var encoded = [];
3821
3848
  var _iterator = _createForOfIteratorHelper(key.entries()),
3822
3849
  _step;
@@ -3826,14 +3853,14 @@
3826
3853
  i = _step$value[0],
3827
3854
  item = _step$value[1];
3828
3855
  var encodedItem = _encode(item, true); // encode the array item
3829
- encoded[i] = encodedItem;
3856
+ setArrayValue(encoded, i, encodedItem);
3830
3857
  }
3831
3858
  } catch (err) {
3832
3859
  _iterator.e(err);
3833
3860
  } finally {
3834
3861
  _iterator.f();
3835
3862
  }
3836
- encoded.push(keyTypeToEncodedChar.invalid + '-'); // append an extra item, so empty arrays sort correctly
3863
+ safePush(encoded, keyTypeToEncodedChar.invalid + '-'); // append an extra item, so empty arrays sort correctly
3837
3864
  var encodedKey = JSON.stringify(encoded);
3838
3865
  if (CFG.escapeNULForSQLiteStatements === false) {
3839
3866
  encodedKey = encodedKey.replaceAll(String.raw(_templateObject || (_templateObject = _taggedTemplateLiteral(["\0"], ["\\u0000"]))), '\0');
@@ -3854,7 +3881,7 @@
3854
3881
  for (var i = 0; i < decoded.length; i++) {
3855
3882
  var item = decoded[i];
3856
3883
  var decodedItem = _decode(item, true); // decode the item
3857
- decoded[i] = decodedItem;
3884
+ setArrayValue(decoded, i, decodedItem);
3858
3885
  }
3859
3886
  return decoded;
3860
3887
  }
@@ -4152,7 +4179,7 @@
4152
4179
  // May throw (from binary)
4153
4180
  var arr = /** @type {Array<any>} */input;
4154
4181
  var len = arr.length;
4155
- seen.push(input);
4182
+ safePush(seen, input);
4156
4183
 
4157
4184
  /** @type {(KeyValueObject|Value)[]} */
4158
4185
  var keys = [];
@@ -4187,7 +4214,7 @@
4187
4214
  }) || fullKeys && keys.every(function (k) {
4188
4215
  return cmp(k, key) !== 0;
4189
4216
  })) {
4190
- keys.push(fullKeys ? key : key.value);
4217
+ safePush(keys, fullKeys ? key : key.value);
4191
4218
  }
4192
4219
  } catch (err) {
4193
4220
  if (!multiEntry) {
@@ -4347,7 +4374,7 @@
4347
4374
  if (key.failure) {
4348
4375
  return true;
4349
4376
  }
4350
- result.push(key.value);
4377
+ safePush(result, key.value);
4351
4378
  return false;
4352
4379
  }) ? {
4353
4380
  failure: true
@@ -4408,11 +4435,21 @@
4408
4435
  identifiers.forEach(function (identifier) {
4409
4436
  var hop = Object.hasOwn(value, identifier);
4410
4437
  if (!hop) {
4411
- value[identifier] = {};
4438
+ Object.defineProperty(value, identifier, {
4439
+ value: {},
4440
+ enumerable: true,
4441
+ writable: true,
4442
+ configurable: true
4443
+ });
4412
4444
  }
4413
4445
  value = value[identifier];
4414
4446
  });
4415
- value[(/** @type {string} */last)] = key; // key is already a `keyValue` in our processing so no need to convert
4447
+ Object.defineProperty(value, /** @type {string} */last, {
4448
+ value: key,
4449
+ enumerable: true,
4450
+ writable: true,
4451
+ configurable: true
4452
+ }); // key is already a `keyValue` in our processing so no need to convert
4416
4453
  }
4417
4454
 
4418
4455
  /**
@@ -4490,6 +4527,7 @@
4490
4527
  * @returns {Key[]}
4491
4528
  */
4492
4529
  function findMultiEntryMatches(keyEntry, range) {
4530
+ /** @type {unknown[]} */
4493
4531
  var matches = [];
4494
4532
  if (Array.isArray(keyEntry)) {
4495
4533
  var _iterator4 = _createForOfIteratorHelper(keyEntry),
@@ -4506,13 +4544,13 @@
4506
4544
  } else {
4507
4545
  var nested = findMultiEntryMatches(key, range);
4508
4546
  if (nested.length > 0) {
4509
- matches.push(key);
4547
+ safePush(matches, key);
4510
4548
  }
4511
4549
  continue;
4512
4550
  }
4513
4551
  }
4514
4552
  if (isNullish(range) || isKeyInRange(key, range, true)) {
4515
- matches.push(key);
4553
+ safePush(matches, key);
4516
4554
  }
4517
4555
  }
4518
4556
  } catch (err) {
@@ -4521,7 +4559,7 @@
4521
4559
  _iterator4.f();
4522
4560
  }
4523
4561
  } else if (isNullish(range) || isKeyInRange(keyEntry, range, true)) {
4524
- matches.push(keyEntry);
4562
+ safePush(matches, keyEntry);
4525
4563
  }
4526
4564
  return matches;
4527
4565
  }
@@ -4543,12 +4581,13 @@
4543
4581
  }
4544
4582
  case 'array':
4545
4583
  {
4584
+ /** @type {ValueType[]} */
4546
4585
  var array = [];
4547
4586
  var len = value.length;
4548
4587
  var index = 0;
4549
4588
  while (index < len) {
4550
4589
  var entry = convertKeyToValue(value[index]);
4551
- array[index] = entry;
4590
+ setArrayValue(array, index, entry);
4552
4591
  index++;
4553
4592
  }
4554
4593
  return array;
@@ -5308,6 +5347,7 @@
5308
5347
  }
5309
5348
 
5310
5349
  var uniqueID = 0;
5350
+ var activeTransactions = new Set();
5311
5351
  var listeners$1 = ['onabort', 'oncomplete', 'onerror'];
5312
5352
  var readonlyProperties$4 = ['objectStoreNames', 'mode', 'durability', 'db', 'error'];
5313
5353
 
@@ -5439,6 +5479,7 @@
5439
5479
  me.__mode = mode;
5440
5480
  me.__durability = durability;
5441
5481
  me.__db = db;
5482
+ activeTransactions.add(me);
5442
5483
  me.__error = null;
5443
5484
  // @ts-expect-error Part of `ShimEventTarget`
5444
5485
  me.__setOptions({
@@ -5951,6 +5992,7 @@
5951
5992
  me.__errored = true;
5952
5993
  throw e;
5953
5994
  } finally {
5995
+ activeTransactions.delete(me);
5954
5996
  me.__storeHandles = {};
5955
5997
  }
5956
5998
  }
@@ -6157,13 +6199,19 @@
6157
6199
  });
6158
6200
  }
6159
6201
  me.__active = false; // Setting here and in requestsFinished for https://github.com/w3c/IndexedDB/issues/87
6160
-
6202
+ activeTransactions.delete(me);
6161
6203
  if (err !== null) {
6162
6204
  me.__error = err;
6163
6205
  }
6164
- if (me.__requestsFinished) {
6206
+ if (me.__requestsFinished && err !== null) {
6165
6207
  // The transaction has already completed, so we can't call "onerror" or "onabort".
6166
- // So throw the error instead.
6208
+ // So throw the error instead. `err` is only ever `null` here via
6209
+ // `IDBTransaction.prototype.abort`'s own `__abortTransaction(null)`
6210
+ // call, which now checks `__requestsFinished` itself first and
6211
+ // throws `InvalidStateError` synchronously before ever reaching
6212
+ // this point -- so this guard is just defense in depth against
6213
+ // `err` somehow being `null` some other way, not something this
6214
+ // path should see in practice.
6167
6215
  setTimeout(function () {
6168
6216
  throw err;
6169
6217
  }, 0);
@@ -6283,6 +6331,14 @@
6283
6331
  if (me.__committed) {
6284
6332
  throw createDOMException('InvalidStateError', 'The transaction has already been committed');
6285
6333
  }
6334
+ if (me.__requestsFinished) {
6335
+ // All requests have already finished and the transaction is
6336
+ // auto-committing (the async SQL commit round trip just hasn't
6337
+ // resolved yet) -- too late to abort per spec, even though
6338
+ // `__committed` itself isn't set until that round trip actually
6339
+ // finishes.
6340
+ throw createDOMException('InvalidStateError', 'The transaction is already committing');
6341
+ }
6286
6342
  me.__abortTransaction(null);
6287
6343
  };
6288
6344
 
@@ -6364,7 +6420,7 @@
6364
6420
  * @returns {void}
6365
6421
  */
6366
6422
  IDBTransaction.__assertActive = function (tx) {
6367
- if (!tx || !tx.__active || tx.__committed) {
6423
+ if (!tx || !tx.__active || !tx.__handlerActive || tx.__committed) {
6368
6424
  throw createDOMException('TransactionInactiveError', 'A request was placed against a transaction which is currently not active, or which is finished');
6369
6425
  }
6370
6426
  };
@@ -6391,6 +6447,9 @@
6391
6447
  Object.defineProperty(IDBTransaction, 'prototype', {
6392
6448
  writable: false
6393
6449
  });
6450
+ /* eslint-enable unicorn/no-top-level-side-effects -- Would be good */
6451
+
6452
+ IDBTransaction.activeTransactions = activeTransactions;
6394
6453
 
6395
6454
  var TypesonPromise = /*#__PURE__*/_createClass(function TypesonPromise(e) {
6396
6455
  _classCallCheck(this, TypesonPromise);
@@ -7777,7 +7836,7 @@
7777
7836
  }
7778
7837
  }
7779
7838
  },
7780
- k = {
7839
+ D = {
7781
7840
  map: {
7782
7841
  test: function test(e) {
7783
7842
  return "Map" === toStringTag(e);
@@ -7790,7 +7849,7 @@
7790
7849
  }
7791
7850
  }
7792
7851
  },
7793
- D = {
7852
+ R = {
7794
7853
  nan: {
7795
7854
  test: function test(e) {
7796
7855
  return Number.isNaN(e);
@@ -8139,8 +8198,8 @@
8139
8198
  revive: function revive() {}
8140
8199
  }
8141
8200
  }],
8142
- ne = [D, M, L, F],
8143
- 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 : []);
8201
+ ne = [R, M, L, F],
8202
+ 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 : []);
8144
8203
  var ue = ce.concat({
8145
8204
  checkDataCloneException: {
8146
8205
  test: function test(e) {
@@ -8157,11 +8216,56 @@
8157
8216
  return false;
8158
8217
  }
8159
8218
  }
8219
+ }),
8220
+ ye = ue.concat({
8221
+ checkSharedArrayBufferException: {
8222
+ test: function test(e) {
8223
+ if ("SharedArrayBuffer" === {}.toString.call(e).slice(8, -1)) throw new DOMException("The object cannot be cloned.", "DataCloneError");
8224
+ return false;
8225
+ }
8226
+ }
8160
8227
  });
8161
8228
 
8162
8229
  // See: https://stackoverflow.com/questions/42170826/categories-for-rejection-by-the-structured-cloning-algorithm
8163
8230
 
8164
- var typeson = new Typeson().register(ue);
8231
+ // Although typeson-registry already has a FileList type in its structured cloning presets,
8232
+ // we need to override it so it works with our tests
8233
+
8234
+ var specSet = ye.flatMap(function (preset) {
8235
+ return Array.isArray(preset) ? preset : [preset];
8236
+ }).find(function (preset) {
8237
+ return preset && !Array.isArray(preset) && 'filelist' in preset;
8238
+ });
8239
+ var origFileList = specSet && !Array.isArray(specSet) && 'filelist' in specSet ? specSet.filelist : undefined;
8240
+ var origTest = origFileList && _typeof(origFileList) === 'object' && 'test' in origFileList && typeof origFileList.test === 'function' ? origFileList.test : undefined;
8241
+ var origRevive = origFileList && _typeof(origFileList) === 'object' && 'revive' in origFileList && typeof origFileList.revive === 'function' ? origFileList.revive : undefined;
8242
+ var customFileList = origFileList ? _objectSpread2(_objectSpread2({}, origFileList), {}, {
8243
+ /**
8244
+ * @param {unknown} x
8245
+ * @param {import('typeson').StateObject} state
8246
+ * @returns {boolean}
8247
+ */
8248
+ test: function test(x, state) {
8249
+ if (typeof FileList !== 'undefined') {
8250
+ return x instanceof FileList;
8251
+ }
8252
+ return typeof origTest === 'function' ? origTest(x, state) : false;
8253
+ },
8254
+ /**
8255
+ * @param {unknown} x
8256
+ * @param {import('typeson').StateObject} state
8257
+ * @returns {unknown}
8258
+ */
8259
+ revive: function revive(x, state) {
8260
+ if (typeof FileList !== 'undefined') {
8261
+ return Reflect.construct(FileList, [x]);
8262
+ }
8263
+ return typeof origRevive === 'function' ? origRevive(x, state) : undefined;
8264
+ }
8265
+ }) : undefined;
8266
+ var typeson = new Typeson().register([ye, customFileList ? {
8267
+ filelist: customFileList
8268
+ } : {}]);
8165
8269
 
8166
8270
  /**
8167
8271
  * @param {(preset: import('typeson-registry').Preset) =>
@@ -8170,7 +8274,7 @@
8170
8274
  */
8171
8275
  function register(func) {
8172
8276
  // eslint-disable-next-line unicorn/no-top-level-assignment-in-function -- Should be one-time cache
8173
- typeson = new Typeson().register(func(ue));
8277
+ typeson = new Typeson().register(func(ye));
8174
8278
  }
8175
8279
 
8176
8280
  /**
@@ -11097,7 +11201,29 @@
11097
11201
  return new SyncPromise(function (resolve) {
11098
11202
  setTimeout(function () {
11099
11203
  entry.dispatchEvent(e); // No need to catch errors
11100
- resolve(undefined);
11204
+ // Unlike a native `Promise`, `SyncPromise#then` chains
11205
+ // synchronously off `resolve()` (verified directly:
11206
+ // `SyncPromise.resolve().then(cb)` runs `cb` before
11207
+ // even the calling code below it finishes) -- so
11208
+ // resolving immediately here would let the
11209
+ // `connectionsClosed()` check below run before a
11210
+ // same-task-but-microtask-later continuation of a
11211
+ // `versionchange` listener above (e.g. an `await`-
11212
+ // based one that then calls `db.close()`, as in
11213
+ // `transaction-lifetime.any.js`) ever gets a turn,
11214
+ // incorrectly firing `blocked` even though the
11215
+ // connection was about to close in time. Give real
11216
+ // microtask-deferred continuations a small, bounded
11217
+ // number of turns first -- same pattern as
11218
+ // `IDBTransaction.js`'s `checkQueueEntry`.
11219
+ var attemptsLeft = 10;
11220
+ (function wait() {
11221
+ if (attemptsLeft-- <= 0) {
11222
+ resolve(undefined);
11223
+ return;
11224
+ }
11225
+ queueMicrotask(wait);
11226
+ })();
11101
11227
  }, 0);
11102
11228
  });
11103
11229
  });
@@ -11157,6 +11283,23 @@
11157
11283
  */
11158
11284
  var websqlDBCache = {};
11159
11285
 
11286
+ /**
11287
+ * Tracks databases with a creation/upgrade currently in flight but not yet
11288
+ * committed or aborted -- keyed by (unescaped) database name. The
11289
+ * `dbVersions` row in `sysdb` these entries shadow is written *before*
11290
+ * `upgradeneeded` is even dispatched to user code (as the success
11291
+ * callback of that very `INSERT`/`UPDATE`), on the same shared `sysdb`
11292
+ * connection `databases()` itself reads from -- so, absent this, a
11293
+ * `databases()` call made while an upgrade is still pending would see
11294
+ * the new row (or new version) immediately, rather than only once the
11295
+ * corresponding `versionchange` transaction has genuinely committed, as
11296
+ * required by `get-databases.any.js`'s "doesn't pick up changes that
11297
+ * haven't committed" test. `IDBFactory.prototype.databases` filters
11298
+ * its SQL results against this map.
11299
+ * @type {Map<string, {oldVersion: Integer, newVersion: Integer}>}
11300
+ */
11301
+ var pendingVersionChanges = new Map();
11302
+
11160
11303
  /** @type {import('websql-configurable/lib/websql/WebSQLDatabase.js').default} */
11161
11304
  var sysdb;
11162
11305
  var nameCounter = 0;
@@ -11430,6 +11573,7 @@
11430
11573
  }
11431
11574
  var req = IDBOpenDBRequest.__createInstance();
11432
11575
  var calledDbCreateError = false;
11576
+ var isRevertingSysdb = false;
11433
11577
  if (CFG.autoName && name === '') {
11434
11578
  // eslint-disable-next-line unicorn/no-top-level-assignment-in-function -- Necessary?
11435
11579
  name = 'autoNamedDatabase_' + nameCounter++;
@@ -11456,9 +11600,22 @@
11456
11600
  * @returns {boolean}
11457
11601
  */
11458
11602
  function dbCreateError(tx, err) {
11459
- if (calledDbCreateError) {
11603
+ if (calledDbCreateError || isRevertingSysdb) {
11460
11604
  return false;
11461
11605
  }
11606
+ // Defensive cleanup: `pendingVersionChanges.set(name, ...)` (see
11607
+ // below) runs *before* the `dbVersions` `INSERT`/`UPDATE` it
11608
+ // guards is even attempted, but only `versionSet`'s own
11609
+ // `on__beforecomplete`/`on__preabort` handlers ever clear it --
11610
+ // and `versionSet` never runs at all if that `INSERT`/`UPDATE`,
11611
+ // or an earlier step in this same `open()` flow, fails first.
11612
+ // Without this, such a failure would leave the entry orphaned
11613
+ // forever, silently corrupting `databases()` for any *other*,
11614
+ // unrelated later `open()` call that happens to reuse the same
11615
+ // database name (common in WPT tests, e.g. generic names like
11616
+ // "DB1"/"TestDatabase" reused across different test files in
11617
+ // the same process). A no-op if no entry was ever set.
11618
+ pendingVersionChanges.delete(name);
11462
11619
  var er = err ? webSQLErrback(err) : (/** @type {Error} */tx);
11463
11620
  calledDbCreateError = true;
11464
11621
  // Re: why bubbling here (and how cancelable is only really relevant for `window.onerror`) see: https://github.com/w3c/IndexedDB/issues/86
@@ -11507,36 +11664,46 @@
11507
11664
  */
11508
11665
  var sysdbFinishedCb = function sysdbFinishedCb(systx, err, cb) {
11509
11666
  if (err) {
11510
- try {
11511
- systx.executeSql('ROLLBACK', [], cb, cb);
11512
- } catch (err) {
11513
- // Browser may fail with expired transaction above so
11514
- // no choice but to manually revert
11667
+ /**
11668
+ * @param {any} [errorToShow]
11669
+ * @returns {void}
11670
+ */
11671
+ var manualRevert = function manualRevert(errorToShow) {
11672
+ /**
11673
+ * @param {string} [msg]
11674
+ * @throws {Error}
11675
+ * @returns {never}
11676
+ */
11677
+ function reportError(msg) {
11678
+ throw new Error('Unable to roll back upgrade transaction!' + (msg || ''));
11679
+ }
11515
11680
  sysdb.transaction(function (systx) {
11516
- /**
11517
- *
11518
- * @param {string} msg
11519
- * @throws {Error}
11520
- * @returns {never}
11521
- */
11522
- function reportError(msg) {
11523
- throw new Error('Unable to roll back upgrade transaction!' + (msg || ''));
11524
- }
11525
-
11526
11681
  // Attempt to revert
11527
11682
  if (oldVersion === 0) {
11528
- systx.executeSql('DELETE FROM dbVersions WHERE "name" = ?', [sqlSafeName], function () {
11529
- // @ts-expect-error Force to work
11530
- cb(reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11531
- },
11532
- // @ts-expect-error Force to work
11533
- reportError);
11683
+ systx.executeSql('DELETE FROM dbVersions WHERE "name" = ?', [sqlSafeName]);
11534
11684
  } else {
11535
- systx.executeSql('UPDATE dbVersions SET "version" = ? WHERE "name" = ?', [oldVersion, sqlSafeName], cb,
11536
- // @ts-expect-error Force to work
11537
- reportError);
11685
+ systx.executeSql('UPDATE dbVersions SET "version" = ? WHERE "name" = ?', [oldVersion, sqlSafeName]);
11538
11686
  }
11687
+ }, function (sqlErr) {
11688
+ isRevertingSysdb = false;
11689
+ cb(sqlErr); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11690
+ }, function () {
11691
+ isRevertingSysdb = false;
11692
+ cb(errorToShow || reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11539
11693
  });
11694
+ };
11695
+ try {
11696
+ systx.executeSql('ROLLBACK', [], function () {
11697
+ cb(); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11698
+ }, function (tx, sqlErr) {
11699
+ // Browser/Node may fail with expired transaction, so manually revert
11700
+ manualRevert(sqlErr);
11701
+ return false;
11702
+ });
11703
+ } catch (e) {
11704
+ // Browser may fail with expired transaction above so
11705
+ // no choice but to manually revert
11706
+ manualRevert(e);
11540
11707
  }
11541
11708
  return;
11542
11709
  }
@@ -11570,22 +11737,43 @@
11570
11737
  // open/close the transaction's active-handler window itself.
11571
11738
  req.transaction.__handlerActive = true;
11572
11739
  req.dispatchEvent(e);
11573
- // Give any same-tick microtask scheduled from within the
11740
+ // Give any microtask-scheduled continuation of the
11574
11741
  // `upgradeneeded` handler (e.g. a plain
11575
- // `Promise.resolve().then(...)`) a chance to run -- and still
11576
- // observe the transaction as active -- before we deactivate it
11577
- // again, per https://github.com/w3c/IndexedDB/issues/87. Only
11578
- // the flag reset itself is deferred here -- unlike
11579
- // `IDBTransaction.js`'s `advanceAfterDispatch`, `finished()`
11580
- // (this transaction's own queue-advancement/completion signal)
11581
- // still runs synchronously, at exactly its previous timing: an
11582
- // earlier attempt at deferring `finished()` too raced against
11583
- // unrelated test setup that assumed a freshly deleted/created
11584
- // database's upgrade transaction had already fully completed by
11585
- // the time this function returns.
11586
- queueMicrotask(function () {
11587
- req.transaction.__handlerActive = false;
11588
- });
11742
+ // `Promise.resolve().then(...)`, or an `await`-based
11743
+ // continuation of a promise resolved from within the handler,
11744
+ // such as testharness.js's own `EventWatcher`) a chance to
11745
+ // run -- and still observe the transaction as active -- before
11746
+ // we deactivate it again, per
11747
+ // https://github.com/w3c/IndexedDB/issues/87. A single deferred
11748
+ // tick isn't always enough: an `await`-based continuation can
11749
+ // take more than one microtask turn to resume (e.g.
11750
+ // `transaction-lifetime.any.js`'s `EventWatcher`-based
11751
+ // `await eventWatcher.wait_for('upgradeneeded')`), so retry a
11752
+ // small, bounded number of times first -- same pattern as
11753
+ // `IDBTransaction.js`'s `checkQueueEntry` -- rather than
11754
+ // resetting after only one. Only the flag reset itself is
11755
+ // deferred here -- unlike `IDBTransaction.js`'s
11756
+ // `advanceAfterDispatch`, `finished()` (this transaction's own
11757
+ // queue-advancement/completion signal) still runs
11758
+ // synchronously, at exactly its previous timing: an earlier
11759
+ // attempt at deferring `finished()` too raced against unrelated
11760
+ // test setup that assumed a freshly deleted/created database's
11761
+ // upgrade transaction had already fully completed by the time
11762
+ // this function returns.
11763
+ /**
11764
+ * @param {Integer} attemptsLeft
11765
+ * @returns {void}
11766
+ */
11767
+ function deferHandlerActiveReset(attemptsLeft) {
11768
+ if (attemptsLeft <= 0) {
11769
+ req.transaction.__handlerActive = false;
11770
+ return;
11771
+ }
11772
+ queueMicrotask(function () {
11773
+ deferHandlerActiveReset(attemptsLeft - 1);
11774
+ });
11775
+ }
11776
+ deferHandlerActiveReset(10);
11589
11777
  if (e.__legacyOutputDidListenersThrowError) {
11590
11778
  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
11591
11779
  req.transaction.__abortTransaction(createDOMException('AbortError', 'A request was aborted.'));
@@ -11601,6 +11789,7 @@
11601
11789
  * @returns {void}
11602
11790
  */
11603
11791
  function (ev) {
11792
+ pendingVersionChanges.delete(name);
11604
11793
  connection.__upgradeTransaction = null;
11605
11794
  /** @type {import('./IDBDatabase.js').IDBDatabaseFull} */
11606
11795
  req.__result.__versionTransaction = null;
@@ -11615,6 +11804,8 @@
11615
11804
 
11616
11805
  // eslint-disable-next-line camelcase -- Clear API
11617
11806
  req.transaction.on__preabort = function () {
11807
+ isRevertingSysdb = true;
11808
+ pendingVersionChanges.delete(name);
11618
11809
  connection.__upgradeTransaction = null;
11619
11810
  // We ensure any cache is deleted before any request error events fire and try to reopen
11620
11811
  if (useDatabaseCache) {
@@ -11631,6 +11822,7 @@
11631
11822
  req.__result = undefined;
11632
11823
  req.__done = false;
11633
11824
  connection.close();
11825
+ isRevertingSysdb = true;
11634
11826
  setTimeout(function () {
11635
11827
  var err = createDOMException('AbortError', 'The upgrade transaction was aborted.');
11636
11828
  sysdbFinishedCb(systx, err, function (reportError) {
@@ -11677,6 +11869,10 @@
11677
11869
  // });
11678
11870
  };
11679
11871
  }
11872
+ pendingVersionChanges.set(name, {
11873
+ oldVersion: oldVersion,
11874
+ newVersion: version
11875
+ });
11680
11876
  if (oldVersion === 0) {
11681
11877
  systx.executeSql('INSERT INTO dbVersions VALUES (?,?)', [sqlSafeName, version], versionSet, dbCreateError);
11682
11878
  } else {
@@ -11688,7 +11884,26 @@
11688
11884
  }
11689
11885
  sysdbFinishedCb = function sysdbFinishedCb(systx, err, cb) {
11690
11886
  if (err) {
11691
- rollback(err, cb);
11887
+ rollback(err,
11888
+ /**
11889
+ * @param {Error} [reportError]
11890
+ * @returns {void}
11891
+ */
11892
+ function (reportError) {
11893
+ sysdb.transaction(function (systx) {
11894
+ if (oldVersion === 0) {
11895
+ systx.executeSql('DELETE FROM dbVersions WHERE "name" = ?', [sqlSafeName]);
11896
+ } else {
11897
+ systx.executeSql('UPDATE dbVersions SET "version" = ? WHERE "name" = ?', [oldVersion, sqlSafeName]);
11898
+ }
11899
+ }, function (sqlErr) {
11900
+ isRevertingSysdb = false;
11901
+ cb(sqlErr); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11902
+ }, function () {
11903
+ isRevertingSysdb = false;
11904
+ cb(reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11905
+ });
11906
+ });
11692
11907
  } else {
11693
11908
  commit(cb);
11694
11909
  }
@@ -11979,6 +12194,15 @@
11979
12194
  IDBFactory.prototype.databases = function () {
11980
12195
  var me = this;
11981
12196
  var calledDbCreateError = false;
12197
+ // Snapshotted *now*, synchronously, at call time -- not read later
12198
+ // from inside the SQL query's callback below, which runs on a
12199
+ // deferred macrotask (see `nodeSQLiteDatabase.js`'s `exec`) and so
12200
+ // could otherwise race against (and lose to) an in-flight upgrade's
12201
+ // own commit/abort handler clearing its `pendingVersionChanges`
12202
+ // entry in the meantime -- which would make this method incorrectly
12203
+ // reflect a since-committed change that hadn't committed yet when
12204
+ // it was actually called.
12205
+ var pendingVersionChangesSnapshot = new Map(pendingVersionChanges);
11982
12206
  return new Promise(function (resolve, reject) {
11983
12207
  // eslint-disable-line promise/avoid-new -- Own polyfill
11984
12208
  if (!(me instanceof IDBFactory)) {
@@ -12008,10 +12232,29 @@
12008
12232
  var dbNames = [];
12009
12233
  for (var i = 0; i < data.rows.length; i++) {
12010
12234
  var _data$rows$item2 = /** @type {{name: string, version: Integer}} */data.rows.item(i),
12011
- name = _data$rows$item2.name,
12235
+ encodedName = _data$rows$item2.name,
12012
12236
  version = _data$rows$item2.version;
12237
+ var name = unescapeSQLiteResponse(encodedName);
12238
+ // A row for a database whose creation/upgrade hasn't
12239
+ // committed yet (see `pendingVersionChanges`) must
12240
+ // not reflect that in-flight change: a brand new
12241
+ // database (`oldVersion === 0`) isn't reported at
12242
+ // all until its creation commits, and an existing
12243
+ // database being upgraded is still reported, but
12244
+ // with its pre-upgrade version.
12245
+ var pending = pendingVersionChangesSnapshot.get(name);
12246
+ if (pending) {
12247
+ if (pending.oldVersion === 0) {
12248
+ continue;
12249
+ }
12250
+ dbNames.push({
12251
+ name: name,
12252
+ version: pending.oldVersion
12253
+ });
12254
+ continue;
12255
+ }
12013
12256
  dbNames.push({
12014
- name: unescapeSQLiteResponse(name),
12257
+ name: name,
12015
12258
  version: version
12016
12259
  });
12017
12260
  }
@@ -12431,9 +12674,26 @@
12431
12674
  // Key.convertValueToKey(key); // Already checked by `continue` or `continuePrimaryKey`
12432
12675
  sqlValues.push(/** @type {string} */_encode(key));
12433
12676
  } else if (continueCall && me.__key !== undefined) {
12434
- sql.push('AND', quotedKeyColumnName, op + ' ?');
12435
12677
  // Key.convertValueToKey(me.__key); // Already checked when stored
12436
- sqlValues.push(/** @type {string} */_encode(me.__key));
12678
+ if (!me.__unique && me.__keyColumnName !== 'key' && me.__primaryKey !== undefined) {
12679
+ // A plain `continue()` on a non-unique index cursor must find
12680
+ // the next record strictly after the (key, primaryKey) pair
12681
+ // this cursor last returned -- a scalar `key > lastKey` alone
12682
+ // would wrongly exclude a *different*, not-yet-visited record
12683
+ // that's still tied on key with the last one (e.g. another
12684
+ // record that always had that same indexed value), and would
12685
+ // also wrongly re-admit the *same* record forever if a
12686
+ // same-transaction `update()` bumped its own key back above
12687
+ // the threshold, since a scalar comparison can't distinguish
12688
+ // "some other record newly tied" from "this record moved
12689
+ // past its own last position." Comparing the full tuple (via
12690
+ // this OR) against both key and primary key resolves both.
12691
+ sql.push('AND (', quotedKeyColumnName, op, '?', 'OR (', quotedKeyColumnName, '= ?', 'AND', quotedKey, op, '?))');
12692
+ sqlValues.push(/** @type {string} */_encode(me.__key), /** @type {string} */_encode(me.__key), /** @type {string} */_encode(me.__primaryKey));
12693
+ } else {
12694
+ sql.push('AND', quotedKeyColumnName, op + ' ?');
12695
+ sqlValues.push(/** @type {string} */_encode(me.__key));
12696
+ }
12437
12697
  }
12438
12698
  if (!me.__count) {
12439
12699
  // 1. Sort by key
@@ -13063,9 +13323,15 @@
13063
13323
  * @returns {void}
13064
13324
  */
13065
13325
  function addToQueue(clonedValue) {
13066
- // We set the `invalidateCache` argument to `false` since the old value shouldn't be accessed
13326
+ // `invalidateCache: true` so any cursor on this store (including
13327
+ // this one) drops its prefetched row batch, forcing its next
13328
+ // `continue()` to re-query live rather than serve rows snapshotted
13329
+ // before this update -- needed for the compound-tuple
13330
+ // continuation logic in `__findBasic` to see this update's
13331
+ // effect on ordering (see "Modify records during cursor
13332
+ // iteration" in idbcursor_update_index.any.js).
13067
13333
  // @ts-ignore -- API (not erring in TS 6)
13068
- IDBObjectStore.__storingRecordObjectStore(request, me.__store, false, clonedValue, false, key);
13334
+ IDBObjectStore.__storingRecordObjectStore(request, me.__store, true, clonedValue, false, key);
13069
13335
  }
13070
13336
  if (me.__store.keyPath !== null) {
13071
13337
  var _me$__store$__validat = me.__store.__validateKeyAndValueAndCloneValue(valueToUpdate, undefined, true),